diff --git a/res/drawable-hdpi/ic_photo_camera_dark.png b/res/drawable-hdpi/ic_photo_camera_dark.png new file mode 100644 index 0000000000..f937d64f37 Binary files /dev/null and b/res/drawable-hdpi/ic_photo_camera_dark.png differ diff --git a/res/drawable-hdpi/ic_photo_camera_light.png b/res/drawable-hdpi/ic_photo_camera_light.png new file mode 100644 index 0000000000..f5a275c380 Binary files /dev/null and b/res/drawable-hdpi/ic_photo_camera_light.png differ diff --git a/res/drawable-mdpi/ic_photo_camera_dark.png b/res/drawable-mdpi/ic_photo_camera_dark.png new file mode 100644 index 0000000000..aa3a3e5717 Binary files /dev/null and b/res/drawable-mdpi/ic_photo_camera_dark.png differ diff --git a/res/drawable-mdpi/ic_photo_camera_light.png b/res/drawable-mdpi/ic_photo_camera_light.png new file mode 100644 index 0000000000..c26a846e37 Binary files /dev/null and b/res/drawable-mdpi/ic_photo_camera_light.png differ diff --git a/res/drawable-xhdpi/ic_photo_camera_dark.png b/res/drawable-xhdpi/ic_photo_camera_dark.png new file mode 100644 index 0000000000..c1a3549bfc Binary files /dev/null and b/res/drawable-xhdpi/ic_photo_camera_dark.png differ diff --git a/res/drawable-xhdpi/ic_photo_camera_light.png b/res/drawable-xhdpi/ic_photo_camera_light.png new file mode 100644 index 0000000000..62d83b253d Binary files /dev/null and b/res/drawable-xhdpi/ic_photo_camera_light.png differ diff --git a/res/drawable-xxhdpi/ic_photo_camera_dark.png b/res/drawable-xxhdpi/ic_photo_camera_dark.png new file mode 100644 index 0000000000..5b96a4c6b9 Binary files /dev/null and b/res/drawable-xxhdpi/ic_photo_camera_dark.png differ diff --git a/res/drawable-xxhdpi/ic_photo_camera_light.png b/res/drawable-xxhdpi/ic_photo_camera_light.png new file mode 100644 index 0000000000..d23e4b47a8 Binary files /dev/null and b/res/drawable-xxhdpi/ic_photo_camera_light.png differ diff --git a/res/drawable-xxxhdpi/ic_photo_camera_dark.png b/res/drawable-xxxhdpi/ic_photo_camera_dark.png new file mode 100644 index 0000000000..23a9c2efd0 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_photo_camera_dark.png differ diff --git a/res/drawable-xxxhdpi/ic_photo_camera_light.png b/res/drawable-xxxhdpi/ic_photo_camera_light.png new file mode 100644 index 0000000000..7f43cc3152 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_photo_camera_light.png differ diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml index 584d735204..897560ed06 100644 --- a/res/layout/conversation_activity.xml +++ b/res/layout/conversation_activity.xml @@ -97,19 +97,37 @@ android:contentDescription="@string/conversation_activity__compose_description" android:textColor="?conversation_editor_text_color" /> - + + + + + + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 4676554e84..0d81e30bc2 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -37,6 +37,7 @@ + diff --git a/res/values/strings.xml b/res/values/strings.xml index e03e974f0e..098e948456 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -50,6 +50,7 @@ Can\'t find an app to select media. + Take Photo Picture Video Audio diff --git a/res/values/themes.xml b/res/values/themes.xml index b2af44d0ff..aa6eb50b24 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -53,6 +53,7 @@ @drawable/ic_movie_creation_light @drawable/ic_volume_up_light @drawable/ic_account_box_light + @drawable/ic_photo_camera_light @drawable/conversation_item_background @color/conversation_item_received_background_light @@ -188,6 +189,7 @@ @drawable/ic_movie_creation_dark @drawable/ic_volume_up_dark @drawable/ic_account_box_dark + @drawable/ic_photo_camera_dark @drawable/ic_add_white_24dp @drawable/ic_group_white_24dp diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index e4999e15e3..11ddd2618e 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -24,7 +24,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract; import android.support.annotation.NonNull; @@ -44,6 +43,7 @@ import android.view.View.OnFocusChangeListener; import android.view.View.OnKeyListener; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; +import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; @@ -51,9 +51,9 @@ import com.afollestad.materialdialogs.AlertDialogWrapper; import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener; +import org.thoughtcrime.securesms.components.AnimatingToggle; import org.thoughtcrime.securesms.components.ComposeText; import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; -import org.thoughtcrime.securesms.components.emoji.EmojiProvider; import org.thoughtcrime.securesms.components.emoji.EmojiToggle; import org.thoughtcrime.securesms.components.SendButton; import org.thoughtcrime.securesms.contacts.ContactAccessor; @@ -136,10 +136,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private static final int PICK_AUDIO = 3; private static final int PICK_CONTACT_INFO = 4; private static final int GROUP_EDIT = 5; + private static final int CAPTURE_PHOTO = 6; private MasterSecret masterSecret; private ComposeText composeText; + private AnimatingToggle buttonToggle; private SendButton sendButton; + private ImageButton attachButton; private TextView charactersLeft; private ConversationFragment fragment; @@ -237,7 +240,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity Log.w(TAG, "onActivityResult called: " + reqCode + ", " + resultCode + " , " + data); super.onActivityResult(reqCode, resultCode, data); - if (data == null || resultCode != RESULT_OK) return; + if ((data == null && reqCode != CAPTURE_PHOTO) || resultCode != RESULT_OK) return; + switch (reqCode) { case PICK_IMAGE: addAttachmentImage(data.getData()); @@ -251,6 +255,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity case PICK_CONTACT_INFO: addAttachmentContactInfo(data.getData()); break; + case CAPTURE_PHOTO: + if (attachmentManager.getCaptureFile() != null) { + addAttachmentImage(Uri.fromFile(attachmentManager.getCaptureFile())); + } + break; case GROUP_EDIT: this.recipients = RecipientFactory.getRecipientsForIds(this, data.getLongArrayExtra(GroupCreateActivity.GROUP_RECIPIENT_EXTRA), true); initializeTitleBar(); @@ -596,6 +605,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (draftText == null && draftImage == null && draftAudio == null && draftVideo == null) { initializeDraftFromDatabase(); + } else { + updateToggleButtonState(); } } @@ -631,6 +642,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity addAttachmentVideo(Uri.parse(draft.getValue())); } } + + updateToggleButtonState(); } }.execute(); } @@ -675,7 +688,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void initializeViews() { + 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); emojiDrawer = Optional.absent(); @@ -687,6 +702,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity SendButtonListener sendButtonListener = new SendButtonListener(); ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener(); + attachButton.setOnClickListener(new AttachButtonListener()); sendButton.setOnClickListener(sendButtonListener); sendButton.setEnabled(true); sendButton.addOnTransportChangedListener(new OnTransportChangedListener() { @@ -775,6 +791,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void addAttachment(int type) { Log.w("ComposeMessageActivity", "Selected: " + type); switch (type) { + case AttachmentTypeSelectorAdapter.TAKE_PHOTO: + attachmentManager.capturePhoto(this, CAPTURE_PHOTO); break; case AttachmentTypeSelectorAdapter.ADD_IMAGE: AttachmentManager.selectImage(this, PICK_IMAGE); break; case AttachmentTypeSelectorAdapter.ADD_VIDEO: @@ -989,6 +1007,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } fragment.scrollToBottom(); + attachmentManager.cleanup(); } private void sendMessage() { @@ -1081,6 +1100,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity }.execute(message); } + private void updateToggleButtonState() { + if (composeText.getText().length() == 0 && !attachmentManager.isAttachmentPresent()) { + buttonToggle.display(attachButton); + } else { + buttonToggle.display(sendButton); + } + } // Listeners @@ -1124,7 +1150,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } } + private class AttachButtonListener implements OnClickListener { + @Override + public void onClick(View v) { + handleAddAttachment(); + } + } + private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener { + + int beforeLength; + @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { @@ -1146,12 +1182,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } } + @Override + public void beforeTextChanged(CharSequence s, int start, int count,int after) { + beforeLength = composeText.getText().length(); + } + @Override public void afterTextChanged(Editable s) { calculateCharactersRemaining(); + + if (composeText.getText().length() == 0 || beforeLength == 0) { + updateToggleButtonState(); + } } - @Override - public void beforeTextChanged(CharSequence s, int start, int count,int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before,int count) {} @@ -1176,5 +1220,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Override public void onAttachmentChanged() { initializeSecurity(); + updateToggleButtonState(); } } diff --git a/src/org/thoughtcrime/securesms/components/AnimatingToggle.java b/src/org/thoughtcrime/securesms/components/AnimatingToggle.java new file mode 100644 index 0000000000..e88808ef8d --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/AnimatingToggle.java @@ -0,0 +1,108 @@ +package org.thoughtcrime.securesms.components; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; +import android.widget.FrameLayout; + +public class AnimatingToggle extends FrameLayout { + + private static final int SPEED_MILLIS = 200; + + public AnimatingToggle(Context context) { + super(context); + } + + public AnimatingToggle(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AnimatingToggle(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) { + super.addView(child, index, params); + + if (getChildCount() == 1) child.setVisibility(View.VISIBLE); + else child.setVisibility(View.GONE); + } + + public void display(View view) { + if (view.getVisibility() == View.VISIBLE) return; + + int oldViewIndex = getVisibleViewIndex(); + int newViewIndex = getViewIndex(view); + + int sign; + + if (oldViewIndex < newViewIndex) sign = -1; + else sign = 1; + + TranslateAnimation oldViewAnimation = createTranslation(0.0f, sign * 1.0f); + TranslateAnimation newViewAnimation = createTranslation(sign * -1.0f, 0.0f); + + animateOut(oldViewIndex, oldViewAnimation); + animateIn(newViewIndex, newViewAnimation); + } + + private void animateOut(int viewIndex, TranslateAnimation animation) { + final View view = getChildAt(viewIndex); + + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + view.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + + view.startAnimation(animation); + } + + private void animateIn(int viewIndex, TranslateAnimation animation) { + final View view = getChildAt(viewIndex); + view.setVisibility(View.VISIBLE); + view.startAnimation(animation); + } + + private int getVisibleViewIndex() { + for (int i=0;i getItemList(Context context) { List data = new ArrayList<>(4); - addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_picture), ResUtil.getDrawableRes(context, R.attr.conversation_attach_image), ADD_IMAGE); - addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_video), ResUtil.getDrawableRes(context, R.attr.conversation_attach_video), ADD_VIDEO); - addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_audio), ResUtil.getDrawableRes(context, R.attr.conversation_attach_sound), ADD_SOUND); - addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_contact), ResUtil.getDrawableRes(context, R.attr.conversation_attach_contact_info), ADD_CONTACT_INFO); + addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_take_photo), ResUtil.getDrawableRes(context, R.attr.conversation_attach_photo), TAKE_PHOTO); + addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_picture), ResUtil.getDrawableRes(context, R.attr.conversation_attach_image), ADD_IMAGE); + addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_video), ResUtil.getDrawableRes(context, R.attr.conversation_attach_video), ADD_VIDEO); + addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_audio), ResUtil.getDrawableRes(context, R.attr.conversation_attach_sound), ADD_SOUND); + addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_contact), ResUtil.getDrawableRes(context, R.attr.conversation_attach_contact_info), ADD_CONTACT_INFO); return data; }