Java tutorial
/** * Wire * Copyright (C) 2016 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.waz.zclient.ui.cursor; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.ColorDrawable; import android.os.Handler; import android.support.v4.content.ContextCompat; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; import com.waz.api.IConversation; import com.waz.api.Message; import com.waz.api.MessageContent; import com.waz.zclient.ui.R; import com.waz.zclient.ui.animation.interpolators.penner.Expo; import com.waz.zclient.ui.animation.interpolators.penner.Quart; import com.waz.zclient.ui.theme.ThemeUtils; import com.waz.zclient.ui.utils.CursorUtils; import com.waz.zclient.ui.views.CursorIconButton; import com.waz.zclient.utils.LayoutSpec; import com.waz.zclient.utils.ViewUtils; import java.util.Arrays; import java.util.List; public class CursorLayout extends FrameLayout implements TextView.OnEditorActionListener, TextWatcher, CursorToolbar.Callback, EditMessageCursorToolbar.Callback { private static final long TOOLTIP_DURATION = 1500; private static List<CursorMenuItem> mainCursorItems = Arrays.asList(CursorMenuItem.VIDEO_MESSAGE, CursorMenuItem.CAMERA, CursorMenuItem.SKETCH, CursorMenuItem.GIF, CursorMenuItem.AUDIO_MESSAGE, CursorMenuItem.MORE); private static List<CursorMenuItem> secondaryCursorItems = Arrays.asList(CursorMenuItem.PING, CursorMenuItem.FILE, CursorMenuItem.LOCATION, CursorMenuItem.DUMMY, CursorMenuItem.DUMMY, CursorMenuItem.LESS); private View editMessageBackgroundView; private CursorToolbarFrame cursorToolbarFrame; private CursorEditText newCursorEditText; private ShieldViewWithBanner shieldViewWithBanner; private CursorIconButton sendButton; private CursorToolbar mainToolbar; private CursorToolbar secondaryToolbar; private EditMessageCursorToolbar editMessageCursorToolbar; private View topBorder; private TextView tooltip; private TextView hintView; private View dividerView; private CursorIconButton emojiButton; private CursorCallback cursorCallback; private boolean sendButtonIsVisible; private boolean tooltipEnabled; private boolean isEditingMessage; private int anchorPositionPx2; private int maxLines = 2; private boolean keyboardIsVisible; private int cursorHeight; private int cursorToolbarAnimationDuration; private ObjectAnimator showMainToolbarAnimator; private ObjectAnimator showSecondaryToolbarAnimator; private ObjectAnimator hideMainToolbarAnimator; private ObjectAnimator hideSecondaryToolbarAnimator; private ObjectAnimator showEditMessageToolbarAnimator; private ObjectAnimator hideEditMessageToolbarAnimator; private Message message; private int defaultEditTextColor; private int defaultDividerColor; public CursorLayout(Context context) { this(context, null); } public CursorLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CursorLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); anchorPositionPx2 = getResources().getDimensionPixelSize(R.dimen.cursor_anchor2); } /** * The width of the view is needed as it defines the third * anchor position. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (LayoutSpec.get(getContext()) == LayoutSpec.LAYOUT_PHONE || LayoutSpec.get(getContext()) == LayoutSpec.LAYOUT_KINDLE) { int paddingEdge = getResources().getDimensionPixelSize(R.dimen.cursor_toolbar_padding_horizontal_edge); cursorToolbarFrame.setPadding(paddingEdge, 0, paddingEdge, 0); return; } int width = MeasureSpec.getSize(widthMeasureSpec); anchorPositionPx2 = CursorUtils.getCursorEditTextAnchorPosition(getContext(), width); if (ViewUtils.isInPortrait(getContext())) { int left = CursorUtils.getCursorMenuLeftMargin(getContext(), width); ((FrameLayout.LayoutParams) newCursorEditText.getLayoutParams()).leftMargin = anchorPositionPx2; ((FrameLayout.LayoutParams) hintView.getLayoutParams()).leftMargin = anchorPositionPx2; cursorToolbarFrame.setPadding(left, 0, left, 0); } else { int left = CursorUtils.getCursorMenuLeftMargin(getContext(), width); ((FrameLayout.LayoutParams) newCursorEditText.getLayoutParams()).leftMargin = anchorPositionPx2; ((FrameLayout.LayoutParams) hintView.getLayoutParams()).leftMargin = anchorPositionPx2; cursorToolbarFrame.setPadding(left, 0, left, 0); } } @Override protected void onFinishInflate() { super.onFinishInflate(); editMessageBackgroundView = ViewUtils.getView(this, R.id.fl__edit_message__background); cursorToolbarFrame = ViewUtils.getView(this, R.id.cal__cursor); newCursorEditText = ViewUtils.getView(this, R.id.cet__cursor); shieldViewWithBanner = ViewUtils.getView(this, R.id.svwb); mainToolbar = ViewUtils.getView(this, R.id.c__cursor__main); secondaryToolbar = ViewUtils.getView(this, R.id.c__cursor__secondary); editMessageCursorToolbar = ViewUtils.getView(this, R.id.emct__edit_message__toolbar); topBorder = ViewUtils.getView(this, R.id.v__top_bar__cursor); tooltip = ViewUtils.getView(this, R.id.ctv__cursor); hintView = ViewUtils.getView(this, R.id.ttv__cursor_hint); dividerView = ViewUtils.getView(this, R.id.v__cursor__divider); FrameLayout emojiButtonContainer = ViewUtils.getView(this, R.id.fl__cursor__emoji_container); FrameLayout sendButtonContainer = ViewUtils.getView(this, R.id.fl__cursor__send_button_container); mainToolbar.setCursorItems(mainCursorItems); secondaryToolbar.setCursorItems(secondaryCursorItems); editMessageCursorToolbar.setVisibility(GONE); editMessageCursorToolbar.setCallback(this); cursorHeight = getResources().getDimensionPixelSize(R.dimen.new_cursor_height); secondaryToolbar.setTranslationY(2 * cursorHeight); cursorToolbarAnimationDuration = getResources().getInteger(R.integer.wire__animation__delay__regular); tooltip.setVisibility(View.GONE); connectEditText(); editMessageBackgroundView.setVisibility(GONE); defaultEditTextColor = newCursorEditText.getCurrentTextColor(); ColorDrawable dividerBg = (ColorDrawable) dividerView.getBackground(); defaultDividerColor = dividerBg.getColor(); // Emoji button LayoutInflater inflater = LayoutInflater.from(getContext()); emojiButton = (CursorIconButton) inflater.inflate(R.layout.cursor__item, this, false); emojiButton.setText(R.string.glyph__emoji); emojiButton.setPressedBackgroundColor(ContextCompat.getColor(getContext(), R.color.light_graphite)); int buttonWidth = getResources().getDimensionPixelSize(R.dimen.cursor__menu_button__diameter); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(buttonWidth, buttonWidth); params.gravity = Gravity.END | Gravity.CENTER_VERTICAL; params.setMarginEnd(getResources().getDimensionPixelSize(R.dimen.chathead__margin)); emojiButtonContainer.addView(emojiButton, params); emojiButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (cursorCallback == null) { return; } mainToolbar.unselectItems(); if (getContext().getString(R.string.glyph__emoji).equals(emojiButton.getText())) { emojiButton.setText(R.string.glyph__keyboard); cursorCallback.onEmojiButtonClicked(true); } else { emojiButton.setText(R.string.glyph__emoji); cursorCallback.onEmojiButtonClicked(false); } } }); // Send button sendButton = (CursorIconButton) inflater.inflate(R.layout.cursor__item, this, false); sendButton.setText(R.string.glyph__send); if (!ThemeUtils.isDarkTheme(getContext())) { sendButton.setTextColor(ContextCompat.getColor(getContext(), R.color.text__primary_dark)); } else { sendButton.setTextColor(ContextCompat.getColor(getContext(), R.color.text__primary_light)); } sendButtonContainer.addView(sendButton, new FrameLayout.LayoutParams(buttonWidth, buttonWidth)); sendButton.setVisibility(View.INVISIBLE); } public void setCursorCallback(CursorCallback cursorCallback) { this.cursorCallback = cursorCallback; mainToolbar.setCallback(this); secondaryToolbar.setCallback(this); } public void setAccentColor(int accentColor) { newCursorEditText.setAccentColor(accentColor); mainToolbar.setAccentColor(accentColor); sendButton.setSolidBackgroundColor(accentColor); } private void connectEditText() { newCursorEditText.addTextChangedListener(this); newCursorEditText.setOnEditorActionListener(this); newCursorEditText.setOnFocusChangeListener(new OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean hasFocus) { if (hasFocus && cursorCallback != null) { cursorCallback.onFocusChange(hasFocus); } } }); newCursorEditText.setFocusableInTouchMode(true); } public void enableMessageWriting() { newCursorEditText.requestFocus(); } // Edit Text @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } /** * Notifies container that edit text has changed. And if text is not empty it triggers * also the typing indicator. */ @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { if (isEditingMessage()) { if (message == null) { return; } boolean enableControls = !TextUtils.equals(newCursorEditText.getText(), message.getBody()); hintView.setVisibility(View.GONE); editMessageCursorToolbar.enableEditControls(enableControls); return; } String text = charSequence.toString(); if (cursorCallback != null) { cursorCallback.onEditTextHasChanged(newCursorEditText.getSelectionStart(), text); } if (TextUtils.isEmpty(charSequence.toString())) { hintView.setVisibility(View.VISIBLE); } else { hintView.setVisibility(View.GONE); } showTopbar(newCursorEditText.getLineCount() > maxLines); } @Override public void afterTextChanged(Editable editable) { // do nothing } /** * EditText callback when user sent text. */ @Override public boolean onEditorAction(TextView textView, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEND || (actionId != EditorInfo.IME_ACTION_UNSPECIFIED && event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN)) { if (isEditingMessage()) { onApproveEditMessage(); return true; } String sendText = textView.getText().toString(); if (TextUtils.isEmpty(sendText)) { return false; } if (cursorCallback != null) { cursorCallback.onMessageSubmitted(sendText); } return true; } return false; } public void showSendButtonAsEnterKey(boolean show) { if (show) { newCursorEditText.setImeOptions(EditorInfo.IME_ACTION_SEND); } else { newCursorEditText.setImeOptions(EditorInfo.IME_ACTION_NONE); } } public void showSendButton(boolean show) { if (sendButtonIsVisible == show) { return; } sendButtonIsVisible = show; if (show) { if (isEditingMessage()) { return; } int duration = getResources().getInteger(R.integer.animation_duration_medium); sendButton.setVisibility(View.VISIBLE); sendButton.setAlpha(0); sendButton.animate().alpha(1f).setInterpolator(new Quart.EaseOut()).setDuration(duration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { sendButton.setAlpha(1); } }); sendButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (cursorCallback != null) { String sendText = newCursorEditText.getText().toString(); if (TextUtils.isEmpty(sendText)) { return; } cursorCallback.onMessageSubmitted(sendText); } } }); } else { sendButton.setVisibility(View.INVISIBLE); sendButton.setOnClickListener(null); } } public void setText(String text) { newCursorEditText.setText(text); newCursorEditText.setSelection(text.length()); } public void appendText(String text) { newCursorEditText.append(text); newCursorEditText.setSelection(newCursorEditText.getText().length()); } public void notifyKeyboardVisibilityChanged(boolean keyboardIsVisible, View currentFocus) { this.keyboardIsVisible = keyboardIsVisible; if (keyboardIsVisible) { cursorToolbarFrame.shrink(); shieldViewWithBanner.setEnabled(false); if (newCursorEditText.hasFocus() && cursorCallback != null) { cursorCallback.onCursorClicked(); } } else { cursorToolbarFrame.expand(); if (hasText()) { shieldViewWithBanner.setEnabled(false); } else { shieldViewWithBanner.setEnabled(true); } } } public boolean hasText() { return !TextUtils.isEmpty(newCursorEditText.getText().toString()); } public String getText() { return newCursorEditText.getText().toString(); } public int getSelection() { return newCursorEditText.getSelectionEnd(); } public void setSelection(int selection) { this.newCursorEditText.setSelection(selection); } public void tearDown() { shieldViewWithBanner.tearDown(); } public void setConversation(IConversation conversation) { shieldViewWithBanner.setConversation(conversation); enableMessageWriting(); resetMainAndSecondaryToolbars(); closeEditMessage(false); } @Override public void onCursorButtonClicked(CursorMenuItem item) { if (item == CursorMenuItem.DUMMY) { return; } if (cursorCallback != null) { cursorCallback.onCursorButtonClicked(item); } switch (item) { case MORE: showSecondaryCursorToolbar(); break; case LESS: hideSecondaryCursorToolbar(); break; case CAMERA: case AUDIO_MESSAGE: resetEmojiButton(); break; } } @Override public void onCursorButtonLongPressed(CursorMenuItem cursorMenuItem) { if (cursorMenuItem == CursorMenuItem.DUMMY) { return; } if (cursorCallback != null) { cursorCallback.onCursorButtonLongPressed(cursorMenuItem); } } @Override public void onMotionEvent(CursorMenuItem cursorMenuItem, MotionEvent motionEvent) { if (cursorCallback != null) { cursorCallback.onMotionEventFromCursorButton(cursorMenuItem, motionEvent); } } @Override public void onShowTooltip(CursorMenuItem item, String message, View anchor) { if (tooltipEnabled) { return; } int w = (int) (anchor.getX() + anchor.getMeasuredWidth() / 2); int gravity; if (w < getMeasuredWidth() / 4) { gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; } else if (w > 3 * getMeasuredWidth() / 4) { gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; } else { gravity = Gravity.CENTER; } ((FrameLayout.LayoutParams) tooltip.getLayoutParams()).gravity = gravity; tooltipEnabled = true; tooltip.setText(message); tooltip.animate().alpha(1).withStartAction(new Runnable() { @Override public void run() { tooltip.setVisibility(View.VISIBLE); tooltip.setAlpha(0); } }); if (cursorCallback != null) { cursorCallback.onShowedActionHint(item); } new Handler().postDelayed(new Runnable() { @Override public void run() { dismissToolbar(); } }, TOOLTIP_DURATION); } @Override public void onCloseEditMessage() { closeEditMessage(true); } @Override public void onResetEditMessage() { if (message == null) { return; } newCursorEditText.setText(message.getBody()); newCursorEditText.setSelection(newCursorEditText.getText().length()); } @Override public void onApproveEditMessage() { if (TextUtils.isEmpty(newCursorEditText.getText().toString().trim())) { message.recall(); Toast.makeText(getContext(), R.string.conversation__message_action__delete__confirmation, Toast.LENGTH_SHORT).show(); } else { message.update(new MessageContent.Text(newCursorEditText.getText().toString())); } if (cursorCallback != null) { cursorCallback.onApprovedMessageEditing(message); } closeEditMessage(true); } public boolean isEditingMessage() { return isEditingMessage; } public void editMessage(Message message) { isEditingMessage = true; this.message = message; newCursorEditText.setText(message.getBody()); newCursorEditText.setSelection(newCursorEditText.getText().length()); if (sendButtonIsVisible) { showSendButton(false); } setBackgroundColor(ContextCompat.getColor(getContext(), R.color.white)); ViewUtils.fadeInView(editMessageBackgroundView); newCursorEditText.setTextColor(ContextCompat.getColor(getContext(), R.color.text__primary_light)); dividerView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.separator_light)); showEditMessageToolbar(); } public void closeEditMessage(boolean animated) { if (!isEditingMessage) { return; } message = null; newCursorEditText.setText(""); hintView.setVisibility(VISIBLE); isEditingMessage = false; if (animated) { hideEditMessageToolbar(); ViewUtils.fadeOutView(editMessageBackgroundView); } else { editMessageCursorToolbar.setVisibility(GONE); editMessageBackgroundView.setVisibility(GONE); resetMainAndSecondaryToolbars(); } setBackgroundColor(ContextCompat.getColor(getContext(), R.color.transparent)); newCursorEditText.setTextColor(defaultEditTextColor); dividerView.setBackgroundColor(defaultDividerColor); if (cursorCallback != null) { cursorCallback.onClosedMessageEditing(); } } private void dismissToolbar() { if (!tooltipEnabled) { return; } tooltipEnabled = false; tooltip.animate().alpha(0).withEndAction(new Runnable() { @Override public void run() { tooltip.setVisibility(View.GONE); } }); } private void hideEditMessageToolbar() { if (showMainToolbarAnimator == null) { showMainToolbarAnimator = getShowToolbarAnimator(mainToolbar, -cursorHeight, 0); } if (hideEditMessageToolbarAnimator == null) { hideEditMessageToolbarAnimator = getHideToolbarAnimator(editMessageCursorToolbar, 0, 2 * cursorHeight); } mainToolbar.setVisibility(VISIBLE); showMainToolbarAnimator.start(); hideEditMessageToolbarAnimator.start(); } private void showEditMessageToolbar() { ObjectAnimator hideAnimator; if (mainToolbar.getVisibility() == VISIBLE) { if (hideMainToolbarAnimator == null) { hideMainToolbarAnimator = getHideToolbarAnimator(mainToolbar, 0, -cursorHeight); } hideAnimator = hideMainToolbarAnimator; } else { if (hideSecondaryToolbarAnimator == null) { hideSecondaryToolbarAnimator = getHideToolbarAnimator(secondaryToolbar, 0, 2 * cursorHeight); } hideAnimator = hideSecondaryToolbarAnimator; } if (showEditMessageToolbarAnimator == null) { showEditMessageToolbarAnimator = getShowToolbarAnimator(editMessageCursorToolbar, 2 * cursorHeight, 0); } editMessageCursorToolbar.setVisibility(VISIBLE); hideAnimator.start(); showEditMessageToolbarAnimator.start(); } private void showSecondaryCursorToolbar() { if (hideMainToolbarAnimator == null) { hideMainToolbarAnimator = getHideToolbarAnimator(mainToolbar, 0, -cursorHeight); } if (showSecondaryToolbarAnimator == null) { showSecondaryToolbarAnimator = getShowToolbarAnimator(secondaryToolbar, 2 * cursorHeight, 0); } secondaryToolbar.setVisibility(VISIBLE); hideMainToolbarAnimator.start(); showSecondaryToolbarAnimator.start(); } private void hideSecondaryCursorToolbar() { if (showMainToolbarAnimator == null) { showMainToolbarAnimator = getShowToolbarAnimator(mainToolbar, -cursorHeight, 0); } if (hideSecondaryToolbarAnimator == null) { hideSecondaryToolbarAnimator = getHideToolbarAnimator(secondaryToolbar, 0, 2 * cursorHeight); } mainToolbar.setVisibility(VISIBLE); showMainToolbarAnimator.start(); hideSecondaryToolbarAnimator.start(); } private ObjectAnimator getShowToolbarAnimator(View view, float fromValue, float toValue) { ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, fromValue, toValue); animator.setDuration(cursorToolbarAnimationDuration); animator.setStartDelay(getResources().getInteger(R.integer.animation_delay_short)); animator.setInterpolator(new Expo.EaseOut()); return animator; } private ObjectAnimator getHideToolbarAnimator(final View view, float fromValue, float toValue) { ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, fromValue, toValue); animator.setDuration(cursorToolbarAnimationDuration); animator.setInterpolator(new Expo.EaseIn()); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { if (view == null) { return; } view.setVisibility(GONE); } @Override public void onAnimationEnd(Animator animation) { if (view == null) { return; } view.setVisibility(GONE); } }); return animator; } public void showTopbar(boolean show) { if (show) { topBorder.setVisibility(View.VISIBLE); } else { topBorder.setVisibility(View.INVISIBLE); } } public void onExtendedCursorClosed() { mainToolbar.unselectItems(); resetEmojiButton(); } private void resetMainAndSecondaryToolbars() { mainToolbar.setTranslationY(0); mainToolbar.setVisibility(VISIBLE); secondaryToolbar.setTranslationY(2 * cursorHeight); secondaryToolbar.setVisibility(GONE); } private void resetEmojiButton() { emojiButton.setText(R.string.glyph__emoji); } }