Commit 3fcc23e4 authored by Yusuke Iwaki's avatar Yusuke Iwaki Committed by GitHub

Merge branch 'develop' into feature/burger-menu-budge

parents 3d855539 7f855b85
......@@ -6,6 +6,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SlidingPaneLayout;
......@@ -27,16 +28,19 @@ import chat.rocket.android.helper.FileUploadHelper;
import chat.rocket.android.helper.LoadMoreScrollListener;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.helper.RecyclerViewAutoScrollManager;
import chat.rocket.android.helper.RecyclerViewScrolledToBottomListener;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.layouthelper.chatroom.MessageFormManager;
import chat.rocket.android.layouthelper.chatroom.MessageListAdapter;
import chat.rocket.android.layouthelper.chatroom.AbstractNewMessageIndicatorManager;
import chat.rocket.android.layouthelper.chatroom.PairedMessage;
import chat.rocket.android.layouthelper.extra_action.MessageExtraActionBehavior;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.layouthelper.extra_action.upload.AudioUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.AbstractUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.AudioUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.ImageUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.VideoUploadActionItem;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.Message;
......@@ -70,6 +74,10 @@ public class RoomFragment extends AbstractChatRoomFragment
private LoadMoreScrollListener scrollListener;
private RealmObjectObserver<LoadMessageProcedure> procedureObserver;
private MessageFormManager messageFormManager;
private RecyclerViewAutoScrollManager autoScrollManager;
private AbstractNewMessageIndicatorManager newMessageIndicatorManager;
private Snackbar unreadIndicator;
private boolean previousUnreadMessageExists;
public RoomFragment() {
}
......@@ -145,6 +153,15 @@ public class RoomFragment extends AbstractChatRoomFragment
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(),
LinearLayoutManager.VERTICAL, true);
listView.setLayoutManager(layoutManager);
autoScrollManager = new RecyclerViewAutoScrollManager(layoutManager) {
@Override
protected void onAutoScrollMissed() {
if (newMessageIndicatorManager != null) {
newMessageIndicatorManager.updateNewMessageCount(getUnreadMessageCount());
}
}
};
adapter.registerAdapterDataObserver(autoScrollManager);
scrollListener = new LoadMoreScrollListener(layoutManager, 40) {
@Override
......@@ -153,11 +170,69 @@ public class RoomFragment extends AbstractChatRoomFragment
}
};
listView.addOnScrollListener(scrollListener);
listView.addOnScrollListener(
new RecyclerViewScrolledToBottomListener(layoutManager, 1, this::markAsReadIfNeeded));
newMessageIndicatorManager = new AbstractNewMessageIndicatorManager() {
@Override
protected void onShowIndicator(int count, boolean onlyAlreadyShown) {
if ((onlyAlreadyShown && unreadIndicator != null && unreadIndicator.isShown())
|| !onlyAlreadyShown) {
unreadIndicator = getUnreadCountIndicatorView(count);
unreadIndicator.show();
}
}
@Override
protected void onHideIndicator() {
if (unreadIndicator != null && unreadIndicator.isShown()) {
unreadIndicator.dismiss();
}
}
};
setupSideMenu();
setupMessageComposer();
}
private void scrollToLatestMessage() {
RecyclerView listView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
if (listView != null) {
listView.scrollToPosition(0);
}
}
private Snackbar getUnreadCountIndicatorView(int count) {
// TODO: replace with another custom View widget, not to hide message composer.
final String caption = getResources().getString(
R.string.fmt_dialog_view_latest_message_title, count);
return Snackbar.make(rootView, caption, Snackbar.LENGTH_LONG)
.setAction(R.string.dialog_view_latest_message_action, view -> scrollToLatestMessage());
}
private int getUnreadMessageCount() {
RoomSubscription room = realmHelper.executeTransactionForRead(realm ->
realm.where(RoomSubscription.class).equalTo(RoomSubscription.ROOM_ID, roomId).findFirst());
if (room != null) {
return realmHelper.executeTransactionForReadResults(realm ->
realm.where(Message.class)
.equalTo(Message.ROOM_ID, roomId)
.greaterThanOrEqualTo(Message.TIMESTAMP, room.getLastSeen())
.notEqualTo(Message.USER_ID, userId)
.findAll()).size();
} else {
return 0;
}
}
@Override
public void onDestroyView() {
RecyclerView listView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
listView.getAdapter().unregisterAdapterDataObserver(autoScrollManager);
super.onDestroyView();
}
@Override
public void onItemClick(PairedMessage pairedMessage) {
if (pairedMessage.target != null) {
......@@ -230,7 +305,13 @@ public class RoomFragment extends AbstractChatRoomFragment
.put(Message.SYNC_STATE, SyncState.NOT_SYNCED)
.put(Message.TIMESTAMP, System.currentTimeMillis())
.put(Message.ROOM_ID, roomId)
.put(Message.MESSAGE, messageText))));
.put(Message.USER, new JSONObject()
.put(User.ID, userId))
.put(Message.MESSAGE, messageText)))
.onSuccess(_task -> {
scrollToLatestMessage();
return null;
}));
messageFormManager.registerExtraActionItem(new ImageUploadActionItem());
messageFormManager.registerExtraActionItem(new AudioUploadActionItem());
messageFormManager.registerExtraActionItem(new VideoUploadActionItem());
......@@ -275,6 +356,12 @@ public class RoomFragment extends AbstractChatRoomFragment
setToolbarRoomIcon(0);
}
setToolbarTitle(roomSubscription.getName());
boolean unreadMessageExists = roomSubscription.isAlert();
if (newMessageIndicatorManager != null && previousUnreadMessageExists && !unreadMessageExists) {
newMessageIndicatorManager.reset();
}
previousUnreadMessageExists = unreadMessageExists;
}
private void onUpdateLoadMessageProcedure(LoadMessageProcedure procedure) {
......@@ -346,7 +433,6 @@ public class RoomFragment extends AbstractChatRoomFragment
roomObserver.sub();
procedureObserver.sub();
closeSideMenuIfNeeded();
markAsReadIfNeeded();
}
@Override
......
package chat.rocket.android.helper;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
/**
* workaround for bug https://code.google.com/p/android/issues/detail?id=174227
*/
public class RecyclerViewAutoScrollManager extends RecyclerView.AdapterDataObserver {
private final LinearLayoutManager linearLayoutManager;
public RecyclerViewAutoScrollManager(LinearLayoutManager linearLayoutManager) {
this.linearLayoutManager = linearLayoutManager;
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
if (linearLayoutManager.findFirstVisibleItemPosition() <= positionStart) {
linearLayoutManager.scrollToPosition(positionStart);
} else {
onAutoScrollMissed();
}
}
protected void onAutoScrollMissed() {
//do nothing by default.
}
}
package chat.rocket.android.helper;
import android.os.Handler;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
/**
* ScrollListener for detecting scrolled on the bottom of the RecyclerView.
*/
public class RecyclerViewScrolledToBottomListener extends RecyclerView.OnScrollListener {
/**
* callback.
*/
public interface Callback {
void onScrolledToBottom();
}
private final LinearLayoutManager layoutManager;
private final int thresholdPosition;
private final Handler handler;
private final Callback callback;
/**
* Trigger callback if the bottom item position > thresholdPosition.
*/
public RecyclerViewScrolledToBottomListener(LinearLayoutManager layoutManager,
int thresholdPosition, Callback callback) {
this.layoutManager = layoutManager;
this.thresholdPosition = thresholdPosition;
this.callback = callback;
this.handler = new Handler() {
@Override
public void handleMessage(android.os.Message msg) {
onScrollEnd();
}
};
}
@Override
public void onScrolled(RecyclerView recyclerView, int deltaX, int deltaY) {
super.onScrolled(recyclerView, deltaX, deltaY);
handler.removeMessages(0);
handler.sendEmptyMessageDelayed(0, 120);
}
private void onScrollEnd() {
if (layoutManager.getReverseLayout()) {
if (layoutManager.findFirstVisibleItemPosition() <= thresholdPosition) {
doCallback();
}
} else {
if (layoutManager.findLastVisibleItemPosition() >= thresholdPosition) {
doCallback();
}
}
}
private void doCallback() {
if (callback != null) {
callback.onScrolledToBottom();
}
}
}
......@@ -38,7 +38,7 @@ public abstract class ExtRealmModelListAdapter<T extends RealmObject, VM,
notifyItemChanged(position + 1);
}
protected ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position + 1, count);
......@@ -108,7 +108,7 @@ public abstract class ExtRealmModelListAdapter<T extends RealmObject, VM,
}
@Override
public ListUpdateCallback getListUpdateCallback() {
protected ListUpdateCallback getListUpdateCallback() {
return listUpdateCallback;
}
}
package chat.rocket.android.layouthelper.chatroom;
/**
* manager class for showing "You have XX messages" indicator.
*/
public abstract class AbstractNewMessageIndicatorManager {
private int count;
private boolean onlyAlreadyShown;
/**
* update the number of unread message.
*/
public void updateNewMessageCount(int count) {
if (count > 0) {
this.count = count;
update();
onlyAlreadyShown = true;
} else {
reset();
}
}
/**
* Should call this method when user checked new message.
*/
public void reset() {
count = 0;
onlyAlreadyShown = false;
update();
}
private void update() {
if (count > 0) {
onShowIndicator(count, onlyAlreadyShown);
} else {
onHideIndicator();
}
}
protected abstract void onShowIndicator(int count, boolean onlyAlreadyShown);
protected abstract void onHideIndicator();
}
......@@ -25,6 +25,7 @@ public class Message extends RealmObject {
@SuppressWarnings({"PMD.AvoidFieldNameMatchingTypeName"})
public static final String MESSAGE = "msg";
public static final String USER = "u";
public static final String USER_ID = "u._id";
public static final String GROUPABLE = "groupable";
public static final String ATTACHMENTS = "attachments";
public static final String URLS = "urls";
......
......@@ -18,6 +18,9 @@
<string name="resend">Resend</string>
<string name="discard">Discard</string>
<string name="fmt_dialog_view_latest_message_title">New %d messages</string>
<string name="dialog_view_latest_message_action">View</string>
<string name="file_uploading_title">Uploading…</string>
<string name="dialog_user_registration_email">Email</string>
......
......@@ -103,7 +103,31 @@ public abstract class RealmModelListAdapter<T extends RealmObject, VM,
protected abstract DiffUtil.Callback getDiffCallback(List<VM> oldData, List<VM> newData);
protected abstract ListUpdateCallback getListUpdateCallback();
private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
notifyItemRangeChanged(position, count, payload);
}
};
protected ListUpdateCallback getListUpdateCallback() {
return listUpdateCallback;
}
public void setOnItemClickListener(OnItemClickListener<VM> onItemClickListener) {
this.onItemClickListener = onItemClickListener;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment