Commit 41cd2147 authored by Yusuke Iwaki's avatar Yusuke Iwaki

modify Realm-based list to enable separating the view model from the raw realm model.

parent d80e6b52
...@@ -13,7 +13,6 @@ import chat.rocket.android.model.ddp.Message; ...@@ -13,7 +13,6 @@ import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.model.ddp.RoomSubscription; import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.model.internal.LoadMessageProcedure; import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.realm_helper.RealmHelper; import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmModelListView;
import chat.rocket.android.realm_helper.RealmObjectObserver; import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.realm_helper.RealmStore; import chat.rocket.android.realm_helper.RealmStore;
import io.realm.Realm; import io.realm.Realm;
...@@ -70,13 +69,13 @@ public class RoomFragment extends AbstractChatRoomFragment { ...@@ -70,13 +69,13 @@ public class RoomFragment extends AbstractChatRoomFragment {
} }
@Override protected void onSetupView() { @Override protected void onSetupView() {
RealmModelListView listView = (RealmModelListView) rootView.findViewById(R.id.listview); RecyclerView listView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
listView.setAdapter(realmHelper.createListAdapter(getContext(),
realmHelper.bindListView(listView,
realm -> realm.where(Message.class) realm -> realm.where(Message.class)
.equalTo("rid", roomId) .equalTo("rid", roomId)
.findAllSorted("ts", Sort.DESCENDING), .findAllSorted("ts", Sort.DESCENDING),
context -> new MessageListAdapter(context, hostname)); context -> new MessageListAdapter(context, hostname)
));
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(), RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext(),
LinearLayoutManager.VERTICAL, true); LinearLayoutManager.VERTICAL, true);
......
...@@ -4,12 +4,16 @@ import android.content.Context; ...@@ -4,12 +4,16 @@ import android.content.Context;
import android.view.View; import android.view.View;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.model.ddp.Message; import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.realm_adapter.RealmModelListAdapter; import chat.rocket.android.realm_helper.RealmModelListAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** /**
* message list adapter for chat room. * target list adapter for chat room.
*/ */
public class MessageListAdapter extends RealmModelListAdapter<Message, MessageViewHolder> { public class MessageListAdapter
extends RealmModelListAdapter<Message, PairedMessage, MessageViewHolder> {
private final String hostname; private final String hostname;
...@@ -18,7 +22,7 @@ public class MessageListAdapter extends RealmModelListAdapter<Message, MessageVi ...@@ -18,7 +22,7 @@ public class MessageListAdapter extends RealmModelListAdapter<Message, MessageVi
this.hostname = hostname; this.hostname = hostname;
} }
@Override protected int getItemViewType(Message model) { @Override protected int getRealmModelViewType(PairedMessage model) {
return 0; return 0;
} }
...@@ -29,4 +33,18 @@ public class MessageListAdapter extends RealmModelListAdapter<Message, MessageVi ...@@ -29,4 +33,18 @@ public class MessageListAdapter extends RealmModelListAdapter<Message, MessageVi
@Override protected MessageViewHolder onCreateRealmModelViewHolder(int viewType, View itemView) { @Override protected MessageViewHolder onCreateRealmModelViewHolder(int viewType, View itemView) {
return new MessageViewHolder(itemView, hostname); return new MessageViewHolder(itemView, hostname);
} }
@Override protected List<PairedMessage> mapResultsToViewModel(List<Message> results) {
if (results.isEmpty()) {
return Collections.emptyList();
}
ArrayList<PairedMessage> extMessages = new ArrayList<>();
for (int i = 0; i < results.size() - 1; i++) {
extMessages.add(new PairedMessage(results.get(i), results.get(i + 1)));
}
extMessages.add(new PairedMessage(results.get(results.size() - 1), null));
return extMessages;
}
} }
package chat.rocket.android.layouthelper.chatroom; package chat.rocket.android.layouthelper.chatroom;
import android.support.annotation.Nullable;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.model.ddp.Message; import chat.rocket.android.helper.DateTime;
import chat.rocket.android.realm_adapter.RealmModelViewHolder; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.realm_helper.RealmModelViewHolder;
import chat.rocket.android.renderer.MessageRenderer; import chat.rocket.android.renderer.MessageRenderer;
import chat.rocket.android.widget.message.RocketChatMessageLayout; import chat.rocket.android.widget.message.RocketChatMessageLayout;
/** /**
*/ */
public class MessageViewHolder extends RealmModelViewHolder<Message> { public class MessageViewHolder extends RealmModelViewHolder<PairedMessage> {
private final ImageView avatar; private final ImageView avatar;
private final TextView username; private final TextView username;
private final TextView timestamp; private final TextView timestamp;
private final View userAndTimeContainer;
private final String hostname; private final String hostname;
private final RocketChatMessageLayout body; private final RocketChatMessageLayout body;
private final View newDayContainer;
private final TextView newDayText;
public MessageViewHolder(View itemView, String hostname) { public MessageViewHolder(View itemView, String hostname) {
super(itemView); super(itemView);
avatar = (ImageView) itemView.findViewById(R.id.user_avatar); avatar = (ImageView) itemView.findViewById(R.id.user_avatar);
username = (TextView) itemView.findViewById(R.id.username); username = (TextView) itemView.findViewById(R.id.username);
timestamp = (TextView) itemView.findViewById(R.id.timestamp); timestamp = (TextView) itemView.findViewById(R.id.timestamp);
userAndTimeContainer = itemView.findViewById(R.id.user_and_timestamp_container);
body = (RocketChatMessageLayout) itemView.findViewById(R.id.message_body); body = (RocketChatMessageLayout) itemView.findViewById(R.id.message_body);
newDayContainer = itemView.findViewById(R.id.newday_container);
newDayText = (TextView) itemView.findViewById(R.id.newday_text);
this.hostname = hostname; this.hostname = hostname;
} }
public void bind(Message message) { public void bind(PairedMessage pairedMessage) {
new MessageRenderer(itemView.getContext(), message) new MessageRenderer(itemView.getContext(), pairedMessage.target)
.avatarInto(avatar, hostname) .avatarInto(avatar, hostname)
.usernameInto(username) .usernameInto(username)
.timestampInto(timestamp) .timestampInto(timestamp)
.bodyInto(body); .bodyInto(body);
renderNewDayAndSequential(pairedMessage);
}
private void renderNewDayAndSequential(PairedMessage pairedMessage) {
//see Rocket.Chat:packages/rocketchat-livechat/app/client/views/message.coffee
if (!pairedMessage.hasSameDate()) {
setNewDay(DateTime.fromEpocMs(pairedMessage.target.getTs(), DateTime.Format.DATE));
setSequential(false);
} else if (!pairedMessage.target.isGroupable() || !pairedMessage.nextSibling.isGroupable()
|| !pairedMessage.hasSameUser()) {
setNewDay(null);
setSequential(false);
} else {
setNewDay(null);
setSequential(true);
}
}
private void setSequential( boolean sequential) {
if (sequential) {
avatar.setVisibility(View.INVISIBLE);
userAndTimeContainer.setVisibility(View.GONE);
} else {
avatar.setVisibility(View.VISIBLE);
userAndTimeContainer.setVisibility(View.VISIBLE);
}
}
private void setNewDay(@Nullable String text) {
if (TextUtils.isEmpty(text)) {
newDayContainer.setVisibility(View.GONE);
} else {
newDayText.setText(text);
newDayContainer.setVisibility(View.VISIBLE);
}
} }
} }
package chat.rocket.android.layouthelper.chatroom;
import chat.rocket.android.helper.DateTime;
import chat.rocket.android.model.ddp.Message;
/**
* View Model for messages in chatroom.
*/
public class PairedMessage {
final Message target;
final Message nextSibling;
public PairedMessage(Message target, Message nextSibling) {
this.target = target;
this.nextSibling = nextSibling;
}
public boolean hasSameDate() {
return nextSibling != null
&& DateTime.fromEpocMs(nextSibling.getTs(), DateTime.Format.DATE)
.equals(DateTime.fromEpocMs(target.getTs(), DateTime.Format.DATE));
}
public boolean hasSameUser() {
return nextSibling != null
&& nextSibling.getU() != null && target.getU() != null
&& nextSibling.getU().get_id().equals(target.getU().get_id());
}
}
...@@ -111,6 +111,10 @@ public class Message extends RealmObject { ...@@ -111,6 +111,10 @@ public class Message extends RealmObject {
messageJson.remove("ts"); messageJson.remove("ts");
messageJson.put("ts", ts).put("syncstate", SyncState.SYNCED); messageJson.put("ts", ts).put("syncstate", SyncState.SYNCED);
if (messageJson.isNull("groupable")) {
messageJson.put("groupable", true);
}
return messageJson; return messageJson;
} }
} }
...@@ -12,7 +12,7 @@ import chat.rocket.android.widget.message.RocketChatMessageLayout; ...@@ -12,7 +12,7 @@ import chat.rocket.android.widget.message.RocketChatMessageLayout;
*/ */
public class MessageRenderer extends AbstractRenderer<Message> { public class MessageRenderer extends AbstractRenderer<Message> {
private UserRenderer userRenderer; private final UserRenderer userRenderer;
public MessageRenderer(Context context, Message message) { public MessageRenderer(Context context, Message message) {
super(context, message); super(context, message);
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<chat.rocket.android.realm_helper.RealmModelListView <android.support.v7.widget.RecyclerView
android:id="@+id/listview" android:id="@+id/recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
/> />
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_marginEnd="8dp"> android:layout_marginEnd="8dp">
<LinearLayout <LinearLayout
android:id="@+id/user_and_timestamp_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal">
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
android:background="@color/newday_color"/> android:background="@color/newday_color"/>
<TextView <TextView
android:id="@+id/list_item_message_newday_text" android:id="@+id/newday_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/newday_color" android:textColor="@color/newday_color"
......
...@@ -36,5 +36,4 @@ dependencies { ...@@ -36,5 +36,4 @@ dependencies {
compile rootProject.ext.supportAnnotations compile rootProject.ext.supportAnnotations
compile rootProject.ext.supportAppCompat compile rootProject.ext.supportAppCompat
compile rootProject.ext.supportDesign compile rootProject.ext.supportDesign
compile 'io.realm:android-adapters:1.4.0'
} }
package chat.rocket.android.realm_adapter;
import android.content.Context;
import android.support.annotation.LayoutRes;
import android.view.View;
import android.view.ViewGroup;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmRecyclerViewAdapter;
import io.realm.RealmResults;
public abstract class RealmModelListAdapter<T extends RealmObject,
VH extends RealmModelViewHolder<T>> extends RealmRecyclerViewAdapter<T, VH> {
public interface Query<T extends RealmObject> {
RealmResults<T> queryItems(Realm realm);
}
public interface Constructor<T extends RealmObject, VH extends RealmModelViewHolder<T>> {
RealmModelListAdapter<T, VH> getNewInstance(Context context);
}
public RealmModelListAdapter(Context context) {
super(context, null, true);
}
protected abstract int getItemViewType(T model);
protected abstract @LayoutRes int getLayout(int viewType);
protected abstract VH onCreateRealmModelViewHolder(int viewType, View itemView);
@Override public final int getItemViewType(int position) {
return getItemViewType(getItem(position));
}
@Override public VH onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = inflater.inflate(getLayout(viewType), parent, false);
return onCreateRealmModelViewHolder(viewType, itemView);
}
@Override public void onBindViewHolder(VH holder, int position) {
holder.bind(getItem(position));
}
}
package chat.rocket.android.realm_helper; package chat.rocket.android.realm_helper;
import android.content.Context;
import android.os.Looper; import android.os.Looper;
import android.support.v7.widget.RecyclerView;
import bolts.Task; import bolts.Task;
import bolts.TaskCompletionSource; import bolts.TaskCompletionSource;
import chat.rocket.android.realm_adapter.RealmModelListAdapter;
import chat.rocket.android.realm_adapter.RealmModelViewHolder;
import io.realm.Realm; import io.realm.Realm;
import io.realm.RealmConfiguration; import io.realm.RealmConfiguration;
import io.realm.RealmObject; import io.realm.RealmObject;
...@@ -171,12 +171,9 @@ public class RealmHelper { ...@@ -171,12 +171,9 @@ public class RealmHelper {
return new RealmObjectObserver<T>(this, query); return new RealmObjectObserver<T>(this, query);
} }
public <T extends RealmObject, VH extends RealmModelViewHolder<T>> void bindListView( public <T extends RealmObject, VM, VH extends RealmModelViewHolder<VM>>
RealmModelListView listView, RecyclerView.Adapter<VH> createListAdapter(Context context, RealmListObserver.Query<T> query,
RealmModelListAdapter.Query<T> query, RealmModelListAdapter.Constructor<T, VM, VH> constructor) {
RealmModelListAdapter.Constructor<T, VH> constructor) { return constructor.getNewInstance(context).initializeWith(this, query);
if (listView != null) {
listView.setup(this, query, constructor);
}
} }
} }
package chat.rocket.android.realm_helper;
import android.content.Context;
import android.support.annotation.LayoutRes;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import io.realm.RealmObject;
import java.util.List;
public abstract class RealmModelListAdapter<T extends RealmObject, VM,
VH extends RealmModelViewHolder<VM>> extends RecyclerView.Adapter<VH> {
public interface Constructor<T extends RealmObject, VM, VH extends RealmModelViewHolder<VM>> {
RealmModelListAdapter<T, VM, VH> getNewInstance(Context context);
}
private final LayoutInflater inflater;
private RealmListObserver<T> realmListObserver;
private List<VM> adapterData;
protected RealmModelListAdapter(Context context) {
this.inflater = LayoutInflater.from(context);
}
/*package*/ RealmModelListAdapter<T, VM, VH> initializeWith(RealmHelper realmHelper,
RealmListObserver.Query<T> query) {
realmListObserver = new RealmListObserver<>(realmHelper, query)
.setOnUpdateListener(new RealmListObserver.OnUpdateListener<T>() {
@Override public void onUpdateResults(List<T> results) {
updateData(results);
}
});
return this;
}
@Override public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
realmListObserver.sub();
}
@Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
realmListObserver.unsub();
super.onDetachedFromRecyclerView(recyclerView);
}
protected abstract int getRealmModelViewType(VM model);
protected abstract @LayoutRes int getLayout(int viewType);
protected abstract VH onCreateRealmModelViewHolder(int viewType, View itemView);
protected abstract List<VM> mapResultsToViewModel(List<T> results);
@Override public final int getItemViewType(int position) {
return getRealmModelViewType(getItem(position));
}
@Override public VH onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = inflater.inflate(getLayout(viewType), parent, false);
return onCreateRealmModelViewHolder(viewType, itemView);
}
@Override public void onBindViewHolder(VH holder, int position) {
holder.bind(getItem(position));
}
@Override public int getItemCount() {
return adapterData != null ? adapterData.size() : 0;
}
private VM getItem(int position) {
return adapterData.get(position);
}
private void updateData(List<T> newData) {
if (adapterData == null) {
adapterData = mapResultsToViewModel(newData);
notifyDataSetChanged();
} else {
// TODO: use DillUtils!
adapterData = mapResultsToViewModel(newData);
notifyDataSetChanged();
}
}
}
package chat.rocket.android.realm_helper;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import chat.rocket.android.realm_adapter.RealmModelListAdapter;
import chat.rocket.android.realm_adapter.RealmModelViewHolder;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmResults;
public class RealmModelListView extends RecyclerView {
private Realm realm;
public RealmModelListView(Context context) {
super(context);
}
public RealmModelListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RealmModelListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/*package*/ <T extends RealmObject, VH extends RealmModelViewHolder<T>> void setup(
final RealmHelper realmHelper,
final RealmModelListAdapter.Query<T> query,
final RealmModelListAdapter.Constructor<T, VH> constructor) {
addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
@Override public void onViewAttachedToWindow(View view) {
realm = realmHelper.instance();
RealmResults<T> results = query.queryItems(realm);
if (getAdapter() instanceof RealmModelListAdapter) {
((RealmModelListAdapter<T, VH>) getAdapter()).updateData(results);
} else {
RealmModelListAdapter<T, VH> adapter = constructor.getNewInstance(view.getContext());
adapter.updateData(results);
setAdapter(adapter);
}
}
@Override public void onViewDetachedFromWindow(View view) {
if (realm != null && !realm.isClosed()) {
realm.close();
}
}
});
}
// just for preventing from unexpected overriding.
@Override public final void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
}
}
package chat.rocket.android.realm_adapter; package chat.rocket.android.realm_helper;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
import io.realm.RealmObject;
public abstract class RealmModelViewHolder<T extends RealmObject> extends RecyclerView.ViewHolder { public abstract class RealmModelViewHolder<T> extends RecyclerView.ViewHolder {
public RealmModelViewHolder(View itemView) { public RealmModelViewHolder(View itemView) {
super(itemView); super(itemView);
......
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