diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml index fde6002d97..abeb5b498d 100644 --- a/res/layout/conversation_activity.xml +++ b/res/layout/conversation_activity.xml @@ -1,72 +1,59 @@ - + android:layout_height="match_parent"> - + - + + android:layout_height="0dp" + android:layout_weight="1" /> - + - - + - + - - + - - - - + - - - - + + + + + diff --git a/res/layout/emoji_drawer_stub.xml b/res/layout/emoji_drawer_stub.xml deleted file mode 100644 index dd8612ec42..0000000000 --- a/res/layout/emoji_drawer_stub.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index 7fdde5e127..49d64a0c62 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -37,7 +37,6 @@ import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.support.v4.view.WindowCompat; import android.text.Editable; -import android.text.InputType; import android.text.TextWatcher; import android.util.Log; import android.view.KeyEvent; @@ -48,9 +47,7 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnKeyListener; -import android.view.ViewStub; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.ImageButton; import android.widget.TextView; @@ -67,6 +64,8 @@ import org.thoughtcrime.securesms.components.ComposeText; import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout; import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener; import org.thoughtcrime.securesms.components.SendButton; +import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.DrawerState; +import org.thoughtcrime.securesms.components.InputAwareLayout; import org.thoughtcrime.securesms.components.emoji.EmojiDrawer.EmojiEventListener; import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; import org.thoughtcrime.securesms.components.emoji.EmojiToggle; @@ -113,13 +112,11 @@ import org.thoughtcrime.securesms.util.DirectoryHelper; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.GroupUtil; -import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.whispersystems.libaxolotl.InvalidMessageException; -import org.whispersystems.libaxolotl.util.guava.Optional; import java.io.IOException; import java.security.NoSuchAlgorithmException; @@ -161,24 +158,24 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private static final int GROUP_EDIT = 5; private static final int TAKE_PHOTO = 6; - private MasterSecret masterSecret; - protected ComposeText composeText; - private AnimatingToggle buttonToggle; - private SendButton sendButton; - private ImageButton attachButton; - protected ConversationTitleView titleView; - private TextView charactersLeft; - private ConversationFragment fragment; - private Button unblockButton; - private KeyboardAwareLinearLayout container; - private View composePanel; - private View composeBubble; + private MasterSecret masterSecret; + protected ComposeText composeText; + private AnimatingToggle buttonToggle; + private SendButton sendButton; + private ImageButton attachButton; + protected ConversationTitleView titleView; + private TextView charactersLeft; + private ConversationFragment fragment; + private Button unblockButton; + private InputAwareLayout container; + private View composePanel; + private View composeBubble; private AttachmentTypeSelectorAdapter attachmentAdapter; private AttachmentManager attachmentManager; private BroadcastReceiver securityUpdateReceiver; private BroadcastReceiver groupUpdateReceiver; - private Optional emojiDrawer = Optional.absent(); + private EmojiDrawer emojiDrawer; private EmojiToggle emojiToggle; protected HidingImageButton quickAttachmentToggle; private QuickAttachmentDrawer quickAttachmentDrawer; @@ -265,19 +262,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } @Override public void onConfigurationChanged(Configuration newConfig) { + Log.w(TAG, "onConfigurationChanged(" + newConfig.orientation + ")"); super.onConfigurationChanged(newConfig); composeText.setTransport(sendButton.getSelectedTransport()); quickAttachmentDrawer.onConfigurationChanged(); - hideEmojiDrawer(false); + if (container.getCurrentInput() == emojiDrawer) container.hideAttachedInput(); } @Override protected void onDestroy() { saveDraft(); - if (recipients != null) recipients.removeListener(this); + if (recipients != null) recipients.removeListener(this); if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver); - if (groupUpdateReceiver != null) unregisterReceiver(groupUpdateReceiver); - if (isEmojiDrawerOpen()) hideEmojiDrawer(false); + if (groupUpdateReceiver != null) unregisterReceiver(groupUpdateReceiver); super.onDestroy(); } @@ -386,19 +383,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onBackPressed() { Log.w(TAG, "onBackPressed()"); - if (isEmojiDrawerOpen()) { - Log.w(TAG, "hiding emoji popup"); - hideEmojiDrawer(false); - } else if (quickAttachmentDrawer.isOpen()) { - quickAttachmentDrawer.close(); - } else { - super.onBackPressed(); - } + if (container.isInputOpen()) container.hideCurrentInput(composeText); + else super.onBackPressed(); } @Override public void onKeyboardShown() { - hideEmojiDrawer(true); + emojiToggle.setToEmoji(); } //////// Event Handlers @@ -758,17 +749,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void initializeViews() { - titleView = (ConversationTitleView) getSupportActionBar().getCustomView(); - buttonToggle = (AnimatingToggle) findViewById(R.id.button_toggle); - sendButton = (SendButton) findViewById(R.id.send_button); - attachButton = (ImageButton) findViewById(R.id.attach_button); - composeText = (ComposeText) findViewById(R.id.embedded_text_editor); - charactersLeft = (TextView) findViewById(R.id.space_left); - emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle); - unblockButton = (Button) findViewById(R.id.unblock_button); - composePanel = findViewById(R.id.bottom_panel); - composeBubble = findViewById(R.id.compose_bubble); - container = (KeyboardAwareLinearLayout) findViewById(R.id.layout_container); + titleView = (ConversationTitleView) getSupportActionBar().getCustomView(); + buttonToggle = (AnimatingToggle) findViewById(R.id.button_toggle); + sendButton = (SendButton) findViewById(R.id.send_button); + attachButton = (ImageButton) findViewById(R.id.attach_button); + composeText = (ComposeText) findViewById(R.id.embedded_text_editor); + charactersLeft = (TextView) findViewById(R.id.space_left); + emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle); + emojiDrawer = (EmojiDrawer) findViewById(R.id.emoji_drawer); + unblockButton = (Button) findViewById(R.id.unblock_button); + composePanel = findViewById(R.id.bottom_panel); + composeBubble = findViewById(R.id.compose_bubble); + container = (InputAwareLayout) findViewById(R.id.layout_container); container.addOnKeyboardShownListener(this); @@ -780,7 +772,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle); titleView = (ConversationTitleView) getSupportActionBar().getCustomView(); unblockButton = (Button) findViewById(R.id.unblock_button); - composePanel = findViewById(R.id.bottom_panel); + composePanel = findViewById(R.id.bottom_panel); quickAttachmentDrawer = (QuickAttachmentDrawer) findViewById(R.id.quick_attachment_drawer); quickAttachmentToggle = (HidingImageButton) findViewById(R.id.quick_attachment_toggle); @@ -796,6 +788,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity SendButtonListener sendButtonListener = new SendButtonListener(); ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener(); + emojiToggle.attach(emojiDrawer); + emojiToggle.setOnClickListener(new EmojiToggleListener()); + emojiDrawer.setEmojiEventListener(new EmojiEventListener() { + @Override public void onKeyEvent(KeyEvent keyEvent) { + composeText.dispatchKeyEvent(keyEvent); + } + + @Override public void onEmojiSelected(String emoji) { + composeText.insertEmoji(emoji); + } + }); + composeText.setOnEditorActionListener(sendButtonListener); attachButton.setOnClickListener(new AttachButtonListener()); sendButton.setOnClickListener(sendButtonListener); @@ -831,7 +835,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity composeText.setOnEditorActionListener(sendButtonListener); composeText.setOnClickListener(composeKeyPressedListener); composeText.setOnFocusChangeListener(composeKeyPressedListener); - emojiToggle.setOnClickListener(new EmojiToggleListener()); if (QuickAttachmentDrawer.isDeviceSupported(this)) { quickAttachmentDrawer.setListener(this); @@ -848,48 +851,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity getSupportActionBar().setDisplayShowTitleEnabled(false); } - private EmojiDrawer getEmojiDrawer() { - if (!emojiDrawer.isPresent()) { - EmojiDrawer emojiDrawer = (EmojiDrawer)((ViewStub)findViewById(R.id.emoji_drawer_stub)).inflate(); - emojiDrawer.setEmojiEventListener(new EmojiEventListener() { - @Override public void onKeyEvent(KeyEvent keyEvent) { - composeText.dispatchKeyEvent(keyEvent); - } - - @Override public void onEmojiSelected(String emoji) { - Log.w(TAG, "onEmojiSelected()"); - composeText.insertEmoji(emoji); - } - }); - this.emojiDrawer = Optional.of(emojiDrawer); - } - return emojiDrawer.get(); - } - - private void showEmojiDrawer() { - getEmojiDrawer().show(container); - emojiToggle.setToIme(); - } - - protected void hideEmojiDrawer(boolean expectingKeyboard) { - if (isEmojiDrawerOpen()) { - if (!expectingKeyboard || container.isLandscape()) { - getEmojiDrawer().dismiss(); - } else { - container.postOnKeyboardOpen(new Runnable() { - @Override public void run() { - getEmojiDrawer().dismiss(); - } - }); - } - } - emojiToggle.setToEmoji(); - } - - private boolean isEmojiDrawerOpen() { - return emojiDrawer.isPresent() && emojiDrawer.get().isShowing(); - } - private void initializeResources() { recipients = RecipientFactory.getRecipientsForIds(this, getIntent().getLongArrayExtra(RECIPIENTS_EXTRA), true); threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); @@ -1302,26 +1263,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } @Override - public void onAttachmentDrawerClosed() { - getSupportActionBar().show(); - } - - @Override - public void onAttachmentDrawerOpened() { - getSupportActionBar().hide(); + public void onAttachmentDrawerStateChanged(DrawerState drawerState) { + if (drawerState == DrawerState.FULL_EXPANDED) { + getSupportActionBar().hide(); + } else { + getSupportActionBar().show(); + } } @Override public void onImageCapture(@NonNull final byte[] imageBytes) { attachmentManager.setCaptureUri(CaptureProvider.getInstance(this).create(masterSecret, recipients, imageBytes)); addAttachmentImage(masterSecret, attachmentManager.getCaptureUri()); - quickAttachmentDrawer.close(); + quickAttachmentDrawer.hide(false); } @Override public void onCameraFail(FailureReason reason) { Toast.makeText(this, R.string.quick_camera_unavailable, Toast.LENGTH_SHORT).show(); - quickAttachmentDrawer.close(); + quickAttachmentDrawer.hide(false); quickAttachmentToggle.disable(); } @@ -1335,46 +1295,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } } - private void openKeyboardForComposition() { - composeText.post(new Runnable() { - @Override public void run() { - composeText.requestFocus(); - ServiceUtil.getInputMethodManager(ConversationActivity.this).showSoftInput(composeText, 0); - } - }); - } - - private void hideKeyboard() { - ServiceUtil.getInputMethodManager(this).hideSoftInputFromWindow(composeText.getWindowToken(), 0); - } - private class EmojiToggleListener implements OnClickListener { - @Override - public void onClick(View v) { - Log.w(TAG, "EmojiToggleListener onClick()"); - if (isEmojiDrawerOpen()) { - hideEmojiDrawer(true); - openKeyboardForComposition(); - } else { - container.postOnKeyboardClose(new Runnable() { - @Override public void run() { - showEmojiDrawer(); - } - }); - quickAttachmentDrawer.close(); - hideKeyboard(); - } + + @Override public void onClick(View v) { + if (container.getCurrentInput() == emojiDrawer) container.showSoftkey(composeText); + else container.show(composeText, emojiDrawer); } } private class QuickAttachmentToggleListener implements OnClickListener { @Override public void onClick(View v) { - InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - input.hideSoftInputFromWindow(composeText.getWindowToken(), 0); composeText.clearFocus(); - hideEmojiDrawer(false); - quickAttachmentDrawer.open(); + container.show(composeText, quickAttachmentDrawer); } } @@ -1421,7 +1354,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onClick(View v) { - hideEmojiDrawer(true); + container.showSoftkey(composeText); } @Override @@ -1447,13 +1380,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public void onTextChanged(CharSequence s, int start, int before,int count) {} @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus && isEmojiDrawerOpen()) { - hideEmojiDrawer(true); - } else if (hasFocus && quickAttachmentDrawer.isOpen()) { - quickAttachmentDrawer.close(); - } - } + public void onFocusChange(View v, boolean hasFocus) {} } @Override diff --git a/src/org/thoughtcrime/securesms/ConversationPopupActivity.java b/src/org/thoughtcrime/securesms/ConversationPopupActivity.java index b165601008..b34efc4239 100644 --- a/src/org/thoughtcrime/securesms/ConversationPopupActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationPopupActivity.java @@ -113,11 +113,6 @@ public class ConversationPopupActivity extends ConversationActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(false); } - @Override - protected void hideEmojiDrawer(boolean expectingKeyboard) { - super.hideEmojiDrawer(false); - } - @Override protected void sendComplete(long threadId) { super.sendComplete(threadId); diff --git a/src/org/thoughtcrime/securesms/components/InputAwareLayout.java b/src/org/thoughtcrime/securesms/components/InputAwareLayout.java new file mode 100644 index 0000000000..5436ec9150 --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/InputAwareLayout.java @@ -0,0 +1,94 @@ +package org.thoughtcrime.securesms.components; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.widget.EditText; + +import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener; +import org.thoughtcrime.securesms.util.ServiceUtil; + +public class InputAwareLayout extends KeyboardAwareLinearLayout implements OnKeyboardShownListener { + private InputView current; + + public InputAwareLayout(Context context) { + this(context, null); + } + + public InputAwareLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public InputAwareLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + addOnKeyboardShownListener(this); + } + + @Override public void onKeyboardShown() { + hideAttachedInput(); + } + + public void show(@NonNull final EditText imeTarget, @NonNull final InputView input) { + if (isKeyboardOpen()) { + hideSoftkey(imeTarget, new Runnable() { + @Override public void run() { + input.show(getKeyboardHeight(), true); + } + }); + } else if (current != null && current.isShowing()) { + current.hide(true); + input.show(getKeyboardHeight(), true); + } else { + input.show(getKeyboardHeight(), false); + } + + current = input; + } + + public InputView getCurrentInput() { + return current; + } + + public void hideCurrentInput(EditText imeTarget) { + if (isKeyboardOpen()) hideSoftkey(imeTarget, null); + else hideAttachedInput(); + } + + public void hideAttachedInput() { + if (current != null) current.hide(true); + current = null; + } + + public boolean isInputOpen() { + return (isKeyboardOpen() || (current != null && current.isShowing())); + } + + public void showSoftkey(final EditText inputTarget) { + postOnKeyboardOpen(new Runnable() { + @Override public void run() { + hideAttachedInput(); + } + }); + inputTarget.post(new Runnable() { + @Override public void run() { + inputTarget.requestFocus(); + ServiceUtil.getInputMethodManager(inputTarget.getContext()).showSoftInput(inputTarget, 0); + } + }); + } + + private void hideSoftkey(final EditText inputTarget, @Nullable Runnable runAfterClose) { + if (runAfterClose != null) postOnKeyboardClose(runAfterClose); + + ServiceUtil.getInputMethodManager(inputTarget.getContext()) + .hideSoftInputFromWindow(inputTarget.getWindowToken(), 0); + } + + public interface InputView { + void show(int height, boolean immediate); + void hide(boolean immediate); + boolean isShowing(); + } +} + diff --git a/src/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java b/src/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java index 200a93aeb0..d0ad8ed731 100644 --- a/src/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java +++ b/src/org/thoughtcrime/securesms/components/KeyboardAwareLinearLayout.java @@ -16,6 +16,7 @@ */ package org.thoughtcrime.securesms.components; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Rect; import android.os.Build; @@ -42,14 +43,17 @@ import java.util.Set; public class KeyboardAwareLinearLayout extends LinearLayoutCompat { private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName(); - private final Rect oldRect = new Rect(); - private final Rect newRect = new Rect(); - private final Set hiddenListeners = new HashSet<>(); - private final Set shownListeners = new HashSet<>(); - private final int minKeyboardSize; - private final int minCustomKeyboardSize; - private final int defaultCustomKeyboardSize; - private final int minCustomKeyboardTopMargin; + private final Rect oldRect = new Rect(); + private final Rect newRect = new Rect(); + private final Set hiddenListeners = new HashSet<>(); + private final Set 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; @@ -64,16 +68,19 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat { public KeyboardAwareLinearLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + final 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) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); updateRotation(); updateKeyboardState(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private void updateRotation() { @@ -86,10 +93,8 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat { } 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(); + if (viewInset == 0 && Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) viewInset = getViewInset(); + final int availableHeight = this.getRootView().getHeight() - statusBarHeight - viewInset; getWindowVisibleDisplayFrame(newRect); final int oldKeyboardHeight = availableHeight - (oldRect.bottom - oldRect.top); @@ -104,11 +109,8 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat { oldRect.set(newRect); } + @TargetApi(VERSION_CODES.LOLLIPOP) private int getViewInset() { - if (Build.VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) { - return 0; - } - try { Field attachInfoField = View.class.getDeclaredField("mAttachInfo"); attachInfoField.setAccessible(true); @@ -141,6 +143,10 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat { notifyHiddenListeners(); } + public boolean isKeyboardOpen() { + return keyboardOpen; + } + public int getKeyboardHeight() { return isLandscape() ? getKeyboardLandscapeHeight() : getKeyboardPortraitHeight(); } diff --git a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java index 9e6cf5de24..7d98262e85 100644 --- a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java +++ b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java @@ -21,32 +21,31 @@ import android.view.ViewGroup; import android.widget.ImageButton; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.InputManager.InputView; import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout; import org.thoughtcrime.securesms.components.camera.QuickCamera.QuickCameraListener; import org.thoughtcrime.securesms.util.ServiceUtil; +import org.thoughtcrime.securesms.util.Util; -public class QuickAttachmentDrawer extends ViewGroup { - private static final String TAG = QuickAttachmentDrawer.class.getSimpleName(); - private static final float FULL_EXPANDED_ANCHOR_POINT = 1.f; - private static final float COLLAPSED_ANCHOR_POINT = 0.f; +public class QuickAttachmentDrawer extends ViewGroup implements InputView { + private static final String TAG = QuickAttachmentDrawer.class.getSimpleName(); private final ViewDragHelper dragHelper; private QuickCamera quickCamera; private int coverViewPosition; - private KeyboardAwareLinearLayout coverView; + private KeyboardAwareLinearLayout container; + private View coverView; private View controls; private ImageButton fullScreenButton; private ImageButton swapCameraButton; private ImageButton shutterButton; - private float slideOffset; + private int slideOffset; private float initialMotionX; private float initialMotionY; private int rotation; - private int slideRange; private AttachmentDrawerListener listener; private int halfExpandedHeight; - private float halfExpandedAnchorPoint; private DrawerState drawerState = DrawerState.COLLAPSED; private boolean halfModeUnsupported = VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH; @@ -82,16 +81,19 @@ public class QuickAttachmentDrawer extends ViewGroup { Camera.getNumberOfCameras() > 0; } - public boolean isOpen() { + @Override + public boolean isShowing() { return drawerState.isVisible(); } - public void close() { - setDrawerStateAndAnimate(DrawerState.COLLAPSED); + @Override + public void hide(boolean immediate) { + setDrawerStateAndUpdate(DrawerState.COLLAPSED, immediate); } - public void open() { - setDrawerStateAndAnimate(DrawerState.HALF_EXPANDED); + @Override + public void show(int height, boolean immediate) { + setDrawerStateAndUpdate(DrawerState.HALF_EXPANDED, immediate); } public void onConfigurationChanged() { @@ -99,11 +101,11 @@ public class QuickAttachmentDrawer extends ViewGroup { final boolean rotationChanged = this.rotation != rotation; this.rotation = rotation; if (rotationChanged) { - if (isOpen()) { + if (isShowing()) { quickCamera.onPause(); } updateControlsView(); - setDrawerStateAndAnimate(drawerState); + setDrawerStateAndUpdate(drawerState); } } @@ -134,23 +136,19 @@ public class QuickAttachmentDrawer extends ViewGroup { return isLandscape() || halfModeUnsupported; } - private KeyboardAwareLinearLayout getCoverView() { - if (coverView != null) return coverView; - - final View coverViewChild = getChildAt(coverViewPosition); - if (coverViewChild != null && !(coverViewChild instanceof KeyboardAwareLinearLayout)) { - throw new IllegalStateException("cover view must be a KeyboardAwareLinearLayout"); - } - - coverView = (KeyboardAwareLinearLayout) coverViewChild; + private View getCoverView() { + if (coverView == null) coverView = getChildAt(coverViewPosition); return coverView; } + private KeyboardAwareLinearLayout getContainer() { + if (container == null) container = (KeyboardAwareLinearLayout)getParent(); + return container; + } + private void updateHalfExpandedAnchorPoint() { - if (getCoverView() != null) { - slideRange = getMeasuredHeight(); - halfExpandedHeight = coverView.getKeyboardHeight(); - halfExpandedAnchorPoint = computeSlideOffsetFromCoverBottom(slideRange - halfExpandedHeight); + if (getContainer() != null) { + halfExpandedHeight = getContainer().getKeyboardHeight(); } } @@ -159,8 +157,6 @@ public class QuickAttachmentDrawer extends ViewGroup { final int paddingLeft = getPaddingLeft(); final int paddingTop = getPaddingTop(); - updateHalfExpandedAnchorPoint(); - for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); final int childHeight = child.getMeasuredHeight(); @@ -177,8 +173,8 @@ public class QuickAttachmentDrawer extends ViewGroup { } else if (child == controls) { childBottom = getMeasuredHeight(); } else { - childBottom = computeCoverBottomPosition(slideOffset); - childTop = childBottom - childHeight; + childTop = computeCoverTopPosition(slideOffset); + childBottom = childTop + childHeight; } final int childRight = childLeft + child.getMeasuredWidth(); @@ -233,7 +229,7 @@ public class QuickAttachmentDrawer extends ViewGroup { default: childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); break; - } + } child.measure(childWidthSpec, childHeightSpec); } @@ -269,11 +265,11 @@ public class QuickAttachmentDrawer extends ViewGroup { ViewCompat.postInvalidateOnAnimation(this); } - if (slideOffset == COLLAPSED_ANCHOR_POINT && quickCamera.isStarted()) { + if (slideOffset == 0 && quickCamera.isStarted()) { quickCamera.onPause(); controls.setVisibility(INVISIBLE); quickCamera.setVisibility(INVISIBLE); - } else if (slideOffset != COLLAPSED_ANCHOR_POINT && !quickCamera.isStarted() & !paused) { + } else if (slideOffset != 0 && !quickCamera.isStarted() & !paused) { controls.setVisibility(VISIBLE); quickCamera.setVisibility(VISIBLE); quickCamera.onResume(); @@ -284,7 +280,6 @@ public class QuickAttachmentDrawer extends ViewGroup { switch (drawerState) { case COLLAPSED: fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen); - if (listener != null) listener.onAttachmentDrawerClosed(); break; case HALF_EXPANDED: if (isFullscreenOnly()) { @@ -292,30 +287,37 @@ public class QuickAttachmentDrawer extends ViewGroup { return; } fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen); - if (listener != null) listener.onAttachmentDrawerOpened(); break; case FULL_EXPANDED: fullScreenButton.setImageResource(isFullscreenOnly() ? R.drawable.quick_camera_hide : R.drawable.quick_camera_exit_fullscreen); - if (listener != null) listener.onAttachmentDrawerOpened(); break; } - this.drawerState = drawerState; - } - public float getTargetSlideOffset() { - switch (drawerState) { - case FULL_EXPANDED: return FULL_EXPANDED_ANCHOR_POINT; - case HALF_EXPANDED: return halfExpandedAnchorPoint; - default: return COLLAPSED_ANCHOR_POINT; + if (listener != null && drawerState != this.drawerState) { + this.drawerState = drawerState; + listener.onAttachmentDrawerStateChanged(drawerState); } } - public void setDrawerStateAndAnimate(final DrawerState requestedDrawerState) { + public int getTargetSlideOffset() { + switch (drawerState) { + case FULL_EXPANDED: return getMeasuredHeight(); + case HALF_EXPANDED: return halfExpandedHeight; + default: return 0; + } + } + + public void setDrawerStateAndUpdate(final DrawerState requestedDrawerState) { + setDrawerStateAndUpdate(requestedDrawerState, false); + } + + public void setDrawerStateAndUpdate(final DrawerState requestedDrawerState, boolean instant) { DrawerState oldDrawerState = this.drawerState; setDrawerState(requestedDrawerState); if (oldDrawerState != drawerState) { - slideTo(getTargetSlideOffset()); + updateHalfExpandedAnchorPoint(); + slideTo(getTargetSlideOffset(), instant); } } @@ -325,8 +327,7 @@ public class QuickAttachmentDrawer extends ViewGroup { } public interface AttachmentDrawerListener extends QuickCameraListener { - void onAttachmentDrawerClosed(); - void onAttachmentDrawerOpened(); + void onAttachmentDrawerStateChanged(DrawerState drawerState); } private class ViewDragHelperCallback extends ViewDragHelper.Callback { @@ -351,10 +352,7 @@ public class QuickAttachmentDrawer extends ViewGroup { @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - final int expandedTop = computeCoverBottomPosition(FULL_EXPANDED_ANCHOR_POINT) - coverView.getHeight(); - final int collapsedTop = computeCoverBottomPosition(COLLAPSED_ANCHOR_POINT) - coverView.getHeight(); - final int newTop = Math.min(Math.max(coverView.getTop() + dy, expandedTop), collapsedTop); - slideOffset = computeSlideOffsetFromCoverBottom(newTop + coverView.getHeight()); + slideOffset = Util.clamp(slideOffset - dy, 0, getMeasuredHeight()); requestLayout(); } @@ -367,25 +365,20 @@ public class QuickAttachmentDrawer extends ViewGroup { if (direction > 1) { drawerState = DrawerState.FULL_EXPANDED; } else if (direction < -1) { - boolean halfExpand = (slideOffset > halfExpandedAnchorPoint && !isLandscape()); + boolean halfExpand = (slideOffset > halfExpandedHeight && !isLandscape()); drawerState = halfExpand ? DrawerState.HALF_EXPANDED : DrawerState.COLLAPSED; } else if (!isLandscape()) { - if (halfExpandedAnchorPoint != 1 && slideOffset >= (1.f + halfExpandedAnchorPoint) / 2) { + if (slideOffset >= (halfExpandedHeight + getMeasuredHeight()) / 2) { drawerState = DrawerState.FULL_EXPANDED; - } else if (halfExpandedAnchorPoint == 1 && slideOffset >= 0.5f) { - drawerState = DrawerState.FULL_EXPANDED; - } else if (halfExpandedAnchorPoint != 1 && slideOffset >= halfExpandedAnchorPoint) { - drawerState = DrawerState.HALF_EXPANDED; - } else if (halfExpandedAnchorPoint != 1 && slideOffset >= halfExpandedAnchorPoint / 2) { + } else if (slideOffset >= halfExpandedHeight / 2) { drawerState = DrawerState.HALF_EXPANDED; } } setDrawerState(drawerState); - float slideOffset = getTargetSlideOffset(); + int slideOffset = getTargetSlideOffset(); dragHelper.captureChildView(coverView, 0); - Log.w(TAG, String.format("setting cover at %d", computeCoverBottomPosition(slideOffset) - coverView.getHeight())); - dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverBottomPosition(slideOffset) - coverView.getHeight()); + dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverTopPosition(slideOffset)); dragHelper.captureChildView(quickCamera, 0); dragHelper.settleCapturedViewAt(quickCamera.getLeft(), computeCameraTopPosition(slideOffset)); ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this); @@ -394,7 +387,7 @@ public class QuickAttachmentDrawer extends ViewGroup { @Override public int getViewVerticalDragRange(View child) { - return slideRange; + return getMeasuredHeight(); } @Override @@ -465,28 +458,22 @@ public class QuickAttachmentDrawer extends ViewGroup { screenY >= viewLocation[1] && screenY < viewLocation[1] + quickCamera.getHeight(); } - private int computeCameraTopPosition(float slideOffset) { - float clampedOffset = slideOffset - halfExpandedAnchorPoint; - if (clampedOffset < COLLAPSED_ANCHOR_POINT) { - clampedOffset = COLLAPSED_ANCHOR_POINT; - } else { - clampedOffset = clampedOffset / (FULL_EXPANDED_ANCHOR_POINT - halfExpandedAnchorPoint); - } - float slidePixelOffset = slideOffset * slideRange + - (quickCamera.getMeasuredHeight() - coverView.getKeyboardHeight()) / 2 * - (FULL_EXPANDED_ANCHOR_POINT - clampedOffset); - float marginPixelOffset = (getMeasuredHeight() - quickCamera.getMeasuredHeight()) / 2 * clampedOffset; - return (int) (getMeasuredHeight() - slidePixelOffset + marginPixelOffset); + private int computeCameraTopPosition(int slideOffset) { + final int baseCameraTop = (quickCamera.getMeasuredHeight() - halfExpandedHeight) / 2; + final int baseOffset = getMeasuredHeight() - slideOffset - baseCameraTop; + final float slop = Util.clamp((float)(slideOffset - halfExpandedHeight) / (getMeasuredHeight() - halfExpandedHeight), + 0f, + 1f); + return baseOffset + (int)(slop * baseCameraTop); } - private int computeCoverBottomPosition(float slideOffset) { - int slidePixelOffset = (int) (slideOffset * slideRange); - return getMeasuredHeight() - getPaddingBottom() - slidePixelOffset; + private int computeCoverTopPosition(int slideOffset) { + return getMeasuredHeight() - getPaddingBottom() - slideOffset - getCoverView().getMeasuredHeight(); } - private void slideTo(float slideOffset) { - if (dragHelper != null && !halfModeUnsupported) { - dragHelper.smoothSlideViewTo(coverView, coverView.getLeft(), computeCoverBottomPosition(slideOffset) - coverView.getHeight()); + private void slideTo(int slideOffset, boolean forceInstant) { + if (dragHelper != null && !halfModeUnsupported && !forceInstant) { + dragHelper.smoothSlideViewTo(coverView, coverView.getLeft(), computeCoverTopPosition(slideOffset)); dragHelper.smoothSlideViewTo(quickCamera, quickCamera.getLeft(), computeCameraTopPosition(slideOffset)); ViewCompat.postInvalidateOnAnimation(this); } else { @@ -497,11 +484,6 @@ public class QuickAttachmentDrawer extends ViewGroup { } } - private float computeSlideOffsetFromCoverBottom(int topPosition) { - final int topBoundCollapsed = computeCoverBottomPosition(0); - return (float) (topBoundCollapsed - topPosition) / slideRange; - } - public void onPause() { paused = true; quickCamera.onPause(); @@ -524,7 +506,7 @@ public class QuickAttachmentDrawer extends ViewGroup { @Override public void onClick(View v) { boolean crop = drawerState != DrawerState.FULL_EXPANDED; - int imageHeight = crop ? coverView.getKeyboardHeight() : quickCamera.getMeasuredHeight(); + int imageHeight = crop ? getContainer().getKeyboardHeight() : quickCamera.getMeasuredHeight(); Rect previewRect = new Rect(0, 0, quickCamera.getMeasuredWidth(), imageHeight); quickCamera.takePicture(previewRect); } @@ -543,11 +525,11 @@ public class QuickAttachmentDrawer extends ViewGroup { @Override public void onClick(View v) { if (drawerState != DrawerState.FULL_EXPANDED) { - setDrawerStateAndAnimate(DrawerState.FULL_EXPANDED); + setDrawerStateAndUpdate(DrawerState.FULL_EXPANDED); } else if (isFullscreenOnly()) { - setDrawerStateAndAnimate(DrawerState.COLLAPSED); + setDrawerStateAndUpdate(DrawerState.COLLAPSED); } else { - setDrawerStateAndAnimate(DrawerState.HALF_EXPANDED); + setDrawerStateAndUpdate(DrawerState.HALF_EXPANDED); } } } diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java index 0ea5183618..ad5bcd1c21 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java +++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiDrawer.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.components.emoji; import android.content.Context; +import android.content.res.Configuration; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.PagerAdapter; @@ -18,7 +19,7 @@ import android.widget.LinearLayout; import com.astuetz.PagerSlidingTabStrip; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout; +import org.thoughtcrime.securesms.components.InputManager.InputView; import org.thoughtcrime.securesms.components.RepeatableImageKey; import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener; import org.thoughtcrime.securesms.components.emoji.EmojiPageView.EmojiSelectionListener; @@ -27,7 +28,7 @@ import org.thoughtcrime.securesms.util.ResUtil; import java.util.LinkedList; import java.util.List; -public class EmojiDrawer extends LinearLayout { +public class EmojiDrawer extends LinearLayout implements InputView { private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL); private ViewPager pager; @@ -35,6 +36,7 @@ public class EmojiDrawer extends LinearLayout { private PagerSlidingTabStrip strip; private RecentEmojiPageModel recentModel; private EmojiEventListener listener; + private EmojiDrawerListener drawerListener; public EmojiDrawer(Context context) { this(context, null); @@ -43,6 +45,9 @@ public class EmojiDrawer extends LinearLayout { public EmojiDrawer(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(VERTICAL); + } + + private void initView() { final View v = LayoutInflater.from(getContext()).inflate(R.layout.emoji_drawer, this, true); initializeResources(v); initializePageModels(); @@ -53,6 +58,10 @@ public class EmojiDrawer extends LinearLayout { this.listener = listener; } + public void setDrawerListener(EmojiDrawerListener listener) { + this.drawerListener = listener; + } + private void initializeResources(View v) { Log.w("EmojiDrawer", "initializeResources()"); this.pager = (ViewPager) v.findViewById(R.id.emoji_pager); @@ -66,20 +75,27 @@ public class EmojiDrawer extends LinearLayout { }); } + @Override public boolean isShowing() { return getVisibility() == VISIBLE; } - public void show(KeyboardAwareLinearLayout container) { + @Override + public void show(int height, boolean immediate) { + if (this.pager == null) initView(); ViewGroup.LayoutParams params = getLayoutParams(); - params.height = container.getKeyboardHeight(); + params.height = height; Log.w("EmojiDrawer", "showing emoji drawer with height " + params.height); setLayoutParams(params); setVisibility(VISIBLE); + if (drawerListener != null) drawerListener.onShown(); } - public void dismiss() { + @Override + public void hide(boolean immediate) { setVisibility(GONE); + if (drawerListener != null) drawerListener.onHidden(); + Log.w("EmojiDrawer", "hide()"); } private void initializeEmojiGrid() { @@ -161,4 +177,9 @@ public class EmojiDrawer extends LinearLayout { public interface EmojiEventListener extends EmojiSelectionListener { void onKeyEvent(KeyEvent keyEvent); } + + public interface EmojiDrawerListener { + void onShown(); + void onHidden(); + } } diff --git a/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java b/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java index 554fd34b5c..5ffeb9b8ac 100644 --- a/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java +++ b/src/org/thoughtcrime/securesms/components/emoji/EmojiToggle.java @@ -8,8 +8,9 @@ import android.util.AttributeSet; import android.widget.ImageButton; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.components.emoji.EmojiDrawer.EmojiDrawerListener; -public class EmojiToggle extends ImageButton { +public class EmojiToggle extends ImageButton implements EmojiDrawerListener { private Drawable emojiToggle; private Drawable imeToggle; @@ -48,4 +49,16 @@ public class EmojiToggle extends ImageButton { drawables.recycle(); setToEmoji(); } + + public void attach(EmojiDrawer drawer) { + drawer.setDrawerListener(this); + } + + @Override public void onShown() { + setToIme(); + } + + @Override public void onHidden() { + setToEmoji(); + } } diff --git a/src/org/thoughtcrime/securesms/util/Util.java b/src/org/thoughtcrime/securesms/util/Util.java index fda6e4bd7f..a7ce4ce806 100644 --- a/src/org/thoughtcrime/securesms/util/Util.java +++ b/src/org/thoughtcrime/securesms/util/Util.java @@ -348,4 +348,8 @@ public class Util { public static int clamp(int value, int min, int max) { return Math.min(Math.max(value, min), max); } + + public static float clamp(float value, float min, float max) { + return Math.min(Math.max(value, min), max); + } }