mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 03:39:05 +01:00
[android] Split out an EmojiTextInputView from TextInputViews
This also removes the TextInputController whose job is now done by the view.
This commit is contained in:
@@ -32,7 +32,7 @@ import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.EmojiTextInputView;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.android.DozeWatchdog;
|
||||
import org.briarproject.briar.api.android.LockManager;
|
||||
@@ -169,7 +169,7 @@ public interface AndroidComponent
|
||||
|
||||
void inject(NotificationCleanupService notificationCleanupService);
|
||||
|
||||
void inject(TextInputView textInputView);
|
||||
void inject(EmojiTextInputView textInputView);
|
||||
|
||||
void inject(BriarModelLoader briarModelLoader);
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ import org.briarproject.briar.android.controller.handler.UiExceptionHandler;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.SendListener;
|
||||
import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.SendListener;
|
||||
import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogPost;
|
||||
|
||||
@@ -64,10 +64,10 @@ import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.TextAttachmentController;
|
||||
import org.briarproject.briar.android.view.TextAttachmentController.AttachImageListener;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.AttachImageListener;
|
||||
import org.briarproject.briar.android.view.TextInputView.SendListener;
|
||||
import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||
|
||||
@@ -23,8 +23,8 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.SendListener;
|
||||
import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -17,8 +17,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.view.LargeTextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.SendListener;
|
||||
import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -27,12 +27,12 @@ import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListe
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDataSource;
|
||||
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.KeyboardAwareLinearLayout;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.SendListener;
|
||||
import org.briarproject.briar.android.view.TextSendController;
|
||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||
import org.briarproject.briar.android.view.UnreadMessageButton;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,61 +1,111 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.AppCompatImageButton;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.vanniktech.emoji.EmojiEditText;
|
||||
import com.vanniktech.emoji.EmojiPopup;
|
||||
import com.vanniktech.emoji.RecentEmoji;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.view.KeyEvent.KEYCODE_ENTER;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||
import static org.briarproject.briar.android.view.TextInputView.TextValidityListener;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
class TextInputController implements TextWatcher {
|
||||
public class EmojiTextInputView extends KeyboardAwareLinearLayout implements
|
||||
TextWatcher {
|
||||
|
||||
@Inject
|
||||
RecentEmoji recentEmoji;
|
||||
|
||||
private final Context ctx;
|
||||
private final AppCompatImageButton emojiToggle;
|
||||
private final EmojiPopup emojiPopup;
|
||||
private final EmojiEditText editText;
|
||||
private final EditText editText;
|
||||
|
||||
@Nullable
|
||||
private TextValidityListener listener;
|
||||
private TextInputListener listener;
|
||||
private int maxLength = Integer.MAX_VALUE;
|
||||
private final boolean emptyTextAllowed;
|
||||
private boolean emptyTextAllowed = false;
|
||||
private boolean isEmpty = true;
|
||||
|
||||
TextInputController(View rootView, AppCompatImageButton emojiToggle,
|
||||
EmojiEditText editText, RecentEmoji recentEmoji,
|
||||
boolean emptyTextAllowed) {
|
||||
ctx = rootView.getContext();
|
||||
this.emojiToggle = emojiToggle;
|
||||
this.editText = editText;
|
||||
this.editText.addTextChangedListener(this);
|
||||
this.editText.setOnClickListener(v -> showSoftKeyboard());
|
||||
public EmojiTextInputView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public EmojiTextInputView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public EmojiTextInputView(Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
// inflate layout
|
||||
LayoutInflater inflater = (LayoutInflater) requireNonNull(
|
||||
context.getSystemService(LAYOUT_INFLATER_SERVICE));
|
||||
inflater.inflate(R.layout.emoji_text_input_view, this, true);
|
||||
|
||||
// get attributes
|
||||
TypedArray a = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.EmojiTextInputView);
|
||||
int paddingBottom = a.getDimensionPixelSize(
|
||||
R.styleable.EmojiTextInputView_textPaddingBottom, 0);
|
||||
int paddingEnd = a.getDimensionPixelSize(
|
||||
R.styleable.EmojiTextInputView_textPaddingEnd, 0);
|
||||
int maxLines =
|
||||
a.getInteger(R.styleable.EmojiTextInputView_maxTextLines, 0);
|
||||
a.recycle();
|
||||
|
||||
// apply attributes to editText
|
||||
editText = findViewById(R.id.input_text);
|
||||
editText.setPadding(0, 0, paddingEnd, paddingBottom);
|
||||
if (maxLines > 0) editText.setMaxLines(maxLines);
|
||||
editText.setOnClickListener(v -> showSoftKeyboard());
|
||||
editText.addTextChangedListener(this);
|
||||
// support sending with Ctrl+Enter
|
||||
editText.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (listener != null && keyCode == KEYCODE_ENTER &&
|
||||
event.isCtrlPressed()) {
|
||||
listener.onSendEvent();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
emojiToggle = findViewById(R.id.emoji_toggle);
|
||||
|
||||
// stuff we can't do in edit mode goes below
|
||||
if (isInEditMode()) {
|
||||
emojiPopup = null;
|
||||
return;
|
||||
}
|
||||
BriarApplication app =
|
||||
(BriarApplication) context.getApplicationContext();
|
||||
app.getApplicationComponent().inject(this);
|
||||
emojiPopup = EmojiPopup.Builder
|
||||
.fromRootView(rootView)
|
||||
.fromRootView(this)
|
||||
.setRecentEmoji(recentEmoji)
|
||||
.setOnEmojiPopupShownListener(this::showKeyboardIcon)
|
||||
.setOnEmojiPopupDismissListener(this::showEmojiIcon)
|
||||
.build(this.editText);
|
||||
this.emojiToggle.setOnClickListener(v -> emojiPopup.toggle());
|
||||
this.emptyTextAllowed = emptyTextAllowed;
|
||||
.build((EmojiEditText) editText);
|
||||
emojiToggle.setOnClickListener(v -> emojiPopup.toggle());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,17 +133,43 @@ class TextInputController implements TextWatcher {
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
editText.setEnabled(enabled);
|
||||
emojiToggle.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGravity(int gravity) {
|
||||
editText.setGravity(gravity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
|
||||
return editText.requestFocus(direction, previouslyFocusedRect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||
}
|
||||
|
||||
void setTextInputListener(@Nullable TextInputListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
void setAllowEmptyText(boolean emptyTextAllowed) {
|
||||
this.emptyTextAllowed = emptyTextAllowed;
|
||||
}
|
||||
|
||||
void setMaxLength(int maxLength) {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return getText() == null;
|
||||
}
|
||||
|
||||
boolean isTooLong() {
|
||||
return editText.getText() != null &&
|
||||
utf8IsTooLong(editText.getText().toString().trim(), maxLength);
|
||||
void setMaxLines(int maxLines) {
|
||||
editText.setMaxLines(maxLines);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,44 +188,27 @@ class TextInputController implements TextWatcher {
|
||||
editText.setText(null);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return getText() == null;
|
||||
}
|
||||
|
||||
boolean isTooLong() {
|
||||
return editText.getText() != null &&
|
||||
utf8IsTooLong(editText.getText().toString().trim(), maxLength);
|
||||
}
|
||||
|
||||
CharSequence getHint() {
|
||||
return editText.getHint();
|
||||
}
|
||||
|
||||
void setHint(@StringRes int res) {
|
||||
setHint(ctx.getString(res));
|
||||
setHint(getContext().getString(res));
|
||||
}
|
||||
|
||||
void setHint(CharSequence hint) {
|
||||
editText.setHint(hint);
|
||||
}
|
||||
|
||||
void setTextValidityListener(@Nullable TextValidityListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
boolean requestFocus(int direction, Rect previouslyFocusedRect) {
|
||||
return editText.requestFocus(direction, previouslyFocusedRect);
|
||||
}
|
||||
|
||||
void onDetachedFromWindow() {
|
||||
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||
}
|
||||
|
||||
void showSoftKeyboard() {
|
||||
Object o = ctx.getSystemService(INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
||||
imm.showSoftInput(editText, SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
void hideSoftKeyboard() {
|
||||
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||
IBinder token = editText.getWindowToken();
|
||||
Object o = ctx.getSystemService(INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
||||
imm.hideSoftInputFromWindow(token, 0);
|
||||
}
|
||||
|
||||
private void showEmojiIcon() {
|
||||
emojiToggle.setImageResource(R.drawable.ic_emoji_toggle);
|
||||
}
|
||||
@@ -158,9 +217,23 @@ class TextInputController implements TextWatcher {
|
||||
emojiToggle.setImageResource(R.drawable.ic_keyboard);
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
editText.setEnabled(enabled);
|
||||
emojiToggle.setEnabled(enabled);
|
||||
void showSoftKeyboard() {
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
||||
imm.showSoftInput(editText, SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
void hideSoftKeyboard() {
|
||||
if (emojiPopup.isShowing()) emojiPopup.dismiss();
|
||||
IBinder token = editText.getWindowToken();
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
|
||||
imm.hideSoftInputFromWindow(token, 0);
|
||||
}
|
||||
|
||||
interface TextInputListener {
|
||||
void onTextIsEmptyChanged(boolean isEmpty);
|
||||
void onSendEvent();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
Taken from Signal, licences under GPLv3
|
||||
*/
|
||||
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.content.Context.WINDOW_SERVICE;
|
||||
import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
/**
|
||||
* RelativeLayout that, when a view container, will report back when it thinks
|
||||
* a soft keyboard has been opened and what its height would be.
|
||||
*/
|
||||
@UiThread
|
||||
public class KeyboardAwareLinearLayout extends LinearLayout {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyboardAwareLinearLayout.class.getName());
|
||||
|
||||
private final Rect rect = new Rect();
|
||||
private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>();
|
||||
private final int minKeyboardSize;
|
||||
private final int minCustomKeyboardSize;
|
||||
private final int defaultCustomKeyboardSize;
|
||||
private final int minCustomKeyboardTopMargin;
|
||||
private final int statusBarHeight;
|
||||
|
||||
private int viewInset;
|
||||
|
||||
private boolean keyboardOpen = false;
|
||||
private int rotation = -1;
|
||||
|
||||
public KeyboardAwareLinearLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public KeyboardAwareLinearLayout(Context context,
|
||||
@Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public KeyboardAwareLinearLayout(Context context,
|
||||
@Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
rotation = getDeviceRotation();
|
||||
int statusBarRes = getResources()
|
||||
.getIdentifier("status_bar_height", "dimen", "android");
|
||||
minKeyboardSize =
|
||||
getResources().getDimensionPixelSize(R.dimen.min_keyboard_size);
|
||||
minCustomKeyboardSize = getResources()
|
||||
.getDimensionPixelSize(R.dimen.min_custom_keyboard_size);
|
||||
defaultCustomKeyboardSize = getResources()
|
||||
.getDimensionPixelSize(R.dimen.default_custom_keyboard_size);
|
||||
minCustomKeyboardTopMargin = getResources()
|
||||
.getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin);
|
||||
statusBarHeight = statusBarRes > 0 ?
|
||||
getResources().getDimensionPixelSize(statusBarRes) : 0;
|
||||
viewInset = getViewInset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
updateRotation();
|
||||
updateKeyboardState();
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private void updateRotation() {
|
||||
int oldRotation = rotation;
|
||||
rotation = getDeviceRotation();
|
||||
if (oldRotation != rotation) {
|
||||
LOG.info("Rotation changed");
|
||||
onKeyboardClose();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateKeyboardState() {
|
||||
if (isLandscape()) {
|
||||
if (keyboardOpen) onKeyboardClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewInset == 0 && Build.VERSION.SDK_INT >= 21)
|
||||
viewInset = getViewInset();
|
||||
int availableHeight =
|
||||
getRootView().getHeight() - statusBarHeight - viewInset;
|
||||
getWindowVisibleDisplayFrame(rect);
|
||||
|
||||
int keyboardHeight = availableHeight - (rect.bottom - rect.top);
|
||||
|
||||
if (keyboardHeight > minKeyboardSize) {
|
||||
if (getKeyboardHeight() != keyboardHeight)
|
||||
setKeyboardPortraitHeight(keyboardHeight);
|
||||
if (!keyboardOpen) onKeyboardOpen(keyboardHeight);
|
||||
} else if (keyboardOpen) {
|
||||
onKeyboardClose();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
private int getViewInset() {
|
||||
try {
|
||||
Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
|
||||
attachInfoField.setAccessible(true);
|
||||
Object attachInfo = attachInfoField.get(this);
|
||||
if (attachInfo != null) {
|
||||
Field stableInsetsField =
|
||||
attachInfo.getClass().getDeclaredField("mStableInsets");
|
||||
stableInsetsField.setAccessible(true);
|
||||
Rect insets = (Rect) stableInsetsField.get(attachInfo);
|
||||
return insets.bottom;
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
LOG.log(WARNING,
|
||||
"field reflection error when measuring view inset", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
LOG.log(WARNING,
|
||||
"access reflection error when measuring view inset", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void onKeyboardOpen(int keyboardHeight) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("onKeyboardOpen(" + keyboardHeight + ")");
|
||||
keyboardOpen = true;
|
||||
|
||||
notifyShownListeners();
|
||||
}
|
||||
|
||||
protected void onKeyboardClose() {
|
||||
LOG.info("onKeyboardClose()");
|
||||
keyboardOpen = false;
|
||||
}
|
||||
|
||||
public boolean isKeyboardOpen() {
|
||||
return keyboardOpen;
|
||||
}
|
||||
|
||||
public int getKeyboardHeight() {
|
||||
return isLandscape() ? getKeyboardLandscapeHeight() :
|
||||
getKeyboardPortraitHeight();
|
||||
}
|
||||
|
||||
public boolean isLandscape() {
|
||||
int rotation = getDeviceRotation();
|
||||
return rotation == ROTATION_90 || rotation == ROTATION_270;
|
||||
}
|
||||
|
||||
private int getDeviceRotation() {
|
||||
WindowManager windowManager =
|
||||
(WindowManager) getContext().getSystemService(WINDOW_SERVICE);
|
||||
return requireNonNull(windowManager).getDefaultDisplay().getRotation();
|
||||
}
|
||||
|
||||
private int getKeyboardLandscapeHeight() {
|
||||
return Math.max(getHeight(), getRootView().getHeight()) / 2;
|
||||
}
|
||||
|
||||
private int getKeyboardPortraitHeight() {
|
||||
SharedPreferences prefs =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
int keyboardHeight = prefs.getInt("keyboard_height_portrait",
|
||||
defaultCustomKeyboardSize);
|
||||
return clamp(keyboardHeight, minCustomKeyboardSize,
|
||||
getRootView().getHeight() - minCustomKeyboardTopMargin);
|
||||
}
|
||||
|
||||
private int clamp(int value, int min, int max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
private void setKeyboardPortraitHeight(int height) {
|
||||
SharedPreferences prefs =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
prefs.edit().putInt("keyboard_height_portrait", height).apply();
|
||||
}
|
||||
|
||||
public void addOnKeyboardShownListener(OnKeyboardShownListener listener) {
|
||||
shownListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeOnKeyboardShownListener(
|
||||
OnKeyboardShownListener listener) {
|
||||
shownListeners.remove(listener);
|
||||
}
|
||||
|
||||
private void notifyShownListeners() {
|
||||
// Make a copy as listeners may remove themselves when called
|
||||
Set<OnKeyboardShownListener> listeners = new HashSet<>(shownListeners);
|
||||
for (OnKeyboardShownListener listener : listeners) {
|
||||
listener.onKeyboardShown();
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnKeyboardShownListener {
|
||||
void onKeyboardShown();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
@@ -14,6 +13,7 @@ import org.briarproject.briar.R;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.view.Gravity.BOTTOM;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
@UiThread
|
||||
@@ -32,18 +32,6 @@ public class LargeTextInputView extends TextInputView {
|
||||
public LargeTextInputView(Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inflateLayout(Context context) {
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.text_input_view_large, this, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUpViews(Context context, @Nullable AttributeSet attrs) {
|
||||
super.setUpViews(context, attrs);
|
||||
|
||||
// get attributes
|
||||
TypedArray attributes = context.obtainStyledAttributes(attrs,
|
||||
@@ -57,17 +45,23 @@ public class LargeTextInputView extends TextInputView {
|
||||
attributes.recycle();
|
||||
|
||||
if (buttonText != null) setButtonText(buttonText);
|
||||
if (maxLines > 0) editText.setMaxLines(maxLines);
|
||||
if (maxLines > 0) textInput.setMaxLines(maxLines);
|
||||
if (fillHeight) {
|
||||
ViewGroup layout = findViewById(R.id.input_layout);
|
||||
LayoutParams params = (LayoutParams) layout.getLayoutParams();
|
||||
params.height = 0;
|
||||
params.weight = 1;
|
||||
layout.setLayoutParams(params);
|
||||
ViewGroup.LayoutParams editParams = editText.getLayoutParams();
|
||||
editParams.height = MATCH_PARENT;
|
||||
editText.setLayoutParams(editParams);
|
||||
ViewGroup.LayoutParams inputParams = textInput.getLayoutParams();
|
||||
inputParams.height = MATCH_PARENT;
|
||||
textInput.setLayoutParams(inputParams);
|
||||
}
|
||||
textInput.setGravity(BOTTOM);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLayout() {
|
||||
return R.layout.text_input_view_large;
|
||||
}
|
||||
|
||||
public void setButtonText(String text) {
|
||||
|
||||
@@ -27,8 +27,6 @@ import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||
import org.briarproject.briar.android.view.TextInputView.AttachImageListener;
|
||||
import org.briarproject.briar.android.view.TextInputView.SendListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -67,7 +65,7 @@ public class TextAttachmentController extends TextSendController {
|
||||
|
||||
public TextAttachmentController(TextInputView v, SendListener listener,
|
||||
AttachImageListener imageListener, WindowManager windowManager) {
|
||||
super(v, listener, true);
|
||||
super(v, listener, false);
|
||||
this.imageListener = imageListener;
|
||||
|
||||
imageLayout = v.findViewById(R.id.imageLayout);
|
||||
@@ -100,13 +98,18 @@ public class TextAttachmentController extends TextSendController {
|
||||
}
|
||||
|
||||
@Override
|
||||
void onSendButtonClicked() {
|
||||
public void onSendEvent() {
|
||||
if (canSend()) {
|
||||
listener.onSendClick(textInput.getText(), imageUris);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canSendEmptyText() {
|
||||
return !imageUris.isEmpty();
|
||||
}
|
||||
|
||||
private void onImageButtonClicked() {
|
||||
Intent intent = new Intent(SDK_INT >= 19 ?
|
||||
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
||||
@@ -272,4 +275,8 @@ public class TextAttachmentController extends TextSendController {
|
||||
};
|
||||
}
|
||||
|
||||
public interface AttachImageListener {
|
||||
void onAttachImage(Intent intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,48 +2,33 @@ package org.briarproject.briar.android.view;
|
||||
|
||||
import android.animation.LayoutTransition;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.AppCompatImageButton;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import com.vanniktech.emoji.EmojiEditText;
|
||||
import com.vanniktech.emoji.RecentEmoji;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import org.briarproject.briar.android.view.KeyboardAwareLinearLayout.OnKeyboardShownListener;
|
||||
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.view.KeyEvent.KEYCODE_ENTER;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
public class TextInputView extends LinearLayout {
|
||||
|
||||
@Inject
|
||||
RecentEmoji recentEmoji;
|
||||
|
||||
TextInputController textInputController;
|
||||
@Nullable
|
||||
TextSendController textSendController;
|
||||
EmojiEditText editText;
|
||||
final EmojiTextInputView textInput;
|
||||
|
||||
public TextInputView(Context context) {
|
||||
this(context, null);
|
||||
@@ -56,26 +41,15 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
public TextInputView(Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
if (!isInEditMode()) {
|
||||
BriarApplication app =
|
||||
(BriarApplication) context.getApplicationContext();
|
||||
app.getApplicationComponent().inject(this);
|
||||
}
|
||||
setSaveEnabled(true);
|
||||
setOrientation(VERTICAL);
|
||||
setLayoutTransition(new LayoutTransition());
|
||||
inflateLayout(context);
|
||||
setSaveEnabled(true);
|
||||
if (!isInEditMode()) setUpViews(context, attrs);
|
||||
}
|
||||
|
||||
protected void inflateLayout(Context context) {
|
||||
// inflate layout
|
||||
LayoutInflater inflater = (LayoutInflater) requireNonNull(
|
||||
context.getSystemService(LAYOUT_INFLATER_SERVICE));
|
||||
inflater.inflate(R.layout.text_input_view, this, true);
|
||||
}
|
||||
inflater.inflate(getLayout(), this, true);
|
||||
|
||||
@CallSuper
|
||||
protected void setUpViews(Context context, @Nullable AttributeSet attrs) {
|
||||
// get attributes
|
||||
TypedArray attributes = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.TextInputView);
|
||||
@@ -84,12 +58,14 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
.getBoolean(R.styleable.TextInputView_allowEmptyText, false);
|
||||
attributes.recycle();
|
||||
|
||||
// set up input controller
|
||||
AppCompatImageButton emojiToggle = findViewById(R.id.emoji_toggle);
|
||||
editText = findViewById(R.id.input_text);
|
||||
textInputController = new TextInputController(this, emojiToggle,
|
||||
editText, recentEmoji, allowEmptyText);
|
||||
if (hint != null) textInputController.setHint(hint);
|
||||
textInput = findViewById(R.id.emojiTextInput);
|
||||
textInput.setAllowEmptyText(allowEmptyText);
|
||||
if (hint != null) textInput.setHint(hint);
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected int getLayout() {
|
||||
return R.layout.text_input_view;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -118,71 +94,56 @@ public class TextInputView extends KeyboardAwareLinearLayout {
|
||||
*/
|
||||
public <T extends TextSendController> void setSendController(T controller) {
|
||||
textSendController = controller;
|
||||
textInputController.setTextValidityListener(textSendController);
|
||||
|
||||
// support sending with Ctrl+Enter
|
||||
editText.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (keyCode == KEYCODE_ENTER && event.isCtrlPressed()) {
|
||||
textSendController.onSendButtonClicked();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public TextInputController getTextInputController() {
|
||||
return textInputController;
|
||||
textInput.setTextInputListener(textSendController);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
textInputController.setEnabled(enabled);
|
||||
textInput.setEnabled(enabled);
|
||||
requireNonNull(textSendController).setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
|
||||
return textInputController
|
||||
.requestFocus(direction, previouslyFocusedRect);
|
||||
return textInput.requestFocus(direction, previouslyFocusedRect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
textInputController.onDetachedFromWindow();
|
||||
EmojiTextInputView getEmojiTextInputView() {
|
||||
return textInput;
|
||||
}
|
||||
|
||||
public void clearText() {
|
||||
textInputController.clearText();
|
||||
textInput.clearText();
|
||||
}
|
||||
|
||||
public void setHint(@StringRes int res) {
|
||||
textInputController.setHint(getContext().getString(res));
|
||||
textInput.setHint(getContext().getString(res));
|
||||
}
|
||||
|
||||
public void setMaxTextLength(int maxLength) {
|
||||
textInputController.setMaxLength(maxLength);
|
||||
textInput.setMaxLength(maxLength);
|
||||
}
|
||||
|
||||
public boolean isKeyboardOpen() {
|
||||
return textInput.isKeyboardOpen();
|
||||
}
|
||||
|
||||
public void showSoftKeyboard() {
|
||||
textInputController.showSoftKeyboard();
|
||||
textInput.showSoftKeyboard();
|
||||
}
|
||||
|
||||
public void hideSoftKeyboard() {
|
||||
textInputController.hideSoftKeyboard();
|
||||
textInput.hideSoftKeyboard();
|
||||
}
|
||||
|
||||
interface TextValidityListener {
|
||||
void onTextIsEmptyChanged(boolean isEmpty);
|
||||
public void addOnKeyboardShownListener(OnKeyboardShownListener listener) {
|
||||
textInput.addOnKeyboardShownListener(listener);
|
||||
}
|
||||
|
||||
public interface AttachImageListener {
|
||||
void onAttachImage(Intent intent);
|
||||
}
|
||||
|
||||
public interface SendListener {
|
||||
void onSendClick(@Nullable String text, List<Uri> imageUris);
|
||||
public void removeOnKeyboardShownListener(
|
||||
OnKeyboardShownListener listener) {
|
||||
textInput.removeOnKeyboardShownListener(listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.Nullable;
|
||||
@@ -9,40 +10,61 @@ import android.view.View;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.TextInputView.SendListener;
|
||||
import org.briarproject.briar.android.view.TextInputView.TextValidityListener;
|
||||
import org.briarproject.briar.android.view.EmojiTextInputView.TextInputListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static android.support.design.widget.Snackbar.LENGTH_SHORT;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public class TextSendController implements TextValidityListener {
|
||||
public class TextSendController implements TextInputListener {
|
||||
|
||||
protected final TextInputController textInput;
|
||||
protected final EmojiTextInputView textInput;
|
||||
protected final View sendButton;
|
||||
protected final SendListener listener;
|
||||
protected boolean enabled = true;
|
||||
protected final boolean allowEmptyText;
|
||||
|
||||
private final boolean allowEmptyText;
|
||||
private boolean wasEmpty = true;
|
||||
|
||||
public TextSendController(TextInputView v, SendListener listener,
|
||||
boolean allowEmptyText) {
|
||||
this.sendButton = v.findViewById(R.id.btn_send);
|
||||
this.sendButton.setOnClickListener(view -> onSendButtonClicked());
|
||||
this.sendButton.setOnClickListener(view -> onSendEvent());
|
||||
this.sendButton.setEnabled(allowEmptyText);
|
||||
this.listener = listener;
|
||||
this.textInput = v.getTextInputController();
|
||||
this.textInput = v.getEmojiTextInputView();
|
||||
this.allowEmptyText = allowEmptyText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextIsEmptyChanged(boolean isEmpty) {
|
||||
sendButton.setEnabled(enabled && !isEmpty);
|
||||
sendButton.setEnabled(enabled && (!isEmpty || canSendEmptyText()));
|
||||
wasEmpty = isEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendEvent() {
|
||||
if (canSend()) {
|
||||
listener.onSendClick(textInput.getText(), emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
protected final boolean canSend() {
|
||||
if (textInput.isTooLong()) {
|
||||
Snackbar.make(sendButton, R.string.text_too_long, LENGTH_SHORT)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
return enabled && (canSendEmptyText() || !textInput.isEmpty());
|
||||
}
|
||||
|
||||
protected boolean canSendEmptyText() {
|
||||
return allowEmptyText;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Parcelable onSaveInstanceState(@Nullable Parcelable superState) {
|
||||
return superState;
|
||||
@@ -55,23 +77,12 @@ public class TextSendController implements TextValidityListener {
|
||||
|
||||
@CallSuper
|
||||
public void setEnabled(boolean enabled) {
|
||||
sendButton.setEnabled(enabled && (!wasEmpty || allowEmptyText));
|
||||
sendButton.setEnabled(enabled && (!wasEmpty || canSendEmptyText()));
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
void onSendButtonClicked() {
|
||||
if (canSend()) {
|
||||
listener.onSendClick(textInput.getText(), emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean canSend() {
|
||||
if (textInput.isTooLong()) {
|
||||
Snackbar.make(sendButton, R.string.text_too_long, LENGTH_SHORT)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
return enabled && (allowEmptyText || !textInput.isEmpty());
|
||||
public interface SendListener {
|
||||
void onSendClick(@Nullable String text, List<Uri> imageUris);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user