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.ktpull/1710/head
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
Before Width: | Height: | Size: 834 B |
Before Width: | Height: | Size: 404 B |
Before Width: | Height: | Size: 966 B |
Before Width: | Height: | Size: 292 B |
Before Width: | Height: | Size: 123 B |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 502 B |
Before Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 662 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 201 B |
Before Width: | Height: | Size: 347 B |
Before Width: | Height: | Size: 595 B |
Before Width: | Height: | Size: 233 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 493 B |
Before Width: | Height: | Size: 291 B |