Merge remote-tracking branch 'origin/dev' into merge-groups-to-dev

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt
#	app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt
pull/1710/head
SessionHero01 4 months ago
commit cc769c0c30
No known key found for this signature in database

@ -406,14 +406,13 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
@SuppressWarnings("CodeBlock2Expr")
@SuppressLint("InlinedApi")
private void saveToDisk() {
Log.w("ACL", "Asked to save to disk!");
MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem == null) return;
SaveAttachmentTask.showOneTimeWarningDialogOrSave(this, 1, () -> {
Permissions.with(this)
.request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
.maxSdkVersion(Build.VERSION_CODES.P)
.maxSdkVersion(Build.VERSION_CODES.P) // Note: P is API 28
.withPermanentDenialDialog(getPermanentlyDeniedStorageText())
.onAnyDenied(() -> {
Toast.makeText(this, getPermanentlyDeniedStorageText(), Toast.LENGTH_LONG).show();

@ -24,6 +24,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.IBinder;
@ -40,11 +42,13 @@ import com.squareup.phrase.Phrase;
import java.security.Signature;
import network.loki.messenger.R;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.ThemeUtil;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.crypto.BiometricSecretProvider;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.AnimationCompleteListener;
import org.thoughtcrime.securesms.util.ResUtil;
//TODO Rename to ScreenLockActivity and refactor to Kotlin.
public class PassphrasePromptActivity extends BaseActionBarActivity {
@ -68,11 +72,17 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
private KeyCachingService keyCachingService;
private int accentColor;
private int errorColor;
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate()");
super.onCreate(savedInstanceState);
accentColor = ThemeUtil.getThemedColor(this, R.attr.accentColor);
errorColor = ThemeUtil.getThemedColor(this, R.attr.danger);
setContentView(R.layout.prompt_passphrase_activity);
initializeResources();
@ -171,7 +181,7 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
fingerprintListener = new FingerprintListener();
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);
lockScreenButton.setOnClickListener(v -> resumeScreenLock());
}
@ -251,15 +261,15 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
// authentication failed
onAuthenticationFailed();
} else {
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.setImageResource(R.drawable.ic_check);
fingerprintPrompt.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
handleAuthenticated();
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);
}
}).start();
}
@ -281,15 +291,15 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
return;
}
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.setImageResource(R.drawable.ic_check);
fingerprintPrompt.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
handleAuthenticated();
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);
}
}).start();
}
@ -298,8 +308,8 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
public void onAuthenticationFailed() {
Log.w(TAG, "onAuthenticationFailed()");
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.setImageResource(R.drawable.ic_x);
fingerprintPrompt.getBackground().setColorFilter(errorColor, PorterDuff.Mode.SRC_IN);
TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
shake.setDuration(50);
@ -311,7 +321,7 @@ public class PassphrasePromptActivity extends BaseActionBarActivity {
@Override
public void onAnimationEnd(Animation animation) {
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
fingerprintPrompt.getBackground().setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);
}
@Override

@ -17,6 +17,7 @@ import android.widget.Space
import android.widget.TextView
import androidx.annotation.AttrRes
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import androidx.annotation.StyleRes
@ -111,8 +112,6 @@ class SessionDialogBuilder(val context: Context) {
fun view(@LayoutRes layout: Int): View = LayoutInflater.from(context).inflate(layout, contentView)
fun iconAttribute(@AttrRes icon: Int): AlertDialog.Builder = dialogBuilder.setIconAttribute(icon)
fun singleChoiceItems(
options: Collection<String>,
currentSelected: Int = 0,

@ -15,6 +15,7 @@ import android.view.inputmethod.InputConnection;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.os.BuildCompat;
import androidx.core.view.inputmethod.EditorInfoCompat;
import androidx.core.view.inputmethod.InputConnectionCompat;
@ -22,9 +23,8 @@ import androidx.core.view.inputmethod.InputContentInfoCompat;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
public class ComposeText extends EmojiEditText {
public class ComposeText extends AppCompatEditText {
private CharSequence hint;
private SpannableString subHint;
@ -101,31 +101,6 @@ public class ComposeText extends EmojiEditText {
}
}
public void setCursorPositionChangedListener(@Nullable CursorPositionChangedListener listener) {
this.cursorPositionChangedListener = listener;
}
public void setTransport() {
final boolean useSystemEmoji = TextSecurePreferences.isSystemEmojiPreferred(getContext());
final boolean isIncognito = TextSecurePreferences.isIncognitoKeyboardEnabled(getContext());
int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND;
int inputType = getInputType();
setImeActionLabel(null, 0);
if (useSystemEmoji) {
inputType = (inputType & ~InputType.TYPE_MASK_VARIATION) | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE;
}
setInputType(inputType);
if (isIncognito) {
setImeOptions(imeOptions | 16777216);
} else {
setImeOptions(imeOptions);
}
}
@Override
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
InputConnection inputConnection = super.onCreateInputConnection(editorInfo);
@ -141,10 +116,6 @@ public class ComposeText extends EmojiEditText {
return InputConnectionCompat.createWrapper(inputConnection, editorInfo, new CommitContentListener(mediaListener));
}
public void setMediaListener(@Nullable InputPanel.MediaListener mediaListener) {
this.mediaListener = mediaListener;
}
private void initialize() {
if (TextSecurePreferences.isIncognitoKeyboardEnabled(getContext())) {
setImeOptions(getImeOptions() | 16777216);

@ -1,68 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import network.loki.messenger.R;
public class ConversationItemAlertView extends LinearLayout {
private static final String TAG = ConversationItemAlertView.class.getSimpleName();
private ImageView approvalIndicator;
private ImageView failedIndicator;
public ConversationItemAlertView(Context context) {
this(context, null);
}
public ConversationItemAlertView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(attrs);
}
public ConversationItemAlertView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(attrs);
}
private void initialize(AttributeSet attrs) {
inflate(getContext(), R.layout.alert_view, this);
approvalIndicator = findViewById(R.id.pending_approval_indicator);
failedIndicator = findViewById(R.id.sms_failed_indicator);
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.AlertView, 0, 0);
boolean useSmallIcon = typedArray.getBoolean(R.styleable.AlertView_useSmallIcon, false);
typedArray.recycle();
if (useSmallIcon) {
int size = getResources().getDimensionPixelOffset(R.dimen.alertview_small_icon_size);
failedIndicator.getLayoutParams().width = size;
failedIndicator.getLayoutParams().height = size;
requestLayout();
}
}
}
public void setNone() {
this.setVisibility(View.GONE);
}
public void setPendingApproval() {
this.setVisibility(View.VISIBLE);
approvalIndicator.setVisibility(View.VISIBLE);
failedIndicator.setVisibility(View.GONE);
}
public void setFailed() {
this.setVisibility(View.VISIBLE);
approvalIndicator.setVisibility(View.GONE);
failedIndicator.setVisibility(View.VISIBLE);
}
}

@ -1,104 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import network.loki.messenger.R;
public class DeliveryStatusView extends FrameLayout {
private static final String TAG = DeliveryStatusView.class.getSimpleName();
private static final RotateAnimation ROTATION_ANIMATION = new RotateAnimation(0, 360f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
static {
ROTATION_ANIMATION.setInterpolator(new LinearInterpolator());
ROTATION_ANIMATION.setDuration(1500);
ROTATION_ANIMATION.setRepeatCount(Animation.INFINITE);
}
private final ImageView pendingIndicator;
private final ImageView sentIndicator;
private final ImageView deliveredIndicator;
private final ImageView readIndicator;
public DeliveryStatusView(Context context) {
this(context, null);
}
public DeliveryStatusView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DeliveryStatusView(final Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
inflate(context, R.layout.delivery_status_view, this);
this.deliveredIndicator = findViewById(R.id.delivered_indicator);
this.sentIndicator = findViewById(R.id.sent_indicator);
this.pendingIndicator = findViewById(R.id.pending_indicator);
this.readIndicator = findViewById(R.id.read_indicator);
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DeliveryStatusView, 0, 0);
setTint(typedArray.getColor(R.styleable.DeliveryStatusView_iconColor, getResources().getColor(R.color.core_white)));
typedArray.recycle();
}
}
public void setNone() {
this.setVisibility(View.GONE);
}
public void setPending() {
this.setVisibility(View.GONE);
pendingIndicator.setVisibility(View.VISIBLE);
pendingIndicator.startAnimation(ROTATION_ANIMATION);
sentIndicator.setVisibility(View.GONE);
deliveredIndicator.setVisibility(View.GONE);
readIndicator.setVisibility(View.GONE);
}
public void setSent() {
this.setVisibility(View.GONE);
pendingIndicator.setVisibility(View.GONE);
pendingIndicator.clearAnimation();
sentIndicator.setVisibility(View.VISIBLE);
deliveredIndicator.setVisibility(View.GONE);
readIndicator.setVisibility(View.GONE);
}
public void setDelivered() {
this.setVisibility(View.GONE);
pendingIndicator.setVisibility(View.GONE);
pendingIndicator.clearAnimation();
sentIndicator.setVisibility(View.GONE);
deliveredIndicator.setVisibility(View.VISIBLE);
readIndicator.setVisibility(View.GONE);
}
public void setRead() {
this.setVisibility(View.GONE);
pendingIndicator.setVisibility(View.GONE);
pendingIndicator.clearAnimation();
sentIndicator.setVisibility(View.GONE);
deliveredIndicator.setVisibility(View.GONE);
readIndicator.setVisibility(View.VISIBLE);
}
public void setTint(int color) {
pendingIndicator.setColorFilter(color);
deliveredIndicator.setColorFilter(color);
sentIndicator.setColorFilter(color);
readIndicator.setColorFilter(color);
}
}

@ -1,185 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import network.loki.messenger.R;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.mms.DocumentSlide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.guava.Optional;
public class DocumentView extends FrameLayout {
private static final String TAG = DocumentView.class.getSimpleName();
private final @NonNull AnimatingToggle controlToggle;
private final @NonNull ImageView downloadButton;
private final @NonNull ProgressWheel downloadProgress;
private final @NonNull View container;
private final @NonNull ViewGroup iconContainer;
private final @NonNull TextView fileName;
private final @NonNull TextView fileSize;
private final @NonNull TextView document;
private @Nullable SlideClickListener downloadListener;
private @Nullable SlideClickListener viewListener;
private @Nullable DocumentSlide documentSlide;
public DocumentView(@NonNull Context context) {
this(context, null);
}
public DocumentView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DocumentView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflate(context, R.layout.document_view, this);
this.container = findViewById(R.id.document_container);
this.iconContainer = findViewById(R.id.icon_container);
this.controlToggle = findViewById(R.id.control_toggle);
this.downloadButton = findViewById(R.id.download);
this.downloadProgress = findViewById(R.id.download_progress);
this.fileName = findViewById(R.id.file_name);
this.fileSize = findViewById(R.id.file_size);
this.document = findViewById(R.id.document);
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.DocumentView, 0, 0);
int titleColor = typedArray.getInt(R.styleable.DocumentView_doc_titleColor, Color.BLACK);
int captionColor = typedArray.getInt(R.styleable.DocumentView_doc_captionColor, Color.BLACK);
int downloadTint = typedArray.getInt(R.styleable.DocumentView_doc_downloadButtonTint, Color.WHITE);
typedArray.recycle();
fileName.setTextColor(titleColor);
fileSize.setTextColor(captionColor);
downloadButton.setColorFilter(downloadTint, PorterDuff.Mode.MULTIPLY);
downloadProgress.setBarColor(downloadTint);
}
}
public void setDownloadClickListener(@Nullable SlideClickListener listener) {
this.downloadListener = listener;
}
public void setDocumentClickListener(@Nullable SlideClickListener listener) {
this.viewListener = listener;
}
public void setDocument(final @NonNull DocumentSlide documentSlide,
final boolean showControls)
{
if (showControls && documentSlide.isPendingDownload()) {
controlToggle.displayQuick(downloadButton);
downloadButton.setOnClickListener(new DownloadClickedListener(documentSlide));
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
} else if (showControls && documentSlide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED) {
controlToggle.displayQuick(downloadProgress);
downloadProgress.spin();
} else {
controlToggle.displayQuick(iconContainer);
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
}
this.documentSlide = documentSlide;
this.fileName.setText(documentSlide.getFileName().or(getContext().getString(R.string.attachmentsErrorNotSupported)));
this.fileSize.setText(Util.getPrettyFileSize(documentSlide.getFileSize()));
this.document.setText(getFileType(documentSlide.getFileName()));
this.setOnClickListener(new OpenClickedListener(documentSlide));
}
@Override
public void setFocusable(boolean focusable) {
super.setFocusable(focusable);
this.downloadButton.setFocusable(focusable);
}
@Override
public void setClickable(boolean clickable) {
super.setClickable(clickable);
this.downloadButton.setClickable(clickable);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
this.downloadButton.setEnabled(enabled);
}
private @NonNull String getFileType(Optional<String> fileName) {
if (!fileName.isPresent()) return "";
String[] parts = fileName.get().split("\\.");
if (parts.length < 2) {
return "";
}
String suffix = parts[parts.length - 1];
if (suffix.length() <= 3) {
return suffix;
}
return "";
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventAsync(final PartProgressEvent event) {
if (documentSlide != null && event.attachment.equals(documentSlide.asAttachment())) {
downloadProgress.setInstantProgress(((float) event.progress) / event.total);
}
}
private class DownloadClickedListener implements View.OnClickListener {
private final @NonNull DocumentSlide slide;
private DownloadClickedListener(@NonNull DocumentSlide slide) {
this.slide = slide;
}
@Override
public void onClick(View v) {
if (downloadListener != null) downloadListener.onClick(v, slide);
}
}
private class OpenClickedListener implements View.OnClickListener {
private final @NonNull DocumentSlide slide;
private OpenClickedListener(@NonNull DocumentSlide slide) {
this.slide = slide;
}
@Override
public void onClick(View v) {
if (!slide.isPendingDownload() && !slide.isInProgress() && viewListener != null) {
viewListener.onClick(v, slide);
}
}
}
}

@ -14,8 +14,9 @@ import android.text.style.TypefaceSpan;
import android.util.AttributeSet;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.util.ResUtil;
import org.session.libsession.utilities.CenterAlignedRelativeSizeSpan;
@ -74,8 +75,8 @@ public class FromTextView extends EmojiTextView {
setText(builder);
if (recipient.isBlocked()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_block_grey600_18dp, 0, 0, 0);
else if (recipient.isMuted()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off_grey600_18dp, 0, 0, 0);
if (recipient.isBlocked()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_ban, 0, 0, 0);
else if (recipient.isMuted()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off, 0, 0, 0);
else setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}

@ -1,87 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.Editable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import network.loki.messenger.R;
public class LabeledEditText extends FrameLayout implements View.OnFocusChangeListener {
private TextView label;
private EditText input;
private View border;
private ViewGroup textContainer;
public LabeledEditText(@NonNull Context context) {
super(context);
init(null);
}
public LabeledEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
private void init(@Nullable AttributeSet attrs) {
inflate(getContext(), R.layout.labeled_edit_text, this);
String labelText = "";
int backgroundColor = Color.BLACK;
int textLayout = R.layout.labeled_edit_text_default;
if (attrs != null) {
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.LabeledEditText, 0, 0);
labelText = typedArray.getString(R.styleable.LabeledEditText_labeledEditText_label);
backgroundColor = typedArray.getColor(R.styleable.LabeledEditText_labeledEditText_background, Color.BLACK);
textLayout = typedArray.getResourceId(R.styleable.LabeledEditText_labeledEditText_textLayout, R.layout.labeled_edit_text_default);
typedArray.recycle();
}
label = findViewById(R.id.label);
border = findViewById(R.id.border);
textContainer = findViewById(R.id.text_container);
inflate(getContext(), textLayout, textContainer);
input = findViewById(R.id.input);
label.setText(labelText);
label.setBackgroundColor(backgroundColor);
if (TextUtils.isEmpty(labelText)) {
label.setVisibility(INVISIBLE);
}
input.setOnFocusChangeListener(this);
}
public EditText getInput() {
return input;
}
public void setText(String text) {
input.setText(text);
}
public Editable getText() {
return input.getText();
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
border.setBackgroundResource(hasFocus ? R.drawable.labeled_edit_text_background_active
: R.drawable.labeled_edit_text_background_inactive);
}
}

@ -68,7 +68,7 @@ class ProfilePictureView @JvmOverloads constructor(
}
private fun createUnknownRecipientDrawable(): Drawable {
return ResourceContactPhoto(R.drawable.ic_profile_default)
return ResourceContactPhoto(R.drawable.ic_user_filled_custom)
.asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false, resourcePadding)
}

@ -1,79 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import network.loki.messenger.R;
public class RemovableEditableMediaView extends FrameLayout {
private final @NonNull ImageView remove;
private final @NonNull ImageView edit;
private final int removeSize;
private final int editSize;
private @Nullable View current;
public RemovableEditableMediaView(Context context) {
this(context, null);
}
public RemovableEditableMediaView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RemovableEditableMediaView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.remove = (ImageView)LayoutInflater.from(context).inflate(R.layout.media_view_remove_button, this, false);
this.edit = (ImageView)LayoutInflater.from(context).inflate(R.layout.media_view_edit_button, this, false);
this.removeSize = getResources().getDimensionPixelSize(R.dimen.media_bubble_remove_button_size);
this.editSize = getResources().getDimensionPixelSize(R.dimen.media_bubble_edit_button_size);
this.remove.setVisibility(View.GONE);
this.edit.setVisibility(View.GONE);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
this.addView(remove);
this.addView(edit);
}
public void display(@Nullable View view, boolean editable) {
edit.setVisibility(editable ? View.VISIBLE : View.GONE);
if (view == current) return;
if (current != null) current.setVisibility(View.GONE);
if (view != null) {
view.setPadding(view.getPaddingLeft(), removeSize / 2, removeSize / 2, (int)(8 * getResources().getDisplayMetrics().density));
edit.setPadding(0, 0, removeSize / 2, 0);
view.setVisibility(View.VISIBLE);
remove.setVisibility(View.VISIBLE);
} else {
remove.setVisibility(View.GONE);
edit.setVisibility(View.GONE);
}
current = view;
}
public void setRemoveClickListener(View.OnClickListener listener) {
this.remove.setOnClickListener(listener);
}
public void setEditClickListener(View.OnClickListener listener) {
this.edit.setOnClickListener(listener);
}
}

@ -1,84 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import androidx.appcompat.widget.AppCompatImageButton;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
public class RepeatableImageKey extends AppCompatImageButton {
private KeyEventListener listener;
public RepeatableImageKey(Context context) {
super(context);
init();
}
public RepeatableImageKey(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RepeatableImageKey(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOnClickListener(new RepeaterClickListener());
setOnTouchListener(new RepeaterTouchListener());
}
public void setOnKeyEventListener(KeyEventListener listener) {
this.listener = listener;
}
private void notifyListener() {
if (this.listener != null) this.listener.onKeyEvent();
}
private class RepeaterClickListener implements OnClickListener {
@Override public void onClick(View v) {
notifyListener();
}
}
private class Repeater implements Runnable {
@Override
public void run() {
notifyListener();
postDelayed(this, ViewConfiguration.getKeyRepeatDelay());
}
}
private class RepeaterTouchListener implements OnTouchListener {
private final Repeater repeater;
RepeaterTouchListener() {
this.repeater = new Repeater();
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
view.postDelayed(repeater, ViewConfiguration.getKeyRepeatTimeout());
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
return false;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
view.removeCallbacks(repeater);
return false;
default:
return false;
}
}
}
public interface KeyEventListener {
void onKeyEvent();
}
}

@ -39,7 +39,7 @@ public class SearchToolbar extends Toolbar {
}
private void initialize() {
setNavigationIcon(getContext().getResources().getDrawable(R.drawable.ic_baseline_clear_24));
setNavigationIcon(getContext().getResources().getDrawable(R.drawable.ic_x));
inflateMenu(R.menu.conversation_list_search);
this.searchItem = getMenu().findItem(R.id.action_filter_search);

@ -1,182 +0,0 @@
package org.thoughtcrime.securesms.components
import android.animation.LayoutTransition
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.annimon.stream.Stream
import com.pnikosis.materialishprogress.ProgressWheel
import kotlin.math.max
import network.loki.messenger.R
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY
import org.session.libsession.utilities.ViewUtil
import org.thoughtcrime.securesms.events.PartProgressEvent
import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.ui.getSubbedString
class TransferControlView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context!!, attrs, defStyleAttr) {
private var slides: List<Slide>? = null
private var current: View? = null
private val progressWheel: ProgressWheel
private val downloadDetails: View
private val downloadDetailsText: TextView
private val downloadProgress: MutableMap<Attachment, Float>
init {
inflate(context, R.layout.transfer_controls_view, this)
isLongClickable = false
ViewUtil.setBackground(this, ContextCompat.getDrawable(context!!, R.drawable.transfer_controls_background))
visibility = GONE
layoutTransition = LayoutTransition()
this.downloadProgress = HashMap()
this.progressWheel = ViewUtil.findById(this, R.id.progress_wheel)
this.downloadDetails = ViewUtil.findById(this, R.id.download_details)
this.downloadDetailsText = ViewUtil.findById(this, R.id.download_details_text)
}
override fun setFocusable(focusable: Boolean) {
super.setFocusable(focusable)
downloadDetails.isFocusable = focusable
}
override fun setClickable(clickable: Boolean) {
super.setClickable(clickable)
downloadDetails.isClickable = clickable
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
EventBus.getDefault().unregister(this)
}
private fun setSlides(slides: List<Slide>) {
require(slides.isNotEmpty()) { "Must provide at least one slide." }
this.slides = slides
if (!isUpdateToExistingSet(slides)) {
downloadProgress.clear()
Stream.of(slides).forEach { s: Slide -> downloadProgress[s.asAttachment()] = 0f }
}
for (slide in slides) {
if (slide.asAttachment().transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) {
downloadProgress[slide.asAttachment()] = 1f
}
}
when (getTransferState(slides)) {
AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED -> showProgressSpinner(calculateProgress(downloadProgress))
AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING, AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED -> {
downloadDetailsText.text = getDownloadText(this.slides!!)
display(downloadDetails)
}
else -> display(null)
}
}
@JvmOverloads
fun showProgressSpinner(progress: Float = calculateProgress(downloadProgress)) {
if (progress == 0f) {
progressWheel.spin()
} else {
progressWheel.setInstantProgress(progress)
}
display(progressWheel)
}
fun clear() {
clearAnimation()
visibility = GONE
if (current != null) {
current!!.clearAnimation()
current!!.visibility = GONE
}
current = null
slides = null
}
private fun isUpdateToExistingSet(slides: List<Slide>): Boolean {
if (slides.size != downloadProgress.size) {
return false
}
for (slide in slides) {
if (!downloadProgress.containsKey(slide.asAttachment())) {
return false
}
}
return true
}
private fun getTransferState(slides: List<Slide>): Int {
var transferState = AttachmentTransferProgress.TRANSFER_PROGRESS_DONE
for (slide in slides) {
transferState = if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING && transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) {
slide.transferState
} else {
max(transferState.toDouble(), slide.transferState.toDouble()).toInt()
}
}
return transferState
}
private fun getDownloadText(slides: List<Slide>): String {
if (slides.size == 1) {
return slides[0].contentDescription
} else {
val downloadCount = Stream.of(slides).reduce(0) { count: Int, slide: Slide ->
if (slide.transferState != AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) count + 1 else count
}
return context.getSubbedString(R.string.andMore, COUNT_KEY to downloadCount.toString())
}
}
private fun display(view: View?) {
if (current != null) {
current!!.visibility = GONE
}
if (view != null) {
view.visibility = VISIBLE
} else {
visibility = GONE
}
current = view
}
private fun calculateProgress(downloadProgress: Map<Attachment, Float>): Float {
var totalProgress = 0f
for (progress in downloadProgress.values) {
totalProgress += progress / downloadProgress.size
}
return totalProgress
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventAsync(event: PartProgressEvent) {
if (downloadProgress.containsKey(event.attachment)) {
downloadProgress[event.attachment] = event.progress.toFloat() / event.total
progressWheel.setInstantProgress(calculateProgress(downloadProgress))
}
}
}

@ -1,18 +0,0 @@
package org.thoughtcrime.securesms.components.dialogs
import android.content.Context
import network.loki.messenger.R
import org.thoughtcrime.securesms.showSessionDialog
class DeleteMediaDialog {
companion object {
@JvmStatic
fun show(context: Context, recordCount: Int, doDelete: Runnable) = context.showSessionDialog {
iconAttribute(R.attr.dialog_alert_icon)
title(context.resources.getQuantityString(R.plurals.deleteMessage, recordCount, recordCount))
text(context.resources.getString(R.string.deleteMessageDescriptionEveryone))
dangerButton(R.string.delete) { doDelete.run() }
cancelButton()
}
}
}

@ -9,7 +9,6 @@ class DeleteMediaPreviewDialog {
@JvmStatic
fun show(context: Context, doDelete: Runnable) {
context.showSessionDialog {
iconAttribute(R.attr.dialog_alert_icon)
title(context.resources.getQuantityString(R.plurals.deleteMessage, 1, 1))
text(R.string.deleteMessageDescriptionEveryone)
dangerButton(R.string.delete) { doDelete.run() }

@ -37,7 +37,7 @@ public class EmojiImageView extends AppCompatImageView {
Drawable emojiDrawable = EmojiProvider.getEmojiDrawable(getContext(), emoji);
if (emojiDrawable == null) {
// fallback
setImageResource(R.drawable.ic_outline_disabled_by_default_24);
setImageResource(R.drawable.ic_square_x);
} else {
setImageDrawable(emojiDrawable);
}

@ -1,162 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
import com.bumptech.glide.RequestManager;
import org.thoughtcrime.securesms.util.ResUtil;
import org.session.libsession.utilities.ThemeUtil;
import java.util.LinkedList;
import java.util.List;
import network.loki.messenger.R;
/**
* A provider to select emoji in the {@link org.thoughtcrime.securesms.components.emoji.MediaKeyboard}.
*/
public class EmojiKeyboardProvider implements MediaKeyboardProvider,
MediaKeyboardProvider.TabIconProvider,
MediaKeyboardProvider.BackspaceObserver,
VariationSelectorListener
{
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
private final Context context;
private final List<EmojiPageModel> models;
private final RecentEmojiPageModel recentModel;
private final EmojiPagerAdapter emojiPagerAdapter;
private final EmojiEventListener emojiEventListener;
private Controller controller;
public EmojiKeyboardProvider(@NonNull Context context, @Nullable EmojiEventListener emojiEventListener) {
this.context = context;
this.emojiEventListener = emojiEventListener;
this.models = new LinkedList<>();
this.recentModel = new RecentEmojiPageModel(context);
this.emojiPagerAdapter = new EmojiPagerAdapter(context, models, new EmojiEventListener() {
@Override
public void onEmojiSelected(String emoji) {
recentModel.onCodePointSelected(emoji);
if (emojiEventListener != null) {
emojiEventListener.onEmojiSelected(emoji);
}
}
@Override
public void onKeyEvent(KeyEvent keyEvent) {
if (emojiEventListener != null) {
emojiEventListener.onKeyEvent(keyEvent);
}
}
}, this);
models.add(recentModel);
models.addAll(EmojiPages.DISPLAY_PAGES);
}
@Override
public void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider) {
presenter.present(this, emojiPagerAdapter, this, this, null, null, recentModel.getEmoji().size() > 0 ? 0 : 1);
}
@Override
public void setController(@Nullable Controller controller) {
this.controller = controller;
}
@Override
public int getProviderIconView(boolean selected) {
if (selected) {
return ThemeUtil.isDarkTheme(context) ? R.layout.emoji_keyboard_icon_dark_selected : R.layout.emoji_keyboard_icon_light_selected;
} else {
return ThemeUtil.isDarkTheme(context) ? R.layout.emoji_keyboard_icon_dark : R.layout.emoji_keyboard_icon_light;
}
}
@Override
public void loadCategoryTabIcon(@NonNull RequestManager glideRequests, @NonNull ImageView imageView, int index) {
Drawable drawable = ResUtil.getDrawable(context, models.get(index).getIconAttr());
imageView.setImageDrawable(drawable);
}
@Override
public void onBackspaceClicked() {
if (emojiEventListener != null) {
emojiEventListener.onKeyEvent(DELETE_KEY_EVENT);
}
}
@Override
public void onVariationSelectorStateChanged(boolean open) {
if (controller != null) {
controller.setViewPagerEnabled(!open);
}
}
@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof EmojiKeyboardProvider;
}
private static class EmojiPagerAdapter extends PagerAdapter {
private Context context;
private List<EmojiPageModel> pages;
private EmojiEventListener emojiSelectionListener;
private VariationSelectorListener variationSelectorListener;
public EmojiPagerAdapter(@NonNull Context context,
@NonNull List<EmojiPageModel> pages,
@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener)
{
super();
this.context = context;
this.pages = pages;
this.emojiSelectionListener = emojiSelectionListener;
this.variationSelectorListener = variationSelectorListener;
}
@Override
public int getCount() {
return pages.size();
}
@Override
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener, false);
container.addView(page);
return page;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
EmojiPageView current = (EmojiPageView) object;
current.onSelected();
super.setPrimaryItem(container, position, object);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
}

File diff suppressed because one or more lines are too long

@ -14,7 +14,6 @@ import android.util.TypedValue;
import network.loki.messenger.R;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.guava.Optional;
@ -26,7 +25,6 @@ public class EmojiTextView extends AppCompatTextView {
private CharSequence previousText;
private BufferType previousBufferType = BufferType.NORMAL;
private float originalFontSize;
private boolean useSystemEmoji;
private boolean sizeChangeInProgress;
private int maxLength;
private CharSequence overflowText;
@ -81,9 +79,8 @@ public class EmojiTextView extends AppCompatTextView {
previousText = text;
previousOverflowText = overflowText;
previousBufferType = type;
useSystemEmoji = useSystemEmoji();
if (useSystemEmoji || candidates == null || candidates.size() == 0) {
if (candidates == null || candidates.size() == 0) {
super.setText(new SpannableStringBuilder(Optional.fromNullable(text).or("")).append(Optional.fromNullable(overflowText).or("")), BufferType.NORMAL);
if (getEllipsize() == TextUtils.TruncateAt.END && maxLength > 0) {
@ -117,7 +114,7 @@ public class EmojiTextView extends AppCompatTextView {
EmojiParser.CandidateList newCandidates = EmojiProvider.getCandidates(newContent);
if (useSystemEmoji || newCandidates == null || newCandidates.size() == 0) {
if (newCandidates == null || newCandidates.size() == 0) {
super.setText(newContent, BufferType.NORMAL);
} else {
CharSequence emojified = EmojiProvider.emojify(newCandidates, newContent, this, false);
@ -166,13 +163,9 @@ public class EmojiTextView extends AppCompatTextView {
return Util.equals(finalPrevText, finalText) &&
Util.equals(finalPrevOverflowText, finalOverflowText) &&
Util.equals(previousBufferType, bufferType) &&
useSystemEmoji == useSystemEmoji() &&
!sizeChangeInProgress;
}
private boolean useSystemEmoji() {
return TextSecurePreferences.isSystemEmojiPreferred(getContext());
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {

@ -1,84 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import org.session.libsession.utilities.TextSecurePreferences;
import network.loki.messenger.R;
public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.MediaKeyboardListener {
private Drawable emojiToggle;
private Drawable stickerToggle;
private Drawable mediaToggle;
private Drawable imeToggle;
public EmojiToggle(Context context) {
super(context);
initialize();
}
public EmojiToggle(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public EmojiToggle(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public void setToMedia() {
setImageDrawable(mediaToggle);
}
public void setToIme() {
setImageDrawable(imeToggle);
}
private void initialize() {
TypedArray drawables = getContext().obtainStyledAttributes(new int[] {
R.attr.conversation_emoji_toggle,
R.attr.conversation_sticker_toggle,
R.attr.conversation_keyboard_toggle});
this.emojiToggle = drawables.getDrawable(0);
this.stickerToggle = drawables.getDrawable(1);
this.imeToggle = drawables.getDrawable(2);
this.mediaToggle = emojiToggle;
drawables.recycle();
setToMedia();
}
public void attach(MediaKeyboard drawer) {
drawer.setKeyboardListener(this);
}
public void setStickerMode(boolean stickerMode) {
this.mediaToggle = stickerMode ? stickerToggle : emojiToggle;
if (getDrawable() != imeToggle) {
setToMedia();
}
}
@Override public void onShown() {
setToIme();
}
@Override public void onHidden() {
setToMedia();
}
@Override
public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) {
}
}

@ -1,276 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
import org.thoughtcrime.securesms.components.RepeatableImageKey;
import org.session.libsignal.utilities.Log;
import com.bumptech.glide.Glide;
import java.util.Arrays;
import network.loki.messenger.R;
public class MediaKeyboard extends FrameLayout implements InputView,
MediaKeyboardProvider.Presenter,
MediaKeyboardProvider.Controller,
MediaKeyboardBottomTabAdapter.EventListener
{
private static final String TAG = Log.tag(MediaKeyboard.class);
private RecyclerView categoryTabs;
private ViewPager categoryPager;
private ViewGroup providerTabs;
private RepeatableImageKey backspaceButton;
private RepeatableImageKey backspaceButtonBackup;
private View searchButton;
private View addButton;
private MediaKeyboardListener keyboardListener;
private MediaKeyboardProvider[] providers;
private int providerIndex;
private MediaKeyboardBottomTabAdapter categoryTabAdapter;
public MediaKeyboard(Context context) {
this(context, null);
}
public MediaKeyboard(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setProviders(int startIndex, MediaKeyboardProvider... providers) {
if (!Arrays.equals(this.providers, providers)) {
this.providers = providers;
this.providerIndex = startIndex;
requestPresent(providers, providerIndex);
}
}
public void setKeyboardListener(MediaKeyboardListener listener) {
this.keyboardListener = listener;
}
@Override
public boolean isShowing() {
return getVisibility() == VISIBLE;
}
@Override
public void show(int height, boolean immediate) {
if (this.categoryPager == null) initView();
ViewGroup.LayoutParams params = getLayoutParams();
params.height = height;
Log.i(TAG, "showing emoji drawer with height " + params.height);
setLayoutParams(params);
setVisibility(VISIBLE);
if (keyboardListener != null) keyboardListener.onShown();
requestPresent(providers, providerIndex);
}
@Override
public void hide(boolean immediate) {
setVisibility(GONE);
if (keyboardListener != null) keyboardListener.onHidden();
Log.i(TAG, "hide()");
}
@Override
public void present(@NonNull MediaKeyboardProvider provider,
@NonNull PagerAdapter pagerAdapter,
@NonNull MediaKeyboardProvider.TabIconProvider tabIconProvider,
@Nullable MediaKeyboardProvider.BackspaceObserver backspaceObserver,
@Nullable MediaKeyboardProvider.AddObserver addObserver,
@Nullable MediaKeyboardProvider.SearchObserver searchObserver,
int startingIndex)
{
if (categoryPager == null) return;
if (!provider.equals(providers[providerIndex])) return;
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(provider);
boolean isSolo = providers.length == 1;
presentProviderStrip(isSolo);
presentCategoryPager(pagerAdapter, tabIconProvider, startingIndex);
presentProviderTabs(providers, providerIndex);
presentSearchButton(searchObserver);
presentBackspaceButton(backspaceObserver, isSolo);
presentAddButton(addObserver);
}
@Override
public int getCurrentPosition() {
return categoryPager != null ? categoryPager.getCurrentItem() : 0;
}
@Override
public void requestDismissal() {
hide(true);
providerIndex = 0;
keyboardListener.onKeyboardProviderChanged(providers[providerIndex]);
}
@Override
public boolean isVisible() {
return getVisibility() == View.VISIBLE;
}
@Override
public void onTabSelected(int index) {
if (categoryPager != null) {
categoryPager.setCurrentItem(index);
categoryTabs.smoothScrollToPosition(index);
}
}
@Override
public void setViewPagerEnabled(boolean enabled) {
if (categoryPager != null) {
categoryPager.setEnabled(enabled);
}
}
private void initView() {
final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true);
this.categoryTabs = view.findViewById(R.id.media_keyboard_tabs);
this.categoryPager = view.findViewById(R.id.media_keyboard_pager);
this.providerTabs = view.findViewById(R.id.media_keyboard_provider_tabs);
this.backspaceButton = view.findViewById(R.id.media_keyboard_backspace);
this.backspaceButtonBackup = view.findViewById(R.id.media_keyboard_backspace_backup);
this.searchButton = view.findViewById(R.id.media_keyboard_search);
this.addButton = view.findViewById(R.id.media_keyboard_add);
this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(Glide.with(this), this);
categoryTabs.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
categoryTabs.setAdapter(categoryTabAdapter);
}
private void requestPresent(@NonNull MediaKeyboardProvider[] providers, int newIndex) {
providers[providerIndex].setController(null);
providerIndex = newIndex;
providers[providerIndex].setController(this);
providers[providerIndex].requestPresentation(this, providers.length == 1);
}
private void presentCategoryPager(@NonNull PagerAdapter pagerAdapter,
@NonNull MediaKeyboardProvider.TabIconProvider iconProvider,
int startingIndex) {
if (categoryPager.getAdapter() != pagerAdapter) {
categoryPager.setAdapter(pagerAdapter);
}
categoryPager.setCurrentItem(startingIndex);
categoryPager.clearOnPageChangeListeners();
categoryPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {
}
@Override
public void onPageSelected(int i) {
categoryTabAdapter.setActivePosition(i);
categoryTabs.smoothScrollToPosition(i);
}
@Override
public void onPageScrollStateChanged(int i) {
}
});
categoryTabAdapter.setTabIconProvider(iconProvider, pagerAdapter.getCount());
categoryTabAdapter.setActivePosition(startingIndex);
}
private void presentProviderTabs(@NonNull MediaKeyboardProvider[] providers, int selected) {
providerTabs.removeAllViews();
LayoutInflater inflater = LayoutInflater.from(getContext());
for (int i = 0; i < providers.length; i++) {
MediaKeyboardProvider provider = providers[i];
View view = inflater.inflate(provider.getProviderIconView(i == selected), providerTabs, false);
view.setTag(provider);
final int index = i;
view.setOnClickListener(v -> {
requestPresent(providers, index);
});
providerTabs.addView(view);
}
}
private void presentBackspaceButton(@Nullable MediaKeyboardProvider.BackspaceObserver backspaceObserver,
boolean useBackupPosition)
{
if (backspaceObserver != null) {
if (useBackupPosition) {
backspaceButton.setVisibility(INVISIBLE);
backspaceButton.setOnKeyEventListener(null);
backspaceButtonBackup.setVisibility(VISIBLE);
backspaceButtonBackup.setOnKeyEventListener(backspaceObserver::onBackspaceClicked);
} else {
backspaceButton.setVisibility(VISIBLE);
backspaceButton.setOnKeyEventListener(backspaceObserver::onBackspaceClicked);
backspaceButtonBackup.setVisibility(GONE);
backspaceButtonBackup.setOnKeyEventListener(null);
}
} else {
backspaceButton.setVisibility(INVISIBLE);
backspaceButton.setOnKeyEventListener(null);
backspaceButtonBackup.setVisibility(GONE);
backspaceButton.setOnKeyEventListener(null);
}
}
private void presentAddButton(@Nullable MediaKeyboardProvider.AddObserver addObserver) {
if (addObserver != null) {
addButton.setVisibility(VISIBLE);
addButton.setOnClickListener(v -> addObserver.onAddClicked());
} else {
addButton.setVisibility(GONE);
addButton.setOnClickListener(null);
}
}
private void presentSearchButton(@Nullable MediaKeyboardProvider.SearchObserver searchObserver) {
searchButton.setVisibility(searchObserver != null ? VISIBLE : INVISIBLE);
}
private void presentProviderStrip(boolean isSolo) {
int visibility = isSolo ? View.GONE : View.VISIBLE;
searchButton.setVisibility(visibility);
backspaceButton.setVisibility(visibility);
providerTabs.setVisibility(visibility);
}
public interface MediaKeyboardListener {
void onShown();
void onHidden();
void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider);
}
}

@ -1,97 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider.TabIconProvider;
import com.bumptech.glide.RequestManager;
import network.loki.messenger.R;
public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKeyboardBottomTabAdapter.MediaKeyboardBottomTabViewHolder> {
private final RequestManager glideRequests;
private final EventListener eventListener;
private TabIconProvider tabIconProvider;
private int activePosition;
private int count;
public MediaKeyboardBottomTabAdapter(@NonNull RequestManager glideRequests, @NonNull EventListener eventListener) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
}
@Override
public @NonNull MediaKeyboardBottomTabViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new MediaKeyboardBottomTabViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_keyboard_bottom_tab_item, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull MediaKeyboardBottomTabViewHolder viewHolder, int i) {
viewHolder.bind(glideRequests, eventListener, tabIconProvider, i, i == activePosition);
}
@Override
public void onViewRecycled(@NonNull MediaKeyboardBottomTabViewHolder holder) {
holder.recycle();
}
@Override
public int getItemCount() {
return count;
}
public void setTabIconProvider(@NonNull TabIconProvider iconProvider, int count) {
this.tabIconProvider = iconProvider;
this.count = count;
notifyDataSetChanged();
}
public void setActivePosition(int position) {
this.activePosition = position;
notifyDataSetChanged();
}
static class MediaKeyboardBottomTabViewHolder extends RecyclerView.ViewHolder {
private final ImageView image;
private final View indicator;
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView) {
super(itemView);
this.image = itemView.findViewById(R.id.media_keyboard_bottom_tab_image);
this.indicator = itemView.findViewById(R.id.media_keyboard_bottom_tab_indicator);
}
void bind(@NonNull RequestManager glideRequests,
@NonNull EventListener eventListener,
@NonNull TabIconProvider tabIconProvider,
int index,
boolean selected)
{
tabIconProvider.loadCategoryTabIcon(glideRequests, image, index);
image.setAlpha(selected ? 1 : 0.5f);
image.setSelected(selected);
indicator.setVisibility(selected ? View.VISIBLE : View.INVISIBLE);
itemView.setOnClickListener(v -> eventListener.onTabSelected(index));
}
void recycle() {
itemView.setOnClickListener(null);
}
}
interface EventListener {
void onTabSelected(int index);
}
}

@ -1,53 +0,0 @@
package org.thoughtcrime.securesms.components.emoji;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import android.widget.ImageView;
import com.bumptech.glide.RequestManager;
public interface MediaKeyboardProvider {
@LayoutRes int getProviderIconView(boolean selected);
/** @return True if the click was handled with provider-specific logic, otherwise false */
void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider);
void setController(@Nullable Controller controller);
interface BackspaceObserver {
void onBackspaceClicked();
}
interface AddObserver {
void onAddClicked();
}
interface SearchObserver {
void onSearchOpened();
void onSearchClosed();
void onSearchChanged(@NonNull String query);
}
interface Controller {
void setViewPagerEnabled(boolean enabled);
}
interface Presenter {
void present(@NonNull MediaKeyboardProvider provider,
@NonNull PagerAdapter pagerAdapter,
@NonNull TabIconProvider iconProvider,
@Nullable BackspaceObserver backspaceObserver,
@Nullable AddObserver addObserver,
@Nullable SearchObserver searchObserver,
int startingIndex);
int getCurrentPosition();
void requestDismissal();
boolean isVisible();
}
interface TabIconProvider {
void loadCategoryTabIcon(@NonNull RequestManager glideRequests, @NonNull ImageView imageView, int index);
}
}

@ -55,7 +55,7 @@ class UserView : LinearLayout {
val address = user.address.serialize()
binding.profilePictureView.update(user)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_baseline_edit_24)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_radio_unselected)
binding.nameTextView.text = if (user.isGroupOrCommunityRecipient) user.name else getUserDisplayName(address)
when (actionIndicator) {
ActionIndicator.None -> {
@ -63,14 +63,14 @@ class UserView : LinearLayout {
}
ActionIndicator.Menu -> {
binding.actionIndicatorImageView.visibility = View.VISIBLE
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_more_horiz_white)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_circle_dots_custom)
}
ActionIndicator.Tick -> {
binding.actionIndicatorImageView.visibility = View.VISIBLE
if (isSelected) {
binding.actionIndicatorImageView.setImageResource(R.drawable.padded_circle_accent)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_radio_selected)
} else {
binding.actionIndicatorImageView.setImageDrawable(null)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_radio_unselected)
}
}
}
@ -79,9 +79,9 @@ class UserView : LinearLayout {
fun toggleCheckbox(isSelected: Boolean = false) {
binding.actionIndicatorImageView.visibility = View.VISIBLE
if (isSelected) {
binding.actionIndicatorImageView.setImageResource(R.drawable.padded_circle_accent)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_radio_selected)
} else {
binding.actionIndicatorImageView.setImageDrawable(null)
binding.actionIndicatorImageView.setImageResource(R.drawable.ic_radio_unselected)
}
}

@ -116,7 +116,7 @@ class ConversationActionBarView @JvmOverloads constructor(
settings += ConversationSetting(
subtitleTxt,
ConversationSettingType.EXPIRATION,
R.drawable.ic_timer,
R.drawable.ic_clock_11,
resources.getString(R.string.AccessibilityId_disappearingMessagesDisappear)
)
}
@ -129,7 +129,7 @@ class ConversationActionBarView @JvmOverloads constructor(
}
?: context.getString(R.string.notificationsMuted),
ConversationSettingType.NOTIFICATION,
R.drawable.ic_outline_notifications_off_24
R.drawable.ic_volume_off
)
}

@ -63,13 +63,13 @@ internal fun StartConversationScreen(
val newMessageTitleTxt:String = context.resources.getQuantityString(R.plurals.messageNew, 1, 1)
ItemButton(
text = newMessageTitleTxt,
icon = R.drawable.ic_message,
icon = R.drawable.ic_message_square,
modifier = Modifier.contentDescription(R.string.AccessibilityId_messageNew),
onClick = delegate::onNewMessageSelected)
Divider(startIndent = LocalDimensions.current.minItemButtonHeight)
ItemButton(
textId = R.string.groupCreate,
icon = R.drawable.ic_group,
icon = R.drawable.ic_users_group_custom,
modifier = Modifier.contentDescription(R.string.AccessibilityId_groupCreate),
onClick = delegate::onCreateGroupSelected
)
@ -83,7 +83,7 @@ internal fun StartConversationScreen(
Divider(startIndent = LocalDimensions.current.minItemButtonHeight)
ItemButton(
textId = R.string.sessionInviteAFriend,
icon = R.drawable.ic_invite_friend,
icon = R.drawable.ic_user_round_plus,
Modifier.contentDescription(R.string.AccessibilityId_sessionInviteAFriendButton),
onClick = delegate::onInviteFriend
)

@ -161,7 +161,7 @@ private fun EnterAccountId(
.fillMaxWidth(),
style = LocalType.current.small,
color = LocalColors.current.textSecondary,
iconRes = R.drawable.ic_circle_question_mark,
iconRes = R.drawable.ic_circle_help,
onClick = onHelp
)
}

@ -386,10 +386,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private val glide by lazy { Glide.with(this) }
private val lockViewHitMargin by lazy { toPx(40, resources) }
private val gifButton by lazy { InputBarButton(this, R.drawable.ic_gif_white_24dp, hasOpaqueBackground = true, isGIFButton = true) }
private val documentButton by lazy { InputBarButton(this, R.drawable.ic_document_small_dark, hasOpaqueBackground = true) }
private val libraryButton by lazy { InputBarButton(this, R.drawable.ic_baseline_photo_library_24, hasOpaqueBackground = true) }
private val cameraButton by lazy { InputBarButton(this, R.drawable.ic_baseline_photo_camera_24, hasOpaqueBackground = true) }
private val gifButton by lazy { InputBarButton(this, R.drawable.ic_gif, hasOpaqueBackground = true) }
private val documentButton by lazy { InputBarButton(this, R.drawable.ic_file, hasOpaqueBackground = true) }
private val libraryButton by lazy { InputBarButton(this, R.drawable.ic_images, hasOpaqueBackground = true) }
private val cameraButton by lazy { InputBarButton(this, R.drawable.ic_camera, hasOpaqueBackground = true) }
private val messageToScrollTimestamp = AtomicLong(-1)
private val messageToScrollAuthor = AtomicReference<Address?>(null)
private val firstLoad = AtomicBoolean(true)
@ -852,7 +852,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
)
// we need to add the inline icon
val drawable = ContextCompat.getDrawable(this@ConversationActivityV2, R.drawable.ic_external)
val drawable = ContextCompat.getDrawable(this@ConversationActivityV2, R.drawable.ic_square_arrow_up_right)
val imageSize = toPx(10, resources)
val imagePaddingTop = toPx(4, resources)
drawable?.setBounds(0, 0, imageSize, imageSize)

@ -37,6 +37,7 @@ import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY
import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber
import org.session.libsession.utilities.ThemeUtil
import org.session.libsession.utilities.getColorFromAttr
import org.thoughtcrime.securesms.components.emoji.EmojiImageView
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
import org.thoughtcrime.securesms.components.menu.ActionItem
@ -98,6 +99,12 @@ class ConversationReactionOverlay : FrameLayout {
private val scope = CoroutineScope(Dispatchers.Default)
private var job: Job? = null
private val iconMore by lazy {
val d = ContextCompat.getDrawable(context, R.drawable.ic_plus)
d?.setTint(context.getColorFromAttr(android.R.attr.textColor))
d
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@ -434,7 +441,7 @@ class ConversationReactionOverlay : FrameLayout {
view.translationY = 0f
val isAtCustomIndex = i == customEmojiIndex
if (isAtCustomIndex) {
view.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_baseline_add_24))
view.setImageDrawable(iconMore)
view.tag = null
} else {
view.setImageEmoji(emojis[i])

@ -250,7 +250,7 @@ fun CellButtons(
onReply?.let {
LargeItemButton(
R.string.reply,
R.drawable.ic_message_details__reply,
R.drawable.ic_reply,
onClick = it
)
Divider()
@ -266,7 +266,7 @@ fun CellButtons(
onSave?.let {
LargeItemButton(
R.string.save,
R.drawable.ic_baseline_save_24,
R.drawable.ic_arrow_down_to_line,
onClick = it
)
Divider()
@ -275,7 +275,7 @@ fun CellButtons(
onResend?.let {
LargeItemButton(
R.string.resend,
R.drawable.ic_message_details__refresh,
R.drawable.ic_refresh_cw,
onClick = it
)
Divider()
@ -283,7 +283,7 @@ fun CellButtons(
LargeItemButton(
R.string.delete,
R.drawable.ic_delete,
R.drawable.ic_trash_2,
colors = dangerButtonColors(),
onClick = onDelete
)
@ -351,13 +351,16 @@ fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
Surface(
shape = CircleShape,
color = blackAlpha40,
modifier = modifier,
modifier = modifier
.clickable { onClick() },
contentColor = Color.White,
) {
Icon(
painter = painterResource(id = R.drawable.ic_expand),
painter = painterResource(id = R.drawable.ic_maximize_2),
contentDescription = stringResource(id = R.string.AccessibilityId_expand),
modifier = Modifier.clickable { onClick() },
modifier = Modifier
.padding(LocalDimensions.current.xxsSpacing)
.size(LocalDimensions.current.xsSpacing),
)
}
}

@ -15,19 +15,19 @@ class ExpirationTimerView @JvmOverloads constructor(
defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
private val frames = intArrayOf(
R.drawable.timer00,
R.drawable.timer05,
R.drawable.timer10,
R.drawable.timer15,
R.drawable.timer20,
R.drawable.timer25,
R.drawable.timer30,
R.drawable.timer35,
R.drawable.timer40,
R.drawable.timer45,
R.drawable.timer50,
R.drawable.timer55,
R.drawable.timer60
R.drawable.ic_clock_0,
R.drawable.ic_clock_1,
R.drawable.ic_clock_2,
R.drawable.ic_clock_3,
R.drawable.ic_clock_4,
R.drawable.ic_clock_5,
R.drawable.ic_clock_6,
R.drawable.ic_clock_7,
R.drawable.ic_clock_8,
R.drawable.ic_clock_9,
R.drawable.ic_clock_10,
R.drawable.ic_clock_11,
R.drawable.ic_clock_12
)
fun setTimerIcon() {
@ -36,13 +36,13 @@ class ExpirationTimerView @JvmOverloads constructor(
fun setExpirationTime(startedAt: Long, expiresIn: Long) {
if (expiresIn == 0L) {
setImageResource(R.drawable.timer55)
setImageResource(R.drawable.ic_clock_11)
return
}
if (startedAt == 0L) {
// timer has not started
setImageResource(R.drawable.timer60)
setImageResource(R.drawable.ic_clock_12)
return
}

@ -84,8 +84,8 @@ class InputBar @JvmOverloads constructor(
var voiceMessageDurationMS = 0L
var voiceRecorderState = VoiceRecorderState.Idle
private val attachmentsButton = InputBarButton(context, R.drawable.ic_plus_24).apply { contentDescription = context.getString(R.string.AccessibilityId_attachmentsButton)}
val microphoneButton = InputBarButton(context, R.drawable.ic_microphone).apply { contentDescription = context.getString(R.string.AccessibilityId_voiceMessageNew)}
private val attachmentsButton = InputBarButton(context, R.drawable.ic_plus).apply { contentDescription = context.getString(R.string.AccessibilityId_attachmentsButton)}
val microphoneButton = InputBarButton(context, R.drawable.ic_mic).apply { contentDescription = context.getString(R.string.AccessibilityId_voiceMessageNew)}
private val sendButton = InputBarButton(context, R.drawable.ic_arrow_up, true).apply { contentDescription = context.getString(R.string.AccessibilityId_send)}
init {

@ -28,7 +28,6 @@ class InputBarButton : RelativeLayout {
private val gestureHandler = Handler(Looper.getMainLooper())
private var isSendButton = false
private var hasOpaqueBackground = false
private var isGIFButton = false
@DrawableRes private var iconID = 0
private var longPressCallback: Runnable? = null
private var onDownTimestamp = 0L
@ -73,7 +72,7 @@ class InputBarButton : RelativeLayout {
private val imageView by lazy {
val result = ImageView(context)
val size = if (isGIFButton) toPx(24, resources) else toPx(16, resources)
val size = toPx(20, resources)
result.layoutParams = LayoutParams(size, size)
result.scaleType = ImageView.ScaleType.CENTER_INSIDE
result.setImageResource(iconID)
@ -88,11 +87,10 @@ class InputBarButton : RelativeLayout {
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { throw IllegalAccessException("Use InputBarButton(context:iconID:) instead.") }
constructor(context: Context, @DrawableRes iconID: Int, isSendButton: Boolean = false,
hasOpaqueBackground: Boolean = false, isGIFButton: Boolean = false) : super(context) {
hasOpaqueBackground: Boolean = false) : super(context) {
this.isSendButton = isSendButton
this.iconID = iconID
this.hasOpaqueBackground = hasOpaqueBackground
this.isGIFButton = isGIFButton
val size = resources.getDimension(R.dimen.input_bar_button_expanded_size).toInt()
val layoutParams = LayoutParams(size, size)
this.layoutParams = layoutParams

@ -65,7 +65,7 @@ class InputBarRecordingView : RelativeLayout {
fun show(scope: CoroutineScope) {
startTimestamp = Date().time
binding.recordButtonOverlayImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_microphone, context.theme))
binding.recordButtonOverlayImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_mic, context.theme))
binding.inputBarCancelButton.alpha = 0.0f
binding.inputBarMiddleContentContainer.alpha = 1.0f
binding.lockView.alpha = 1.0f

@ -3,11 +3,16 @@ package org.thoughtcrime.securesms.conversation.v2.messages
import android.Manifest
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.compose.ui.graphics.Color
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
@ -43,10 +48,17 @@ class ControlMessageView : LinearLayout {
private val binding = ViewControlMessageBinding.inflate(LayoutInflater.from(context), this, true)
private val infoDrawable by lazy {
val d = ResourcesCompat.getDrawable(resources, R.drawable.ic_info_outline_white_24dp, context.theme)
d?.setTint(context.getColorFromAttr(R.attr.message_received_text_color))
d
val iconSize by lazy {
resources.getDimensionPixelSize(R.dimen.medium_spacing)
}
private val infoDrawable: Drawable? by lazy {
val icon = ResourcesCompat.getDrawable(resources, R.drawable.ic_info, context.theme)?.toBitmap()
if(icon != null) {
val d = BitmapDrawable(resources, Bitmap.createScaledBitmap(icon, iconSize, iconSize, true))
d.setTint(context.getColorFromAttr(R.attr.message_received_text_color))
d
} else null
}
constructor(context: Context) : super(context)
@ -106,7 +118,7 @@ class ControlMessageView : LinearLayout {
message.isMediaSavedNotification -> {
binding.iconImageView.apply {
setImageDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.ic_file_download_white_36dp, context.theme)
ResourcesCompat.getDrawable(resources, R.drawable.ic_arrow_down_to_line, context.theme)
)
isVisible = true
}
@ -127,15 +139,30 @@ class ControlMessageView : LinearLayout {
binding.root.contentDescription = context.getString(R.string.AccessibilityId_message_request_config_message)
}
message.isCallLog -> {
val drawable = when {
message.isIncomingCall -> R.drawable.ic_incoming_call
message.isOutgoingCall -> R.drawable.ic_outgoing_call
else -> R.drawable.ic_missed_call
val drawableRes = when {
message.isIncomingCall -> R.drawable.ic_phone_incoming
message.isOutgoingCall -> R.drawable.ic_phone_outgoing
else -> R.drawable.ic_phone_missed
}
// Since this is using text drawable we need to go the long way around to size and style the drawable
// We could set the colour and style directly in the drawable's xml but it then makes it non reusable
// This will all be simplified once we turn this all to Compose
val icon = ResourcesCompat.getDrawable(resources, drawableRes, context.theme)?.toBitmap()
icon?.let{
val drawable = BitmapDrawable(resources, Bitmap.createScaledBitmap(icon, iconSize, iconSize, true));
binding.callTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(
drawable,null, null, null)
val iconTint = when {
message.isIncomingCall || message.isOutgoingCall -> R.attr.message_received_text_color
else -> R.attr.danger
}
drawable.setTint(context.getColorFromAttr(iconTint))
}
binding.textView.isVisible = false
binding.callTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(
ResourcesCompat.getDrawable(resources, drawable, context.theme),
null, null, null)
binding.callTextView.text = messageBody
if (message.expireStarted > 0 && message.expiresIn > 0) {

@ -11,6 +11,7 @@ import network.loki.messenger.R
import network.loki.messenger.databinding.ViewOpenGroupInvitationBinding
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.utilities.OpenGroupUrlParser
import org.session.libsession.utilities.getColorFromAttr
import org.thoughtcrime.securesms.conversation.v2.dialogs.JoinOpenGroupDialog
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.util.getAccentColor
@ -29,8 +30,10 @@ class OpenGroupInvitationView : LinearLayout {
val data = umd.kind as UpdateMessageData.Kind.OpenGroupInvitation
this.data = data
val iconID = if (message.isOutgoing) R.drawable.ic_globe else R.drawable.ic_plus
val backgroundColor = if (!message.isOutgoing) context.getAccentColor()
else ContextCompat.getColor(context, R.color.transparent_black_6)
with(binding){
openGroupInvitationIconImageView.setImageResource(iconID)
openGroupInvitationIconBackground.backgroundTintList = ColorStateList.valueOf(backgroundColor)

@ -97,14 +97,17 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
if (!hasAttachments) {
binding.quoteViewAccentLine.setBackgroundColor(getLineColor(isOutgoingMessage))
} else if (attachments != null) {
binding.quoteViewAttachmentPreviewImageView.imageTintList = ColorStateList.valueOf(ResourcesCompat.getColor(resources, R.color.white, context.theme))
val backgroundColor = context.getAccentColor()
binding.quoteViewAttachmentPreviewContainer.backgroundTintList = ColorStateList.valueOf(backgroundColor)
binding.quoteViewAttachmentPreviewImageView.imageTintList = ColorStateList.valueOf(
context.getColorFromAttr(
if(isOutgoingMessage && mode == Mode.Regular) R.attr.message_sent_text_color
else R.attr.message_received_text_color
)
)
binding.quoteViewAttachmentPreviewImageView.isVisible = false
binding.quoteViewAttachmentThumbnailImageView.root.isVisible = false
when {
attachments.audioSlide != null -> {
binding.quoteViewAttachmentPreviewImageView.setImageResource(R.drawable.ic_microphone)
binding.quoteViewAttachmentPreviewImageView.setImageResource(R.drawable.ic_mic)
binding.quoteViewAttachmentPreviewImageView.isVisible = true
// A missing file name is the legacy way to determine if an audio attachment is
// a voice note vs. other arbitrary audio attachments.
@ -118,7 +121,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
}
}
attachments.documentSlide != null -> {
binding.quoteViewAttachmentPreviewImageView.setImageResource(R.drawable.ic_document_large_light)
binding.quoteViewAttachmentPreviewImageView.setImageResource(R.drawable.ic_file)
binding.quoteViewAttachmentPreviewImageView.isVisible = true
binding.quoteViewBodyTextView.text = resources.getString(R.string.document)
}

@ -20,6 +20,8 @@ import androidx.core.view.children
import androidx.core.view.isVisible
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import java.util.Locale
import kotlin.math.roundToInt
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewVisibleMessageContentBinding
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@ -38,8 +40,6 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.util.GlowViewUtilities
import org.thoughtcrime.securesms.util.SearchUtil
import org.thoughtcrime.securesms.util.getAccentColor
import java.util.Locale
import kotlin.math.roundToInt
class VisibleMessageContentView : ConstraintLayout {
private val binding: ViewVisibleMessageContentBinding by lazy { ViewVisibleMessageContentBinding.bind(this) }
@ -148,9 +148,13 @@ class VisibleMessageContentView : ConstraintLayout {
// When in a link preview ensure the bodyTextView can expand to the full width
binding.bodyTextView.maxWidth = binding.linkPreviewView.root.layoutParams.width
}
// AUDIO
message is MmsMessageRecord && message.slideDeck.audioSlide != null -> {
hideBody = true
// Show any text message associated with the audio message (which may be a voice clip - but could also be a mp3 or such)
hideBody = false
// Audio attachment
if (mediaDownloaded || mediaInProgress || message.isOutgoing) {
binding.voiceMessageView.root.indexInAdapter = indexInAdapter
@ -161,7 +165,7 @@ class VisibleMessageContentView : ConstraintLayout {
onContentClick.add { binding.voiceMessageView.root.togglePlayback() }
onContentDoubleTap = { binding.voiceMessageView.root.handleDoubleTap() }
} else {
hideBody = true
// If it's an audio message but we haven't downloaded it yet show it as pending
(message.slideDeck.audioSlide?.asAttachment() as? DatabaseAttachment)?.let { attachment ->
binding.pendingAttachmentView.root.bind(
PendingAttachmentView.AttachmentType.AUDIO,
@ -172,14 +176,17 @@ class VisibleMessageContentView : ConstraintLayout {
}
}
}
// DOCUMENT
message is MmsMessageRecord && message.slideDeck.documentSlide != null -> {
hideBody = true // TODO: check if this is still the logic we want
// Show any message that came with the attached document
hideBody = false
// Document attachment
if (mediaDownloaded || mediaInProgress || message.isOutgoing) {
binding.documentView.root.bind(message, getTextColor(context, message))
} else {
hideBody = true
// If the document hasn't been downloaded yet then show it as pending
(message.slideDeck.documentSlide?.asAttachment() as? DatabaseAttachment)?.let { attachment ->
binding.pendingAttachmentView.root.bind(
PendingAttachmentView.AttachmentType.DOCUMENT,
@ -190,6 +197,7 @@ class VisibleMessageContentView : ConstraintLayout {
}
}
}
// IMAGE / VIDEO
message is MmsMessageRecord && !suppressThumbnails && message.slideDeck.asAttachments().isNotEmpty() -> {
if (mediaDownloaded || mediaInProgress || message.isOutgoing) {
@ -201,7 +209,7 @@ class VisibleMessageContentView : ConstraintLayout {
isStart = isStartOfMessageCluster,
isEnd = isEndOfMessageCluster
)
binding.albumThumbnailView.root.modifyLayoutParams<ConstraintLayout.LayoutParams> {
binding.albumThumbnailView.root.modifyLayoutParams<LayoutParams> {
horizontalBias = if (message.isOutgoing) 1f else 0f
}
onContentClick.add { event ->

@ -62,6 +62,7 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsignal.utilities.AccountId
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.util.DateUtils
@ -93,7 +94,11 @@ class VisibleMessageView : FrameLayout {
ViewEmojiReactionsBinding.bind(binding.emojiReactionsView.inflate())
}
private val swipeToReplyIcon = ContextCompat.getDrawable(context, R.drawable.ic_baseline_reply_24)!!.mutate()
private val swipeToReplyIcon by lazy {
val d = ContextCompat.getDrawable(context, R.drawable.ic_reply)!!.mutate()
d.setTint(context.getColorFromAttr(R.attr.colorControlNormal))
d
}
private val swipeToReplyIconRect = Rect()
private var dx = 0.0f
private var previousTranslationX = 0.0f
@ -102,6 +107,8 @@ class VisibleMessageView : FrameLayout {
private var longPressCallback: Runnable? = null
private var onDownTimestamp = 0L
private var onDoubleTap: (() -> Unit)? = null
private var isOutgoing: Boolean = false
var indexInAdapter: Int = -1
var snIsSelected = false
set(value) {
@ -154,6 +161,7 @@ class VisibleMessageView : FrameLayout {
onAttachmentNeedsDownload: (DatabaseAttachment) -> Unit,
lastSentMessageId: Long
) {
isOutgoing = message.isOutgoing
replyDisabled = message.isOpenGroupInvitation
val threadID = message.threadId
val thread = threadDb.getRecipientForThreadId(threadID) ?: return
@ -390,13 +398,13 @@ class VisibleMessageView : FrameLayout {
private fun getMessageStatusInfo(message: MessageRecord): MessageStatusInfo? = when {
message.isFailed ->
MessageStatusInfo(R.drawable.ic_delivery_status_failed,
MessageStatusInfo(R.drawable.ic_triangle_alert,
getThemedColor(context, R.attr.danger),
R.string.messageStatusFailedToSend
)
message.isSyncFailed ->
MessageStatusInfo(
R.drawable.ic_delivery_status_failed,
R.drawable.ic_triangle_alert,
context.getColorFromAttr(R.attr.warning),
R.string.messageStatusFailedToSync
)
@ -404,14 +412,14 @@ class VisibleMessageView : FrameLayout {
// Non-mms messages (or quote messages, which happen to be mms for some reason) display 'Sending'..
if (!message.isMms || (message as? MmsMessageRecord)?.quote != null) {
MessageStatusInfo(
R.drawable.ic_delivery_status_sending,
R.drawable.ic_circle_dots_custom,
context.getColorFromAttr(R.attr.message_status_color),
R.string.sending
)
} else {
// ..and Mms messages display 'Uploading'.
MessageStatusInfo(
R.drawable.ic_delivery_status_sending,
R.drawable.ic_circle_dots_custom,
context.getColorFromAttr(R.attr.message_status_color),
R.string.uploading
)
@ -419,19 +427,19 @@ class VisibleMessageView : FrameLayout {
}
message.isResyncing ->
MessageStatusInfo(
R.drawable.ic_delivery_status_sending,
R.drawable.ic_circle_dots_custom,
context.getColorFromAttr(R.attr.message_status_color),
R.string.messageStatusSyncing
)
message.isRead || message.isIncoming ->
MessageStatusInfo(
R.drawable.ic_delivery_status_read,
R.drawable.ic_eye,
context.getColorFromAttr(R.attr.message_status_color),
R.string.read
)
message.isSyncing || message.isSent -> // syncing should happen silently in the bg so we can mark it as sent
MessageStatusInfo(
R.drawable.ic_delivery_status_sent,
R.drawable.ic_circle_check,
context.getColorFromAttr(R.attr.message_status_color),
R.string.disappearingMessagesSent
)
@ -457,12 +465,14 @@ class VisibleMessageView : FrameLayout {
}
override fun onDraw(canvas: Canvas) {
val spacing = context.resources.getDimensionPixelSize(R.dimen.small_spacing)
val spacing = context.resources.getDimensionPixelSize(R.dimen.medium_spacing)
val iconSize = toPx(24, context.resources)
val left = binding.messageInnerContainer.left + binding.messageContentView.root.right + spacing
val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.marginBottom - (iconSize / 2)
val left = if(isOutgoing) binding.messageInnerContainer.right + spacing
else binding.messageInnerContainer.left + binding.messageContentView.root.right + spacing
val top = (binding.messageInnerContainer.height / 2) + (iconSize / 2)
val right = left + iconSize
val bottom = top + iconSize
swipeToReplyIconRect.left = left
swipeToReplyIconRect.top = top
swipeToReplyIconRect.right = right

@ -6,29 +6,27 @@ import android.graphics.Outline
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.ViewOutlineProvider
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isVisible
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions
import network.loki.messenger.R
import network.loki.messenger.databinding.ThumbnailViewBinding
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
import org.session.libsession.utilities.Util.equals
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsignal.utilities.ListenableFuture
import org.session.libsignal.utilities.SettableFuture
import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget
import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.RequestManager
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.ui.afterMeasured
import java.lang.Float.min
@ -54,6 +52,12 @@ open class ThumbnailView @JvmOverloads constructor(
private var slide: Slide? = null
private val errorDrawable by lazy {
val drawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_triangle_alert, context.theme)
drawable?.setTint(context.getColorFromAttr(android.R.attr.textColorTertiary))
drawable
}
init {
attrs?.let { context.theme.obtainStyledAttributes(it, R.styleable.ThumbnailView, 0, 0) }
?.apply {
@ -178,7 +182,7 @@ open class ThumbnailView @JvmOverloads constructor(
.overrideDimensions()
.transition(DrawableTransitionOptions.withCrossFade())
.transform(CenterCrop())
.missingThumbnailPicture(slide.isInProgress)
.missingThumbnailPicture(slide.isInProgress, errorDrawable)
private fun buildPlaceholderGlideRequest(
glide: RequestManager,
@ -186,8 +190,6 @@ open class ThumbnailView @JvmOverloads constructor(
): RequestBuilder<Bitmap> = glide.asBitmap()
.load(slide.getPlaceholderRes(context.theme))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.overrideDimensions()
.fitCenter()
open fun clear(glideRequests: RequestManager) {
glideRequests.clear(binding.thumbnailImage)
@ -217,5 +219,6 @@ open class ThumbnailView @JvmOverloads constructor(
}
private fun <T> RequestBuilder<T>.missingThumbnailPicture(
inProgress: Boolean
) = takeIf { inProgress } ?: apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture))
inProgress: Boolean,
errorDrawable: Drawable?
) = takeIf { inProgress } ?: apply(RequestOptions.errorOf(errorDrawable))

@ -104,9 +104,6 @@ class EditLegacyGroupActivity : PassphraseRequiredActionBarActivity() {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_edit_closed_group)
supportActionBar!!.setHomeAsUpIndicator(
ThemeUtil.getThemedDrawableResId(this, R.attr.actionModeCloseDrawable))
groupID = intent.getStringExtra(groupIDKey)!!
val groupInfo = DatabaseComponent.get(this).groupDatabase().getGroup(groupID).get()
originalName = groupInfo.title

@ -36,6 +36,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
@ -67,6 +68,8 @@ import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.theme.ThemeColors
@Composable
fun EditGroupScreen(
@ -197,17 +200,17 @@ fun EditGroup(
)
)
IconButton(
modifier = Modifier.size(LocalDimensions.current.spacing),
onClick = onEditNameConfirmed
) {
Icon(
painter = painterResource(R.drawable.check),
contentDescription = stringResource(R.string.AccessibilityId_confirm),
tint = LocalColors.current.text,
)
}
IconButton(
modifier = Modifier.size(LocalDimensions.current.spacing),
onClick = onEditNameConfirmed
) {
Icon(
painter = painterResource(R.drawable.ic_check),
contentDescription = stringResource(R.string.AccessibilityId_confirm),
tint = LocalColors.current.text,
)
}
}
} else {
@ -222,23 +225,23 @@ fun EditGroup(
.padding(vertical = LocalDimensions.current.smallSpacing),
)
Box(modifier = Modifier.weight(1f)) {
if (canEditName) {
IconButton(
modifier = Modifier.qaTag(stringResource(R.string.AccessibilityId_groupName)),
onClick = onEditNameClicked
) {
Icon(
painterResource(R.drawable.ic_baseline_edit_24),
contentDescription = stringResource(R.string.edit),
tint = LocalColors.current.text,
)
}
Box(modifier = Modifier.weight(1f)) {
if (canEditName) {
IconButton(
modifier = Modifier.qaTag(stringResource(R.string.AccessibilityId_groupName)),
onClick = onEditNameClicked
) {
Icon(
painterResource(R.drawable.ic_pencil),
contentDescription = stringResource(R.string.edit),
tint = LocalColors.current.text,
)
}
}
}
}
}
}
// Header & Add member button
Row(
@ -391,7 +394,7 @@ private fun MemberActionSheet(
if (member.canRemove) {
this += ActionSheetItemData(
title = context.resources.getQuantityString(R.plurals.groupRemoveUserOnly, 1),
iconRes = R.drawable.ic_delete_24,
iconRes = R.drawable.ic_trash_2,
onClick = onRemove,
qaTag = R.string.AccessibilityId_removeContact
)
@ -400,7 +403,7 @@ private fun MemberActionSheet(
if (BuildConfig.DEBUG && member.canPromote) {
this += ActionSheetItemData(
title = context.getString(R.string.adminPromoteToAdmin),
iconRes = R.drawable.ic_profile_default,
iconRes = R.drawable.ic_user_filled_custom,
onClick = onPromote
)
}
@ -452,7 +455,8 @@ fun EditMemberItem(
){
if (member.canEdit) {
Icon(
painter = painterResource(R.drawable.ic_circle_dot_dot_dot),
painter = painterResource(R.drawable.ic_circle_dots_custom),
tint = LocalColors.current.text,
contentDescription = stringResource(R.string.AccessibilityId_sessionSettings)
)
}
@ -611,8 +615,10 @@ private fun EditGroupPreview() {
@Preview
@Composable
private fun EditGroupEditNamePreview() {
PreviewTheme {
private fun EditGroupEditNamePreview(
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
val oneMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"),
name = "Test User",

@ -106,7 +106,7 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto
if (configFactory.wasKickedFromGroupV2(recipient)) {
text = context.getString(R.string.delete)
contentDescription = context.getString(R.string.AccessibilityId_delete)
drawableStartRes = R.drawable.ic_delete_24
drawableStartRes = R.drawable.ic_trash_2
} else {
text = context.getString(R.string.leave)
contentDescription = context.getString(R.string.AccessibilityId_leave)
@ -118,14 +118,14 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto
recipient.isLocalNumber -> {
text = context.getString(R.string.hide)
contentDescription = context.getString(R.string.AccessibilityId_clear)
drawableStartRes = R.drawable.ic_delete_24
drawableStartRes = R.drawable.ic_trash_2
}
// 1on1
else -> {
text = context.getString(R.string.delete)
contentDescription = context.getString(R.string.AccessibilityId_delete)
drawableStartRes = R.drawable.ic_delete_24
drawableStartRes = R.drawable.ic_trash_2
}
}

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.home
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
@ -51,14 +52,9 @@ class ConversationView : LinearLayout {
fun bind(thread: ThreadRecord, isTyping: Boolean, overriddenSnippet: CharSequence?) {
this.thread = thread
if (thread.isPinned) {
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(
0,
0,
R.drawable.ic_pin,
0
)
binding.iconPinned.isVisible = true
} else {
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
binding.iconPinned.isVisible = false
}
binding.root.background = if (thread.unreadCount > 0) {
ContextCompat.getDrawable(context, R.drawable.conversation_unread_background)
@ -96,7 +92,7 @@ class ConversationView : LinearLayout {
val recipient = thread.recipient
binding.muteIndicatorImageView.isVisible = recipient.isMuted || recipient.notifyType != NOTIFY_TYPE_ALL
val drawableRes = if (recipient.isMuted || recipient.notifyType == NOTIFY_TYPE_NONE) {
R.drawable.ic_outline_notifications_off_24
R.drawable.ic_volume_off
} else {
R.drawable.ic_notifications_mentions
}
@ -122,15 +118,16 @@ class ConversationView : LinearLayout {
}
binding.typingIndicatorView.root.visibility = if (isTyping) View.VISIBLE else View.GONE
binding.statusIndicatorImageView.visibility = View.VISIBLE
binding.statusIndicatorImageView.imageTintList = ColorStateList.valueOf(ThemeUtil.getThemedColor(context, android.R.attr.textColorTertiary)) // tertiary in the current xml styling is actually what figma uses as secondary text color...
when {
!thread.isOutgoing -> binding.statusIndicatorImageView.visibility = View.GONE
thread.isFailed -> {
val drawable = ContextCompat.getDrawable(context, R.drawable.ic_error)?.mutate()
drawable?.setTint(ThemeUtil.getThemedColor(context, R.attr.danger))
val drawable = ContextCompat.getDrawable(context, R.drawable.ic_triangle_alert)?.mutate()
binding.statusIndicatorImageView.setImageDrawable(drawable)
binding.statusIndicatorImageView.imageTintList = ColorStateList.valueOf(ThemeUtil.getThemedColor(context, R.attr.danger))
}
thread.isPending -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dot_dot_dot)
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
thread.isPending -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dots_custom)
thread.isRead -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
else -> binding.statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
}
binding.profilePictureView.update(thread.recipient)

@ -53,7 +53,7 @@ internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) {
stringResource(R.string.recoveryPasswordBannerTitle),
style = LocalType.current.h8
)
Spacer(Modifier.requiredWidth(LocalDimensions.current.xxsSpacing))
Spacer(Modifier.requiredWidth(LocalDimensions.current.xsSpacing))
SessionShieldIcon()
}
Text(

@ -108,12 +108,12 @@ class KeyboardPageSearchView @JvmOverloads constructor(
fun showRequested(): Boolean = state == State.SHOW_REQUESTED
fun enableBackNavigation(enable: Boolean = true) {
navButton.setImageResource(if (enable) R.drawable.ic_arrow_left else R.drawable.ic_search_24)
navButton.setImageResource(if (enable) R.drawable.ic_chevron_left else R.drawable.ic_search)
if (enable) {
navButton.setImageResource(R.drawable.ic_arrow_left)
navButton.setImageResource(R.drawable.ic_chevron_left)
navButton.setOnClickListener { callbacks?.onNavigationClicked() }
} else {
navButton.setImageResource(R.drawable.ic_search_24)
navButton.setImageResource(R.drawable.ic_search)
navButton.setOnClickListener(null)
}
}

@ -17,6 +17,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.painterResource
@ -73,7 +74,8 @@ fun DocumentsPage(
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painterResource(R.drawable.ic_document_large_dark),
painterResource(R.drawable.ic_file),
colorFilter = ColorFilter.tint(LocalColors.current.text),
contentDescription = null
)

@ -33,7 +33,7 @@ fun MediaOverviewTopAppBar(
actionModeActions = {
IconButton(onClick = onSaveClicked) {
Icon(
painterResource(R.drawable.ic_baseline_save_24),
painterResource(R.drawable.ic_arrow_down_to_line),
contentDescription = stringResource(R.string.save),
tint = LocalColors.current.text,
)
@ -41,7 +41,7 @@ fun MediaOverviewTopAppBar(
IconButton(onClick = onDeleteClicked) {
Icon(
painterResource(R.drawable.ic_baseline_delete_24),
painterResource(R.drawable.ic_trash_2),
contentDescription = stringResource(R.string.delete),
tint = LocalColors.current.text,
)

@ -153,29 +153,20 @@ private fun ThumbnailRow(
it.diskCacheStrategy(DiskCacheStrategy.NONE)
}
} else {
// The resource given by the placeholder needs tinting according to our theme.
// But the missing thumbnail picture does not.
var (placeholder, shouldTint) = if (item.hasPlaceholder) {
item.placeholder(LocalContext.current) to true
val placeholder = if (item.hasPlaceholder) {
item.placeholder(LocalContext.current)
} else {
R.drawable.ic_missing_thumbnail_picture to false
R.drawable.ic_triangle_alert
}
if (placeholder == 0) {
placeholder = R.drawable.ic_missing_thumbnail_picture
shouldTint = false
}
Image(
painter = painterResource(placeholder),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Inside,
colorFilter = if (shouldTint) {
ColorFilter.tint(LocalColors.current.textSecondary)
} else {
null
}
colorFilter = ColorFilter.tint(LocalColors.current.textSecondary)
)
}
@ -211,7 +202,8 @@ private fun ThumbnailRow(
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.4f)),
contentScale = ContentScale.Inside,
painter = painterResource(R.drawable.ic_check_white_48dp),
painter = painterResource(R.drawable.ic_check),
colorFilter = ColorFilter.tint(Color.White),
contentDescription = stringResource(R.string.AccessibilityId_select),
)
}

@ -138,14 +138,12 @@ public class MediaRailAdapter extends RecyclerView.Adapter<MediaRailAdapter.Medi
private final ThumbnailView image;
private final View outline;
private final View deleteButton;
private final View captionIndicator;
MediaViewHolder(@NonNull View itemView) {
super(itemView);
image = itemView.findViewById(R.id.rail_item_image);
outline = itemView.findViewById(R.id.rail_item_outline);
deleteButton = itemView.findViewById(R.id.rail_item_delete);
captionIndicator = itemView.findViewById(R.id.rail_item_caption);
}
void bind(@NonNull Media media, boolean isActive, @NonNull RequestManager glideRequests,
@ -158,8 +156,6 @@ public class MediaRailAdapter extends RecyclerView.Adapter<MediaRailAdapter.Medi
outline.setVisibility(isActive ? View.VISIBLE : View.GONE);
captionIndicator.setVisibility(media.getCaption().isPresent() ? View.VISIBLE : View.GONE);
if (editable && isActive) {
deleteButton.setVisibility(View.VISIBLE);
deleteButton.setOnClickListener(v -> railItemListener.onRailItemDeleteClicked(distanceFromActive));

@ -1,21 +1,12 @@
package org.thoughtcrime.securesms.mediasend;
import android.annotation.SuppressLint;
import androidx.lifecycle.ViewModelProvider;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
@ -28,33 +19,36 @@ import android.view.inputmethod.EditorInfo;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import com.bumptech.glide.Glide;
import org.session.libsession.utilities.MediaTypes;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.utilities.ListenableFuture;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.SettableFuture;
import org.session.libsignal.utilities.guava.Optional;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.components.ControllableViewPager;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.util.SimpleTextWatcher;
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
import com.bumptech.glide.Glide;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment;
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
import org.thoughtcrime.securesms.util.PushCharacterCalculator;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.session.libsignal.utilities.guava.Optional;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.Stub;
import org.session.libsignal.utilities.ListenableFuture;
import org.session.libsignal.utilities.SettableFuture;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -85,9 +79,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
private ImageButton sendButton;
private ComposeText composeText;
private ViewGroup composeContainer;
private EmojiEditText captionText;
private EmojiToggle emojiToggle;
private Stub<MediaKeyboard> emojiDrawer;
private ViewGroup playbackControlsContainer;
private TextView charactersLeft;
private View closeButton;
@ -144,9 +135,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
sendButton = view.findViewById(R.id.mediasend_send_button);
composeText = view.findViewById(R.id.mediasend_compose_text);
composeContainer = view.findViewById(R.id.mediasend_compose_container);
captionText = view.findViewById(R.id.mediasend_caption);
emojiToggle = view.findViewById(R.id.mediasend_emoji_toggle);
emojiDrawer = new Stub<>(view.findViewById(R.id.mediasend_emoji_drawer_stub));
fragmentPager = view.findViewById(R.id.mediasend_pager);
mediaRail = view.findViewById(R.id.mediasend_media_rail);
playbackControlsContainer = view.findViewById(R.id.mediasend_playback_controls_container);
@ -163,13 +151,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
processMedia(fragmentPagerAdapter.getAllMedia(), fragmentPagerAdapter.getSavedState());
});
// sendButton.addOnTransportChangedListener((newTransport, manuallySelected) -> {
// presentCharactersRemaining();
// composeText.setTransport(newTransport);
// sendButtonBkg.getBackground().setColorFilter(getResources().getColor(R.color.transparent), PorterDuff.Mode.MULTIPLY);
// sendButtonBkg.getBackground().invalidateSelf();
// });
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
composeText.setOnKeyListener(composeKeyPressedListener);
@ -177,7 +158,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
composeText.setOnClickListener(composeKeyPressedListener);
composeText.setOnFocusChangeListener(composeKeyPressedListener);
captionText.clearFocus();
composeText.requestFocus();
fragmentPagerAdapter = new MediaSendFragmentPagerAdapter(getChildFragmentManager());
@ -195,32 +175,19 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
hud.addOnKeyboardShownListener(this);
hud.addOnKeyboardHiddenListener(this);
captionText.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
viewModel.onCaptionChanged(text);
}
});
composeText.append(viewModel.getBody());
Recipient recipient = Recipient.from(requireContext(), getArguments().getParcelable(KEY_ADDRESS), false);
String displayName = Optional.fromNullable(recipient.getName())
.or(Optional.fromNullable(recipient.getProfileName())
.or(recipient.getAddress().serialize()));
composeText.setHint(getString(R.string.message, displayName), null);
composeText.setHint(getString(R.string.message), null);
composeText.setOnEditorActionListener((v, actionId, event) -> {
boolean isSend = actionId == EditorInfo.IME_ACTION_SEND;
if (isSend) sendButton.performClick();
return isSend;
});
if (TextSecurePreferences.isSystemEmojiPreferred(getContext())) {
emojiToggle.setVisibility(View.GONE);
} else {
emojiToggle.setOnClickListener(this::onEmojiToggleClicked);
}
closeButton.setOnClickListener(v -> requireActivity().onBackPressed());
}
@ -274,18 +241,12 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
@Override
public void onKeyboardShown() {
if (captionText.hasFocus()) {
mediaRail.setVisibility(View.VISIBLE);
composeContainer.setVisibility(View.GONE);
captionText.setVisibility(View.VISIBLE);
} else if (composeText.hasFocus()) {
if (composeText.hasFocus()) {
mediaRail.setVisibility(View.VISIBLE);
composeContainer.setVisibility(View.VISIBLE);
captionText.setVisibility(View.GONE);
} else {
mediaRail.setVisibility(View.GONE);
composeContainer.setVisibility(View.VISIBLE);
captionText.setVisibility(View.GONE);
}
}
@ -293,10 +254,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
public void onKeyboardHidden() {
composeContainer.setVisibility(View.VISIBLE);
mediaRail.setVisibility(View.VISIBLE);
if (!Util.isEmpty(viewModel.getSelectedMedia().getValue()) && viewModel.getSelectedMedia().getValue().size() > 1) {
captionText.setVisibility(View.VISIBLE);
}
}
public void onTouchEventsNeeded(boolean needed) {
@ -325,7 +282,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
fragmentPagerAdapter.setMedia(media);
mediaRail.setVisibility(View.VISIBLE);
captionText.setVisibility((media.size() > 1 || media.get(0).getCaption().isPresent()) ? View.VISIBLE : View.GONE);
mediaRailAdapter.setMedia(media);
});
@ -336,10 +292,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
mediaRailAdapter.setActivePosition(position);
mediaRail.smoothScrollToPosition(position);
if (fragmentPagerAdapter.getAllMedia().size() > position) {
captionText.setText(fragmentPagerAdapter.getAllMedia().get(position).getCaption().or(""));
}
View playbackControls = fragmentPagerAdapter.getPlaybackControls(position);
if (playbackControls != null) {
@ -359,11 +311,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
});
}
private EmojiEditText getActiveInputField() {
if (captionText.hasFocus()) return captionText;
else return composeText;
}
private void presentCharactersRemaining() {
String messageBody = composeText.getTextTrimmed();
@ -381,29 +328,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
}
}
private void onEmojiToggleClicked(View v) {
if (!emojiDrawer.resolved()) {
emojiDrawer.get().setProviders(0, new EmojiKeyboardProvider(requireContext(), new EmojiEventListener() {
@Override
public void onKeyEvent(KeyEvent keyEvent) {
getActiveInputField().dispatchKeyEvent(keyEvent);
}
@Override
public void onEmojiSelected(String emoji) {
getActiveInputField().insertEmoji(emoji);
}
}));
emojiToggle.attach(emojiDrawer.get());
}
if (hud.getCurrentInput() == emojiDrawer.get()) {
hud.showSoftkey(composeText);
} else {
hud.hideSoftkey(composeText, () -> hud.post(() -> hud.show(composeText, emojiDrawer.get())));
}
}
@SuppressLint("StaticFieldLeak")
private void processMedia(@NonNull List<Media> mediaList, @NonNull Map<Uri, Object> savedState) {
Map<Media, ListenableFuture<Bitmap>> futures = new HashMap<>();

@ -1,24 +1,24 @@
package org.thoughtcrime.securesms.mediasend;
import android.app.Application;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import android.text.TextUtils;
import com.annimon.stream.Stream;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.guava.Optional;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.session.libsession.utilities.Util;
import org.session.libsignal.utilities.guava.Optional;
import java.util.Collections;
import java.util.HashMap;
@ -231,13 +231,6 @@ class MediaSendViewModel extends ViewModel {
}
}
void onCaptionChanged(@NonNull String newCaption) {
if (position.getValue() >= 0 && !Util.isEmpty(selectedMedia.getValue())) {
selectedMedia.getValue().get(position.getValue()).setCaption(TextUtils.isEmpty(newCaption) ? null : newCaption);
}
}
void saveDrawState(@NonNull Map<Uri, Object> state) {
savedDrawState.clear();
savedDrawState.putAll(state);

@ -74,6 +74,6 @@ public class AudioSlide extends Slide {
@Override
public @DrawableRes int getPlaceholderRes(Theme theme) {
return ResUtil.getDrawableRes(theme, R.attr.conversation_icon_attach_audio);
return R.drawable.ic_volume_2;
}
}

@ -55,7 +55,7 @@ public class VideoSlide extends Slide {
@Override
public @DrawableRes int getPlaceholderRes(Theme theme) {
return ResUtil.getDrawableRes(theme, R.attr.conversation_icon_attach_video);
return R.drawable.ic_square_play;
}
@Override

@ -57,7 +57,7 @@ class MultipleRecipientNotificationBuilder(context: Context, privacy: Notificati
fun addActions(markAsReadIntent: PendingIntent?) {
val markAllAsReadAction = NotificationCompat.Action(
R.drawable.check,
R.drawable.ic_check,
context.getString(R.string.messageMarkRead),
markAsReadIntent
)

@ -157,7 +157,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
@Nullable PendingIntent wearableReplyIntent,
@NonNull ReplyMethod replyMethod)
{
Action markAsReadAction = new Action(R.drawable.check,
Action markAsReadAction = new Action(R.drawable.ic_check,
context.getString(R.string.messageMarkRead),
markReadIntent);
@ -169,9 +169,9 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
String actionName = context.getString(R.string.reply);
String label = context.getString(replyMethodLongDescription(replyMethod));
Action replyAction = new Action(R.drawable.ic_reply_white_36dp, actionName, quickReplyIntent);
Action replyAction = new Action(R.drawable.ic_reply, actionName, quickReplyIntent);
replyAction = new Action.Builder(R.drawable.ic_reply_white_36dp,
replyAction = new Action.Builder(R.drawable.ic_reply,
actionName,
wearableReplyIntent)
.addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())

@ -88,7 +88,7 @@ private fun RecoveryPassword(state: State, onChange: (String) -> Unit = {}, onCo
Spacer(Modifier.width(LocalDimensions.current.xxsSpacing))
Icon(
modifier = Modifier.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.ic_shield_outline),
painter = painterResource(id = R.drawable.ic_recovery_password_custom),
contentDescription = null,
)
}

@ -50,6 +50,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
@ -129,7 +130,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
private val onPickImage = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
){ result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
if (result.resultCode != RESULT_OK) return@registerForActivityResult
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
val inputFile: Uri? = result.data?.data ?: viewModel.getTempFile()?.let(Uri::fromFile)
@ -139,7 +140,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
private val hideRecoveryLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
if (result.resultCode != RESULT_OK) return@registerForActivityResult
if(result.data?.getBooleanExtra(RecoveryPasswordActivity.RESULT_RECOVERY_HIDDEN, false) == true){
viewModel.permanentlyHidePassword()
@ -161,7 +162,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
setContentView(binding.root)
// set the toolbar icon to a close icon
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_baseline_close_24)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_x)
// set the compose dialog content
binding.avatarDialog.setThemedContent {
@ -444,24 +445,24 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
}
Divider()
LargeItemButton(R.string.sessionPrivacy, R.drawable.ic_privacy_icon) { push<PrivacySettingsActivity>() }
LargeItemButton(R.string.sessionPrivacy, R.drawable.ic_lock_keyhole) { push<PrivacySettingsActivity>() }
Divider()
LargeItemButton(R.string.sessionNotifications, R.drawable.ic_speaker, Modifier.contentDescription(R.string.AccessibilityId_notifications)) { push<NotificationSettingsActivity>() }
LargeItemButton(R.string.sessionNotifications, R.drawable.ic_volume_2, Modifier.contentDescription(R.string.AccessibilityId_notifications)) { push<NotificationSettingsActivity>() }
Divider()
LargeItemButton(R.string.sessionConversations, R.drawable.ic_conversations, Modifier.contentDescription(R.string.AccessibilityId_sessionConversations)) { push<ChatSettingsActivity>() }
LargeItemButton(R.string.sessionConversations, R.drawable.ic_message_square, Modifier.contentDescription(R.string.AccessibilityId_sessionConversations)) { push<ChatSettingsActivity>() }
Divider()
LargeItemButton(R.string.sessionMessageRequests, R.drawable.ic_message_requests, Modifier.contentDescription(R.string.AccessibilityId_sessionMessageRequests)) { push<MessageRequestsActivity>() }
LargeItemButton(R.string.sessionMessageRequests, R.drawable.ic_message_square_warning, Modifier.contentDescription(R.string.AccessibilityId_sessionMessageRequests)) { push<MessageRequestsActivity>() }
Divider()
LargeItemButton(R.string.sessionAppearance, R.drawable.ic_appearance, Modifier.contentDescription(R.string.AccessibilityId_sessionAppearance)) { push<AppearanceSettingsActivity>() }
LargeItemButton(R.string.sessionAppearance, R.drawable.ic_paintbrush_vertical, Modifier.contentDescription(R.string.AccessibilityId_sessionAppearance)) { push<AppearanceSettingsActivity>() }
Divider()
LargeItemButton(
R.string.sessionInviteAFriend,
R.drawable.ic_invite_friend,
R.drawable.ic_user_round_plus,
Modifier.contentDescription(R.string.AccessibilityId_sessionInviteAFriend)
) { sendInvitationToUseSession() }
Divider()
@ -470,7 +471,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
if (!recoveryHidden) {
LargeItemButton(
R.string.sessionRecoveryPassword,
R.drawable.ic_shield_outline,
R.drawable.ic_recovery_password_custom,
Modifier.contentDescription(R.string.AccessibilityId_sessionRecoveryPasswordMenuItem)
) {
hideRecoveryLauncher.launch(Intent(baseContext, RecoveryPasswordActivity::class.java))
@ -479,11 +480,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
Divider()
}
LargeItemButton(R.string.sessionHelp, R.drawable.ic_help, Modifier.contentDescription(R.string.AccessibilityId_help)) { push<HelpSettingsActivity>() }
LargeItemButton(R.string.sessionHelp, R.drawable.ic_question_custom, Modifier.contentDescription(R.string.AccessibilityId_help)) { push<HelpSettingsActivity>() }
Divider()
LargeItemButton(R.string.sessionClearData,
R.drawable.ic_delete,
R.drawable.ic_trash_2,
Modifier.contentDescription(R.string.AccessibilityId_sessionClearData),
dangerButtonColors()
) { ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") }
@ -562,8 +563,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
// empty state
else -> {
Image(
modifier = Modifier.align(Alignment.Center),
painter = painterResource(id = R.drawable.ic_pictures),
modifier = Modifier.align(Alignment.Center)
.size(40.dp),
painter = painterResource(id = R.drawable.ic_image),
contentDescription = null,
colorFilter = ColorFilter.tint(LocalColors.current.textSecondary)
)

@ -108,7 +108,7 @@ private fun RecoveryPasswordCell(
.padding(vertical = LocalDimensions.current.spacing)
.contentDescription(R.string.AccessibilityId_qrCode),
contentPadding = 10.dp,
icon = R.drawable.session_shield
icon = R.drawable.ic_recovery_password_custom
)
}

@ -5,11 +5,14 @@ import android.os.Bundle
import androidx.activity.viewModels
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.ui.setComposeContent
@AndroidEntryPoint
class RecoveryPasswordActivity : BaseActionBarActivity() {
companion object {
@ -18,6 +21,8 @@ class RecoveryPasswordActivity : BaseActionBarActivity() {
private val viewModel: RecoveryPasswordViewModel by viewModels()
@Inject lateinit var prefs: TextSecurePreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar!!.title = resources.getString(R.string.sessionRecoveryPassword)
@ -33,10 +38,20 @@ class RecoveryPasswordActivity : BaseActionBarActivity() {
val returnIntent = Intent()
returnIntent.putExtra(RESULT_RECOVERY_HIDDEN, true)
setResult(RESULT_OK, returnIntent)
// The returnIntent assumes we're going back to the SettingsActivity, which handles the result - but
// if we entered this activity through the recovery phrase banner then we're going back to the
// HomeActivity, which does not. As such we'll write the change here to cover all our bases.
prefs.setHidePassword(true)
finish()
},
copyMnemonic = viewModel::copyMnemonic
)
}
// Set the seed as having been viewed when the user has seen this activity, which
// removes the reminder banner on the HomeActivity.
prefs.setHasViewedSeed(true)
}
}

@ -7,21 +7,19 @@ import android.content.Context
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.session.libsession.utilities.AppTextSecurePreferences
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.crypto.MnemonicCodec
import org.session.libsignal.utilities.hexEncodedPrivateKey
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
import javax.inject.Inject
@HiltViewModel
class RecoveryPasswordViewModel @Inject constructor(

@ -4,6 +4,7 @@ import android.content.Context;
import android.graphics.Color;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
@ -96,7 +97,7 @@ public final class ImageEditorHud extends LinearLayout {
}
private void updateCropAspectLockImage(boolean cropAspectLocked) {
cropAspectLock.setImageDrawable(getResources().getDrawable(cropAspectLocked ? R.drawable.ic_crop_lock_32 : R.drawable.ic_crop_unlock_32));
cropAspectLock.setImageDrawable(ResourcesCompat.getDrawable(getResources(), cropAspectLocked ? R.drawable.ic_crop_lock_custom : R.drawable.ic_crop_unlock_custom, getContext().getTheme()));
}
private void initializeVisibilityMap() {

@ -36,11 +36,11 @@ public class StickerSelectActivity extends FragmentActivity implements StickerSe
public static final String EXTRA_STICKER_FILE = "extra_sticker_file";
private static final int[] TAB_TITLES = new int[] {
R.drawable.ic_tag_faces_white_24dp,
R.drawable.ic_work_white_24dp,
R.drawable.ic_pets_white_24dp,
R.drawable.ic_local_dining_white_24dp,
R.drawable.ic_wb_sunny_white_24dp
R.drawable.ic_emoji_custom,
R.drawable.ic_briefcase,
R.drawable.ic_paw_print,
R.drawable.ic_utensils_crossed,
R.drawable.ic_sun
};
@Override

@ -248,11 +248,11 @@ public class KeyCachingService extends Service {
.put(APP_NAME_KEY, c.getString(R.string.app_name))
.format().toString();
builder.setContentTitle(unlockedTxt);
builder.setSmallIcon(R.drawable.icon_cached);
builder.setSmallIcon(R.drawable.ic_lock_keyhole_open);
builder.setWhen(0);
builder.setPriority(Notification.PRIORITY_MIN);
builder.addAction(R.drawable.ic_menu_lock_dark, getString(R.string.lockApp), buildLockIntent());
builder.addAction(R.drawable.ic_lock_keyhole, getString(R.string.lockApp), buildLockIntent());
builder.setContentIntent(buildLaunchIntent());
stopForeground(true);

@ -108,7 +108,7 @@ fun AlertDialog(
modifier = Modifier.align(Alignment.TopEnd)
) {
Icon(
painter = painterResource(id = R.drawable.ic_dialog_x),
painter = painterResource(id = R.drawable.ic_x),
tint = LocalColors.current.text,
contentDescription = "back"
)

@ -182,13 +182,13 @@ private fun HorizontalPagerIndicator(
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun RowScope.CarouselPrevButton(pagerState: PagerState) {
CarouselButton(pagerState, pagerState.canScrollBackward, R.drawable.ic_prev, -1)
CarouselButton(pagerState, pagerState.canScrollBackward, R.drawable.ic_chevron_left, -1)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun RowScope.CarouselNextButton(pagerState: PagerState) {
CarouselButton(pagerState, pagerState.canScrollForward, R.drawable.ic_next, 1)
CarouselButton(pagerState, pagerState.canScrollForward, R.drawable.ic_chevron_right, 1)
}
@OptIn(ExperimentalFoundationApi::class)

@ -309,7 +309,7 @@ fun PreviewItemButton() {
PreviewTheme {
ItemButton(
textId = R.string.groupCreate,
icon = R.drawable.ic_group,
icon = R.drawable.ic_users_group_custom,
onClick = {}
)
}
@ -321,7 +321,7 @@ fun PreviewLargeItemButton() {
PreviewTheme {
LargeItemButton(
textId = R.string.groupCreate,
icon = R.drawable.ic_group,
icon = R.drawable.ic_users_group_custom,
onClick = {}
)
}
@ -440,7 +440,7 @@ private fun BaseAvatar(
// image
if (LocalInspectionMode.current) { // this part is used for previews only
Image(
painterResource(id = R.drawable.ic_profile_default),
painterResource(id = R.drawable.ic_user_filled_custom),
colorFilter = ColorFilter.tint(LocalColors.current.textSecondary),
contentScale = ContentScale.Inside,
contentDescription = null,
@ -580,10 +580,11 @@ fun Arc(
@Composable
fun RowScope.SessionShieldIcon() {
Icon(
painter = painterResource(R.drawable.session_shield),
painter = painterResource(R.drawable.ic_recovery_password_custom),
contentDescription = null,
modifier = Modifier
.align(Alignment.CenterVertically)
.size(16.dp)
.wrapContentSize(unbounded = true)
)
}
@ -627,7 +628,7 @@ fun SearchBar(
.background(backgroundColor, RoundedCornerShape(100))
) {
Image(
painterResource(id = R.drawable.ic_search_24),
painterResource(id = R.drawable.ic_search),
contentDescription = null,
colorFilter = ColorFilter.tint(
LocalColors.current.textSecondary

@ -53,7 +53,7 @@ fun AppBarPreview(
actionModeActions = {
IconButton(onClick = {}) {
Icon(
painter = painterResource(id = R.drawable.check),
painter = painterResource(id = R.drawable.ic_check),
contentDescription = "check"
)
}
@ -171,7 +171,7 @@ fun AppBarBackIcon(onBack: () -> Unit) {
onClick = onBack
) {
Icon(
painter = painterResource(id = R.drawable.ic_arrow_left),
painter = painterResource(id = R.drawable.ic_chevron_left),
contentDescription = null
)
}

@ -267,7 +267,12 @@ fun BorderlessButtonWithIcon(
color = color,
onClick = onClick
) {
AnnotatedTextWithIcon(text, iconRes, style = style)
AnnotatedTextWithIcon(
text = text,
iconRes = iconRes,
color = color,
style = style
)
}
}

@ -41,7 +41,7 @@ fun QrImage(
string: String?,
modifier: Modifier = Modifier,
contentPadding: Dp = LocalDimensions.current.smallSpacing,
icon: Int = R.drawable.session_shield
icon: Int = R.drawable.ic_recovery_password_custom
) {
var bitmap: Bitmap? by remember {
mutableStateOf(null)

@ -3,8 +3,6 @@ package org.thoughtcrime.securesms.ui.components
import androidx.annotation.DrawableRes
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -14,10 +12,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.appendInlineContent
@ -25,8 +21,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
@ -40,19 +34,16 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.unit.sp
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.bold
import org.thoughtcrime.securesms.ui.theme.borders
import org.thoughtcrime.securesms.ui.theme.text
import org.thoughtcrime.securesms.ui.theme.textSecondary
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.bold
import kotlin.math.sin
@Preview
@Composable

@ -46,7 +46,7 @@ class CallNotificationBuilder {
val builder = NotificationCompat.Builder(context, NotificationChannels.CALLS)
.setSound(null)
.setSmallIcon(R.drawable.ic_baseline_call_24)
.setSmallIcon(R.drawable.ic_phone)
.setContentIntent(pendingIntent)
.setOngoing(true)
@ -69,7 +69,7 @@ class CallNotificationBuilder {
builder.addAction(getServiceNotificationAction(
context,
WebRtcCallService.ACTION_DENY_CALL,
R.drawable.ic_close_grey600_32dp,
R.drawable.ic_x,
R.string.decline
))
// If notifications aren't enabled, we will trigger the intent from WebRtcCallService
@ -77,7 +77,7 @@ class CallNotificationBuilder {
builder.addAction(getActivityNotificationAction(
context,
if (type == TYPE_INCOMING_PRE_OFFER) WebRtcCallActivity.ACTION_PRE_OFFER else WebRtcCallActivity.ACTION_ANSWER,
R.drawable.ic_phone_grey600_32dp,
R.drawable.ic_phone,
R.string.accept
))
builder.priority = NotificationCompat.PRIORITY_MAX
@ -87,7 +87,7 @@ class CallNotificationBuilder {
builder.addAction(getServiceNotificationAction(
context,
WebRtcCallService.ACTION_LOCAL_HANGUP,
R.drawable.ic_call_end_grey600_32dp,
R.drawable.ic_phone_fill_custom,
R.string.cancel
))
}
@ -96,7 +96,7 @@ class CallNotificationBuilder {
builder.addAction(getServiceNotificationAction(
context,
WebRtcCallService.ACTION_LOCAL_HANGUP,
R.drawable.ic_call_end_grey600_32dp,
R.drawable.ic_phone_fill_custom,
R.string.callsEnd
)).setUsesChronometer(true)
}

@ -55,7 +55,6 @@ class SaveAttachmentTask @JvmOverloads constructor(context: Context, count: Int
// potential risks of other apps accessing their saved attachments.
context.showSessionDialog {
title(R.string.warning)
iconAttribute(R.attr.dialog_alert_icon)
text(context.getString(R.string.attachmentsWarning))
dangerButton(R.string.save) {
// Set our 'haveWarned' SharedPref and perform the save on accept

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/call_action_foreground_highlighted" android:state_selected="true"/>
<item android:color="@color/call_action_foreground"/>
<item android:color="@color/white"/>
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 B

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

Loading…
Cancel
Save