Refactor how message send types are selected.

fork-5.53.8
Greyson Parrelli 2022-06-03 18:07:29 -04:00 zatwierdzone przez GitHub
rodzic bf90909496
commit 4da422fd3c
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
29 zmienionych plików z 591 dodań i 808 usunięć

Wyświetl plik

@ -1,149 +0,0 @@
package org.thoughtcrime.securesms;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.CharacterCalculator;
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
import java.util.Optional;
public class TransportOption implements Parcelable {
public enum Type {
SMS,
TEXTSECURE
}
private final int drawable;
private final int backgroundColor;
private final @NonNull String text;
private final @NonNull Type type;
private final @NonNull String composeHint;
private final @NonNull CharacterCalculator characterCalculator;
private final @NonNull Optional<CharSequence> simName;
private final @NonNull Optional<Integer> simSubscriptionId;
public TransportOption(@NonNull Type type,
@DrawableRes int drawable,
int backgroundColor,
@NonNull String text,
@NonNull String composeHint,
@NonNull CharacterCalculator characterCalculator)
{
this(type, drawable, backgroundColor, text, composeHint, characterCalculator,
Optional.empty(), Optional.empty());
}
public TransportOption(@NonNull Type type,
@DrawableRes int drawable,
int backgroundColor,
@NonNull String text,
@NonNull String composeHint,
@NonNull CharacterCalculator characterCalculator,
@NonNull Optional<CharSequence> simName,
@NonNull Optional<Integer> simSubscriptionId)
{
this.type = type;
this.drawable = drawable;
this.backgroundColor = backgroundColor;
this.text = text;
this.composeHint = composeHint;
this.characterCalculator = characterCalculator;
this.simName = simName;
this.simSubscriptionId = simSubscriptionId;
}
TransportOption(Parcel in) {
this(Type.valueOf(in.readString()),
in.readInt(),
in.readInt(),
in.readString(),
in.readString(),
CharacterCalculator.readFromParcel(in),
Optional.ofNullable(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in)),
in.readInt() == 1 ? Optional.of(in.readInt()) : Optional.empty());
}
public @NonNull Type getType() {
return type;
}
public boolean isType(Type type) {
return this.type == type;
}
public boolean isSms() {
return type == Type.SMS;
}
public CharacterState calculateCharacters(String messageBody) {
return characterCalculator.calculateCharacters(messageBody);
}
public @DrawableRes int getDrawable() {
return drawable;
}
public int getBackgroundColor() {
return backgroundColor;
}
public @NonNull String getComposeHint() {
return composeHint;
}
public @NonNull String getDescription() {
return text;
}
@NonNull
public Optional<CharSequence> getSimName() {
return simName;
}
@NonNull
public Optional<Integer> getSimSubscriptionId() {
return simSubscriptionId;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(type.name());
dest.writeInt(drawable);
dest.writeInt(backgroundColor);
dest.writeString(text);
dest.writeString(composeHint);
CharacterCalculator.writeToParcel(dest, characterCalculator);
TextUtils.writeToParcel(simName.orElse(null), dest, flags);
if (simSubscriptionId.isPresent()) {
dest.writeInt(1);
dest.writeInt(simSubscriptionId.get());
} else {
dest.writeInt(0);
}
}
public static final Creator<TransportOption> CREATOR = new Creator<TransportOption>() {
@Override
public TransportOption createFromParcel(Parcel in) {
return new TransportOption(in);
}
@Override
public TransportOption[] newArray(int size) {
return new TransportOption[size];
}
};
}

Wyświetl plik

@ -1,226 +0,0 @@
package org.thoughtcrime.securesms;
import android.Manifest;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.CharacterCalculator;
import org.thoughtcrime.securesms.util.MmsCharacterCalculator;
import org.thoughtcrime.securesms.util.PushCharacterCalculator;
import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
import org.whispersystems.signalservice.api.util.OptionalUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import static org.thoughtcrime.securesms.TransportOption.Type;
public class TransportOptions {
private static final String TAG = Log.tag(TransportOptions.class);
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
private final Context context;
private final List<TransportOption> enabledTransports;
private Type defaultTransportType = Type.SMS;
private Optional<Integer> defaultSubscriptionId = Optional.empty();
private Optional<TransportOption> selectedOption = Optional.empty();
private final Optional<Integer> systemSubscriptionId;
public TransportOptions(Context context, boolean media) {
this.context = context;
this.enabledTransports = initializeAvailableTransports(media);
this.systemSubscriptionId = new SubscriptionManagerCompat(context).getPreferredSubscriptionId();
}
public void reset(boolean media) {
List<TransportOption> transportOptions = initializeAvailableTransports(media);
this.enabledTransports.clear();
this.enabledTransports.addAll(transportOptions);
if (selectedOption.isPresent() && !isEnabled(selectedOption.get())) {
setSelectedTransport(null);
} else {
this.defaultTransportType = Type.SMS;
this.defaultSubscriptionId = Optional.empty();
notifyTransportChangeListeners();
}
}
public void setDefaultTransport(Type type) {
this.defaultTransportType = type;
if (!selectedOption.isPresent()) {
notifyTransportChangeListeners();
}
}
public void setDefaultSubscriptionId(Optional<Integer> subscriptionId) {
if (defaultSubscriptionId.equals(subscriptionId)) {
return;
}
this.defaultSubscriptionId = subscriptionId;
if (!selectedOption.isPresent()) {
notifyTransportChangeListeners();
}
}
public void setSelectedTransport(@Nullable TransportOption transportOption) {
this.selectedOption = Optional.ofNullable(transportOption);
notifyTransportChangeListeners();
}
public boolean isManualSelection() {
return this.selectedOption.isPresent();
}
public @NonNull TransportOption getSelectedTransport() {
if (selectedOption.isPresent()) return selectedOption.get();
if (defaultTransportType == Type.SMS) {
TransportOption transportOption = findEnabledSmsTransportOption(OptionalUtil.or(defaultSubscriptionId, systemSubscriptionId));
if (transportOption != null) {
return transportOption;
}
}
for (TransportOption transportOption : enabledTransports) {
if (transportOption.getType() == defaultTransportType) {
return transportOption;
}
}
throw new AssertionError("No options of default type!");
}
public static @NonNull TransportOption getPushTransportOption(@NonNull Context context) {
return new TransportOption(Type.TEXTSECURE,
R.drawable.ic_send_lock_24,
context.getResources().getColor(R.color.core_ultramarine),
context.getString(R.string.ConversationActivity_transport_signal),
context.getString(R.string.conversation_activity__type_message_push),
new PushCharacterCalculator());
}
private @Nullable TransportOption findEnabledSmsTransportOption(Optional<Integer> subscriptionId) {
if (subscriptionId.isPresent()) {
final int subId = subscriptionId.get();
for (TransportOption transportOption : enabledTransports) {
if (transportOption.getType() == Type.SMS &&
subId == transportOption.getSimSubscriptionId().orElse(-1)) {
return transportOption;
}
}
}
return null;
}
public void disableTransport(Type type) {
TransportOption selected = selectedOption.orElse(null);
Iterator<TransportOption> iterator = enabledTransports.iterator();
while (iterator.hasNext()) {
TransportOption option = iterator.next();
if (option.isType(type)) {
if (selected == option) {
setSelectedTransport(null);
}
iterator.remove();
}
}
}
public List<TransportOption> getEnabledTransports() {
return enabledTransports;
}
public void addOnTransportChangedListener(OnTransportChangedListener listener) {
this.listeners.add(listener);
}
private List<TransportOption> initializeAvailableTransports(boolean isMediaMessage) {
List<TransportOption> results = new LinkedList<>();
if (isMediaMessage) {
results.addAll(getTransportOptionsForSimCards(context.getString(R.string.ConversationActivity_transport_insecure_mms),
context.getString(R.string.conversation_activity__type_message_mms_insecure),
new MmsCharacterCalculator()));
} else {
results.addAll(getTransportOptionsForSimCards(context.getString(R.string.ConversationActivity_transport_insecure_sms),
context.getString(R.string.conversation_activity__type_message_sms_insecure),
new SmsCharacterCalculator()));
}
results.add(getPushTransportOption(context));
return results;
}
private @NonNull List<TransportOption> getTransportOptionsForSimCards(@NonNull String text,
@NonNull String composeHint,
@NonNull CharacterCalculator characterCalculator)
{
List<TransportOption> results = new LinkedList<>();
SubscriptionManagerCompat subscriptionManager = new SubscriptionManagerCompat(context);
Collection<SubscriptionInfoCompat> subscriptions;
if (Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE)) {
subscriptions = subscriptionManager.getActiveAndReadySubscriptionInfos();
} else {
subscriptions = Collections.emptyList();
}
if (subscriptions.size() < 2) {
results.add(new TransportOption(Type.SMS, R.drawable.ic_send_unlock_24,
context.getResources().getColor(R.color.core_grey_50),
text, composeHint, characterCalculator));
} else {
for (SubscriptionInfoCompat subscriptionInfo : subscriptions) {
results.add(new TransportOption(Type.SMS, R.drawable.ic_send_unlock_24,
context.getResources().getColor(R.color.core_grey_50),
text, composeHint, characterCalculator,
Optional.of(subscriptionInfo.getDisplayName()),
Optional.of(subscriptionInfo.getSubscriptionId())));
}
}
return results;
}
private void notifyTransportChangeListeners() {
for (OnTransportChangedListener listener : listeners) {
listener.onChange(getSelectedTransport(), selectedOption.isPresent());
}
}
private boolean isEnabled(TransportOption transportOption) {
for (TransportOption option : enabledTransports) {
if (option.equals(transportOption)) return true;
}
return false;
}
public interface OnTransportChangedListener {
public void onChange(TransportOption newTransport, boolean manuallySelected);
}
}

Wyświetl plik

@ -1,73 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.graphics.PorterDuff.Mode;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import java.util.List;
public class TransportOptionsAdapter extends BaseAdapter {
private final LayoutInflater inflater;
private List<TransportOption> enabledTransports;
public TransportOptionsAdapter(@NonNull Context context,
@NonNull List<TransportOption> enabledTransports)
{
super();
this.inflater = LayoutInflater.from(context);
this.enabledTransports = enabledTransports;
}
public void setEnabledTransports(List<TransportOption> enabledTransports) {
this.enabledTransports = enabledTransports;
}
@Override
public int getCount() {
return enabledTransports.size();
}
@Override
public Object getItem(int position) {
return enabledTransports.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.transport_selection_list_item, parent, false);
}
TransportOption transport = (TransportOption) getItem(position);
ImageView imageView = convertView.findViewById(R.id.icon);
TextView textView = convertView.findViewById(R.id.text);
TextView subtextView = convertView.findViewById(R.id.subtext);
imageView.getBackground().setColorFilter(transport.getBackgroundColor(), Mode.MULTIPLY);
imageView.setImageResource(transport.getDrawable());
textView.setText(transport.getDescription());
if (transport.getSimName().isPresent()) {
subtextView.setText(transport.getSimName().get());
subtextView.setVisibility(View.VISIBLE);
} else {
subtextView.setVisibility(View.GONE);
}
return convertView;
}
}

Wyświetl plik

@ -1,50 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.ListPopupWindow;
import java.util.LinkedList;
import java.util.List;
public class TransportOptionsPopup extends ListPopupWindow implements ListView.OnItemClickListener {
private final TransportOptionsAdapter adapter;
private final SelectedListener listener;
public TransportOptionsPopup(@NonNull Context context, @NonNull View anchor, @NonNull SelectedListener listener) {
super(context);
this.listener = listener;
this.adapter = new TransportOptionsAdapter(context, new LinkedList<TransportOption>());
setVerticalOffset(context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_yoff));
setHorizontalOffset(context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_xoff));
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
setModal(true);
setAnchorView(anchor);
setAdapter(adapter);
setContentWidth(context.getResources().getDimensionPixelSize(R.dimen.transport_selection_popup_width));
setOnItemClickListener(this);
}
public void display(List<TransportOption> enabledTransports) {
adapter.setEnabledTransports(enabledTransports);
adapter.notifyDataSetChanged();
show();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
listener.onSelected((TransportOption)adapter.getItem(position));
}
public interface SelectedListener {
void onSelected(TransportOption option);
}
}

Wyświetl plik

@ -28,12 +28,12 @@ import androidx.core.view.inputmethod.InputContentInfoCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.components.mention.MentionDeleter;
import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
import org.thoughtcrime.securesms.components.mention.MentionValidatorWatcher;
import org.thoughtcrime.securesms.conversation.MessageSendType;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.RecipientId;
@ -201,13 +201,13 @@ public class ComposeText extends EmojiEditText {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
}
public void setTransport(TransportOption transport) {
public void setMessageSendType(MessageSendType messageSendType) {
final boolean useSystemEmoji = SignalStore.settings().isPreferSystemEmoji();
int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND;
int inputType = getInputType();
if (isLandscape()) setImeActionLabel(transport.getComposeHint(), EditorInfo.IME_ACTION_SEND);
if (isLandscape()) setImeActionLabel(getContext().getString(messageSendType.getComposeHintRes()), EditorInfo.IME_ACTION_SEND);
else setImeActionLabel(null, 0);
if (useSystemEmoji) {
@ -215,9 +215,9 @@ public class ComposeText extends EmojiEditText {
}
setImeOptions(imeOptions);
setHint(transport.getComposeHint(),
transport.getSimName().isPresent()
? getContext().getString(R.string.conversation_activity__from_sim_name, transport.getSimName().get())
setHint(getContext().getString(messageSendType.getComposeHintRes()),
messageSendType.getSimName() != null
? getContext().getString(R.string.conversation_activity__from_sim_name, messageSendType.getSimName())
: null);
setInputType(inputType);
}

Wyświetl plik

@ -1,121 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.TransportOptions;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.TransportOptionsPopup;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Optional;
public class SendButton extends AppCompatImageButton
implements TransportOptions.OnTransportChangedListener,
TransportOptionsPopup.SelectedListener,
View.OnLongClickListener
{
private final TransportOptions transportOptions;
private Optional<TransportOptionsPopup> transportOptionsPopup = Optional.empty();
@SuppressWarnings("unused")
public SendButton(Context context) {
super(context);
this.transportOptions = initializeTransportOptions(false);
ViewUtil.mirrorIfRtl(this, getContext());
}
@SuppressWarnings("unused")
public SendButton(Context context, AttributeSet attrs) {
super(context, attrs);
this.transportOptions = initializeTransportOptions(false);
ViewUtil.mirrorIfRtl(this, getContext());
}
@SuppressWarnings("unused")
public SendButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.transportOptions = initializeTransportOptions(false);
ViewUtil.mirrorIfRtl(this, getContext());
}
private TransportOptions initializeTransportOptions(boolean media) {
if (isInEditMode()) return null;
TransportOptions transportOptions = new TransportOptions(getContext(), media);
transportOptions.addOnTransportChangedListener(this);
setOnLongClickListener(this);
return transportOptions;
}
private TransportOptionsPopup getTransportOptionsPopup() {
if (!transportOptionsPopup.isPresent()) {
transportOptionsPopup = Optional.of(new TransportOptionsPopup(getContext(), this, this));
}
return transportOptionsPopup.get();
}
public boolean isManualSelection() {
return transportOptions.isManualSelection();
}
public void addOnTransportChangedListener(OnTransportChangedListener listener) {
transportOptions.addOnTransportChangedListener(listener);
}
public TransportOption getSelectedTransport() {
return transportOptions.getSelectedTransport();
}
public void resetAvailableTransports(boolean isMediaMessage) {
transportOptions.reset(isMediaMessage);
}
public void disableTransport(TransportOption.Type type) {
transportOptions.disableTransport(type);
}
public void setDefaultTransport(TransportOption.Type type) {
transportOptions.setDefaultTransport(type);
}
public void setTransport(@NonNull TransportOption option) {
transportOptions.setSelectedTransport(option);
}
public void setDefaultSubscriptionId(Optional<Integer> subscriptionId) {
transportOptions.setDefaultSubscriptionId(subscriptionId);
}
@Override
public void onSelected(TransportOption option) {
transportOptions.setSelectedTransport(option);
getTransportOptionsPopup().dismiss();
}
@Override
public void onChange(TransportOption newTransport, boolean isManualSelection) {
setImageResource(newTransport.getDrawable());
setContentDescription(newTransport.getDescription());
}
@Override
public boolean onLongClick(View v) {
if (isEnabled() && transportOptions.getEnabledTransports().size() > 1) {
getTransportOptionsPopup().display(transportOptions.getEnabledTransports());
return true;
}
return false;
}
}

Wyświetl plik

@ -0,0 +1,165 @@
package org.thoughtcrime.securesms.components
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.View.OnLongClickListener
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatImageButton
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.components.menu.ActionItem
import org.thoughtcrime.securesms.components.menu.SignalContextMenu
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.util.ViewUtil
import java.lang.AssertionError
import java.util.concurrent.CopyOnWriteArrayList
/**
* The send button you see in a conversation.
* Also encapsulates the long-press menu that allows users to switch [MessageSendType]s.
*/
class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImageButton(context, attributeSet), OnLongClickListener {
companion object {
private val TAG = Log.tag(SendButton::class.java)
}
private val listeners: MutableList<SendTypeChangedListener> = CopyOnWriteArrayList()
private var availableSendTypes: List<MessageSendType> = MessageSendType.getAllAvailable(context, false)
private var activeMessageSendType: MessageSendType? = null
private var defaultTransportType: MessageSendType.TransportType = MessageSendType.TransportType.SMS
private var defaultSubscriptionId: Int? = null
private var popupContainer: ViewGroup? = null
init {
setOnLongClickListener(this)
ViewUtil.mirrorIfRtl(this, getContext())
}
/**
* @return True if the [selectedSendType] was chosen manually by the user, otherwise false.
*/
val isManualSelection: Boolean
get() = activeMessageSendType != null
/**
* The actively-selected send type.
*/
val selectedSendType: MessageSendType
get() {
activeMessageSendType?.let {
return it
}
if (defaultTransportType === MessageSendType.TransportType.SMS) {
for (type in availableSendTypes) {
if (type.usesSmsTransport && (defaultSubscriptionId == null || type.simSubscriptionId == defaultSubscriptionId)) {
return type
}
}
}
for (type in availableSendTypes) {
if (type.transportType === defaultTransportType) {
return type
}
}
throw AssertionError("No options of default type!")
}
fun addOnSelectionChangedListener(listener: SendTypeChangedListener) {
listeners.add(listener)
}
fun triggerSelectedChangedEvent() {
onSelectionChanged(newType = selectedSendType, isManualSelection = false)
}
fun resetAvailableTransports(isMediaMessage: Boolean) {
availableSendTypes = MessageSendType.getAllAvailable(context, isMediaMessage)
if (!availableSendTypes.contains(activeMessageSendType)) {
Log.w(TAG, "[resetAvailableTransports] The active send type is no longer available. Unsetting.")
setSendType(null)
} else {
defaultTransportType = MessageSendType.TransportType.SMS
defaultSubscriptionId = null
onSelectionChanged(newType = selectedSendType, isManualSelection = false)
}
}
fun disableTransportType(type: MessageSendType.TransportType) {
availableSendTypes = availableSendTypes.filterNot { it.transportType == type }
}
fun setDefaultTransport(type: MessageSendType.TransportType) {
if (defaultTransportType == type) {
return
}
defaultTransportType = type
onSelectionChanged(newType = selectedSendType, isManualSelection = false)
}
fun setSendType(sendType: MessageSendType?) {
if (activeMessageSendType == sendType) {
return
}
activeMessageSendType = sendType
onSelectionChanged(newType = selectedSendType, isManualSelection = true)
}
fun setDefaultSubscriptionId(subscriptionId: Int?) {
if (defaultSubscriptionId == subscriptionId) {
return
}
defaultSubscriptionId = subscriptionId
onSelectionChanged(newType = selectedSendType, isManualSelection = false)
}
/**
* Must be called with a view that is acceptable for determining the bounds of the popup selector.
*/
fun setPopupContainer(container: ViewGroup) {
popupContainer = container
}
private fun onSelectionChanged(newType: MessageSendType, isManualSelection: Boolean) {
setImageResource(newType.buttonDrawableRes)
contentDescription = context.getString(newType.titleRes)
for (listener in listeners) {
listener.onSendTypeChanged(newType, isManualSelection)
}
}
override fun onLongClick(v: View): Boolean {
if (!isEnabled || availableSendTypes.size == 1) {
return false
}
val currentlySelected: MessageSendType = selectedSendType
val items = availableSendTypes
.filterNot { it == currentlySelected }
.map { option ->
ActionItem(
iconRes = option.menuDrawableRes,
title = option.getTitle(context),
action = { setSendType(option) }
)
}
SignalContextMenu.Builder((parent as View), popupContainer!!)
.preferredVerticalPosition(SignalContextMenu.VerticalPosition.ABOVE)
.offsetY(ViewUtil.dpToPx(8))
.show(items)
return true
}
interface SendTypeChangedListener {
fun onSendTypeChanged(newType: MessageSendType, manuallySelected: Boolean)
}
}

Wyświetl plik

@ -25,6 +25,7 @@ class SignalContextMenu private constructor(
val baseOffsetX: Int = 0,
val baseOffsetY: Int = 0,
val horizontalPosition: HorizontalPosition = HorizontalPosition.START,
val verticalPosition: VerticalPosition = VerticalPosition.BELOW,
val onDismiss: Runnable? = null
) : PopupWindow(
LayoutInflater.from(anchor.context).inflate(R.layout.signal_context_menu, null),
@ -41,6 +42,7 @@ class SignalContextMenu private constructor(
init {
setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.signal_context_menu_background))
inputMethodMode = INPUT_METHOD_NOT_NEEDED
isFocusable = true
@ -80,7 +82,10 @@ class SignalContextMenu private constructor(
val offsetY: Int
if (menuBottomBound < screenBottomBound) {
if (verticalPosition == VerticalPosition.ABOVE && menuTopBound > screenTopBound) {
offsetY = -(anchorRect.height() + contentView.measuredHeight + baseOffsetY)
contextMenuList.setItems(items.reversed())
} else if (menuBottomBound < screenBottomBound) {
offsetY = baseOffsetY
} else if (menuTopBound > screenTopBound) {
offsetY = -(anchorRect.height() + contentView.measuredHeight + baseOffsetY)
@ -115,6 +120,10 @@ class SignalContextMenu private constructor(
START, END
}
enum class VerticalPosition {
ABOVE, BELOW
}
/**
* @param anchor The view to put the pop-up on
* @param container A parent of [anchor] that represents the acceptable boundaries of the popup
@ -128,6 +137,7 @@ class SignalContextMenu private constructor(
var offsetX = 0
var offsetY = 0
var horizontalPosition = HorizontalPosition.START
var verticalPosition = VerticalPosition.BELOW
fun onDismiss(onDismiss: Runnable): Builder {
this.onDismiss = onDismiss
@ -149,6 +159,11 @@ class SignalContextMenu private constructor(
return this
}
fun preferredVerticalPosition(verticalPosition: VerticalPosition): Builder {
this.verticalPosition = verticalPosition
return this
}
fun show(items: List<ActionItem>): SignalContextMenu {
return SignalContextMenu(
anchor = anchor,
@ -157,6 +172,7 @@ class SignalContextMenu private constructor(
baseOffsetX = offsetX,
baseOffsetY = offsetY,
horizontalPosition = horizontalPosition,
verticalPosition = verticalPosition,
onDismiss = onDismiss
).show()
}

Wyświetl plik

@ -115,7 +115,6 @@ import org.thoughtcrime.securesms.MuteDialog;
import org.thoughtcrime.securesms.PromptMmsActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
import org.thoughtcrime.securesms.audio.AudioRecorder;
@ -328,7 +327,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import kotlin.Unit;
import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
/**
@ -590,6 +588,8 @@ public class ConversationParentFragment extends Fragment
if (isSearchRequested && savedInstanceState == null) {
onCreateOptionsMenu(toolbar.getMenu(), requireActivity().getMenuInflater());
}
sendButton.post(() -> sendButton.triggerSelectedChangedEvent());
}
// TODO [alex] LargeScreenSupport -- This needs to be fed a stream of intents
@ -672,7 +672,7 @@ public class ConversationParentFragment extends Fragment
EventBus.getDefault().register(this);
initializeMmsEnabledCheck();
initializeIdentityRecords();
composeText.setTransport(sendButton.getSelectedTransport());
composeText.setMessageSendType(sendButton.getSelectedSendType());
Recipient recipientSnapshot = recipient.get();
@ -731,7 +731,7 @@ public class ConversationParentFragment extends Fragment
public void onConfigurationChanged(Configuration newConfig) {
Log.i(TAG, "onConfigurationChanged(" + newConfig.orientation + ")");
super.onConfigurationChanged(newConfig);
composeText.setTransport(sendButton.getSelectedTransport());
composeText.setMessageSendType(sendButton.getSelectedSendType());
if (emojiDrawerStub.resolved() && container.getCurrentInput() == emojiDrawerStub.get()) {
container.hideAttachedInput(true);
@ -823,7 +823,7 @@ public class ConversationParentFragment extends Fragment
return;
}
sendButton.setTransport(result.getTransport());
sendButton.setSendType(result.getMessageSendType());
if (result.isPushPreUpload()) {
sendMediaMessage(result);
@ -831,7 +831,7 @@ public class ConversationParentFragment extends Fragment
}
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds());
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1);
int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1);
boolean initiating = threadId == -1;
QuoteModel quote = result.isViewOnce() ? null : inputPanel.getQuote().orElse(null);
SlideDeck slideDeck = new SlideDeck();
@ -852,7 +852,7 @@ public class ConversationParentFragment extends Fragment
final Context context = requireContext().getApplicationContext();
sendMediaMessage(result.getRecipientId(),
result.getTransport().isSms(),
result.getMessageSendType().usesSmsTransport(),
result.getBody(),
slideDeck,
quote,
@ -1231,7 +1231,7 @@ public class ConversationParentFragment extends Fragment
@Override
public void onAttachmentMediaClicked(@NonNull Media media) {
linkPreviewViewModel.onUserCancel();
startActivityForResult(MediaSelectionActivity.editor(requireActivity(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER);
startActivityForResult(MediaSelectionActivity.editor(requireActivity(), sendButton.getSelectedSendType(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER);
container.hideCurrentInput(composeText);
}
@ -1239,7 +1239,7 @@ public class ConversationParentFragment extends Fragment
public void onAttachmentSelectorClicked(@NonNull AttachmentKeyboardButton button) {
switch (button) {
case GALLERY:
AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedTransport(), inputPanel.getQuote().isPresent());
AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient.get(), composeText.getTextTrimmed(), sendButton.getSelectedSendType(), inputPanel.getQuote().isPresent());
break;
case FILE:
AttachmentManager.selectDocument(this, PICK_DOCUMENT);
@ -1629,21 +1629,21 @@ public class ConversationParentFragment extends Fragment
boolean smsEnabled = true;
if (recipient.get().isPushGroup() || (!recipient.get().isMmsGroup() && !recipient.get().hasSmsAddress())) {
sendButton.disableTransport(Type.SMS);
sendButton.disableTransportType(MessageSendType.TransportType.SMS);
smsEnabled = false;
}
if (!isSecureText && !isPushGroupConversation() && !recipient.get().isServiceIdOnly() && !recipient.get().isReleaseNotes() && smsEnabled) {
sendButton.disableTransport(Type.TEXTSECURE);
sendButton.disableTransportType(MessageSendType.TransportType.SIGNAL);
}
if (!recipient.get().isPushGroup() && recipient.get().isForceSmsSelection() && smsEnabled) {
sendButton.setDefaultTransport(Type.SMS);
sendButton.setDefaultTransport(MessageSendType.TransportType.SMS);
} else {
if (isSecureText || isPushGroupConversation() || recipient.get().isServiceIdOnly() || recipient.get().isReleaseNotes() || !smsEnabled) {
sendButton.setDefaultTransport(Type.TEXTSECURE);
sendButton.setDefaultTransport(MessageSendType.TransportType.SIGNAL);
} else {
sendButton.setDefaultTransport(Type.SMS);
sendButton.setDefaultTransport(MessageSendType.TransportType.SMS);
}
}
@ -1680,7 +1680,7 @@ public class ConversationParentFragment extends Fragment
if (!Util.isEmpty(mediaList)) {
Log.d(TAG, "Handling shared Media.");
Intent sendIntent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), mediaList, recipient.getId(), draftText);
Intent sendIntent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedSendType(), mediaList, recipient.getId(), draftText);
startActivityForResult(sendIntent, MEDIA_SENDER);
return new SettableFuture<>(false);
}
@ -2029,7 +2029,7 @@ public class ConversationParentFragment extends Fragment
private void updateDefaultSubscriptionId(Optional<Integer> defaultSubscriptionId) {
Log.i(TAG, "updateDefaultSubscriptionId(" + defaultSubscriptionId.orElse(null) + ")");
sendButton.setDefaultSubscriptionId(defaultSubscriptionId);
sendButton.setDefaultSubscriptionId(defaultSubscriptionId.orElse(null));
}
private void initializeMmsEnabledCheck() {
@ -2150,6 +2150,8 @@ public class ConversationParentFragment extends Fragment
releaseChannelUnmute = ViewUtil.findStubById(view, R.id.conversation_release_notes_unmute_stub);
joinGroupCallButton = view.findViewById(R.id.conversation_group_call_join);
sendButton.setPopupContainer((ViewGroup) view);
container.setIsBubble(isInBubble());
container.addOnKeyboardShownListener(this);
inputPanel.setListener(this);
@ -2168,16 +2170,16 @@ public class ConversationParentFragment extends Fragment
attachButton.setOnLongClickListener(new AttachButtonLongClickListener());
sendButton.setOnClickListener(sendButtonListener);
sendButton.setEnabled(true);
sendButton.addOnTransportChangedListener((newTransport, manuallySelected) -> {
sendButton.addOnSelectionChangedListener((newMessageSendType, manuallySelected) -> {
calculateCharactersRemaining();
updateLinkPreviewState();
linkPreviewViewModel.onTransportChanged(newTransport.isSms());
composeText.setTransport(newTransport);
linkPreviewViewModel.onTransportChanged(newMessageSendType.usesSmsTransport());
composeText.setMessageSendType(newMessageSendType);
buttonToggle.getBackground().setColorFilter(getButtonToggleBackgroundColor(newTransport), PorterDuff.Mode.MULTIPLY);
buttonToggle.getBackground().setColorFilter(getButtonToggleBackgroundColor(newMessageSendType), PorterDuff.Mode.MULTIPLY);
buttonToggle.getBackground().invalidateSelf();
if (manuallySelected) recordTransportPreference(newTransport);
if (manuallySelected) recordTransportPreference(newMessageSendType);
});
titleView.setOnStoryRingClickListener(v -> handleStoryRingClick());
@ -2222,13 +2224,13 @@ public class ConversationParentFragment extends Fragment
material3OnScrollHelper = new Material3OnScrollHelper(Collections.singletonList(toolbarBackground), Collections.emptyList(), this::updateStatusBarColor);
}
private @ColorInt int getButtonToggleBackgroundColor(TransportOption newTransport) {
if (newTransport.isSms()) {
return newTransport.getBackgroundColor();
private @ColorInt int getButtonToggleBackgroundColor(MessageSendType newTransport) {
if (newTransport.usesSmsTransport()) {
return getResources().getColor(newTransport.getBackgroundColorRes());
} else if (recipient != null) {
return getRecipient().getChatColors().asSingleColor();
} else {
return newTransport.getBackgroundColor();
return getResources().getColor(newTransport.getBackgroundColorRes());
}
}
@ -2728,7 +2730,7 @@ public class ConversationParentFragment extends Fragment
}
Media media = new Media(uri, mimeType, 0, width, height, 0, 0, borderless, videoGif, Optional.empty(), Optional.empty(), Optional.empty());
startActivityForResult(MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER);
startActivityForResult(MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedSendType(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed()), MEDIA_SENDER);
return new SettableFuture<>(false);
} else {
return attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height);
@ -2749,7 +2751,7 @@ public class ConversationParentFragment extends Fragment
}
private void sendSharedContact(List<Contact> contacts) {
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1);
int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1);
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds());
boolean initiating = threadId == -1;
@ -2909,8 +2911,8 @@ public class ConversationParentFragment extends Fragment
private void calculateCharactersRemaining() {
String messageBody = composeText.getTextTrimmed().toString();
TransportOption transportOption = sendButton.getSelectedTransport();
CharacterState characterState = transportOption.calculateCharacters(messageBody);
MessageSendType sendType = sendButton.getSelectedSendType();
CharacterState characterState = sendType.calculateCharacters(messageBody);
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
charactersLeft.setText(String.format(Locale.getDefault(),
@ -2968,7 +2970,7 @@ public class ConversationParentFragment extends Fragment
}
private boolean isSmsForced() {
return sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
return sendButton.isManualSelection() && sendButton.getSelectedSendType().usesSmsTransport();
}
protected Recipient getRecipient() {
@ -2989,9 +2991,9 @@ public class ConversationParentFragment extends Fragment
}
private MediaConstraints getCurrentMediaConstraints() {
return sendButton.getSelectedTransport().getType() == Type.TEXTSECURE
return sendButton.getSelectedSendType().usesSignalTransport()
? MediaConstraints.getPushMediaConstraints()
: MediaConstraints.getMmsMediaConstraints(sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1));
: MediaConstraints.getMmsMediaConstraints(sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1));
}
private void markLastSeen() {
@ -3050,12 +3052,12 @@ public class ConversationParentFragment extends Fragment
}
String message = getMessage();
TransportOption transport = sendButton.getSelectedTransport();
boolean forceSms = (recipient.isForceSmsSelection() || sendButton.isManualSelection()) && transport.isSms();
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1);
MessageSendType sendType = sendButton.getSelectedSendType();
boolean forceSms = (recipient.isForceSmsSelection() || sendButton.isManualSelection()) && sendType.usesSmsTransport();
int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1);
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
boolean initiating = threadId == -1;
boolean needsSplit = !transport.isSms() && message.length() > transport.calculateCharacters(message).maxPrimaryMessageSize;
boolean needsSplit = !sendType.usesSmsTransport() && message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize;
boolean isMediaMessage = attachmentManager.isAttachmentPresent() ||
recipient.isGroup() ||
recipient.getEmail().isPresent() ||
@ -3160,7 +3162,7 @@ public class ConversationParentFragment extends Fragment
final long thread = this.threadId;
if (sendPush) {
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(requireContext(), body, sendButton.getSelectedTransport().calculateCharacters(body).maxPrimaryMessageSize);
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(requireContext(), body, sendButton.getSelectedSendType().calculateCharacters(body).maxPrimaryMessageSize);
body = splitMessage.getBody();
if (splitMessage.getTextSlide().isPresent()) {
@ -3294,7 +3296,7 @@ public class ConversationParentFragment extends Fragment
}
private void updateLinkPreviewState() {
if (SignalStore.settings().isLinkPreviewsEnabled() && isSecureText && !sendButton.getSelectedTransport().isSms() && !attachmentManager.isAttachmentPresent() && getContext() != null) {
if (SignalStore.settings().isLinkPreviewsEnabled() && isSecureText && !sendButton.getSelectedSendType().usesSmsTransport() && !attachmentManager.isAttachmentPresent() && getContext() != null) {
linkPreviewViewModel.onEnabled();
linkPreviewViewModel.onTextChanged(requireContext(), composeText.getTextTrimmed().toString(), composeText.getSelectionStart(), composeText.getSelectionEnd());
} else {
@ -3302,16 +3304,16 @@ public class ConversationParentFragment extends Fragment
}
}
private void recordTransportPreference(TransportOption transportOption) {
private void recordTransportPreference(MessageSendType sendType) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
RecipientDatabase recipientDatabase = SignalDatabase.recipients();
recipientDatabase.setDefaultSubscriptionId(recipient.getId(), transportOption.getSimSubscriptionId().orElse(-1));
recipientDatabase.setDefaultSubscriptionId(recipient.getId(), sendType.getSimSubscriptionIdOr(-1));
if (!recipient.resolve().isPushGroup()) {
recipientDatabase.setForceSmsSelection(recipient.getId(), recipient.get().getRegistered() == RegisteredState.REGISTERED && transportOption.isSms());
recipientDatabase.setForceSmsSelection(recipient.getId(), recipient.get().getRegistered() == RegisteredState.REGISTERED && sendType.usesSmsTransport());
}
return null;
@ -3446,9 +3448,9 @@ public class ConversationParentFragment extends Fragment
}
private void sendVoiceNote(@NonNull Uri uri, long size) {
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedSendType().usesSmsTransport();
boolean initiating = threadId == -1;
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1);
int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1);
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds());
AudioSlide audioSlide = new AudioSlide(requireContext(), uri, size, MediaUtil.AUDIO_AAC, true);
SlideDeck slideDeck = new SlideDeck();
@ -3487,23 +3489,23 @@ public class ConversationParentFragment extends Fragment
}
private void sendSticker(@NonNull StickerLocator stickerLocator, @NonNull String contentType, @NonNull Uri uri, long size, boolean clearCompose) {
if (sendButton.getSelectedTransport().isSms()) {
if (sendButton.getSelectedSendType().usesSmsTransport()) {
Media media = new Media(uri, contentType, System.currentTimeMillis(), StickerSlide.WIDTH, StickerSlide.HEIGHT, size, 0, false, false, Optional.empty(), Optional.empty(), Optional.empty());
Intent intent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedTransport(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed());
Intent intent = MediaSelectionActivity.editor(requireContext(), sendButton.getSelectedSendType(), Collections.singletonList(media), recipient.getId(), composeText.getTextTrimmed());
startActivityForResult(intent, MEDIA_SENDER);
return;
}
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds());
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1);
int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1);
boolean initiating = threadId == -1;
TransportOption transport = sendButton.getSelectedTransport();
MessageSendType sendType = sendButton.getSelectedSendType();
SlideDeck slideDeck = new SlideDeck();
Slide stickerSlide = new StickerSlide(requireContext(), uri, size, stickerLocator, contentType);
slideDeck.addSlide(stickerSlide);
sendMediaMessage(recipient.getId(), transport.isSms(), "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), expiresIn, false, subscriptionId, initiating, clearCompose, null);
sendMediaMessage(recipient.getId(), sendType.usesSmsTransport(), "", slideDeck, null, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), expiresIn, false, subscriptionId, initiating, clearCompose, null);
}
private void silentlySetComposeText(String text) {
@ -3558,7 +3560,7 @@ public class ConversationParentFragment extends Fragment
@Override
public void openGifSearch() {
AttachmentManager.selectGif(this, ConversationParentFragment.PICK_GIF, recipient.getId(), sendButton.getSelectedTransport(), isMms(), composeText.getTextTrimmed());
AttachmentManager.selectGif(this, ConversationParentFragment.PICK_GIF, recipient.getId(), sendButton.getSelectedSendType(), isMms(), composeText.getTextTrimmed());
}
@Override
@ -3651,7 +3653,7 @@ public class ConversationParentFragment extends Fragment
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video))
.onAllGranted(() -> {
composeText.clearFocus();
startActivityForResult(MediaSelectionActivity.camera(requireActivity(), sendButton.getSelectedTransport(), recipient.getId(), inputPanel.getQuote().isPresent()), MEDIA_SENDER);
startActivityForResult(MediaSelectionActivity.camera(requireActivity(), sendButton.getSelectedSendType(), recipient.getId(), inputPanel.getQuote().isPresent()), MEDIA_SENDER);
requireActivity().overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary);
})
.onAnyDenied(() -> Toast.makeText(requireContext(), R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show())
@ -4146,7 +4148,7 @@ public class ConversationParentFragment extends Fragment
}
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.get().getExpiresInSeconds());
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().orElse(-1);
int subscriptionId = sendButton.getSelectedSendType().getSimSubscriptionIdOr(-1);
boolean initiating = threadId == -1;
SlideDeck slideDeck = new SlideDeck();

Wyświetl plik

@ -0,0 +1,164 @@
package org.thoughtcrime.securesms.conversation
import android.Manifest
import android.content.Context
import android.os.Parcelable
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import kotlinx.parcelize.Parcelize
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.util.CharacterCalculator
import org.thoughtcrime.securesms.util.MmsCharacterCalculator
import org.thoughtcrime.securesms.util.PushCharacterCalculator
import org.thoughtcrime.securesms.util.SmsCharacterCalculator
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat
import java.lang.IllegalArgumentException
/**
* The kinds of messages you can send, e.g. a plain Signal message, an SMS message, etc.
*/
@Parcelize
sealed class MessageSendType(
@StringRes
val titleRes: Int,
@StringRes
val composeHintRes: Int,
@DrawableRes
val buttonDrawableRes: Int,
@DrawableRes
val menuDrawableRes: Int,
@ColorRes
val backgroundColorRes: Int,
val transportType: TransportType,
val characterCalculator: CharacterCalculator,
open val simName: CharSequence? = null,
open val simSubscriptionId: Int? = null
) : Parcelable {
@get:JvmName("usesSmsTransport")
val usesSmsTransport
get() = transportType == TransportType.SMS
@get:JvmName("usesSignalTransport")
val usesSignalTransport
get() = transportType == TransportType.SIGNAL
fun calculateCharacters(body: String): CharacterCalculator.CharacterState {
return characterCalculator.calculateCharacters(body)
}
fun getSimSubscriptionIdOr(fallback: Int): Int {
return simSubscriptionId ?: fallback
}
open fun getTitle(context: Context): String {
return context.getString(titleRes)
}
/**
* A type representing an SMS message, with optional SIM fields for multi-SIM devices.
*/
@Parcelize
data class SmsMessageSendType(override val simName: CharSequence? = null, override val simSubscriptionId: Int? = null) : MessageSendType(
titleRes = R.string.ConversationActivity_transport_insecure_sms,
composeHintRes = R.string.conversation_activity__type_message_sms_insecure,
buttonDrawableRes = R.drawable.ic_send_unlock_24,
menuDrawableRes = R.drawable.ic_insecure_24,
backgroundColorRes = R.color.core_grey_50,
transportType = TransportType.SMS,
characterCalculator = SmsCharacterCalculator(),
simName = simName,
simSubscriptionId = simSubscriptionId
) {
override fun getTitle(context: Context): String {
return if (simName == null) {
super.getTitle(context)
} else {
context.getString(R.string.ConversationActivity_transport_insecure_sms_with_sim, simName)
}
}
}
/**
* A type representing an MMS message, with optional SIM fields for multi-SIM devices.
*/
@Parcelize
data class MmsMessageSendType(override val simName: CharSequence? = null, override val simSubscriptionId: Int? = null) : MessageSendType(
titleRes = R.string.ConversationActivity_transport_insecure_mms,
composeHintRes = R.string.conversation_activity__type_message_mms_insecure,
buttonDrawableRes = R.drawable.ic_send_unlock_24,
menuDrawableRes = R.drawable.ic_insecure_24,
backgroundColorRes = R.color.core_grey_50,
transportType = TransportType.SMS,
characterCalculator = MmsCharacterCalculator(),
simName = simName,
simSubscriptionId = simSubscriptionId
) {
override fun getTitle(context: Context): String {
return if (simName == null) {
super.getTitle(context)
} else {
context.getString(R.string.ConversationActivity_transport_insecure_sms_with_sim, simName)
}
}
}
/**
* A type representing a basic Signal message.
*/
@Parcelize
object SignalMessageSendType : MessageSendType(
titleRes = R.string.ConversationActivity_transport_signal,
composeHintRes = R.string.conversation_activity__type_message_push,
buttonDrawableRes = R.drawable.ic_send_lock_24,
menuDrawableRes = R.drawable.ic_secure_24,
backgroundColorRes = R.color.core_ultramarine,
transportType = TransportType.SIGNAL,
characterCalculator = PushCharacterCalculator()
)
enum class TransportType {
SIGNAL, SMS
}
companion object {
/**
* Returns a list of all available [MessageSendType]s. Requires [Manifest.permission.READ_PHONE_STATE] in order to get available
* SMS options.
*/
@JvmStatic
fun getAllAvailable(context: Context, isMedia: Boolean = false): List<MessageSendType> {
val options: MutableList<MessageSendType> = mutableListOf()
options += SignalMessageSendType
if (!Permissions.hasAll(context, Manifest.permission.READ_PHONE_STATE)) {
return options
}
val subscriptions: Collection<SubscriptionInfoCompat> = SubscriptionManagerCompat(context).activeAndReadySubscriptionInfos
if (subscriptions.size < 2) {
options += if (isMedia) MmsMessageSendType() else SmsMessageSendType()
} else {
options += subscriptions.map {
if (isMedia) {
MmsMessageSendType(simName = it.displayName, simSubscriptionId = it.subscriptionId)
} else {
SmsMessageSendType(simName = it.displayName, simSubscriptionId = it.subscriptionId)
}
}
}
return options
}
@JvmStatic
fun getFirstForTransport(context: Context, isMedia: Boolean, transportType: TransportType): MessageSendType {
return getAllAvailable(context, isMedia).firstOrNull { it.transportType == transportType } ?: throw IllegalArgumentException("No options available for desired type $transportType!")
}
}
}

Wyświetl plik

@ -5,10 +5,9 @@ import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import androidx.core.content.ContextCompat
import org.thoughtcrime.securesms.TransportOption
import org.thoughtcrime.securesms.TransportOptions
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mms.MediaConstraints
@ -84,10 +83,9 @@ object Multiselect {
return false
}
val options = TransportOptions(context, true)
options.setDefaultTransport(TransportOption.Type.SMS)
val sendType: MessageSendType = MessageSendType.getFirstForTransport(context, true, MessageSendType.TransportType.SMS)
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(options.selectedTransport.simSubscriptionId.orElse(-1))
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1)
return mmsConstraints.isSatisfied(context, mediaUri, mediaType, mediaSize) || mmsConstraints.canResize(mediaType)
}
@ -108,10 +106,9 @@ object Multiselect {
return false
}
val options = TransportOptions(context, true)
options.setDefaultTransport(TransportOption.Type.SMS)
val sendType: MessageSendType = MessageSendType.getFirstForTransport(context, true, MessageSendType.TransportType.SMS)
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(options.selectedTransport.simSubscriptionId.orElse(-1))
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1)
return mmsConstraints.isSatisfied(context, attachment) || mmsConstraints.canResize(attachment)
}
}

Wyświetl plik

@ -13,7 +13,7 @@ import androidx.lifecycle.ViewModelProviders;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.conversation.MessageSendType;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Fragment;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4SaveResult;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ViewModel;
@ -47,7 +47,7 @@ public class GiphyActivity extends PassphraseRequiredActivity implements Keyboar
private GiphyMp4ViewModel giphyMp4ViewModel;
private AlertDialog progressDialog;
private RecipientId recipientId;
private TransportOption transport;
private MessageSendType sendType;
private CharSequence text;
@Override
@ -62,7 +62,7 @@ public class GiphyActivity extends PassphraseRequiredActivity implements Keyboar
final boolean forMms = getIntent().getBooleanExtra(EXTRA_IS_MMS, false);
recipientId = getIntent().getParcelableExtra(EXTRA_RECIPIENT_ID);
transport = getIntent().getParcelableExtra(EXTRA_TRANSPORT);
sendType = getIntent().getParcelableExtra(EXTRA_TRANSPORT);
text = getIntent().getCharSequenceExtra(EXTRA_TEXT);
giphyMp4ViewModel = ViewModelProviders.of(this, new GiphyMp4ViewModel.Factory(forMms)).get(GiphyMp4ViewModel.class);
@ -121,7 +121,7 @@ public class GiphyActivity extends PassphraseRequiredActivity implements Keyboar
}
Media media = new Media(success.getBlobUri(), mimeType, 0, success.getWidth(), success.getHeight(), 0, 0, false, true, Optional.empty(), Optional.empty(), Optional.empty());
startActivityForResult(MediaSelectionActivity.editor(this, transport, Collections.singletonList(media), recipientId, text), MEDIA_SENDER);
startActivityForResult(MediaSelectionActivity.editor(this, sendType, Collections.singletonList(media), recipientId, text), MEDIA_SENDER);
}
private void handleGiphyMp4ErrorResult(@NonNull GiphyMp4SaveResult.Error error) {

Wyświetl plik

@ -6,8 +6,8 @@ import android.os.Parcelable;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.conversation.MessageSendType;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.recipients.RecipientId;
@ -30,7 +30,7 @@ public class MediaSendActivityResult implements Parcelable {
private final Collection<PreUploadResult> uploadResults;
private final Collection<Media> nonUploadedMedia;
private final String body;
private final TransportOption transport;
private final MessageSendType sendType;
private final boolean viewOnce;
private final Collection<Mention> mentions;
private final StoryType storyType;
@ -47,32 +47,32 @@ public class MediaSendActivityResult implements Parcelable {
public static @NonNull MediaSendActivityResult forPreUpload(@NonNull RecipientId recipientId,
@NonNull Collection<PreUploadResult> uploadResults,
@NonNull String body,
@NonNull TransportOption transport,
@NonNull MessageSendType sendType,
boolean viewOnce,
@NonNull List<Mention> mentions,
@NonNull StoryType storyType)
{
Preconditions.checkArgument(uploadResults.size() > 0, "Must supply uploadResults!");
return new MediaSendActivityResult(recipientId, uploadResults, Collections.emptyList(), body, transport, viewOnce, mentions, storyType);
return new MediaSendActivityResult(recipientId, uploadResults, Collections.emptyList(), body, sendType, viewOnce, mentions, storyType);
}
public static @NonNull MediaSendActivityResult forTraditionalSend(@NonNull RecipientId recipientId,
@NonNull List<Media> nonUploadedMedia,
@NonNull String body,
@NonNull TransportOption transport,
@NonNull MessageSendType sendType,
boolean viewOnce,
@NonNull List<Mention> mentions,
@NonNull StoryType storyType)
{
Preconditions.checkArgument(nonUploadedMedia.size() > 0, "Must supply media!");
return new MediaSendActivityResult(recipientId, Collections.emptyList(), nonUploadedMedia, body, transport, viewOnce, mentions, storyType);
return new MediaSendActivityResult(recipientId, Collections.emptyList(), nonUploadedMedia, body, sendType, viewOnce, mentions, storyType);
}
private MediaSendActivityResult(@NonNull RecipientId recipientId,
@NonNull Collection<PreUploadResult> uploadResults,
@NonNull List<Media> nonUploadedMedia,
@NonNull String body,
@NonNull TransportOption transport,
@NonNull MessageSendType sendType,
boolean viewOnce,
@NonNull List<Mention> mentions,
@NonNull StoryType storyType)
@ -81,7 +81,7 @@ public class MediaSendActivityResult implements Parcelable {
this.uploadResults = uploadResults;
this.nonUploadedMedia = nonUploadedMedia;
this.body = body;
this.transport = transport;
this.sendType = sendType;
this.viewOnce = viewOnce;
this.mentions = mentions;
this.storyType = storyType;
@ -92,7 +92,7 @@ public class MediaSendActivityResult implements Parcelable {
this.uploadResults = ParcelUtil.readParcelableCollection(in, PreUploadResult.class);
this.nonUploadedMedia = ParcelUtil.readParcelableCollection(in, Media.class);
this.body = in.readString();
this.transport = in.readParcelable(TransportOption.class.getClassLoader());
this.sendType = in.readParcelable(MessageSendType.class.getClassLoader());
this.viewOnce = ParcelUtil.readBoolean(in);
this.mentions = ParcelUtil.readParcelableCollection(in, Mention.class);
this.storyType = StoryType.fromCode(in.readInt());
@ -118,8 +118,8 @@ public class MediaSendActivityResult implements Parcelable {
return body;
}
public @NonNull TransportOption getTransport() {
return transport;
public @NonNull MessageSendType getMessageSendType() {
return sendType;
}
public boolean isViewOnce() {
@ -157,7 +157,7 @@ public class MediaSendActivityResult implements Parcelable {
ParcelUtil.writeParcelableCollection(dest, uploadResults);
ParcelUtil.writeParcelableCollection(dest, nonUploadedMedia);
dest.writeString(body);
dest.writeParcelable(transport, 0);
dest.writeParcelable(sendType, 0);
ParcelUtil.writeBoolean(dest, viewOnce);
ParcelUtil.writeParcelableCollection(dest, mentions);
dest.writeInt(storyType.getCode());

Wyświetl plik

@ -23,12 +23,11 @@ import org.signal.core.util.BreakIteratorCompat
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.TransportOption
import org.thoughtcrime.securesms.TransportOptions
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.contacts.paged.ContactSearchState
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFullScreenDialogFragment
import org.thoughtcrime.securesms.conversation.mutiselect.forward.SearchConfigurationProvider
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog
@ -87,12 +86,12 @@ class MediaSelectionActivity :
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
setContentView(R.layout.media_selection_activity)
val transportOption: TransportOption = requireNotNull(intent.getParcelableExtra(TRANSPORT_OPTION))
val sendType: MessageSendType = requireNotNull(intent.getParcelableExtra(MESSAGE_SEND_TYPE))
val initialMedia: List<Media> = intent.getParcelableArrayListExtra(MEDIA) ?: listOf()
val message: CharSequence? = if (shareToTextStory) null else draftText
val isReply: Boolean = intent.getBooleanExtra(IS_REPLY, false)
val factory = MediaSelectionViewModel.Factory(destination, transportOption, initialMedia, message, isReply, isStory, MediaSelectionRepository(this))
val factory = MediaSelectionViewModel.Factory(destination, sendType, initialMedia, message, isReply, isStory, MediaSelectionRepository(this))
viewModel = ViewModelProvider(this, factory)[MediaSelectionViewModel::class.java]
val textStoryToggle: ConstraintLayout = findViewById(R.id.switch_widget)
@ -346,7 +345,7 @@ class MediaSelectionActivity :
private const val NAV_HOST_TAG = "NAV_HOST"
private const val START_ACTION = "start.action"
private const val TRANSPORT_OPTION = "transport.option"
private const val MESSAGE_SEND_TYPE = "message.send.type"
private const val MEDIA = "media"
private const val MESSAGE = "message"
private const val DESTINATION = "destination"
@ -371,14 +370,14 @@ class MediaSelectionActivity :
@JvmStatic
fun camera(
context: Context,
transportOption: TransportOption,
messageSendType: MessageSendType,
recipientId: RecipientId,
isReply: Boolean
): Intent {
return buildIntent(
context = context,
startAction = R.id.action_directly_to_mediaCaptureFragment,
transportOption = transportOption,
messageSendType = messageSendType,
destination = MediaSelectionDestination.SingleRecipient(recipientId),
isReply = isReply
)
@ -387,7 +386,7 @@ class MediaSelectionActivity :
@JvmStatic
fun gallery(
context: Context,
transportOption: TransportOption,
messageSendType: MessageSendType,
media: List<Media>,
recipientId: RecipientId,
message: CharSequence?,
@ -396,7 +395,7 @@ class MediaSelectionActivity :
return buildIntent(
context = context,
startAction = R.id.action_directly_to_mediaGalleryFragment,
transportOption = transportOption,
messageSendType = messageSendType,
media = media,
destination = MediaSelectionDestination.SingleRecipient(recipientId),
message = message,
@ -407,14 +406,14 @@ class MediaSelectionActivity :
@JvmStatic
fun editor(
context: Context,
transportOption: TransportOption,
messageSendType: MessageSendType,
media: List<Media>,
recipientId: RecipientId,
message: CharSequence?
): Intent {
return buildIntent(
context = context,
transportOption = transportOption,
messageSendType = messageSendType,
media = media,
destination = MediaSelectionDestination.SingleRecipient(recipientId),
message = message
@ -424,7 +423,7 @@ class MediaSelectionActivity :
@JvmStatic
fun share(
context: Context,
transportOption: TransportOption,
messageSendType: MessageSendType,
media: List<Media>,
recipientSearchKeys: List<ContactSearchKey.RecipientSearchKey>,
message: CharSequence?,
@ -432,7 +431,7 @@ class MediaSelectionActivity :
): Intent {
return buildIntent(
context = context,
transportOption = transportOption,
messageSendType = messageSendType,
media = media,
destination = MediaSelectionDestination.MultipleRecipients(recipientSearchKeys),
message = message,
@ -444,7 +443,7 @@ class MediaSelectionActivity :
private fun buildIntent(
context: Context,
startAction: Int = -1,
transportOption: TransportOption = TransportOptions.getPushTransportOption(context),
messageSendType: MessageSendType = MessageSendType.SignalMessageSendType,
media: List<Media> = listOf(),
destination: MediaSelectionDestination = MediaSelectionDestination.ChooseAfterMediaSelection,
message: CharSequence? = null,
@ -454,7 +453,7 @@ class MediaSelectionActivity :
): Intent {
return Intent(context, MediaSelectionActivity::class.java).apply {
putExtra(START_ACTION, startAction)
putExtra(TRANSPORT_OPTION, transportOption)
putExtra(MESSAGE_SEND_TYPE, messageSendType)
putParcelableArrayListExtra(MEDIA, ArrayList(media))
putExtra(MESSAGE, message)
putExtra(DESTINATION, destination.toBundle())

Wyświetl plik

@ -11,8 +11,8 @@ import org.signal.core.util.BreakIteratorCompat
import org.signal.core.util.ThreadUtil
import org.signal.core.util.logging.Log
import org.signal.imageeditor.core.model.EditorModel
import org.thoughtcrime.securesms.TransportOption
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase
@ -76,7 +76,7 @@ class MediaSelectionRepository(context: Context) {
singleContact: ContactSearchKey.RecipientSearchKey?,
contacts: List<ContactSearchKey.RecipientSearchKey>,
mentions: List<Mention>,
transport: TransportOption
sendType: MessageSendType
): Maybe<MediaSendActivityResult> {
if (isSms && contacts.isNotEmpty()) {
throw IllegalStateException("Provided recipients to send to, but this is SMS!")
@ -106,9 +106,9 @@ class MediaSelectionRepository(context: Context) {
if (isSms || MessageSender.isLocalSelfSend(context, singleRecipient, isSms)) {
Log.i(TAG, "SMS or local self-send. Skipping pre-upload.")
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions, StoryType.NONE))
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, sendType, isViewOnce, trimmedMentions, StoryType.NONE))
} else {
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, transport.calculateCharacters(trimmedBody).maxPrimaryMessageSize)
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, sendType.calculateCharacters(trimmedBody).maxPrimaryMessageSize)
val splitBody = splitMessage.body
if (splitMessage.textSlide.isPresent) {
@ -135,10 +135,10 @@ class MediaSelectionRepository(context: Context) {
uploadRepository.deleteAbandonedAttachments()
emitter.onComplete()
} else if (uploadResults.isNotEmpty()) {
emitter.onSuccess(MediaSendActivityResult.forPreUpload(singleRecipient!!.id, uploadResults, splitBody, transport, isViewOnce, trimmedMentions, storyType))
emitter.onSuccess(MediaSendActivityResult.forPreUpload(singleRecipient!!.id, uploadResults, splitBody, sendType, isViewOnce, trimmedMentions, storyType))
} else {
Log.w(TAG, "Got empty upload results! isSms: $isSms, updatedMedia.size(): ${updatedMedia.size}, isViewOnce: $isViewOnce, target: $singleContact")
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, transport, isViewOnce, trimmedMentions, storyType))
emitter.onSuccess(MediaSendActivityResult.forTraditionalSend(singleRecipient!!.id, updatedMedia, trimmedBody, sendType, isViewOnce, trimmedMentions, storyType))
}
}
}

Wyświetl plik

@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.mediasend.v2
import android.net.Uri
import org.thoughtcrime.securesms.TransportOption
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendConstants
@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.recipients.Recipient
data class MediaSelectionState(
val transportOption: TransportOption,
val sendType: MessageSendType,
val selectedMedia: List<Media> = listOf(),
val focusedMedia: Media? = null,
val recipient: Recipient? = null,
@ -25,7 +25,7 @@ data class MediaSelectionState(
val isStory: Boolean
) {
val maxSelection = if (transportOption.isSms) {
val maxSelection = if (sendType.usesSmsTransport) {
MediaSendConstants.MAX_SMS
} else {
MediaSendConstants.MAX_PUSH

Wyświetl plik

@ -10,9 +10,9 @@ import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.subjects.PublishSubject
import org.thoughtcrime.securesms.TransportOption
import org.thoughtcrime.securesms.components.mention.MentionAnnotation
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
import org.thoughtcrime.securesms.mediasend.VideoEditorFragment
@ -31,7 +31,7 @@ import java.util.Collections
*/
class MediaSelectionViewModel(
val destination: MediaSelectionDestination,
transportOption: TransportOption,
sendType: MessageSendType,
initialMedia: List<Media>,
initialMessage: CharSequence?,
val isReply: Boolean,
@ -41,7 +41,7 @@ class MediaSelectionViewModel(
private val store: Store<MediaSelectionState> = Store(
MediaSelectionState(
transportOption = transportOption,
sendType = sendType,
message = initialMessage,
isStory = isStory
)
@ -62,7 +62,7 @@ class MediaSelectionViewModel(
store.update {
it.copy(
isMeteredConnection = metered,
isPreUploadEnabled = shouldPreUpload(metered, it.transportOption.isSms, it.recipient)
isPreUploadEnabled = shouldPreUpload(metered, it.sendType.usesSmsTransport, it.recipient)
)
}
}
@ -75,7 +75,7 @@ class MediaSelectionViewModel(
store.update(Recipient.live(recipientSearchKey.recipientId).liveData) { r, s ->
s.copy(
recipient = r,
isPreUploadEnabled = shouldPreUpload(s.isMeteredConnection, s.transportOption.isSms, r)
isPreUploadEnabled = shouldPreUpload(s.isMeteredConnection, s.sendType.usesSmsTransport, r)
)
}
}
@ -246,8 +246,8 @@ class MediaSelectionViewModel(
}
fun getMediaConstraints(): MediaConstraints {
return if (store.state.transportOption.isSms) {
MediaConstraints.getMmsMediaConstraints(store.state.transportOption.simSubscriptionId.orElse(-1))
return if (store.state.sendType.usesSmsTransport) {
MediaConstraints.getMmsMediaConstraints(store.state.sendType.simSubscriptionId ?: -1)
} else {
MediaConstraints.getPushMediaConstraints()
}
@ -293,18 +293,18 @@ class MediaSelectionViewModel(
store.state.editorStateMap,
store.state.quality,
store.state.message,
store.state.transportOption.isSms,
store.state.sendType.usesSmsTransport,
isViewOnceEnabled(),
destination.getRecipientSearchKey(),
selectedContacts.ifEmpty { destination.getRecipientSearchKeyList() },
MentionAnnotation.getMentionsFromAnnotations(store.state.message),
store.state.transportOption
store.state.sendType
)
)
}
private fun isViewOnceEnabled(): Boolean {
return !store.state.transportOption.isSms &&
return !store.state.sendType.usesSmsTransport &&
store.state.selectedMedia.size == 1 &&
store.state.viewOnceToggleState == MediaSelectionState.ViewOnceToggleState.ONCE
}
@ -427,7 +427,7 @@ class MediaSelectionViewModel(
class Factory(
private val destination: MediaSelectionDestination,
private val transportOption: TransportOption,
private val sendType: MessageSendType,
private val initialMedia: List<Media>,
private val initialMessage: CharSequence?,
private val isReply: Boolean,
@ -435,7 +435,7 @@ class MediaSelectionViewModel(
private val repository: MediaSelectionRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(MediaSelectionViewModel(destination, transportOption, initialMedia, initialMessage, isReply, isStory, repository)))
return requireNotNull(modelClass.cast(MediaSelectionViewModel(destination, sendType, initialMedia, initialMessage, isReply, isStory, repository)))
}
}
}

Wyświetl plik

@ -26,8 +26,8 @@ import androidx.viewpager2.widget.ViewPager2
import app.cash.exhaustive.Exhaustive
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.TransportOption
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
@ -200,7 +200,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
state.selectedMedia.map { MediaReviewSelectedItem.Model(it, state.focusedMedia == it) } + MediaReviewAddItem.Model
)
presentSendButton(state.transportOption)
presentSendButton(state.sendType)
presentPager(state)
presentAddMessageEntry(state.message)
presentImageQualityToggle(state.quality)
@ -289,8 +289,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
)
}
private fun presentSendButton(transportOption: TransportOption) {
val sendButtonTint = if (transportOption.type == TransportOption.Type.TEXTSECURE) {
private fun presentSendButton(sendType: MessageSendType) {
val sendButtonTint = if (sendType.usesSignalTransport) {
R.color.core_ultramarine
} else {
R.color.core_grey_50

Wyświetl plik

@ -42,7 +42,6 @@ import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.DocumentView;
@ -50,6 +49,7 @@ import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.components.location.SignalMapView;
import org.thoughtcrime.securesms.components.location.SignalPlace;
import org.thoughtcrime.securesms.conversation.MessageSendType;
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity;
@ -374,12 +374,12 @@ public class AttachmentManager {
selectMediaType(fragment, "*/*", null, requestCode);
}
public static void selectGallery(Fragment fragment, int requestCode, @NonNull Recipient recipient, @NonNull CharSequence body, @NonNull TransportOption transport, boolean hasQuote) {
public static void selectGallery(Fragment fragment, int requestCode, @NonNull Recipient recipient, @NonNull CharSequence body, @NonNull MessageSendType messageSendType, boolean hasQuote) {
Permissions.with(fragment)
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
.ifNecessary()
.withPermanentDenialDialog(fragment.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
.onAllGranted(() -> fragment.startActivityForResult(MediaSelectionActivity.gallery(fragment.requireContext(), transport, Collections.emptyList(), recipient.getId(), body, hasQuote), requestCode))
.onAllGranted(() -> fragment.startActivityForResult(MediaSelectionActivity.gallery(fragment.requireContext(), messageSendType, Collections.emptyList(), recipient.getId(), body, hasQuote), requestCode))
.execute();
}
@ -404,11 +404,11 @@ public class AttachmentManager {
.execute();
}
public static void selectGif(Fragment fragment, int requestCode, RecipientId id, TransportOption selectedTransport, boolean isForMms, CharSequence textTrimmed) {
public static void selectGif(Fragment fragment, int requestCode, RecipientId id, MessageSendType sendType, boolean isForMms, CharSequence textTrimmed) {
Intent intent = new Intent(fragment.requireContext(), GiphyActivity.class);
intent.putExtra(GiphyActivity.EXTRA_IS_MMS, isForMms);
intent.putExtra(GiphyActivity.EXTRA_RECIPIENT_ID, id);
intent.putExtra(GiphyActivity.EXTRA_TRANSPORT, selectedTransport);
intent.putExtra(GiphyActivity.EXTRA_TRANSPORT, sendType);
intent.putExtra(GiphyActivity.EXTRA_TEXT, textTrimmed);
fragment.startActivityForResult(intent, requestCode);
}

Wyświetl plik

@ -16,9 +16,8 @@ import org.signal.core.util.BreakIteratorCompat;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.TransportOptions;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey;
import org.thoughtcrime.securesms.conversation.MessageSendType;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.Mention;
@ -98,13 +97,13 @@ public final class MultiShareSender {
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
List<Mention> mentions = getValidMentionsForRecipient(recipient, multiShareArgs.getMentions());
TransportOption transport = resolveTransportOption(context, recipient);
boolean forceSms = recipient.isForceSmsSelection() && transport.isSms();
int subscriptionId = transport.getSimSubscriptionId().orElse(-1);
MessageSendType sendType = resolveTransportOption(context, recipient);
boolean forceSms = recipient.isForceSmsSelection() && sendType.usesSmsTransport();
int subscriptionId = sendType.getSimSubscriptionIdOr(-1);
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
boolean needsSplit = !transport.isSms() &&
message != null &&
message.length() > transport.calculateCharacters(message).maxPrimaryMessageSize;
boolean needsSplit = !sendType.usesSmsTransport() &&
message != null &&
message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize;
boolean hasMmsMedia = !multiShareArgs.getMedia().isEmpty() ||
(multiShareArgs.getDataUri() != null && multiShareArgs.getDataUri() != Uri.EMPTY) ||
multiShareArgs.getStickerLocator() != null ||
@ -119,8 +118,8 @@ public final class MultiShareSender {
if ((recipient.isMmsGroup() || recipient.getEmail().isPresent()) && !isMmsEnabled) {
results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.MMS_NOT_ENABLED));
} else if (hasMmsMedia && transport.isSms() || hasPushMedia && !transport.isSms() || canSendAsTextStory) {
sendMediaMessageOrCollectStoryToBatch(context, multiShareArgs, recipient, slideDeck, transport, threadId, forceSms, expiresIn, multiShareArgs.isViewOnce(), subscriptionId, mentions, recipientSearchKey.isStory(), sentTimestamp, canSendAsTextStory, storiesBatch);
} else if (hasMmsMedia && sendType.usesSmsTransport() || hasPushMedia && !sendType.usesSmsTransport() || canSendAsTextStory) {
sendMediaMessageOrCollectStoryToBatch(context, multiShareArgs, recipient, slideDeck, sendType, threadId, forceSms, expiresIn, multiShareArgs.isViewOnce(), subscriptionId, mentions, recipientSearchKey.isStory(), sentTimestamp, canSendAsTextStory, storiesBatch);
results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.SUCCESS));
} else if (recipientSearchKey.isStory()) {
results.add(new MultiShareSendResult(recipientSearchKey, MultiShareSendResult.Type.INVALID_SHARE_TO_STORY));
@ -146,28 +145,26 @@ public final class MultiShareSender {
return new MultiShareSendResultCollection(results);
}
public static @NonNull TransportOption getWorstTransportOption(@NonNull Context context, @NonNull Set<ContactSearchKey.RecipientSearchKey> recipientSearchKeys) {
public static @NonNull MessageSendType getWorstTransportOption(@NonNull Context context, @NonNull Set<ContactSearchKey.RecipientSearchKey> recipientSearchKeys) {
for (ContactSearchKey.RecipientSearchKey recipientSearchKey : recipientSearchKeys) {
TransportOption option = resolveTransportOption(context, Recipient.resolved(recipientSearchKey.getRecipientId()).isForceSmsSelection() && !recipientSearchKey.isStory());
if (option.isSms()) {
return option;
MessageSendType type = resolveTransportOption(context, Recipient.resolved(recipientSearchKey.getRecipientId()).isForceSmsSelection() && !recipientSearchKey.isStory());
if (type.usesSmsTransport()) {
return type;
}
}
return TransportOptions.getPushTransportOption(context);
return MessageSendType.SignalMessageSendType.INSTANCE;
}
private static @NonNull TransportOption resolveTransportOption(@NonNull Context context, @NonNull Recipient recipient) {
private static @NonNull MessageSendType resolveTransportOption(@NonNull Context context, @NonNull Recipient recipient) {
return resolveTransportOption(context, !recipient.isDistributionList() && (recipient.isForceSmsSelection() || !recipient.isRegistered()));
}
public static @NonNull TransportOption resolveTransportOption(@NonNull Context context, boolean forceSms) {
public static @NonNull MessageSendType resolveTransportOption(@NonNull Context context, boolean forceSms) {
if (forceSms) {
TransportOptions options = new TransportOptions(context, false);
options.setDefaultTransport(TransportOption.Type.SMS);
return options.getSelectedTransport();
return MessageSendType.getFirstForTransport(context, false, MessageSendType.TransportType.SMS);
} else {
return TransportOptions.getPushTransportOption(context);
return MessageSendType.SignalMessageSendType.INSTANCE;
}
}
@ -175,7 +172,7 @@ public final class MultiShareSender {
@NonNull MultiShareArgs multiShareArgs,
@NonNull Recipient recipient,
@NonNull SlideDeck slideDeck,
@NonNull TransportOption transportOption,
@NonNull MessageSendType sendType,
long threadId,
boolean forceSms,
long expiresIn,
@ -188,8 +185,8 @@ public final class MultiShareSender {
@NonNull List<OutgoingMediaMessage> storiesToBatchSend)
{
String body = multiShareArgs.getDraftText();
if (transportOption.isType(TransportOption.Type.TEXTSECURE) && !forceSms && body != null) {
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body, transportOption.calculateCharacters(body).maxPrimaryMessageSize);
if (sendType.usesSignalTransport() && !forceSms && body != null) {
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body, sendType.calculateCharacters(body).maxPrimaryMessageSize);
body = splitMessage.getBody();
if (splitMessage.getTextSlide().isPresent()) {

Wyświetl plik

@ -11,10 +11,9 @@ import androidx.core.content.ContextCompat
import androidx.core.util.toKotlinPair
import io.reactivex.rxjava3.core.Single
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.TransportOption
import org.thoughtcrime.securesms.TransportOptions
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendConstants
import org.thoughtcrime.securesms.mms.MediaConstraints
@ -180,9 +179,8 @@ class ShareRepository(context: Context) {
return false
}
val options = TransportOptions(context, true)
options.setDefaultTransport(TransportOption.Type.SMS)
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(options.selectedTransport.simSubscriptionId.orElse(-1))
val sendType: MessageSendType = MessageSendType.getFirstForTransport(context, true, MessageSendType.TransportType.SMS)
val mmsConstraints = MediaConstraints.getMmsMediaConstraints(sendType.simSubscriptionId ?: -1)
return mmsConstraints.isSatisfied(context, attachment) || mmsConstraints.canResize(attachment)
}
}

Wyświetl plik

@ -16,35 +16,13 @@
*/
package org.thoughtcrime.securesms.util;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public abstract class CharacterCalculator {
public abstract class CharacterCalculator implements Parcelable {
public abstract CharacterState calculateCharacters(String messageBody);
public static CharacterCalculator readFromParcel(@NonNull Parcel in) {
switch (in.readInt()) {
case 1: return new SmsCharacterCalculator();
case 2: return new MmsCharacterCalculator();
case 3: return new PushCharacterCalculator();
default: throw new IllegalArgumentException("Read an unsupported value for a calculator.");
}
}
public static void writeToParcel(@NonNull Parcel dest, @NonNull CharacterCalculator calculator) {
if (calculator instanceof SmsCharacterCalculator) {
dest.writeInt(1);
} else if (calculator instanceof MmsCharacterCalculator) {
dest.writeInt(2);
} else if (calculator instanceof PushCharacterCalculator) {
dest.writeInt(3);
} else {
throw new IllegalArgumentException("Tried to write an unsupported calculator to a parcel.");
}
}
public static class CharacterState {
public final int charactersRemaining;
public final int messagesSpent;

Wyświetl plik

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.util;
import android.os.Parcel;
public class MmsCharacterCalculator extends CharacterCalculator {
private static final int MAX_SIZE = 5000;
@ -8,4 +10,25 @@ public class MmsCharacterCalculator extends CharacterCalculator {
public CharacterState calculateCharacters(String messageBody) {
return new CharacterState(1, MAX_SIZE - messageBody.length(), MAX_SIZE, MAX_SIZE);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
public static final Creator<MmsCharacterCalculator> CREATOR = new Creator<MmsCharacterCalculator>() {
@Override
public MmsCharacterCalculator createFromParcel(Parcel in) {
return new MmsCharacterCalculator();
}
@Override
public MmsCharacterCalculator[] newArray(int size) {
return new MmsCharacterCalculator[size];
}
};
}

Wyświetl plik

@ -16,6 +16,8 @@
*/
package org.thoughtcrime.securesms.util;
import android.os.Parcel;
public class PushCharacterCalculator extends CharacterCalculator {
private static final int MAX_TOTAL_SIZE = 64 * 1024;
private static final int MAX_PRIMARY_SIZE = 2000;
@ -23,5 +25,26 @@ public class PushCharacterCalculator extends CharacterCalculator {
public CharacterState calculateCharacters(String messageBody) {
return new CharacterState(1, MAX_TOTAL_SIZE - messageBody.length(), MAX_TOTAL_SIZE, MAX_PRIMARY_SIZE);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
public static final Creator<PushCharacterCalculator> CREATOR = new Creator<PushCharacterCalculator>() {
@Override
public PushCharacterCalculator createFromParcel(Parcel in) {
return new PushCharacterCalculator();
}
@Override
public PushCharacterCalculator[] newArray(int size) {
return new PushCharacterCalculator[size];
}
};
}

Wyświetl plik

@ -16,6 +16,7 @@
*/
package org.thoughtcrime.securesms.util;
import android.os.Parcel;
import android.telephony.SmsMessage;
import org.signal.core.util.logging.Log;
@ -62,4 +63,25 @@ public class SmsCharacterCalculator extends CharacterCalculator {
return new CharacterState(messagesSpent, charactersRemaining, maxMessageSize, maxMessageSize);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
public static final Creator<SmsCharacterCalculator> CREATOR = new Creator<SmsCharacterCalculator>() {
@Override
public SmsCharacterCalculator createFromParcel(Parcel in) {
return new SmsCharacterCalculator();
}
@Override
public SmsCharacterCalculator[] newArray(int size) {
return new SmsCharacterCalculator[size];
}
};
}

Wyświetl plik

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M2.833,22.691H13.161C14.72,22.691 15.494,21.917 15.494,20.214V12.308C15.494,10.87 14.93,10.096 13.802,9.897V7.133C13.802,4.247 15.638,2.544 17.971,2.544C20.304,2.544 22.129,4.247 22.129,7.133V9.554C22.129,10.052 22.438,10.317 22.826,10.317C23.19,10.317 23.5,10.074 23.5,9.554V7.265C23.5,3.373 20.968,1.25 17.971,1.25C14.964,1.25 12.431,3.373 12.431,7.265V9.853H2.833C1.274,9.853 0.5,10.616 0.5,12.308V20.214C0.5,21.917 1.274,22.691 2.833,22.691ZM2.866,21.408C2.192,21.408 1.871,21.132 1.871,20.302V12.23C1.871,11.39 2.192,11.125 2.866,11.125H13.139C13.814,11.125 14.123,11.39 14.123,12.23V20.302C14.123,21.132 13.814,21.408 13.139,21.408H2.866Z"
android:strokeWidth="0.25"
android:fillColor="#000000"
android:strokeColor="#000000"/>
</vector>

Wyświetl plik

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6.694,23H17.291C18.891,23 19.685,22.206 19.685,20.458V12.346C19.685,10.746 19.016,9.963 17.666,9.839V7.172C17.666,3.178 15.067,1 11.993,1C8.918,1 6.32,3.178 6.32,7.172V9.839C4.969,9.963 4.3,10.746 4.3,12.346V20.458C4.3,22.206 5.094,23 6.694,23ZM7.715,7.036C7.715,4.075 9.587,2.327 11.993,2.327C14.387,2.327 16.27,4.075 16.27,7.036V9.827H7.715V7.036ZM6.728,21.684C6.036,21.684 5.707,21.4 5.707,20.549V12.267C5.707,11.404 6.036,11.132 6.728,11.132H17.268C17.961,11.132 18.278,11.404 18.278,12.267V20.549C18.278,21.4 17.961,21.684 17.268,21.684H6.728Z"
android:strokeWidth="0.25"
android:fillColor="#000000"
android:strokeColor="#000000"/>
</vector>

Wyświetl plik

@ -259,8 +259,12 @@
<string name="ConversationActivity_calls_not_supported">Calls not supported</string>
<string name="ConversationActivity_this_device_does_not_appear_to_support_dial_actions">This device does not appear to support dial actions.</string>
<string name="ConversationActivity_transport_insecure_sms">Insecure SMS</string>
<!-- A title for the option to send an SMS with a placeholder to put the name of their SIM card -->
<string name="ConversationActivity_transport_insecure_sms_with_sim">Insecure SMS (%1$s)</string>
<string name="ConversationActivity_transport_insecure_mms">Insecure MMS</string>
<string name="ConversationActivity_transport_signal">Signal</string>
<!-- A title for the option to send an SMS with a placeholder to put the name of their SIM card -->
<string name="ConversationActivity_transport_insecure_mms_with_sim">Insecure MMS (%1$s)</string>
<string name="ConversationActivity_transport_signal">Signal message</string>
<string name="ConversationActivity_lets_switch_to_signal">Let\'s switch to Signal %1$s</string>
<string name="ConversationActivity_specify_recipient">Please choose a contact</string>
<string name="ConversationActivity_unblock">Unblock</string>

Wyświetl plik

@ -2,30 +2,22 @@ package org.signal.qr
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.ImageFormat
import android.util.Size
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import androidx.camera.core.AspectRatio
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils.clamp
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import io.reactivex.rxjava3.subjects.PublishSubject
import org.signal.core.util.logging.Log
import org.signal.qr.kitkat.ScanListener
import java.nio.ByteBuffer
import java.util.concurrent.Executors
/**
* API21+ version of QR scanning view. Uses camerax APIs.
*/