Skip to content

Batch updates and empty lists #477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 164 additions & 22 deletions app/src/main/java/com/firebase/uidemo/database/ChatActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.BounceInterpolator;
import android.view.animation.TranslateAnimation;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
Expand Down Expand Up @@ -74,23 +80,19 @@ protected void onCreate(Bundle savedInstanceState) {
mRef = FirebaseDatabase.getInstance().getReference();
mChatRef = mRef.child("chats");

// Allow hitting our custom Send button to send the message
mSendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String uid = mAuth.getCurrentUser().getUid();
String name = "User " + uid.substring(0, 6);

Chat chat = new Chat(name, uid, mMessageEdit.getText().toString());
mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError databaseError, DatabaseReference reference) {
if (databaseError != null) {
Log.e(TAG, "Failed to write message", databaseError.toException());
}
}
});

mMessageEdit.setText("");
sendCurrentMessage();
}
});
// Allow hitting the Send key on the soft keyboard to send the message
mMessageEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND) sendCurrentMessage();
return true;
}
});

Expand All @@ -103,6 +105,23 @@ public void onComplete(DatabaseError databaseError, DatabaseReference reference)
mMessages.setLayoutManager(mManager);
}

private void sendCurrentMessage() {
String uid = mAuth.getCurrentUser().getUid();
String name = "User " + uid.substring(0, 6);

Chat chat = new Chat(name, uid, mMessageEdit.getText().toString());
mChatRef.push().setValue(chat, new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError databaseError, DatabaseReference reference) {
if (databaseError != null) {
Log.e(TAG, "Failed to write message", databaseError.toException());
}
}
});

mMessageEdit.setText("");
}

@Override
public void onStart() {
super.onStart();
Expand All @@ -113,7 +132,7 @@ public void onStart() {
if (!isSignedIn()) {
signInAnonymously();
} else {
attachRecyclerViewAdapter();
attachRecyclerAdapter();
}
}

Expand All @@ -138,7 +157,7 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
updateUI();
}

private void attachRecyclerViewAdapter() {
private void attachRecyclerAdapter() {
Query lastFifty = mChatRef.limitToLast(50);
mRecyclerViewAdapter = new FirebaseRecyclerAdapter<Chat, ChatHolder>(
Chat.class, R.layout.message, ChatHolder.class, lastFifty) {
Expand All @@ -154,6 +173,13 @@ public void populateViewHolder(ChatHolder chatView, Chat chat, int position) {
} else {
chatView.setIsSender(false);
}
chatView.itemView.setTag(R.layout.message, chatView);
chatView.itemView.setTag(position);
}

@Override
protected void onReady() {
findViewById(R.id.emptyTextView).setVisibility(mRecyclerViewAdapter.getItemCount() == 0?View.VISIBLE:View.INVISIBLE);
}
};

Expand All @@ -165,6 +191,24 @@ public void onItemRangeInserted(int positionStart, int itemCount) {
}
});

// Listen for touch gestures on the entire recycler view
mMessages.setOnTouchListener(new RecyclerSwipeListener(mMessages, new RecyclerSwipeListener.SwipeActionListener() {
@Override
public boolean onSwipeLeft(RecyclerView.ViewHolder vh, int x, boolean forceDisable) {
return ((ChatHolder) vh).slideMessage(x, forceDisable);
}

@Override
public void onReset(RecyclerView.ViewHolder vh) {
((ChatHolder) vh).resetSlide();
}

@Override
public void onPerformAction(RecyclerView.ViewHolder vh) {
mRecyclerViewAdapter.getRef((int) ((ChatHolder) vh).itemView.getTag()).removeValue();
}
}));

mMessages.setAdapter(mRecyclerViewAdapter);
}

Expand All @@ -178,7 +222,7 @@ public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Toast.makeText(ChatActivity.this, "Signed In",
Toast.LENGTH_SHORT).show();
attachRecyclerViewAdapter();
attachRecyclerAdapter();
} else {
Toast.makeText(ChatActivity.this, "Sign In Failed",
Toast.LENGTH_SHORT).show();
Expand All @@ -198,7 +242,6 @@ public void updateUI() {
}

public static class Chat {

String name;
String text;
String uid;
Expand Down Expand Up @@ -230,8 +273,10 @@ public static class ChatHolder extends RecyclerView.ViewHolder {
private final TextView mTextField;
private final FrameLayout mLeftArrow;
private final FrameLayout mRightArrow;
private final FrameLayout mMessageRow;
private final RelativeLayout mMessageContainer;
private final LinearLayout mMessage;
private final TextView mRemoveText;
private final int mGreen300;
private final int mGray300;

Expand All @@ -241,8 +286,10 @@ public ChatHolder(View itemView) {
mTextField = (TextView) itemView.findViewById(R.id.message_text);
mLeftArrow = (FrameLayout) itemView.findViewById(R.id.left_arrow);
mRightArrow = (FrameLayout) itemView.findViewById(R.id.right_arrow);
mMessageRow = (FrameLayout) itemView.findViewById(R.id.message_row);
mMessageContainer = (RelativeLayout) itemView.findViewById(R.id.message_container);
mMessage = (LinearLayout) itemView.findViewById(R.id.message);
mRemoveText = (TextView) itemView.findViewById(R.id.remove_text);
mGreen300 = ContextCompat.getColor(itemView.getContext(), R.color.material_green_300);
mGray300 = ContextCompat.getColor(itemView.getContext(), R.color.material_gray_300);
}
Expand All @@ -262,10 +309,8 @@ public void setIsSender(boolean isSender) {
}

((GradientDrawable) mMessage.getBackground()).setColor(color);
((RotateDrawable) mLeftArrow.getBackground()).getDrawable()
.setColorFilter(color, PorterDuff.Mode.SRC);
((RotateDrawable) mRightArrow.getBackground()).getDrawable()
.setColorFilter(color, PorterDuff.Mode.SRC);
((RotateDrawable) mLeftArrow.getBackground()).getDrawable().setColorFilter(color, PorterDuff.Mode.SRC);
((RotateDrawable) mRightArrow.getBackground()).getDrawable().setColorFilter(color, PorterDuff.Mode.SRC);
}

public void setName(String name) {
Expand All @@ -275,5 +320,102 @@ public void setName(String name) {
public void setText(String text) {
mTextField.setText(text);
}

public boolean slideMessage(int x, boolean forceDisable) {
// the user is swiping left over this holder, move the message bubble left and uncover the delete label
mMessageRow.setTranslationX(-x);
mRemoveText.setTranslationX(mRemoveText.getWidth()-Math.min(x, mRemoveText.getWidth()));
mRemoveText.setEnabled(x >= mRemoveText.getWidth() && !forceDisable);
return x >= mRemoveText.getWidth();
}
public void resetSlide() {
Log.d(TAG, "resetSlide");
// animate the message bubble and the remove label back to their original positions
animateRight(mMessageRow, -1*mMessageRow.getTranslationX());
animateRight(mRemoveText, mRemoveText.getWidth() - mRemoveText.getTranslationX());
}
// Helper function that animates the view to the right by offsetX over a 500ms
public static void animateRight(final View view, final float offsetX) {
TranslateAnimation animation = new TranslateAnimation(0.0f, offsetX, 0.0f, 0.0f);
animation.setInterpolator(new BounceInterpolator());
animation.setDuration(500);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override public void onAnimationStart(Animation animation) { }
@Override public void onAnimationRepeat(Animation animation) { }

@Override
public void onAnimationEnd(Animation animation) {
Log.d(TAG, "Moving "+view.getId()+" to x="+(view.getTranslationX()+offsetX));
view.clearAnimation();
view.setTranslationX(view.getTranslationX()+offsetX);
}
});
view.startAnimation(animation);
}
}
// This class listens for swipe events on the recycler view
static class RecyclerSwipeListener implements View.OnTouchListener {
public interface SwipeActionListener {
boolean onSwipeLeft(RecyclerView.ViewHolder vh, int x, boolean forceDisable);
void onReset(RecyclerView.ViewHolder vh);
void onPerformAction(RecyclerView.ViewHolder vh);
}
private boolean mActionEnabled = false;
private int mActionDownX = 0;
private int mActionUpX = 0;
RecyclerView mRecyclerView; // the Recycler that we're listening for swipes on
private RecyclerView.ViewHolder mSelectedViewHolder; // the VH the user originally touched in this move
private SwipeActionListener mListener;

public RecyclerSwipeListener(RecyclerView recyclerView, SwipeActionListener listener) {
mRecyclerView = recyclerView;
mListener = listener;
}

@Override
public boolean onTouch(View view, MotionEvent event) {
View child = mRecyclerView.findChildViewUnder(event.getX(), event.getY());
ChatHolder overViewHolder = null;
if (child != null) {
overViewHolder = (ChatHolder) child.getTag(R.layout.message);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mActionEnabled = false;
if (mSelectedViewHolder != null) {
// the user clicked on an item, while we thought they were holding another item
mListener.onSwipeLeft(mSelectedViewHolder, 0, false);
}
if (child != null) {
mSelectedViewHolder = overViewHolder;
mActionDownX = (int) event.getX();
}
break;
case MotionEvent.ACTION_MOVE:
if (mSelectedViewHolder != null) {
mActionUpX = (int) event.getX();
int difference = mActionDownX - mActionUpX;
mActionEnabled = mListener.onSwipeLeft(mSelectedViewHolder, difference, overViewHolder != mSelectedViewHolder);
}
break;
case MotionEvent.ACTION_UP:
if (mSelectedViewHolder != null) {
if (mActionEnabled && mSelectedViewHolder == overViewHolder) {
mListener.onSwipeLeft(mSelectedViewHolder, 0, false);
mListener.onPerformAction(mSelectedViewHolder);
}
else {
mListener.onReset(mSelectedViewHolder);
}
mActionDownX = mActionUpX = 0;

mSelectedViewHolder = null;
mActionEnabled = false;
break;
}

}
return true;
}
}
}
13 changes: 12 additions & 1 deletion app/src/main/res/layout/activity_chat.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
android:layout_height="match_parent"
tools:context=".database.ChatActivity">

<TextView
android:id="@+id/emptyTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="No messages. Start chatting at the bottom!"
android:padding="16dp" />

<android.support.v7.widget.RecyclerView
android:id="@+id/messagesList"
android:layout_width="match_parent"
Expand Down Expand Up @@ -34,7 +41,11 @@
android:id="@+id/messageEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
android:layout_weight="1"
android:imeActionLabel="@string/button_text_send"
android:imeOptions="actionSend"
android:inputType="text"
android:maxLines="1" />

<Button
android:id="@+id/sendButton"
Expand Down
Loading