Resolve emoji keyboard layout issues

1) orientation changes are now properly handled
2) emoji panel will not overrun the actionbar and composition area
   in space-contentious situations (quick reply popup)

Closes #3553
fixes #3501
fixes #3485
fixes #3199

// FREEBIE
This commit is contained in:
Jake McGinty
2015-07-07 14:25:41 -07:00
committed by Moxie Marlinspike
parent c4524ebbd1
commit bc787f20e3
9 changed files with 129 additions and 107 deletions

View File

@@ -16,7 +16,6 @@
*/
package org.thoughtcrime.securesms.components;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
@@ -27,9 +26,9 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ServiceUtil;
import java.lang.reflect.Field;
import java.util.HashSet;
@@ -46,9 +45,10 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
private final Rect newRect = new Rect();
private final Set<OnKeyboardHiddenListener> hiddenListeners = new HashSet<>();
private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>();
private final int minKeyboardSize;
private final int minKeyboardSize;
private boolean keyboardOpen;
private boolean keyboardOpen = false;
private int rotation = -1;
public KeyboardAwareLinearLayout(Context context) {
this(context, null);
@@ -64,16 +64,29 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updateRotation();
updateKeyboardState();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
int res = getResources().getIdentifier("status_bar_height", "dimen", "android");
private void updateRotation() {
int oldRotation = rotation;
rotation = getDeviceRotation();
if (oldRotation != rotation) {
onKeyboardClose();
oldRect.setEmpty();
}
}
private void updateKeyboardState() {
int res = getResources().getIdentifier("status_bar_height", "dimen", "android");
int statusBarHeight = res > 0 ? getResources().getDimensionPixelSize(res) : 0;
final int availableHeight = this.getRootView().getHeight() - statusBarHeight - getViewInset();
getWindowVisibleDisplayFrame(newRect);
final int oldKeyboardHeight = availableHeight - (oldRect.bottom - oldRect.top);
final int keyboardHeight = availableHeight - (newRect.bottom - newRect.top);
final int keyboardHeight = availableHeight - (newRect.bottom - newRect.top);
if (keyboardHeight - oldKeyboardHeight > minKeyboardSize && !keyboardOpen) {
onKeyboardOpen(keyboardHeight);
@@ -84,7 +97,7 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
oldRect.set(newRect);
}
public void padForCustomKeyboard(int height) {
public void padForCustomKeyboard(final int height) {
setPadding(0, 0, 0, height);
}
@@ -117,22 +130,9 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
protected void onKeyboardOpen(int keyboardHeight) {
keyboardOpen = true;
Log.w(TAG, "onKeyboardOpen(" + keyboardHeight + ")");
WindowManager wm = (WindowManager) getContext().getSystemService(Activity.WINDOW_SERVICE);
if (wm == null || wm.getDefaultDisplay() == null) {
return;
}
int rotation = wm.getDefaultDisplay().getRotation();
switch (rotation) {
case Surface.ROTATION_270:
case Surface.ROTATION_90:
setKeyboardLandscapeHeight(keyboardHeight);
break;
case Surface.ROTATION_0:
case Surface.ROTATION_180:
setKeyboardPortraitHeight(keyboardHeight);
if (!isLandscape()) {
setKeyboardPortraitHeight(keyboardHeight);
}
notifyShownListeners();
unpadForCustomKeyboard();
@@ -140,7 +140,6 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
protected void onKeyboardClose() {
keyboardOpen = false;
Log.w(TAG, "onKeyboardClose()");
notifyHiddenListeners();
}
@@ -149,39 +148,26 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
}
public int getKeyboardHeight() {
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
if (wm == null || wm.getDefaultDisplay() == null) {
throw new AssertionError("WindowManager was null or there is no default display");
}
return isLandscape() ? getKeyboardLandscapeHeight() : getKeyboardPortraitHeight();
}
int rotation = wm.getDefaultDisplay().getRotation();
switch (rotation) {
case Surface.ROTATION_270:
case Surface.ROTATION_90:
return getKeyboardLandscapeHeight();
case Surface.ROTATION_0:
case Surface.ROTATION_180:
default:
return getKeyboardPortraitHeight();
}
public boolean isLandscape() {
int rotation = getDeviceRotation();
return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
}
private int getDeviceRotation() {
return ServiceUtil.getWindowManager(getContext()).getDefaultDisplay().getRotation();
}
private int getKeyboardLandscapeHeight() {
return PreferenceManager.getDefaultSharedPreferences(getContext())
.getInt("keyboard_height_landscape",
getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height));
return Math.max(getHeight(), getRootView().getHeight()) / 2;
}
private int getKeyboardPortraitHeight() {
return PreferenceManager.getDefaultSharedPreferences(getContext())
.getInt("keyboard_height_portrait",
getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height));
}
private void setKeyboardLandscapeHeight(int height) {
PreferenceManager.getDefaultSharedPreferences(getContext())
.edit().putInt("keyboard_height_landscape", height).apply();
int keyboardHeight = PreferenceManager.getDefaultSharedPreferences(getContext())
.getInt("keyboard_height_portrait",
getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height));
return Math.min(keyboardHeight, getRootView().getHeight() - getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_top_margin));
}
private void setKeyboardPortraitHeight(int height) {

View File

@@ -80,6 +80,7 @@ public class EmojiDrawer extends LinearLayoutCompat {
models,
new EmojiSelectionListener() {
@Override public void onEmojiSelected(String emoji) {
Log.w("EmojiDrawer", "onEmojiSelected()");
recentModel.onCodePointSelected(emoji);
if (listener != null) listener.onEmojiSelected(emoji);
}

View File

@@ -1,16 +1,12 @@
package org.thoughtcrime.securesms.components.emoji;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.GridView;
@@ -25,24 +21,27 @@ public class EmojiPageView extends FrameLayout {
private GridView grid;
public EmojiPageView(Context context) {
super(context);
init();
this(context, null);
}
public EmojiPageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
this(context, attrs, 0);
}
public EmojiPageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(VERSION_CODES.LOLLIPOP)
public EmojiPageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
grid = (GridView) view.findViewById(R.id.emoji);
grid.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2 * getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding));
grid.setOnTouchListener(new OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
EmojiView emojiView = (EmojiView)grid.getChildAt(grid.pointToPosition((int)event.getX(), (int)event.getY()));
if (listener != null && emojiView != null) listener.onEmojiSelected(emojiView.getEmoji());
}
return false;
}
});
}
public void onSelected() {
@@ -51,17 +50,6 @@ public class EmojiPageView extends FrameLayout {
}
}
private void init() {
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
grid = (GridView) view.findViewById(R.id.emoji);
grid.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.emoji_drawer_size) + 2 * getResources().getDimensionPixelSize(R.dimen.emoji_drawer_item_padding));
grid.setOnItemClickListener(new OnItemClickListener() {
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (listener != null) listener.onEmojiSelected((String)view.getTag());
}
});
}
public void setModel(EmojiPageModel model) {
this.model = model;
grid.setAdapter(new EmojiGridAdapter(getContext(), model));
@@ -73,9 +61,9 @@ public class EmojiPageView extends FrameLayout {
private static class EmojiGridAdapter extends BaseAdapter {
protected final Context context;
private final int emojiSize;
private final EmojiPageModel model;
protected final Context context;
private final int emojiSize;
private final EmojiPageModel model;
public EmojiGridAdapter(Context context, EmojiPageModel model) {
this.context = context;
@@ -104,14 +92,13 @@ public class EmojiPageView extends FrameLayout {
if (convertView != null && convertView instanceof EmojiView) {
view = (EmojiView)convertView;
} else {
EmojiView emojiView = new EmojiView(context);
final EmojiView emojiView = new EmojiView(context);
emojiView.setPadding(pad, pad, pad, pad);
emojiView.setLayoutParams(new AbsListView.LayoutParams(emojiSize + 2 * pad, emojiSize + 2 * pad));
view = emojiView;
}
view.setEmoji(model.getEmoji()[position]);
view.setTag(model.getEmoji()[position]);
return view;
}
}

View File

@@ -1,31 +1,45 @@
package org.thoughtcrime.securesms.components.emoji;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer.EmojiEventListener;
public class EmojiPopup extends PopupWindow {
private View parent;
private static final String TAG = EmojiPopup.class.getSimpleName();
private KeyboardAwareLinearLayout parent;
public EmojiPopup(View parent) {
public EmojiPopup(KeyboardAwareLinearLayout parent) {
super(new EmojiDrawer(parent.getContext()),
parent.getWidth(),
parent.getResources().getDimensionPixelSize(R.dimen.min_emoji_drawer_height));
this.parent = parent;
Log.w("EmojiPopup", "popup initialized with width " + parent.getWidth());
}
public void setEmojiEventListener(EmojiEventListener listener) {
((EmojiDrawer)getContentView()).setEmojiEventListener(listener);
}
public void show(int height) {
setHeight(height);
public void show() {
setHeight(parent.getKeyboardHeight());
setWidth(parent.getWidth());
parent.padForCustomKeyboard(getHeight());
Log.w(TAG, String.format("show(%d, %d)", getWidth(), getHeight()));
showAtLocation(parent, Gravity.BOTTOM | Gravity.LEFT, 0, 0);
}
@Override
public void dismiss() {
super.dismiss();
}
public void update() {
update(parent, 0, 0, parent.getWidth(), -1);
}
}

View File

@@ -23,22 +23,17 @@ public class EmojiView extends View implements Drawable.Callback {
private final Rect textBounds = new Rect();
public EmojiView(Context context) {
super(context);
this(context, null);
}
public EmojiView(Context context, AttributeSet attrs) {
super(context, attrs);
this(context, attrs, 0);
}
public EmojiView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(VERSION_CODES.LOLLIPOP)
public EmojiView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public void setEmoji(String emoji) {
this.emoji = emoji;
this.drawable = EmojiProvider.getInstance(getContext())
@@ -47,6 +42,10 @@ public class EmojiView extends View implements Drawable.Callback {
postInvalidate();
}
public String getEmoji() {
return emoji;
}
@Override protected void onDraw(Canvas canvas) {
if (drawable != null) {
drawable.setBounds(getPaddingLeft(),