Smooth transition for custom keyboards and softkey

Closes #3786
Fixes #3780
// FREEBIE
This commit is contained in:
Jake McGinty 2015-07-24 13:22:28 -07:00 committed by Moxie Marlinspike
parent 24e14cbc73
commit 13bad6dfed
10 changed files with 344 additions and 321 deletions

View File

@ -1,72 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer <org.thoughtcrime.securesms.components.InputAwareLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/quick_attachment_drawer" android:id="@+id/layout_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="@color/black">
<org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout <org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer
android:id="@+id/layout_container" android:id="@+id/quick_attachment_drawer"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="fill_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical"
android:background="@color/black">
<RelativeLayout android:layout_width="fill_parent" <LinearLayout android:layout_width="match_parent"
android:layout_height="fill_parent" android:layout_height="match_parent"
android:layout_weight="1" android:orientation="vertical"
android:orientation="vertical" android:background="?conversation_background"
android:background="?conversation_background" android:paddingTop="?attr/actionBarSize"
android:paddingTop="?attr/actionBarSize" android:gravity="bottom">
android:gravity="bottom">
<FrameLayout android:id="@+id/fragment_content" <FrameLayout android:id="@+id/fragment_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_above="@+id/bottom_container" /> android:layout_weight="1" />
<LinearLayout <LinearLayout android:id="@+id/bottom_container"
android:layout_alignParentBottom="true" android:layout_width="fill_parent"
android:id="@id/bottom_container" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:orientation="vertical">
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView android:layout_width="fill_parent" <FrameLayout
android:layout_height="0dp" android:id="@+id/attachment_editor"
android:layout_weight="1"> android:layout_width="wrap_content"
<FrameLayout android:layout_height="wrap_content"
android:id="@+id/attachment_editor" android:layout_gravity="center_horizontal"
android:layout_width="wrap_content" android:paddingTop="10dp"
android:layout_height="wrap_content" android:visibility="gone">
android:layout_gravity="center_horizontal"
android:visibility="gone">
<FrameLayout <org.thoughtcrime.securesms.components.ThumbnailView
android:paddingLeft="10dp" android:id="@+id/attachment_thumbnail"
android:paddingTop="10dp" android:layout_width="230dp"
android:layout_width="fill_parent" android:layout_height="150dp"
android:layout_height="fill_parent"> android:contentDescription="@string/conversation_activity__attachment_thumbnail"
app:backgroundColorHint="?conversation_background" />
<org.thoughtcrime.securesms.components.ThumbnailView <ImageView android:id="@+id/remove_image_button"
android:id="@+id/attachment_thumbnail" android:layout_width="wrap_content"
android:layout_width="230dp" android:layout_height="wrap_content"
android:layout_height="150dp" android:src="@drawable/conversation_attachment_close_circle"
android:contentDescription="@string/conversation_activity__attachment_thumbnail" android:layout_gravity="top|left"/>
app:backgroundColorHint="?conversation_background" />
</FrameLayout>
<ImageView android:id="@+id/remove_image_button" </FrameLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/conversation_attachment_close_circle"
android:layout_gravity="top|left"/>
</FrameLayout>
</ScrollView>
<LinearLayout android:id="@+id/bottom_panel" <LinearLayout android:id="@+id/bottom_panel"
android:layout_width="fill_parent" android:layout_width="fill_parent"
@ -157,12 +144,12 @@
android:text="160/160 (1)" /> android:text="160/160 (1)" />
</LinearLayout> </LinearLayout>
</RelativeLayout>
<org.thoughtcrime.securesms.components.emoji.EmojiDrawer
<ViewStub android:id="@+id/emoji_drawer_stub" android:id="@+id/emoji_drawer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:inflatedId="@+id/emoji_drawer"
android:layout="@layout/emoji_drawer_stub" /> </LinearLayout>
</org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout>
</org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer> </org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer>
</org.thoughtcrime.securesms.components.InputAwareLayout>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.emoji.EmojiDrawer
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.1" />

View File

@ -37,7 +37,6 @@ import android.provider.ContactsContract;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.view.WindowCompat; import android.support.v4.view.WindowCompat;
import android.text.Editable; import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -48,9 +47,7 @@ import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener; import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener; import android.view.View.OnKeyListener;
import android.view.ViewStub;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; 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;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener; import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
import org.thoughtcrime.securesms.components.SendButton; 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.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle; 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.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -161,24 +158,24 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private static final int GROUP_EDIT = 5; private static final int GROUP_EDIT = 5;
private static final int TAKE_PHOTO = 6; private static final int TAKE_PHOTO = 6;
private MasterSecret masterSecret; private MasterSecret masterSecret;
protected ComposeText composeText; protected ComposeText composeText;
private AnimatingToggle buttonToggle; private AnimatingToggle buttonToggle;
private SendButton sendButton; private SendButton sendButton;
private ImageButton attachButton; private ImageButton attachButton;
protected ConversationTitleView titleView; protected ConversationTitleView titleView;
private TextView charactersLeft; private TextView charactersLeft;
private ConversationFragment fragment; private ConversationFragment fragment;
private Button unblockButton; private Button unblockButton;
private KeyboardAwareLinearLayout container; private InputAwareLayout container;
private View composePanel; private View composePanel;
private View composeBubble; private View composeBubble;
private AttachmentTypeSelectorAdapter attachmentAdapter; private AttachmentTypeSelectorAdapter attachmentAdapter;
private AttachmentManager attachmentManager; private AttachmentManager attachmentManager;
private BroadcastReceiver securityUpdateReceiver; private BroadcastReceiver securityUpdateReceiver;
private BroadcastReceiver groupUpdateReceiver; private BroadcastReceiver groupUpdateReceiver;
private Optional<EmojiDrawer> emojiDrawer = Optional.absent(); private EmojiDrawer emojiDrawer;
private EmojiToggle emojiToggle; private EmojiToggle emojiToggle;
protected HidingImageButton quickAttachmentToggle; protected HidingImageButton quickAttachmentToggle;
private QuickAttachmentDrawer quickAttachmentDrawer; private QuickAttachmentDrawer quickAttachmentDrawer;
@ -265,19 +262,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
@Override public void onConfigurationChanged(Configuration newConfig) { @Override public void onConfigurationChanged(Configuration newConfig) {
Log.w(TAG, "onConfigurationChanged(" + newConfig.orientation + ")");
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
composeText.setTransport(sendButton.getSelectedTransport()); composeText.setTransport(sendButton.getSelectedTransport());
quickAttachmentDrawer.onConfigurationChanged(); quickAttachmentDrawer.onConfigurationChanged();
hideEmojiDrawer(false); if (container.getCurrentInput() == emojiDrawer) container.hideAttachedInput();
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
saveDraft(); saveDraft();
if (recipients != null) recipients.removeListener(this); if (recipients != null) recipients.removeListener(this);
if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver); if (securityUpdateReceiver != null) unregisterReceiver(securityUpdateReceiver);
if (groupUpdateReceiver != null) unregisterReceiver(groupUpdateReceiver); if (groupUpdateReceiver != null) unregisterReceiver(groupUpdateReceiver);
if (isEmojiDrawerOpen()) hideEmojiDrawer(false);
super.onDestroy(); super.onDestroy();
} }
@ -386,19 +383,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void onBackPressed() { public void onBackPressed() {
Log.w(TAG, "onBackPressed()"); Log.w(TAG, "onBackPressed()");
if (isEmojiDrawerOpen()) { if (container.isInputOpen()) container.hideCurrentInput(composeText);
Log.w(TAG, "hiding emoji popup"); else super.onBackPressed();
hideEmojiDrawer(false);
} else if (quickAttachmentDrawer.isOpen()) {
quickAttachmentDrawer.close();
} else {
super.onBackPressed();
}
} }
@Override @Override
public void onKeyboardShown() { public void onKeyboardShown() {
hideEmojiDrawer(true); emojiToggle.setToEmoji();
} }
//////// Event Handlers //////// Event Handlers
@ -758,17 +749,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void initializeViews() { private void initializeViews() {
titleView = (ConversationTitleView) getSupportActionBar().getCustomView(); titleView = (ConversationTitleView) getSupportActionBar().getCustomView();
buttonToggle = (AnimatingToggle) findViewById(R.id.button_toggle); buttonToggle = (AnimatingToggle) findViewById(R.id.button_toggle);
sendButton = (SendButton) findViewById(R.id.send_button); sendButton = (SendButton) findViewById(R.id.send_button);
attachButton = (ImageButton) findViewById(R.id.attach_button); attachButton = (ImageButton) findViewById(R.id.attach_button);
composeText = (ComposeText) findViewById(R.id.embedded_text_editor); composeText = (ComposeText) findViewById(R.id.embedded_text_editor);
charactersLeft = (TextView) findViewById(R.id.space_left); charactersLeft = (TextView) findViewById(R.id.space_left);
emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle); emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle);
unblockButton = (Button) findViewById(R.id.unblock_button); emojiDrawer = (EmojiDrawer) findViewById(R.id.emoji_drawer);
composePanel = findViewById(R.id.bottom_panel); unblockButton = (Button) findViewById(R.id.unblock_button);
composeBubble = findViewById(R.id.compose_bubble); composePanel = findViewById(R.id.bottom_panel);
container = (KeyboardAwareLinearLayout) findViewById(R.id.layout_container); composeBubble = findViewById(R.id.compose_bubble);
container = (InputAwareLayout) findViewById(R.id.layout_container);
container.addOnKeyboardShownListener(this); container.addOnKeyboardShownListener(this);
@ -780,7 +772,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle); emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle);
titleView = (ConversationTitleView) getSupportActionBar().getCustomView(); titleView = (ConversationTitleView) getSupportActionBar().getCustomView();
unblockButton = (Button) findViewById(R.id.unblock_button); 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); quickAttachmentDrawer = (QuickAttachmentDrawer) findViewById(R.id.quick_attachment_drawer);
quickAttachmentToggle = (HidingImageButton) findViewById(R.id.quick_attachment_toggle); quickAttachmentToggle = (HidingImageButton) findViewById(R.id.quick_attachment_toggle);
@ -796,6 +788,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
SendButtonListener sendButtonListener = new SendButtonListener(); SendButtonListener sendButtonListener = new SendButtonListener();
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener(); 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); composeText.setOnEditorActionListener(sendButtonListener);
attachButton.setOnClickListener(new AttachButtonListener()); attachButton.setOnClickListener(new AttachButtonListener());
sendButton.setOnClickListener(sendButtonListener); sendButton.setOnClickListener(sendButtonListener);
@ -831,7 +835,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
composeText.setOnEditorActionListener(sendButtonListener); composeText.setOnEditorActionListener(sendButtonListener);
composeText.setOnClickListener(composeKeyPressedListener); composeText.setOnClickListener(composeKeyPressedListener);
composeText.setOnFocusChangeListener(composeKeyPressedListener); composeText.setOnFocusChangeListener(composeKeyPressedListener);
emojiToggle.setOnClickListener(new EmojiToggleListener());
if (QuickAttachmentDrawer.isDeviceSupported(this)) { if (QuickAttachmentDrawer.isDeviceSupported(this)) {
quickAttachmentDrawer.setListener(this); quickAttachmentDrawer.setListener(this);
@ -848,48 +851,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
getSupportActionBar().setDisplayShowTitleEnabled(false); 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() { private void initializeResources() {
recipients = RecipientFactory.getRecipientsForIds(this, getIntent().getLongArrayExtra(RECIPIENTS_EXTRA), true); recipients = RecipientFactory.getRecipientsForIds(this, getIntent().getLongArrayExtra(RECIPIENTS_EXTRA), true);
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
@ -1302,26 +1263,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
@Override @Override
public void onAttachmentDrawerClosed() { public void onAttachmentDrawerStateChanged(DrawerState drawerState) {
getSupportActionBar().show(); if (drawerState == DrawerState.FULL_EXPANDED) {
} getSupportActionBar().hide();
} else {
@Override getSupportActionBar().show();
public void onAttachmentDrawerOpened() { }
getSupportActionBar().hide();
} }
@Override @Override
public void onImageCapture(@NonNull final byte[] imageBytes) { public void onImageCapture(@NonNull final byte[] imageBytes) {
attachmentManager.setCaptureUri(CaptureProvider.getInstance(this).create(masterSecret, recipients, imageBytes)); attachmentManager.setCaptureUri(CaptureProvider.getInstance(this).create(masterSecret, recipients, imageBytes));
addAttachmentImage(masterSecret, attachmentManager.getCaptureUri()); addAttachmentImage(masterSecret, attachmentManager.getCaptureUri());
quickAttachmentDrawer.close(); quickAttachmentDrawer.hide(false);
} }
@Override @Override
public void onCameraFail(FailureReason reason) { public void onCameraFail(FailureReason reason) {
Toast.makeText(this, R.string.quick_camera_unavailable, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.quick_camera_unavailable, Toast.LENGTH_SHORT).show();
quickAttachmentDrawer.close(); quickAttachmentDrawer.hide(false);
quickAttachmentToggle.disable(); 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 { private class EmojiToggleListener implements OnClickListener {
@Override
public void onClick(View v) { @Override public void onClick(View v) {
Log.w(TAG, "EmojiToggleListener onClick()"); if (container.getCurrentInput() == emojiDrawer) container.showSoftkey(composeText);
if (isEmojiDrawerOpen()) { else container.show(composeText, emojiDrawer);
hideEmojiDrawer(true);
openKeyboardForComposition();
} else {
container.postOnKeyboardClose(new Runnable() {
@Override public void run() {
showEmojiDrawer();
}
});
quickAttachmentDrawer.close();
hideKeyboard();
}
} }
} }
private class QuickAttachmentToggleListener implements OnClickListener { private class QuickAttachmentToggleListener implements OnClickListener {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
InputMethodManager input = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
input.hideSoftInputFromWindow(composeText.getWindowToken(), 0);
composeText.clearFocus(); composeText.clearFocus();
hideEmojiDrawer(false); container.show(composeText, quickAttachmentDrawer);
quickAttachmentDrawer.open();
} }
} }
@ -1421,7 +1354,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void onClick(View v) { public void onClick(View v) {
hideEmojiDrawer(true); container.showSoftkey(composeText);
} }
@Override @Override
@ -1447,13 +1380,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void onTextChanged(CharSequence s, int start, int before,int count) {} public void onTextChanged(CharSequence s, int start, int before,int count) {}
@Override @Override
public void onFocusChange(View v, boolean hasFocus) { public void onFocusChange(View v, boolean hasFocus) {}
if (hasFocus && isEmojiDrawerOpen()) {
hideEmojiDrawer(true);
} else if (hasFocus && quickAttachmentDrawer.isOpen()) {
quickAttachmentDrawer.close();
}
}
} }
@Override @Override

View File

@ -113,11 +113,6 @@ public class ConversationPopupActivity extends ConversationActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setDisplayHomeAsUpEnabled(false);
} }
@Override
protected void hideEmojiDrawer(boolean expectingKeyboard) {
super.hideEmojiDrawer(false);
}
@Override @Override
protected void sendComplete(long threadId) { protected void sendComplete(long threadId) {
super.sendComplete(threadId); super.sendComplete(threadId);

View File

@ -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();
}
}

View File

@ -16,6 +16,7 @@
*/ */
package org.thoughtcrime.securesms.components; package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
@ -42,14 +43,17 @@ import java.util.Set;
public class KeyboardAwareLinearLayout extends LinearLayoutCompat { public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName(); private static final String TAG = KeyboardAwareLinearLayout.class.getSimpleName();
private final Rect oldRect = new Rect(); private final Rect oldRect = new Rect();
private final Rect newRect = new Rect(); private final Rect newRect = new Rect();
private final Set<OnKeyboardHiddenListener> hiddenListeners = new HashSet<>(); private final Set<OnKeyboardHiddenListener> hiddenListeners = new HashSet<>();
private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>(); private final Set<OnKeyboardShownListener> shownListeners = new HashSet<>();
private final int minKeyboardSize; private final int minKeyboardSize;
private final int minCustomKeyboardSize; private final int minCustomKeyboardSize;
private final int defaultCustomKeyboardSize; private final int defaultCustomKeyboardSize;
private final int minCustomKeyboardTopMargin; private final int minCustomKeyboardTopMargin;
private final int statusBarHeight;
private int viewInset;
private boolean keyboardOpen = false; private boolean keyboardOpen = false;
private int rotation = -1; private int rotation = -1;
@ -64,16 +68,19 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
public KeyboardAwareLinearLayout(Context context, AttributeSet attrs, int defStyle) { public KeyboardAwareLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
final int statusBarRes = getResources().getIdentifier("status_bar_height", "dimen", "android");
minKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_keyboard_size); minKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_keyboard_size);
minCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_size); minCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_size);
defaultCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.default_custom_keyboard_size); defaultCustomKeyboardSize = getResources().getDimensionPixelSize(R.dimen.default_custom_keyboard_size);
minCustomKeyboardTopMargin = getResources().getDimensionPixelSize(R.dimen.min_custom_keyboard_top_margin); 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) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
updateRotation(); updateRotation();
updateKeyboardState(); updateKeyboardState();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} }
private void updateRotation() { private void updateRotation() {
@ -86,10 +93,8 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
} }
private void updateKeyboardState() { private void updateKeyboardState() {
int res = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (viewInset == 0 && Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) viewInset = getViewInset();
int statusBarHeight = res > 0 ? getResources().getDimensionPixelSize(res) : 0; final int availableHeight = this.getRootView().getHeight() - statusBarHeight - viewInset;
final int availableHeight = this.getRootView().getHeight() - statusBarHeight - getViewInset();
getWindowVisibleDisplayFrame(newRect); getWindowVisibleDisplayFrame(newRect);
final int oldKeyboardHeight = availableHeight - (oldRect.bottom - oldRect.top); final int oldKeyboardHeight = availableHeight - (oldRect.bottom - oldRect.top);
@ -104,11 +109,8 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
oldRect.set(newRect); oldRect.set(newRect);
} }
@TargetApi(VERSION_CODES.LOLLIPOP)
private int getViewInset() { private int getViewInset() {
if (Build.VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
return 0;
}
try { try {
Field attachInfoField = View.class.getDeclaredField("mAttachInfo"); Field attachInfoField = View.class.getDeclaredField("mAttachInfo");
attachInfoField.setAccessible(true); attachInfoField.setAccessible(true);
@ -141,6 +143,10 @@ public class KeyboardAwareLinearLayout extends LinearLayoutCompat {
notifyHiddenListeners(); notifyHiddenListeners();
} }
public boolean isKeyboardOpen() {
return keyboardOpen;
}
public int getKeyboardHeight() { public int getKeyboardHeight() {
return isLandscape() ? getKeyboardLandscapeHeight() : getKeyboardPortraitHeight(); return isLandscape() ? getKeyboardLandscapeHeight() : getKeyboardPortraitHeight();
} }

View File

@ -21,32 +21,31 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.InputManager.InputView;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout; import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
import org.thoughtcrime.securesms.components.camera.QuickCamera.QuickCameraListener; import org.thoughtcrime.securesms.components.camera.QuickCamera.QuickCameraListener;
import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.Util;
public class QuickAttachmentDrawer extends ViewGroup { public class QuickAttachmentDrawer extends ViewGroup implements InputView {
private static final String TAG = QuickAttachmentDrawer.class.getSimpleName(); 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;
private final ViewDragHelper dragHelper; private final ViewDragHelper dragHelper;
private QuickCamera quickCamera; private QuickCamera quickCamera;
private int coverViewPosition; private int coverViewPosition;
private KeyboardAwareLinearLayout coverView; private KeyboardAwareLinearLayout container;
private View coverView;
private View controls; private View controls;
private ImageButton fullScreenButton; private ImageButton fullScreenButton;
private ImageButton swapCameraButton; private ImageButton swapCameraButton;
private ImageButton shutterButton; private ImageButton shutterButton;
private float slideOffset; private int slideOffset;
private float initialMotionX; private float initialMotionX;
private float initialMotionY; private float initialMotionY;
private int rotation; private int rotation;
private int slideRange;
private AttachmentDrawerListener listener; private AttachmentDrawerListener listener;
private int halfExpandedHeight; private int halfExpandedHeight;
private float halfExpandedAnchorPoint;
private DrawerState drawerState = DrawerState.COLLAPSED; private DrawerState drawerState = DrawerState.COLLAPSED;
private boolean halfModeUnsupported = VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH; private boolean halfModeUnsupported = VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
@ -82,16 +81,19 @@ public class QuickAttachmentDrawer extends ViewGroup {
Camera.getNumberOfCameras() > 0; Camera.getNumberOfCameras() > 0;
} }
public boolean isOpen() { @Override
public boolean isShowing() {
return drawerState.isVisible(); return drawerState.isVisible();
} }
public void close() { @Override
setDrawerStateAndAnimate(DrawerState.COLLAPSED); public void hide(boolean immediate) {
setDrawerStateAndUpdate(DrawerState.COLLAPSED, immediate);
} }
public void open() { @Override
setDrawerStateAndAnimate(DrawerState.HALF_EXPANDED); public void show(int height, boolean immediate) {
setDrawerStateAndUpdate(DrawerState.HALF_EXPANDED, immediate);
} }
public void onConfigurationChanged() { public void onConfigurationChanged() {
@ -99,11 +101,11 @@ public class QuickAttachmentDrawer extends ViewGroup {
final boolean rotationChanged = this.rotation != rotation; final boolean rotationChanged = this.rotation != rotation;
this.rotation = rotation; this.rotation = rotation;
if (rotationChanged) { if (rotationChanged) {
if (isOpen()) { if (isShowing()) {
quickCamera.onPause(); quickCamera.onPause();
} }
updateControlsView(); updateControlsView();
setDrawerStateAndAnimate(drawerState); setDrawerStateAndUpdate(drawerState);
} }
} }
@ -134,23 +136,19 @@ public class QuickAttachmentDrawer extends ViewGroup {
return isLandscape() || halfModeUnsupported; return isLandscape() || halfModeUnsupported;
} }
private KeyboardAwareLinearLayout getCoverView() { private View getCoverView() {
if (coverView != null) return coverView; if (coverView == null) coverView = getChildAt(coverViewPosition);
final View coverViewChild = getChildAt(coverViewPosition);
if (coverViewChild != null && !(coverViewChild instanceof KeyboardAwareLinearLayout)) {
throw new IllegalStateException("cover view must be a KeyboardAwareLinearLayout");
}
coverView = (KeyboardAwareLinearLayout) coverViewChild;
return coverView; return coverView;
} }
private KeyboardAwareLinearLayout getContainer() {
if (container == null) container = (KeyboardAwareLinearLayout)getParent();
return container;
}
private void updateHalfExpandedAnchorPoint() { private void updateHalfExpandedAnchorPoint() {
if (getCoverView() != null) { if (getContainer() != null) {
slideRange = getMeasuredHeight(); halfExpandedHeight = getContainer().getKeyboardHeight();
halfExpandedHeight = coverView.getKeyboardHeight();
halfExpandedAnchorPoint = computeSlideOffsetFromCoverBottom(slideRange - halfExpandedHeight);
} }
} }
@ -159,8 +157,6 @@ public class QuickAttachmentDrawer extends ViewGroup {
final int paddingLeft = getPaddingLeft(); final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop(); final int paddingTop = getPaddingTop();
updateHalfExpandedAnchorPoint();
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i); final View child = getChildAt(i);
final int childHeight = child.getMeasuredHeight(); final int childHeight = child.getMeasuredHeight();
@ -177,8 +173,8 @@ public class QuickAttachmentDrawer extends ViewGroup {
} else if (child == controls) { } else if (child == controls) {
childBottom = getMeasuredHeight(); childBottom = getMeasuredHeight();
} else { } else {
childBottom = computeCoverBottomPosition(slideOffset); childTop = computeCoverTopPosition(slideOffset);
childTop = childBottom - childHeight; childBottom = childTop + childHeight;
} }
final int childRight = childLeft + child.getMeasuredWidth(); final int childRight = childLeft + child.getMeasuredWidth();
@ -233,7 +229,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
default: default:
childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
break; break;
} }
child.measure(childWidthSpec, childHeightSpec); child.measure(childWidthSpec, childHeightSpec);
} }
@ -269,11 +265,11 @@ public class QuickAttachmentDrawer extends ViewGroup {
ViewCompat.postInvalidateOnAnimation(this); ViewCompat.postInvalidateOnAnimation(this);
} }
if (slideOffset == COLLAPSED_ANCHOR_POINT && quickCamera.isStarted()) { if (slideOffset == 0 && quickCamera.isStarted()) {
quickCamera.onPause(); quickCamera.onPause();
controls.setVisibility(INVISIBLE); controls.setVisibility(INVISIBLE);
quickCamera.setVisibility(INVISIBLE); quickCamera.setVisibility(INVISIBLE);
} else if (slideOffset != COLLAPSED_ANCHOR_POINT && !quickCamera.isStarted() & !paused) { } else if (slideOffset != 0 && !quickCamera.isStarted() & !paused) {
controls.setVisibility(VISIBLE); controls.setVisibility(VISIBLE);
quickCamera.setVisibility(VISIBLE); quickCamera.setVisibility(VISIBLE);
quickCamera.onResume(); quickCamera.onResume();
@ -284,7 +280,6 @@ public class QuickAttachmentDrawer extends ViewGroup {
switch (drawerState) { switch (drawerState) {
case COLLAPSED: case COLLAPSED:
fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen); fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen);
if (listener != null) listener.onAttachmentDrawerClosed();
break; break;
case HALF_EXPANDED: case HALF_EXPANDED:
if (isFullscreenOnly()) { if (isFullscreenOnly()) {
@ -292,30 +287,37 @@ public class QuickAttachmentDrawer extends ViewGroup {
return; return;
} }
fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen); fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen);
if (listener != null) listener.onAttachmentDrawerOpened();
break; break;
case FULL_EXPANDED: case FULL_EXPANDED:
fullScreenButton.setImageResource(isFullscreenOnly() ? R.drawable.quick_camera_hide fullScreenButton.setImageResource(isFullscreenOnly() ? R.drawable.quick_camera_hide
: R.drawable.quick_camera_exit_fullscreen); : R.drawable.quick_camera_exit_fullscreen);
if (listener != null) listener.onAttachmentDrawerOpened();
break; break;
} }
this.drawerState = drawerState;
}
public float getTargetSlideOffset() { if (listener != null && drawerState != this.drawerState) {
switch (drawerState) { this.drawerState = drawerState;
case FULL_EXPANDED: return FULL_EXPANDED_ANCHOR_POINT; listener.onAttachmentDrawerStateChanged(drawerState);
case HALF_EXPANDED: return halfExpandedAnchorPoint;
default: return COLLAPSED_ANCHOR_POINT;
} }
} }
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; DrawerState oldDrawerState = this.drawerState;
setDrawerState(requestedDrawerState); setDrawerState(requestedDrawerState);
if (oldDrawerState != drawerState) { if (oldDrawerState != drawerState) {
slideTo(getTargetSlideOffset()); updateHalfExpandedAnchorPoint();
slideTo(getTargetSlideOffset(), instant);
} }
} }
@ -325,8 +327,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
} }
public interface AttachmentDrawerListener extends QuickCameraListener { public interface AttachmentDrawerListener extends QuickCameraListener {
void onAttachmentDrawerClosed(); void onAttachmentDrawerStateChanged(DrawerState drawerState);
void onAttachmentDrawerOpened();
} }
private class ViewDragHelperCallback extends ViewDragHelper.Callback { private class ViewDragHelperCallback extends ViewDragHelper.Callback {
@ -351,10 +352,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
@Override @Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
final int expandedTop = computeCoverBottomPosition(FULL_EXPANDED_ANCHOR_POINT) - coverView.getHeight(); slideOffset = Util.clamp(slideOffset - dy, 0, getMeasuredHeight());
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());
requestLayout(); requestLayout();
} }
@ -367,25 +365,20 @@ public class QuickAttachmentDrawer extends ViewGroup {
if (direction > 1) { if (direction > 1) {
drawerState = DrawerState.FULL_EXPANDED; drawerState = DrawerState.FULL_EXPANDED;
} else if (direction < -1) { } else if (direction < -1) {
boolean halfExpand = (slideOffset > halfExpandedAnchorPoint && !isLandscape()); boolean halfExpand = (slideOffset > halfExpandedHeight && !isLandscape());
drawerState = halfExpand ? DrawerState.HALF_EXPANDED : DrawerState.COLLAPSED; drawerState = halfExpand ? DrawerState.HALF_EXPANDED : DrawerState.COLLAPSED;
} else if (!isLandscape()) { } else if (!isLandscape()) {
if (halfExpandedAnchorPoint != 1 && slideOffset >= (1.f + halfExpandedAnchorPoint) / 2) { if (slideOffset >= (halfExpandedHeight + getMeasuredHeight()) / 2) {
drawerState = DrawerState.FULL_EXPANDED; drawerState = DrawerState.FULL_EXPANDED;
} else if (halfExpandedAnchorPoint == 1 && slideOffset >= 0.5f) { } else if (slideOffset >= halfExpandedHeight / 2) {
drawerState = DrawerState.FULL_EXPANDED;
} else if (halfExpandedAnchorPoint != 1 && slideOffset >= halfExpandedAnchorPoint) {
drawerState = DrawerState.HALF_EXPANDED;
} else if (halfExpandedAnchorPoint != 1 && slideOffset >= halfExpandedAnchorPoint / 2) {
drawerState = DrawerState.HALF_EXPANDED; drawerState = DrawerState.HALF_EXPANDED;
} }
} }
setDrawerState(drawerState); setDrawerState(drawerState);
float slideOffset = getTargetSlideOffset(); int slideOffset = getTargetSlideOffset();
dragHelper.captureChildView(coverView, 0); dragHelper.captureChildView(coverView, 0);
Log.w(TAG, String.format("setting cover at %d", computeCoverBottomPosition(slideOffset) - coverView.getHeight())); dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverTopPosition(slideOffset));
dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverBottomPosition(slideOffset) - coverView.getHeight());
dragHelper.captureChildView(quickCamera, 0); dragHelper.captureChildView(quickCamera, 0);
dragHelper.settleCapturedViewAt(quickCamera.getLeft(), computeCameraTopPosition(slideOffset)); dragHelper.settleCapturedViewAt(quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this); ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this);
@ -394,7 +387,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
@Override @Override
public int getViewVerticalDragRange(View child) { public int getViewVerticalDragRange(View child) {
return slideRange; return getMeasuredHeight();
} }
@Override @Override
@ -465,28 +458,22 @@ public class QuickAttachmentDrawer extends ViewGroup {
screenY >= viewLocation[1] && screenY < viewLocation[1] + quickCamera.getHeight(); screenY >= viewLocation[1] && screenY < viewLocation[1] + quickCamera.getHeight();
} }
private int computeCameraTopPosition(float slideOffset) { private int computeCameraTopPosition(int slideOffset) {
float clampedOffset = slideOffset - halfExpandedAnchorPoint; final int baseCameraTop = (quickCamera.getMeasuredHeight() - halfExpandedHeight) / 2;
if (clampedOffset < COLLAPSED_ANCHOR_POINT) { final int baseOffset = getMeasuredHeight() - slideOffset - baseCameraTop;
clampedOffset = COLLAPSED_ANCHOR_POINT; final float slop = Util.clamp((float)(slideOffset - halfExpandedHeight) / (getMeasuredHeight() - halfExpandedHeight),
} else { 0f,
clampedOffset = clampedOffset / (FULL_EXPANDED_ANCHOR_POINT - halfExpandedAnchorPoint); 1f);
} return baseOffset + (int)(slop * baseCameraTop);
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 computeCoverBottomPosition(float slideOffset) { private int computeCoverTopPosition(int slideOffset) {
int slidePixelOffset = (int) (slideOffset * slideRange); return getMeasuredHeight() - getPaddingBottom() - slideOffset - getCoverView().getMeasuredHeight();
return getMeasuredHeight() - getPaddingBottom() - slidePixelOffset;
} }
private void slideTo(float slideOffset) { private void slideTo(int slideOffset, boolean forceInstant) {
if (dragHelper != null && !halfModeUnsupported) { if (dragHelper != null && !halfModeUnsupported && !forceInstant) {
dragHelper.smoothSlideViewTo(coverView, coverView.getLeft(), computeCoverBottomPosition(slideOffset) - coverView.getHeight()); dragHelper.smoothSlideViewTo(coverView, coverView.getLeft(), computeCoverTopPosition(slideOffset));
dragHelper.smoothSlideViewTo(quickCamera, quickCamera.getLeft(), computeCameraTopPosition(slideOffset)); dragHelper.smoothSlideViewTo(quickCamera, quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
ViewCompat.postInvalidateOnAnimation(this); ViewCompat.postInvalidateOnAnimation(this);
} else { } 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() { public void onPause() {
paused = true; paused = true;
quickCamera.onPause(); quickCamera.onPause();
@ -524,7 +506,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
boolean crop = drawerState != DrawerState.FULL_EXPANDED; 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); Rect previewRect = new Rect(0, 0, quickCamera.getMeasuredWidth(), imageHeight);
quickCamera.takePicture(previewRect); quickCamera.takePicture(previewRect);
} }
@ -543,11 +525,11 @@ public class QuickAttachmentDrawer extends ViewGroup {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (drawerState != DrawerState.FULL_EXPANDED) { if (drawerState != DrawerState.FULL_EXPANDED) {
setDrawerStateAndAnimate(DrawerState.FULL_EXPANDED); setDrawerStateAndUpdate(DrawerState.FULL_EXPANDED);
} else if (isFullscreenOnly()) { } else if (isFullscreenOnly()) {
setDrawerStateAndAnimate(DrawerState.COLLAPSED); setDrawerStateAndUpdate(DrawerState.COLLAPSED);
} else { } else {
setDrawerStateAndAnimate(DrawerState.HALF_EXPANDED); setDrawerStateAndUpdate(DrawerState.HALF_EXPANDED);
} }
} }
} }

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter; import android.support.v4.view.PagerAdapter;
@ -18,7 +19,7 @@ import android.widget.LinearLayout;
import com.astuetz.PagerSlidingTabStrip; import com.astuetz.PagerSlidingTabStrip;
import org.thoughtcrime.securesms.R; 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;
import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener; import org.thoughtcrime.securesms.components.RepeatableImageKey.KeyEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiPageView.EmojiSelectionListener; 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.LinkedList;
import java.util.List; 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 static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
private ViewPager pager; private ViewPager pager;
@ -35,6 +36,7 @@ public class EmojiDrawer extends LinearLayout {
private PagerSlidingTabStrip strip; private PagerSlidingTabStrip strip;
private RecentEmojiPageModel recentModel; private RecentEmojiPageModel recentModel;
private EmojiEventListener listener; private EmojiEventListener listener;
private EmojiDrawerListener drawerListener;
public EmojiDrawer(Context context) { public EmojiDrawer(Context context) {
this(context, null); this(context, null);
@ -43,6 +45,9 @@ public class EmojiDrawer extends LinearLayout {
public EmojiDrawer(Context context, AttributeSet attrs) { public EmojiDrawer(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
setOrientation(VERTICAL); setOrientation(VERTICAL);
}
private void initView() {
final View v = LayoutInflater.from(getContext()).inflate(R.layout.emoji_drawer, this, true); final View v = LayoutInflater.from(getContext()).inflate(R.layout.emoji_drawer, this, true);
initializeResources(v); initializeResources(v);
initializePageModels(); initializePageModels();
@ -53,6 +58,10 @@ public class EmojiDrawer extends LinearLayout {
this.listener = listener; this.listener = listener;
} }
public void setDrawerListener(EmojiDrawerListener listener) {
this.drawerListener = listener;
}
private void initializeResources(View v) { private void initializeResources(View v) {
Log.w("EmojiDrawer", "initializeResources()"); Log.w("EmojiDrawer", "initializeResources()");
this.pager = (ViewPager) v.findViewById(R.id.emoji_pager); this.pager = (ViewPager) v.findViewById(R.id.emoji_pager);
@ -66,20 +75,27 @@ public class EmojiDrawer extends LinearLayout {
}); });
} }
@Override
public boolean isShowing() { public boolean isShowing() {
return getVisibility() == VISIBLE; 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(); ViewGroup.LayoutParams params = getLayoutParams();
params.height = container.getKeyboardHeight(); params.height = height;
Log.w("EmojiDrawer", "showing emoji drawer with height " + params.height); Log.w("EmojiDrawer", "showing emoji drawer with height " + params.height);
setLayoutParams(params); setLayoutParams(params);
setVisibility(VISIBLE); setVisibility(VISIBLE);
if (drawerListener != null) drawerListener.onShown();
} }
public void dismiss() { @Override
public void hide(boolean immediate) {
setVisibility(GONE); setVisibility(GONE);
if (drawerListener != null) drawerListener.onHidden();
Log.w("EmojiDrawer", "hide()");
} }
private void initializeEmojiGrid() { private void initializeEmojiGrid() {
@ -161,4 +177,9 @@ public class EmojiDrawer extends LinearLayout {
public interface EmojiEventListener extends EmojiSelectionListener { public interface EmojiEventListener extends EmojiSelectionListener {
void onKeyEvent(KeyEvent keyEvent); void onKeyEvent(KeyEvent keyEvent);
} }
public interface EmojiDrawerListener {
void onShown();
void onHidden();
}
} }

View File

@ -8,8 +8,9 @@ import android.util.AttributeSet;
import android.widget.ImageButton; import android.widget.ImageButton;
import org.thoughtcrime.securesms.R; 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 emojiToggle;
private Drawable imeToggle; private Drawable imeToggle;
@ -48,4 +49,16 @@ public class EmojiToggle extends ImageButton {
drawables.recycle(); drawables.recycle();
setToEmoji(); setToEmoji();
} }
public void attach(EmojiDrawer drawer) {
drawer.setDrawerListener(this);
}
@Override public void onShown() {
setToIme();
}
@Override public void onHidden() {
setToEmoji();
}
} }

View File

@ -348,4 +348,8 @@ public class Util {
public static int clamp(int value, int min, int max) { public static int clamp(int value, int min, int max) {
return Math.min(Math.max(value, min), 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);
}
} }