Commit 5fbacb4e authored by Torsten Grote's avatar Torsten Grote

[android] Split out an EmojiTextInputView from TextInputViews

This also removes the TextInputController whose job is now done by the view.
parent c7f4e976
......@@ -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;
......
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) {
}
void setMaxLength(int maxLength) {
this.maxLength = maxLength;
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
editText.setEnabled(enabled);
emojiToggle.setEnabled(enabled);
}
boolean isEmpty() {
return getText() == null;
@Override
public void setGravity(int gravity) {
editText.setGravity(gravity);
}
boolean isTooLong() {
return editText.getText() != null &&
utf8IsTooLong(editText.getText().toString().trim(), maxLength);
@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;
}
void setMaxLines(int maxLines) {
editText.setMaxLines(maxLines);
}
/**
......@@ -112,32 +188,37 @@ 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);
private void showEmojiIcon() {
emojiToggle.setImageResource(R.drawable.ic_emoji_toggle);
}
void onDetachedFromWindow() {
if (emojiPopup.isShowing()) emojiPopup.dismiss();
private void showKeyboardIcon() {
emojiToggle.setImageResource(R.drawable.ic_keyboard);
}
void showSoftKeyboard() {
Object o = ctx.getSystemService(INPUT_METHOD_SERVICE);
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
imm.showSoftInput(editText, SHOW_IMPLICIT);
}
......@@ -145,22 +226,14 @@ class TextInputController implements TextWatcher {
void hideSoftKeyboard() {
if (emojiPopup.isShowing()) emojiPopup.dismiss();
IBinder token = editText.getWindowToken();
Object o = ctx.getSystemService(INPUT_METHOD_SERVICE);
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
InputMethodManager imm = (InputMethodManager) requireNonNull(o);
imm.hideSoftInputFromWindow(token, 0);
}
private void showEmojiIcon() {
emojiToggle.setImageResource(R.drawable.ic_emoji_toggle);
}
private void showKeyboardIcon() {
emojiToggle.setImageResource(R.drawable.ic_keyboard);
}
public void setEnabled(boolean enabled) {
editText.setEnabled(enabled);
emojiToggle.setEnabled(enabled);
interface TextInputListener {
void onTextIsEmptyChanged(boolean isEmpty);
void onSendEvent();
}
}
package org.thoughtcrime.securesms.components;
/*
Taken from Signal, licences under GPLv3
*/
package org.briarproject.briar.android.view;
import android.annotation.TargetApi;
import android.content.Context;
......@@ -24,6 +28,7 @@ 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;
......@@ -38,8 +43,6 @@ public class KeyboardAwareLinearLayout extends LinearLayout {
Logger.getLogger(KeyboardAwareLinearLayout.class.getName());
private final Rect rect = new Rect();
private final Set<OnKeyboardHiddenListener> hiddenListeners =
new HashSet<>();
private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>();
private final int minKeyboardSize;
private final int minCustomKeyboardSize;
......@@ -153,7 +156,6 @@ public class KeyboardAwareLinearLayout extends LinearLayout {
protected void onKeyboardClose() {
LOG.info("onKeyboardClose()");
keyboardOpen = false;
notifyHiddenListeners();
}
public boolean isKeyboardOpen() {
......@@ -173,7 +175,7 @@ public class KeyboardAwareLinearLayout extends LinearLayout {
private int getDeviceRotation() {
WindowManager windowManager =
(WindowManager) getContext().getSystemService(WINDOW_SERVICE);
return windowManager.getDefaultDisplay().getRotation();
return requireNonNull(windowManager).getDefaultDisplay().getRotation();
}
private int getKeyboardLandscapeHeight() {
......@@ -199,43 +201,6 @@ public class KeyboardAwareLinearLayout extends LinearLayout {
prefs.edit().putInt("keyboard_height_portrait", height).apply();
}
public void postOnKeyboardClose(Runnable runnable) {
if (keyboardOpen) {
addOnKeyboardHiddenListener(new OnKeyboardHiddenListener() {
@Override
public void onKeyboardHidden() {
removeOnKeyboardHiddenListener(this);
runnable.run();
}
});
} else {
runnable.run();
}
}
public void postOnKeyboardOpen(Runnable runnable) {
if (!keyboardOpen) {
addOnKeyboardShownListener(new OnKeyboardShownListener() {
@Override
public void onKeyboardShown() {
removeOnKeyboardShownListener(this);
runnable.run();
}
});
} else {
runnable.run();
}
}
public void addOnKeyboardHiddenListener(OnKeyboardHiddenListener listener) {
hiddenListeners.add(listener);
}
public void removeOnKeyboardHiddenListener(
OnKeyboardHiddenListener listener) {
hiddenListeners.remove(listener);
}
public void addOnKeyboardShownListener(OnKeyboardShownListener listener) {
shownListeners.add(listener);
}
......@@ -245,15 +210,6 @@ public class KeyboardAwareLinearLayout extends LinearLayout {
shownListeners.remove(listener);
}
private void notifyHiddenListeners() {
// Make a copy as listeners may remove themselves when called
Set<OnKeyboardHiddenListener> listeners =
new HashSet<>(hiddenListeners);
for (OnKeyboardHiddenListener listener : listeners) {
listener.onKeyboardHidden();
}
}
private void notifyShownListeners() {
// Make a copy as listeners may remove themselves when called
Set<OnKeyboardShownListener> listeners = new HashSet<>(shownListeners);
......@@ -262,11 +218,8 @@ public class KeyboardAwareLinearLayout extends LinearLayout {
}
}
public interface OnKeyboardHiddenListener {
void onKeyboardHidden();
}
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);
}