Commit a64aa9cb authored by Tiago Cunha's avatar Tiago Cunha Committed by GitHub

Merge pull request #262 from RocketChat/feature/edit-message

Edit message support
parents 4e3bdc2d e0b8ffaf
...@@ -12,10 +12,12 @@ import bolts.Task; ...@@ -12,10 +12,12 @@ import bolts.Task;
import chat.rocket.android.helper.CheckSum; import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.service.ConnectivityManager; import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.persistence.realm.models.ddp.RealmPermission;
import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting; import chat.rocket.persistence.realm.models.ddp.RealmPublicSetting;
import chat.rocket.core.SyncState; import chat.rocket.core.SyncState;
import chat.rocket.persistence.realm.models.ddp.RealmMessage; import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import chat.rocket.persistence.realm.models.ddp.RealmRoom; import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import chat.rocket.persistence.realm.models.ddp.RealmRoomRole;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightRoom; import chat.rocket.persistence.realm.models.ddp.RealmSpotlightRoom;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser; import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser;
import chat.rocket.persistence.realm.models.internal.MethodCall; import chat.rocket.persistence.realm.models.internal.MethodCall;
...@@ -362,12 +364,18 @@ public class MethodCallHelper { ...@@ -362,12 +364,18 @@ public class MethodCallHelper {
/** /**
* send message. * send message.
*/ */
public Task<Void> sendMessage(String messageId, String roomId, String msg) { public Task<Void> sendMessage(String messageId, String roomId, String msg, long editedAt) {
try { try {
return sendMessage(new JSONObject() JSONObject messageJson = new JSONObject()
.put("_id", messageId) .put("_id", messageId)
.put("rid", roomId) .put("rid", roomId)
.put("msg", msg)); .put("msg", msg);
if (editedAt == 0) {
return sendMessage(messageJson);
} else {
return updateMessage(messageJson);
}
} catch (JSONException exception) { } catch (JSONException exception) {
return Task.forError(exception); return Task.forError(exception);
} }
...@@ -381,6 +389,11 @@ public class MethodCallHelper { ...@@ -381,6 +389,11 @@ public class MethodCallHelper {
.onSuccessTask(task -> Task.forResult(null)); .onSuccessTask(task -> Task.forResult(null));
} }
private Task<Void> updateMessage(final JSONObject messageJson) {
return call("updateMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
.onSuccessTask(task -> Task.forResult(null));
}
/** /**
* mark all messages are read in the room. * mark all messages are read in the room.
*/ */
...@@ -406,6 +419,40 @@ public class MethodCallHelper { ...@@ -406,6 +419,40 @@ public class MethodCallHelper {
}); });
} }
public Task<Void> getPermissions() {
return call("permissions/get", TIMEOUT_MS)
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray permissions = task.getResult();
for (int i = 0; i < permissions.length(); i++) {
RealmPermission.customizeJson(permissions.getJSONObject(i));
}
return realmHelper.executeTransaction(realm -> {
realm.delete(RealmPermission.class);
realm.createOrUpdateAllFromJson(RealmPermission.class, permissions);
return null;
});
});
}
public Task<Void> getRoomRoles(final String roomId) {
return call("getRoomRoles", TIMEOUT_MS, () -> new JSONArray().put(roomId))
.onSuccessTask(CONVERT_TO_JSON_ARRAY)
.onSuccessTask(task -> {
final JSONArray roomRoles = task.getResult();
for (int i = 0; i < roomRoles.length(); i++) {
RealmRoomRole.customizeJson(roomRoles.getJSONObject(i));
}
return realmHelper.executeTransaction(realm -> {
realm.delete(RealmRoomRole.class);
realm.createOrUpdateAllFromJson(RealmRoomRole.class, roomRoles);
return null;
});
});
}
public Task<Void> searchSpotlightUsers(String term) { public Task<Void> searchSpotlightUsers(String term) {
return searchSpotlight( return searchSpotlight(
RealmSpotlightUser.class, "users", term RealmSpotlightUser.class, "users", term
......
...@@ -42,6 +42,8 @@ public interface RoomContract { ...@@ -42,6 +42,8 @@ public interface RoomContract {
void resendMessage(Message message); void resendMessage(Message message);
void updateMessage(Message message, String content);
void deleteMessage(Message message); void deleteMessage(Message message);
void onUnreadCount(); void onUnreadCount();
......
...@@ -37,6 +37,7 @@ import chat.rocket.android.BackgroundLooper; ...@@ -37,6 +37,7 @@ import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.api.MethodCallHelper; import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.chatroom.dialog.FileUploadProgressDialogFragment; import chat.rocket.android.fragment.chatroom.dialog.FileUploadProgressDialogFragment;
import chat.rocket.android.fragment.chatroom.dialog.MessageOptionsDialogFragment;
import chat.rocket.android.fragment.chatroom.dialog.UsersOfRoomDialogFragment; import chat.rocket.android.fragment.chatroom.dialog.UsersOfRoomDialogFragment;
import chat.rocket.android.helper.AbsoluteUrlHelper; import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.FileUploadHelper; import chat.rocket.android.helper.FileUploadHelper;
...@@ -90,7 +91,8 @@ import permissions.dispatcher.RuntimePermissions; ...@@ -90,7 +91,8 @@ import permissions.dispatcher.RuntimePermissions;
@RuntimePermissions @RuntimePermissions
public class RoomFragment extends AbstractChatRoomFragment public class RoomFragment extends AbstractChatRoomFragment
implements OnBackPressListener, ExtraActionPickerDialogFragment.Callback, implements OnBackPressListener, ExtraActionPickerDialogFragment.Callback,
ModelListAdapter.OnItemClickListener<PairedMessage>, RoomContract.View { ModelListAdapter.OnItemClickListener<PairedMessage>,
ModelListAdapter.OnItemLongClickListener<PairedMessage>, RoomContract.View {
private static final int DIALOG_ID = 1; private static final int DIALOG_ID = 1;
private static final String HOSTNAME = "hostname"; private static final String HOSTNAME = "hostname";
...@@ -118,6 +120,8 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -118,6 +120,8 @@ public class RoomFragment extends AbstractChatRoomFragment
private MethodCallHelper methodCallHelper; private MethodCallHelper methodCallHelper;
private AbsoluteUrlHelper absoluteUrlHelper; private AbsoluteUrlHelper absoluteUrlHelper;
private Message edittingMessage = null;
public RoomFragment() { public RoomFragment() {
} }
...@@ -187,6 +191,7 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -187,6 +191,7 @@ public class RoomFragment extends AbstractChatRoomFragment
adapter = new MessageListAdapter(getContext()); adapter = new MessageListAdapter(getContext());
listView.setAdapter(adapter); listView.setAdapter(adapter);
adapter.setOnItemClickListener(this); adapter.setOnItemClickListener(this);
adapter.setOnItemLongClickListener(this);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(),
LinearLayoutManager.VERTICAL, true); LinearLayoutManager.VERTICAL, true);
...@@ -282,6 +287,20 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -282,6 +287,20 @@ public class RoomFragment extends AbstractChatRoomFragment
presenter.onMessageSelected(pairedMessage.target); presenter.onMessageSelected(pairedMessage.target);
} }
@Override
public boolean onItemLongClick(PairedMessage pairedMessage) {
MessageOptionsDialogFragment messageOptionsDialogFragment = MessageOptionsDialogFragment
.create(pairedMessage.target);
messageOptionsDialogFragment.setOnMessageOptionSelectedListener(message -> {
messageOptionsDialogFragment.dismiss();
onEditMessage(message);
});
messageOptionsDialogFragment.show(getChildFragmentManager(), "MessageOptionsDialogFragment");
return true;
}
private void setupSideMenu() { private void setupSideMenu() {
View sideMenu = rootView.findViewById(R.id.room_side_menu); View sideMenu = rootView.findViewById(R.id.room_side_menu);
sideMenu.findViewById(R.id.btn_users).setOnClickListener(view -> { sideMenu.findViewById(R.id.btn_users).setOnClickListener(view -> {
...@@ -442,6 +461,11 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -442,6 +461,11 @@ public class RoomFragment extends AbstractChatRoomFragment
@Override @Override
public boolean onBackPressed() { public boolean onBackPressed() {
if (edittingMessage != null) {
edittingMessage = null;
messageFormManager.clearComposingText();
return true;
}
return closeSideMenuIfNeeded(); return closeSideMenuIfNeeded();
} }
...@@ -496,7 +520,11 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -496,7 +520,11 @@ public class RoomFragment extends AbstractChatRoomFragment
} }
private void sendMessage(String messageText) { private void sendMessage(String messageText) {
presenter.sendMessage(messageText); if (edittingMessage == null) {
presenter.sendMessage(messageText);
} else {
presenter.updateMessage(edittingMessage, messageText);
}
} }
@Override @Override
...@@ -543,6 +571,7 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -543,6 +571,7 @@ public class RoomFragment extends AbstractChatRoomFragment
public void onMessageSendSuccessfully() { public void onMessageSendSuccessfully() {
scrollToLatestMessage(); scrollToLatestMessage();
messageFormManager.onMessageSend(); messageFormManager.onMessageSend();
edittingMessage = null;
} }
@Override @Override
...@@ -578,4 +607,9 @@ public class RoomFragment extends AbstractChatRoomFragment ...@@ -578,4 +607,9 @@ public class RoomFragment extends AbstractChatRoomFragment
public void manualLoadImages() { public void manualLoadImages() {
adapter.setAutoloadImages(false); adapter.setAutoloadImages(false);
} }
private void onEditMessage(Message message) {
edittingMessage = message;
messageFormManager.setEditMessage(message.getMessage());
}
} }
...@@ -56,6 +56,7 @@ public class RoomPresenter extends BasePresenter<RoomContract.View> ...@@ -56,6 +56,7 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
public void bindView(@NonNull RoomContract.View view) { public void bindView(@NonNull RoomContract.View view) {
super.bindView(view); super.bindView(view);
getRoomRoles();
getRoomInfo(); getRoomInfo();
getRoomHistoryStateInfo(); getRoomHistoryStateInfo();
getMessages(); getMessages();
...@@ -140,6 +141,24 @@ public class RoomPresenter extends BasePresenter<RoomContract.View> ...@@ -140,6 +141,24 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
addSubscription(subscription); addSubscription(subscription);
} }
@Override
public void updateMessage(Message message, String content) {
final Disposable subscription = getCurrentUser()
.flatMap(user -> messageInteractor.update(message, user, content))
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
success -> {
if (success) {
view.onMessageSendSuccessfully();
}
},
Logger::report
);
addSubscription(subscription);
}
@Override @Override
public void deleteMessage(Message message) { public void deleteMessage(Message message) {
final Disposable subscription = messageInteractor.delete(message) final Disposable subscription = messageInteractor.delete(message)
...@@ -183,6 +202,10 @@ public class RoomPresenter extends BasePresenter<RoomContract.View> ...@@ -183,6 +202,10 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
addSubscription(subscription); addSubscription(subscription);
} }
private void getRoomRoles() {
methodCallHelper.getRoomRoles(roomId);
}
private void getRoomInfo() { private void getRoomInfo() {
final Disposable subscription = roomRepository.getById(roomId) final Disposable subscription = roomRepository.getById(roomId)
.distinctUntilChanged() .distinctUntilChanged()
......
package chat.rocket.android.fragment.chatroom.dialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.BottomSheetDialogFragment;
import android.support.v4.util.Pair;
import android.view.View;
import android.widget.TextView;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.Logger;
import chat.rocket.core.interactors.EditMessageInteractor;
import chat.rocket.core.interactors.PermissionInteractor;
import chat.rocket.core.models.Message;
import chat.rocket.core.repositories.MessageRepository;
import chat.rocket.core.repositories.PermissionRepository;
import chat.rocket.core.repositories.PublicSettingRepository;
import chat.rocket.core.repositories.RoomRepository;
import chat.rocket.core.repositories.RoomRoleRepository;
import chat.rocket.core.repositories.UserRepository;
import chat.rocket.persistence.realm.repositories.RealmMessageRepository;
import chat.rocket.persistence.realm.repositories.RealmPermissionRepository;
import chat.rocket.persistence.realm.repositories.RealmPublicSettingRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRepository;
import chat.rocket.persistence.realm.repositories.RealmRoomRoleRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
public class MessageOptionsDialogFragment extends BottomSheetDialogFragment {
public final static String ARG_MESSAGE_ID = "messageId";
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private OnMessageOptionSelectedListener internalListener = new OnMessageOptionSelectedListener() {
@Override
public void onEdit(Message message) {
if (externalListener != null) {
externalListener.onEdit(message);
}
}
};
private OnMessageOptionSelectedListener externalListener = null;
public static MessageOptionsDialogFragment create(@NonNull Message message) {
Bundle bundle = new Bundle();
bundle.putString(ARG_MESSAGE_ID, message.getId());
MessageOptionsDialogFragment messageOptionsDialogFragment = new MessageOptionsDialogFragment();
messageOptionsDialogFragment.setArguments(bundle);
return messageOptionsDialogFragment;
}
public void setOnMessageOptionSelectedListener(
OnMessageOptionSelectedListener onMessageOptionSelectedListener) {
externalListener = onMessageOptionSelectedListener;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(getContext());
bottomSheetDialog.setContentView(R.layout.dialog_message_options);
TextView info = (TextView) bottomSheetDialog.findViewById(R.id.message_options_info);
Bundle args = getArguments();
if (args == null || !args.containsKey(ARG_MESSAGE_ID)) {
info.setText(R.string.message_options_no_message_info);
} else {
setUpDialog(bottomSheetDialog, args.getString(ARG_MESSAGE_ID));
}
return bottomSheetDialog;
}
@Override
public void onDismiss(DialogInterface dialog) {
compositeDisposable.clear();
super.onDismiss(dialog);
}
private void setUpDialog(final BottomSheetDialog bottomSheetDialog, String messageId) {
RocketChatCache cache = new RocketChatCache(bottomSheetDialog.getContext());
String hostname = cache.getSelectedServerHostname();
EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname);
MessageRepository messageRepository = new RealmMessageRepository(hostname);
Disposable disposable = messageRepository.getById(messageId)
.flatMap(it -> {
if (!it.isPresent()) {
return Single.just(Pair.<Message, Boolean>create(null, false));
}
Message message = it.get();
return Single.zip(
Single.just(message),
editMessageInteractor.isAllowed(message),
Pair::create
);
})
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
pair -> {
if (pair.second) {
bottomSheetDialog.findViewById(R.id.message_options_info)
.setVisibility(View.GONE);
View editView = bottomSheetDialog.findViewById(R.id.message_options_edit_action);
editView.setVisibility(View.VISIBLE);
editView.setOnClickListener(view -> internalListener.onEdit(pair.first));
} else {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_permissions_info);
}
},
throwable -> {
((TextView) bottomSheetDialog.findViewById(R.id.message_options_info))
.setText(R.string.message_options_no_message_info);
Logger.report(throwable);
}
);
compositeDisposable.add(disposable);
}
private EditMessageInteractor getEditMessageInteractor(String hostname) {
UserRepository userRepository = new RealmUserRepository(hostname);
RoomRoleRepository roomRoleRepository = new RealmRoomRoleRepository(hostname);
PermissionRepository permissionRepository = new RealmPermissionRepository(hostname);
PermissionInteractor permissionInteractor = new PermissionInteractor(
userRepository,
roomRoleRepository,
permissionRepository
);
MessageRepository messageRepository = new RealmMessageRepository(hostname);
RoomRepository roomRepository = new RealmRoomRepository(hostname);
PublicSettingRepository publicSettingRepository = new RealmPublicSettingRepository(hostname);
return new EditMessageInteractor(
permissionInteractor,
userRepository,
messageRepository,
roomRepository,
publicSettingRepository
);
}
public interface OnMessageOptionSelectedListener {
void onEdit(Message message);
}
}
...@@ -33,6 +33,11 @@ public class MessageFormManager { ...@@ -33,6 +33,11 @@ public class MessageFormManager {
messageFormLayout.setEnabled(true); messageFormLayout.setEnabled(true);
} }
public void setEditMessage(String message) {
clearComposingText();
messageFormLayout.setText(message);
}
private void sendMessage(String message) { private void sendMessage(String message) {
if (sendMessageCallback == null) { if (sendMessageCallback == null) {
return; return;
......
...@@ -18,6 +18,7 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>> ...@@ -18,6 +18,7 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>>
protected final LayoutInflater inflater; protected final LayoutInflater inflater;
private List<VM> adapterData; private List<VM> adapterData;
private OnItemClickListener<VM> onItemClickListener; private OnItemClickListener<VM> onItemClickListener;
private OnItemLongClickListener<VM> onItemLongClickListener;
protected ModelListAdapter(Context context) { protected ModelListAdapter(Context context) {
this.inflater = LayoutInflater.from(context); this.inflater = LayoutInflater.from(context);
...@@ -42,6 +43,20 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>> ...@@ -42,6 +43,20 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>>
@Override @Override
public final VH onCreateViewHolder(ViewGroup parent, int viewType) { public final VH onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = inflater.inflate(getLayout(viewType), parent, false); View itemView = inflater.inflate(getLayout(viewType), parent, false);
itemView.setOnClickListener(view -> {
VM model = (VM) (view.getTag());
if (model != null && onItemClickListener != null) {
onItemClickListener.onItemClick(model);
}
});
itemView.setOnLongClickListener(view -> {
VM model = (VM) (view.getTag());
return model != null && onItemLongClickListener != null
&& onItemLongClickListener.onItemLongClick(model);
});
return onCreateRealmModelViewHolder(viewType, itemView); return onCreateRealmModelViewHolder(viewType, itemView);
} }
...@@ -49,12 +64,6 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>> ...@@ -49,12 +64,6 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>>
public void onBindViewHolder(VH holder, int position) { public void onBindViewHolder(VH holder, int position) {
VM model = getItem(position); VM model = getItem(position);
holder.itemView.setTag(model); holder.itemView.setTag(model);
holder.itemView.setOnClickListener(view -> {
VM model2 = (VM) (view.getTag());
if (model2 != null && onItemClickListener != null) {
onItemClickListener.onItemClick(model2);
}
});
holder.bind(model, shouldAutoloadImages()); holder.bind(model, shouldAutoloadImages());
} }
...@@ -114,6 +123,10 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>> ...@@ -114,6 +123,10 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>>
this.onItemClickListener = onItemClickListener; this.onItemClickListener = onItemClickListener;
} }
public void setOnItemLongClickListener(OnItemLongClickListener<VM> onItemLongClickListener) {
this.onItemLongClickListener = onItemLongClickListener;
}
public interface Constructor<T, VM, VH extends ModelViewHolder<VM>> { public interface Constructor<T, VM, VH extends ModelViewHolder<VM>> {
ModelListAdapter<T, VM, VH> getNewInstance(Context context); ModelListAdapter<T, VM, VH> getNewInstance(Context context);
} }
...@@ -121,4 +134,8 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>> ...@@ -121,4 +134,8 @@ public abstract class ModelListAdapter<T, VM, VH extends ModelViewHolder<VM>>
public interface OnItemClickListener<VM> { public interface OnItemClickListener<VM> {
void onItemClick(VM model); void onItemClick(VM model);
} }
public interface OnItemLongClickListener<VM> {
boolean onItemLongClick(VM model);
}
} }
...@@ -270,6 +270,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -270,6 +270,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
return connectDDPClient() return connectDDPClient()
.flatMap(_val -> Single.fromEmitter(emitter -> { .flatMap(_val -> Single.fromEmitter(emitter -> {
fetchPublicSettings(); fetchPublicSettings();
fetchPermissions();
registerListeners(); registerListeners();
emitter.onSuccess(true); emitter.onSuccess(true);
})); }));
...@@ -279,6 +280,10 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -279,6 +280,10 @@ public class RocketChatWebSocketThread extends HandlerThread {
return new MethodCallHelper(realmHelper, ddpClientRef).getPublicSettings(); return new MethodCallHelper(realmHelper, ddpClientRef).getPublicSettings();
} }
private Task<Void> fetchPermissions() {
return new MethodCallHelper(realmHelper, ddpClientRef).getPermissions();
}
//@DebugLog //@DebugLog
private void registerListeners() { private void registerListeners() {
if (!Thread.currentThread().getName().equals("RC_thread_" + hostname)) { if (!Thread.currentThread().getName().equals("RC_thread_" + hostname)) {
......
...@@ -57,13 +57,14 @@ public class NewMessageObserver extends AbstractModelObserver<RealmMessage> { ...@@ -57,13 +57,14 @@ public class NewMessageObserver extends AbstractModelObserver<RealmMessage> {
final String messageId = message.getId(); final String messageId = message.getId();
final String roomId = message.getRoomId(); final String roomId = message.getRoomId();
final String msg = message.getMessage(); final String msg = message.getMessage();
final long editedAt = message.getEditedAt();
realmHelper.executeTransaction(realm -> realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(RealmMessage.class, new JSONObject() realm.createOrUpdateObjectFromJson(RealmMessage.class, new JSONObject()
.put(RealmMessage.ID, messageId) .put(RealmMessage.ID, messageId)
.put(RealmMessage.SYNC_STATE, SyncState.SYNCING) .put(RealmMessage.SYNC_STATE, SyncState.SYNCING)
) )
).onSuccessTask(task -> methodCall.sendMessage(messageId, roomId, msg) ).onSuccessTask(task -> methodCall.sendMessage(messageId, roomId, msg, editedAt)
).continueWith(task -> { ).continueWith(task -> {
if (task.isFaulted()) { if (task.isFaulted()) {
RCLog.w(task.getError()); RCLog.w(task.getError());
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/message_options_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_8"
android:gravity="center"
tools:text="Edit message" />
<Button
android:id="@+id/message_options_edit_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:text="@string/edit_message"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
\ No newline at end of file
...@@ -51,4 +51,7 @@ ...@@ -51,4 +51,7 @@
<string name="open_your_authentication_app_and_enter_the_code">Open your authentication app and enter the code</string> <string name="open_your_authentication_app_and_enter_the_code">Open your authentication app and enter the code</string>
<string name="two_factor_code">Two-factor code</string> <string name="two_factor_code">Two-factor code</string>
<string name="navigation_search_rooms">Search Rooms</string> <string name="navigation_search_rooms">Search Rooms</string>
<string name="edit_message">Edit message</string>
<string name="message_options_no_message_info">Ooops. Something\'s up!</string>
<string name="message_options_no_permissions_info">You have no permissions</string>
</resources> </resources>
...@@ -6,6 +6,10 @@ import io.realm.RealmMigration; ...@@ -6,6 +6,10 @@ import io.realm.RealmMigration;
import io.realm.RealmObjectSchema; import io.realm.RealmObjectSchema;
import io.realm.RealmSchema; import io.realm.RealmSchema;
import chat.rocket.persistence.realm.models.ddp.RealmMessage;
import chat.rocket.persistence.realm.models.ddp.RealmPermission;
import chat.rocket.persistence.realm.models.ddp.RealmRole;
import chat.rocket.persistence.realm.models.ddp.RealmRoomRole;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightRoom; import chat.rocket.persistence.realm.models.ddp.RealmSpotlightRoom;
import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser; import chat.rocket.persistence.realm.models.ddp.RealmSpotlightUser;
...@@ -38,6 +42,32 @@ public class Migration implements RealmMigration { ...@@ -38,6 +42,32 @@ public class Migration implements RealmMigration {
RealmObjectSchema roomSchema = schema.get("RealmSpotlightUser"); RealmObjectSchema roomSchema = schema.get("RealmSpotlightUser");
roomSchema.addField(RealmSpotlightUser.Columns.NAME, String.class); roomSchema.addField(RealmSpotlightUser.Columns.NAME, String.class);
oldVersion++;
}
if (oldVersion == 3) {
schema.create("RealmRole")
.addField(RealmRole.Columns.ID, String.class, FieldAttribute.PRIMARY_KEY)
.addField(RealmRole.Columns.NAME, String.class);
schema.create("RealmPermission")
.addField(RealmPermission.Columns.ID, String.class, FieldAttribute.PRIMARY_KEY)
.addField(RealmPermission.Columns.NAME, String.class)
.addRealmListField(RealmPermission.Columns.ROLES, schema.get("RealmRole"));
schema.create("RealmRoomRole")
.addField(RealmRoomRole.Columns.ID, String.class, FieldAttribute.PRIMARY_KEY)
.addField(RealmRoomRole.Columns.ROOM_ID, String.class)
.addRealmObjectField(RealmRoomRole.Columns.USER, schema.get("RealmUser"))
.addRealmListField(RealmRoomRole.Columns.ROLES, schema.get("RealmRole"));
oldVersion++;
}
if (oldVersion == 4) {
RealmObjectSchema messageSchema = schema.get("RealmMessage");
messageSchema.addField(RealmMessage.EDITED_AT, long.class);
} }
} }
} }
...@@ -15,7 +15,7 @@ public class RealmStore { ...@@ -15,7 +15,7 @@ public class RealmStore {
.name(name + ".realm") .name(name + ".realm")
.modules(new RocketChatLibraryModule()) .modules(new RocketChatLibraryModule())
.migration(new Migration()) .migration(new Migration())
.schemaVersion(3) .schemaVersion(5)
.build(); .build();
} }
......
...@@ -43,6 +43,7 @@ public class RealmMessage extends RealmObject { ...@@ -43,6 +43,7 @@ public class RealmMessage extends RealmObject {
public static final String GROUPABLE = "groupable"; public static final String GROUPABLE = "groupable";
public static final String ATTACHMENTS = "attachments"; public static final String ATTACHMENTS = "attachments";
public static final String URLS = "urls"; public static final String URLS = "urls";
public static final String EDITED_AT = "editedAt";
@PrimaryKey private String _id; @PrimaryKey private String _id;
private String t; //type: private String t; //type:
...@@ -56,6 +57,7 @@ public class RealmMessage extends RealmObject { ...@@ -56,6 +57,7 @@ public class RealmMessage extends RealmObject {
private String avatar; private String avatar;
private String attachments; //JSONArray. private String attachments; //JSONArray.
private String urls; //JSONArray. private String urls; //JSONArray.
private long editedAt;
public static JSONObject customizeJson(JSONObject messageJson) throws JSONException { public static JSONObject customizeJson(JSONObject messageJson) throws JSONException {
long ts = messageJson.getJSONObject(TIMESTAMP).getLong(JsonConstants.DATE); long ts = messageJson.getJSONObject(TIMESTAMP).getLong(JsonConstants.DATE);
...@@ -66,6 +68,15 @@ public class RealmMessage extends RealmObject { ...@@ -66,6 +68,15 @@ public class RealmMessage extends RealmObject {
messageJson.put(GROUPABLE, true); messageJson.put(GROUPABLE, true);
} }
long editedAt = 0L;
JSONObject editedAtObj = messageJson.optJSONObject(EDITED_AT);
if (editedAtObj != null) {
editedAt = editedAtObj.optLong(JsonConstants.DATE);
}
messageJson.remove(EDITED_AT);
messageJson.put(EDITED_AT, editedAt);
return messageJson; return messageJson;
} }
...@@ -165,6 +176,14 @@ public class RealmMessage extends RealmObject { ...@@ -165,6 +176,14 @@ public class RealmMessage extends RealmObject {
this.avatar = avatar; this.avatar = avatar;
} }
public long getEditedAt() {
return editedAt;
}
public void setEditedAt(long editedAt) {
this.editedAt = editedAt;
}
public Message asMessage() { public Message asMessage() {
return Message.builder() return Message.builder()
.setId(_id) .setId(_id)
...@@ -177,6 +196,7 @@ public class RealmMessage extends RealmObject { ...@@ -177,6 +196,7 @@ public class RealmMessage extends RealmObject {
.setGroupable(groupable) .setGroupable(groupable)
.setAlias(alias) .setAlias(alias)
.setAvatar(avatar) .setAvatar(avatar)
.setEditedAt(editedAt)
.setAttachments(getCoreAttachments()) .setAttachments(getCoreAttachments())
.setWebContents(getWebContents()) .setWebContents(getWebContents())
.build(); .build();
......
package chat.rocket.persistence.realm.models.ddp;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.models.Permission;
import chat.rocket.core.models.Role;
public class RealmPermission extends RealmObject {
public interface Columns {
String ID = "_id";
String NAME = "name";
String ROLES = "roles";
}
@PrimaryKey private String _id;
private String name;
private RealmList<RealmRole> roles;
public static JSONObject customizeJson(JSONObject permissionsJson) throws JSONException {
permissionsJson.put(Columns.NAME, permissionsJson.getString(Columns.ID));
JSONArray roleStrings = permissionsJson.getJSONArray(Columns.ROLES);
JSONArray roles = new JSONArray();
for (int i = 0, size = roleStrings.length(); i < size; i++) {
roles.put(RealmRole.customizeJson(roleStrings.getString(i)));
}
permissionsJson.remove(Columns.ROLES);
permissionsJson.put(Columns.ROLES, roles);
return permissionsJson;
}
public String getId() {
return _id;
}
public void setId(String id) {
this._id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public RealmList<RealmRole> getRoles() {
return roles;
}
public void setRoles(RealmList<RealmRole> roles) {
this.roles = roles;
}
public Permission asPermission() {
int size = this.roles.size();
List<Role> roles = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
roles.add(this.roles.get(i).asRole());
}
return Permission.builder()
.setId(_id)
.setName(name)
.setRoles(roles)
.build();
}
}
package chat.rocket.persistence.realm.models.ddp;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONException;
import org.json.JSONObject;
import chat.rocket.core.models.Role;
public class RealmRole extends RealmObject {
public interface Columns {
String ID = "id";
String NAME = "name";
}
@PrimaryKey private String id;
private String name;
public static JSONObject customizeJson(String roleString) throws JSONException {
JSONObject roleObject = new JSONObject();
roleObject.put(Columns.ID, roleString);
roleObject.put(Columns.NAME, roleString);
return roleObject;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role asRole() {
return Role.builder()
.setId(id)
.setName(name)
.build();
}
}
package chat.rocket.persistence.realm.models.ddp;
import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.core.models.Role;
import chat.rocket.core.models.RoomRole;
public class RealmRoomRole extends RealmObject {
public interface Columns {
String ID = "_id";
String ROOM_ID = "rid";
String USER = "u";
String ROLES = "roles";
}
@PrimaryKey private String _id;
private String rid;
private RealmUser u;
private RealmList<RealmRole> roles;
public static JSONObject customizeJson(JSONObject roomRoles) throws JSONException {
JSONArray roleStrings = roomRoles.getJSONArray(Columns.ROLES);
JSONArray roles = new JSONArray();
for (int i = 0, size = roleStrings.length(); i < size; i++) {
roles.put(RealmRole.customizeJson(roleStrings.getString(i)));
}
roomRoles.remove(Columns.ROLES);
roomRoles.put(Columns.ROLES, roles);
return roomRoles;
}
public String getId() {
return _id;
}
public void setId(String id) {
this._id = id;
}
public String getRoomId() {
return rid;
}
public void setRoomId(String roomId) {
this.rid = roomId;
}
public RealmUser getUser() {
return u;
}
public void setUser(RealmUser user) {
this.u = user;
}
public RealmList<RealmRole> getRoles() {
return roles;
}
public void setRoles(RealmList<RealmRole> roles) {
this.roles = roles;
}
public RoomRole asRoomRole() {
int size = this.roles.size();
List<Role> roles = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
roles.add(this.roles.get(i).asRole());
}
return RoomRole.builder()
.setId(_id)
.setRoomId(rid)
.setUser(u.asUser())
.setRoles(roles)
.build();
}
}
...@@ -71,6 +71,7 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe ...@@ -71,6 +71,7 @@ public class RealmMessageRepository extends RealmRepository implements MessageRe
realmMessage.setTimestamp(message.getTimestamp()); realmMessage.setTimestamp(message.getTimestamp());
realmMessage.setRoomId(message.getRoomId()); realmMessage.setRoomId(message.getRoomId());
realmMessage.setMessage(message.getMessage()); realmMessage.setMessage(message.getMessage());
realmMessage.setEditedAt(message.getEditedAt());
RealmUser realmUser = realmMessage.getUser(); RealmUser realmUser = realmMessage.getUser();
if (realmUser == null) { if (realmUser == null) {
......
package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.fernandocejas.arrow.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
import chat.rocket.core.models.Permission;
import chat.rocket.core.repositories.PermissionRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmPermission;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
public class RealmPermissionRepository extends RealmRepository implements PermissionRepository {
private final String hostname;
public RealmPermissionRepository(String hostname) {
this.hostname = hostname;
}
@Override
public Single<Optional<Permission>> getById(String id) {
return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmPermission.class)
.equalTo(RealmPermission.Columns.ID, id)
.findAll()
.<RealmResults<RealmPermission>>asObservable()),
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
.filter(it -> it.isLoaded() && it.isValid())
.map(it -> {
if (it.size() == 0) {
return Optional.<Permission>absent();
}
return Optional.of(it.get(0).asPermission());
})
.first(Optional.absent()));
}
}
...@@ -17,9 +17,9 @@ public class RealmRepository { ...@@ -17,9 +17,9 @@ public class RealmRepository {
new Handler(looper).post(realm::close); new Handler(looper).post(realm::close);
} }
protected <T extends RealmObject> List<T> safeSubList(RealmResults<T> realmObjects, <T extends RealmObject> List<T> safeSubList(RealmResults<T> realmObjects,
int fromIndex, int fromIndex,
int toIndex) { int toIndex) {
return realmObjects.subList(Math.max(0, fromIndex), Math.min(realmObjects.size(), toIndex)); return realmObjects.subList(Math.max(0, fromIndex), Math.min(realmObjects.size(), toIndex));
} }
} }
package chat.rocket.persistence.realm.repositories;
import android.os.Looper;
import android.support.v4.util.Pair;
import com.fernandocejas.arrow.optional.Optional;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.realm.RealmResults;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomRole;
import chat.rocket.core.models.User;
import chat.rocket.core.repositories.RoomRoleRepository;
import chat.rocket.persistence.realm.RealmStore;
import chat.rocket.persistence.realm.models.ddp.RealmRoomRole;
import chat.rocket.persistence.realm.models.ddp.RealmUser;
import hu.akarnokd.rxjava.interop.RxJavaInterop;
public class RealmRoomRoleRepository extends RealmRepository implements RoomRoleRepository {
private final String hostname;
public RealmRoomRoleRepository(String hostname) {
this.hostname = hostname;
}
@Override
public Single<Optional<RoomRole>> getFor(Room room, User user) {
return Single.defer(() -> Flowable.using(
() -> new Pair<>(RealmStore.getRealm(hostname), Looper.myLooper()),
pair -> RxJavaInterop.toV2Flowable(
pair.first.where(RealmRoomRole.class)
.equalTo(RealmRoomRole.Columns.ROOM_ID, room.getId())
.equalTo(RealmRoomRole.Columns.USER + "." + RealmUser.ID, user.getId())
.findAll()
.<RealmResults<RealmRoomRole>>asObservable()),
pair -> close(pair.first, pair.second)
)
.unsubscribeOn(AndroidSchedulers.from(Looper.myLooper()))
.filter(it -> it.isLoaded() && it.isValid())
.map(it -> {
if (it.size() == 0) {
return Optional.<RoomRole>absent();
}
return Optional.of(it.get(0).asRoomRole());
})
.first(Optional.absent()));
}
}
...@@ -57,6 +57,6 @@ public class LinkMovementMethodCompat extends LinkMovementMethod { ...@@ -57,6 +57,6 @@ public class LinkMovementMethodCompat extends LinkMovementMethod {
} }
} }
return Touch.onTouchEvent(widget, buffer, event); return false;
} }
} }
...@@ -20,6 +20,8 @@ public class Linkify { ...@@ -20,6 +20,8 @@ public class Linkify {
public static void markup(TextView textview) { public static void markup(TextView textview) {
textview.setMovementMethod(LinkMovementMethodCompat.getInstance()); textview.setMovementMethod(LinkMovementMethodCompat.getInstance());
textview.setClickable(false);
textview.setLongClickable(false);
final CharSequence text = textview.getText(); final CharSequence text = textview.getText();
textview.setText(markupInner(text)); textview.setText(markupInner(text));
} }
......
...@@ -12,6 +12,7 @@ import android.util.AttributeSet; ...@@ -12,6 +12,7 @@ import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
...@@ -135,8 +136,8 @@ public class MessageFormLayout extends LinearLayout { ...@@ -135,8 +136,8 @@ public class MessageFormLayout extends LinearLayout {
} }
} }
private TextView getEditor() { private EditText getEditor() {
return (TextView) composer.findViewById(R.id.editor); return (EditText) composer.findViewById(R.id.editor);
} }
public final String getText() { public final String getText() {
...@@ -144,11 +145,19 @@ public class MessageFormLayout extends LinearLayout { ...@@ -144,11 +145,19 @@ public class MessageFormLayout extends LinearLayout {
} }
public final void setText(final CharSequence text) { public final void setText(final CharSequence text) {
final TextView editor = getEditor(); final EditText editor = getEditor();
editor.post(new Runnable() { editor.post(new Runnable() {
@Override @Override
public void run() { public void run() {
editor.setText(text); editor.setText(text);
if (text.length() > 0) {
editor.setSelection(text.length());
InputMethodManager inputMethodManager = (InputMethodManager) editor.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
editor.requestFocus();
inputMethodManager.showSoftInput(editor, 0);
}
} }
}); });
} }
......
plugins { plugins {
id "org.jetbrains.kotlin.jvm" version "1.1.1" id "org.jetbrains.kotlin.jvm" version "1.1.2-2"
} }
apply plugin: 'idea' apply plugin: 'idea'
...@@ -8,7 +8,7 @@ apply plugin: 'java' ...@@ -8,7 +8,7 @@ apply plugin: 'java'
dependencies { dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.1' compile 'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.2-3'
compile 'com.google.code.findbugs:jsr305:3.0.1' compile 'com.google.code.findbugs:jsr305:3.0.1'
...@@ -21,7 +21,7 @@ dependencies { ...@@ -21,7 +21,7 @@ dependencies {
kapt 'com.gabrielittner.auto.value:auto-value-with:1.0.0' kapt 'com.gabrielittner.auto.value:auto-value-with:1.0.0'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:2.7.19" testCompile "org.mockito:mockito-inline:2.8.9"
} }
sourceCompatibility = "1.7" sourceCompatibility = "1.7"
......
package chat.rocket.core;
public interface PermissionsConstants {
String ACCESS_MAILER = "access-mailer";
String ACCESS_PERMISSIONS = "access-permissions";
String ACCESS_ROCKET_MAILER = "access-rocket-mailer";
String ADD_OATH_SERVICE = "add-oath-service";
String ADD_OAUTH_SERVICE = "add-oauth-service";
String ADD_USER = "add-user";
String ADD_USER_TO_ANY_C_ROOM = "add-user-to-any-c-room";
String ADD_USER_TO_ANY_P_ROOM = "add-user-to-any-p-room";
String ADD_USER_TO_JOINED_ROOM = "add-user-to-joined-room";
String ARCHIVE_ROOM = "archive-room";
String ASSIGN_ADMIN_ROLE = "assign-admin-role";
String AUTO_TRANSLATE = "auto-translate";
String BAN_USER = "ban-user";
String BULK_CREATE_C = "bulk-create-c";
String BULK_REGISTER_USER = "bulk-register-user";
String CLEAN_CHANNEL_HISTORY = "clean-channel-history";
String CLOSE_LIVECHAT_ROOM = "close-livechat-room";
String CLOSE_OTHERS_LIVECHAT_ROOM = "close-others-livechat-room";
String CREATE_C = "create-c";
String CREATE_D = "create-d";
String CREATE_P = "create-p";
String CREATE_USER = "create-user";
String DELETE_C = "delete-c";
String DELETE_D = "delete-d";
String DELETE_MESSAGE = "delete-message";
String DELETE_P = "delete-p";
String DELETE_USER = "delete-user";
String EDIT_LIVECHAT_SETTINGS = "edit-livechat-settings";
String EDIT_MESSAGE = "edit-message";
String EDIT_OTHER_USER_ACTIVE_STATUS = "edit-other-user-active-status";
String EDIT_OTHER_USER_INFO = "edit-other-user-info";
String EDIT_OTHER_USER_PASSWORD = "edit-other-user-password";
String EDIT_PRIVILEGED_SETTING = "edit-privileged-setting";
String EDIT_ROOM = "edit-room";
String JOIN_WITHOUT_JOIN_CODE = "join-without-join-code";
String MAIL_MESSAGES = "mail-messages";
String MANAGE_ASSETS = "manage-assets";
String MANAGE_EMOJI = "manage-emoji";
String MANAGE_INTEGRATIONS = "manage-integrations";
String MANAGE_OAUTH_APPS = "manage-oauth-apps";
String MANAGE_OWN_INTEGRATIONS = "manage-own-integrations";
String MANAGE_SOUNDS = "manage-sounds";
String MENTION_ALL = "mention-all";
String MUTE_USER = "mute-user";
String PIN_MESSAGE = "pin-message";
String POST_READONLY = "post-readonly";
String PREVIEW_C_ROOM = "preview-c-room";
String RECEIVE_LIVECHAT = "receive-livechat";
String REMOVE_USER = "remove-user";
String RUN_IMPORT = "run-import";
String RUN_MIGRATION = "run-migration";
String SAVE_OTHERS_LIVECHAT_ROOM_INFO = "save-others-livechat-room-info";
String SET_MODERATOR = "set-moderator";
String SET_OWNER = "set-owner";
String SET_REACT_WHEN_READONLY = "set-react-when-readonly";
String SET_READONLY = "set-readonly";
String SNIPPET_MESSAGE = "snippet-message";
String UNARCHIVE_ROOM = "unarchive-room";
String VIEW_C_ROOM = "view-c-room";
String VIEW_D_ROOM = "view-d-room";
String VIEW_FULL_OTHER_USER_INFO = "view-full-other-user-info";
String VIEW_HISTORY = "view-history";
String VIEW_JOIN_CODE = "view-join-code";
String VIEW_JOINED_ROOM = "view-joined-room";
String VIEW_L_ROOM = "view-l-room";
String VIEW_LIVECHAT_MANAGER = "view-livechat-manager";
String VIEW_LIVECHAT_ROOMS = "view-livechat-rooms";
String VIEW_LOGS = "view-logs";
String VIEW_OTHER_USER_CHANNELS = "view-other-user-channels";
String VIEW_P_ROOM = "view-p-room";
String VIEW_PRIVILEGED_SETTING = "view-privileged-setting";
String VIEW_ROOM_ADMINISTRATION = "view-room-administration";
String VIEW_STATISTICS = "view-statistics";
String VIEW_USER_ADMINISTRATION = "view-user-administration";
}
package chat.rocket.core.interactors
import chat.rocket.core.PermissionsConstants
import chat.rocket.core.PublicSettingsConstants
import chat.rocket.core.models.*
import chat.rocket.core.repositories.*
import chat.rocket.core.utils.Pair
import com.fernandocejas.arrow.optional.Optional
import io.reactivex.Single
import io.reactivex.functions.Function4
class EditMessageInteractor(private val permissionInteractor: PermissionInteractor,
private val userRepository: UserRepository,
private val messageRepository: MessageRepository,
private val roomRepository: RoomRepository,
private val publicSettingRepository: PublicSettingRepository) {
fun isAllowed(message: Message): Single<Boolean> {
return Single.zip<Optional<User>, Optional<Room>, Optional<PublicSetting>, Optional<PublicSetting>, Pair<Optional<Room>, Boolean>>(
userRepository.current.first(Optional.absent()),
roomRepository.getById(message.roomId).first(Optional.absent()),
publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING),
publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT),
Function4 { user, room, allowEdit, editTimeout ->
val editAllowed = allowEdit.isPresent && allowEdit.get().valueAsBoolean
val editTimeLimitInMinutes = editTimeout.longValue()
val editAllowedInTime = if (editTimeLimitInMinutes > 0) {
message.timestamp.millisToMinutes() < editTimeLimitInMinutes
} else {
true
}
val editOwn = user.isPresent && user.get().id == message.user?.id
Pair.create(room, editAllowed && editAllowedInTime && editOwn)
}
)
.flatMap { (room, editAllowed) ->
if (!room.isPresent) {
return@flatMap Single.just(false)
}
permissionInteractor.isAllowed(PermissionsConstants.EDIT_MESSAGE, room.get())
.map { it || editAllowed }
}
}
}
fun Optional<PublicSetting>.longValue(defaultValue: Long = 0) = if (this.isPresent) {
this.get().valueAsLong
} else {
defaultValue
}
fun Long.millisToMinutes() = this / 60_000
\ No newline at end of file
...@@ -12,7 +12,8 @@ import chat.rocket.core.models.User ...@@ -12,7 +12,8 @@ import chat.rocket.core.models.User
import chat.rocket.core.repositories.MessageRepository import chat.rocket.core.repositories.MessageRepository
import chat.rocket.core.repositories.RoomRepository import chat.rocket.core.repositories.RoomRepository
class MessageInteractor(private val messageRepository: MessageRepository, private val roomRepository: RoomRepository) { class MessageInteractor(private val messageRepository: MessageRepository,
private val roomRepository: RoomRepository) {
fun loadMessages(room: Room): Single<Boolean> { fun loadMessages(room: Room): Single<Boolean> {
val roomHistoryState = RoomHistoryState.builder() val roomHistoryState = RoomHistoryState.builder()
...@@ -55,6 +56,7 @@ class MessageInteractor(private val messageRepository: MessageRepository, privat ...@@ -55,6 +56,7 @@ class MessageInteractor(private val messageRepository: MessageRepository, privat
.setMessage(messageText) .setMessage(messageText)
.setGroupable(false) .setGroupable(false)
.setUser(sender) .setUser(sender)
.setEditedAt(0)
.build() .build()
return messageRepository.save(message) return messageRepository.save(message)
...@@ -65,6 +67,14 @@ class MessageInteractor(private val messageRepository: MessageRepository, privat ...@@ -65,6 +67,14 @@ class MessageInteractor(private val messageRepository: MessageRepository, privat
message.withSyncState(SyncState.NOT_SYNCED).withUser(sender)) message.withSyncState(SyncState.NOT_SYNCED).withUser(sender))
} }
fun update(message: Message, sender: User, content: String): Single<Boolean> {
return messageRepository.save(
message.withSyncState(SyncState.NOT_SYNCED)
.withUser(sender)
.withMessage(content)
.withEditedAt(message.editedAt + 1))
}
fun delete(message: Message): Single<Boolean> { fun delete(message: Message): Single<Boolean> {
return messageRepository.delete(message) return messageRepository.delete(message)
} }
......
package chat.rocket.core.interactors
import chat.rocket.core.models.Permission
import chat.rocket.core.models.Room
import chat.rocket.core.models.RoomRole
import chat.rocket.core.repositories.*
import chat.rocket.core.utils.Pair
import com.fernandocejas.arrow.optional.Optional
import io.reactivex.Single
import io.reactivex.functions.BiFunction
class PermissionInteractor(private val userRepository: UserRepository,
private val roomRoleRepository: RoomRoleRepository,
private val permissionRepository: PermissionRepository) {
fun isAllowed(permissionId: String, room: Room): Single<Boolean> {
return userRepository.current
.first(Optional.absent())
.flatMap {
if (!it.isPresent) {
return@flatMap Single.just(false)
}
Single.zip<Optional<RoomRole>, Optional<Permission>, Pair<Optional<RoomRole>, Optional<Permission>>>(
roomRoleRepository.getFor(room, it.get()),
permissionRepository.getById(permissionId),
BiFunction { a, b -> Pair.create(a, b) }
)
.flatMap innerFlatMap@ {
if (!it.first.isPresent || !it.second.isPresent) {
return@innerFlatMap Single.just(false)
}
val commonRoles = it.first.get().roles.intersect(
it.second.get().roles
)
Single.just(commonRoles.isNotEmpty())
}
}
}
}
...@@ -38,10 +38,16 @@ public abstract class Message { ...@@ -38,10 +38,16 @@ public abstract class Message {
@Nullable @Nullable
public abstract String getAvatar(); public abstract String getAvatar();
public abstract long getEditedAt();
public abstract Message withSyncState(int syncState); public abstract Message withSyncState(int syncState);
public abstract Message withUser(User user); public abstract Message withUser(User user);
public abstract Message withMessage(String message);
public abstract Message withEditedAt(long editedAt);
public static Builder builder() { public static Builder builder() {
return new AutoValue_Message.Builder(); return new AutoValue_Message.Builder();
} }
...@@ -59,7 +65,7 @@ public abstract class Message { ...@@ -59,7 +65,7 @@ public abstract class Message {
public abstract Builder setTimestamp(long timestamp); public abstract Builder setTimestamp(long timestamp);
public abstract Builder setMessage(String msg); public abstract Builder setMessage(String message);
public abstract Builder setUser(User user); public abstract Builder setUser(User user);
...@@ -73,6 +79,8 @@ public abstract class Message { ...@@ -73,6 +79,8 @@ public abstract class Message {
public abstract Builder setAvatar(String avatar); public abstract Builder setAvatar(String avatar);
public abstract Builder setEditedAt(long editedAt);
public abstract Message build(); public abstract Message build();
} }
} }
package chat.rocket.core.models;
import com.google.auto.value.AutoValue;
import java.util.List;
@AutoValue
public abstract class Permission {
public abstract String getId();
public abstract String getName();
public abstract List<Role> getRoles();
public static Builder builder() {
return new AutoValue_Permission.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setId(String id);
public abstract Builder setName(String name);
public abstract Builder setRoles(List<Role> roles);
public abstract Permission build();
}
}
...@@ -23,6 +23,10 @@ public abstract class PublicSetting { ...@@ -23,6 +23,10 @@ public abstract class PublicSetting {
return Boolean.parseBoolean(getValue()); return Boolean.parseBoolean(getValue());
} }
public long getValueAsLong() {
return Long.parseLong(getValue());
}
public static Builder builder() { public static Builder builder() {
return new AutoValue_PublicSetting.Builder(); return new AutoValue_PublicSetting.Builder();
} }
......
package chat.rocket.core.models;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class Role {
public abstract String getId();
public abstract String getName();
public static Builder builder() {
return new AutoValue_Role.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setId(String id);
public abstract Builder setName(String name);
public abstract Role build();
}
}
package chat.rocket.core.models;
import com.google.auto.value.AutoValue;
import java.util.List;
@AutoValue
public abstract class RoomRole {
public abstract String getId();
public abstract String getRoomId();
public abstract User getUser();
public abstract List<Role> getRoles();
public static Builder builder() {
return new AutoValue_RoomRole.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setId(String id);
public abstract Builder setRoomId(String roomId);
public abstract Builder setUser(User user);
public abstract Builder setRoles(List<Role> roles);
public abstract RoomRole build();
}
}
package chat.rocket.core.repositories;
import com.fernandocejas.arrow.optional.Optional;
import io.reactivex.Single;
import chat.rocket.core.models.Permission;
public interface PermissionRepository {
Single<Optional<Permission>> getById(String id);
}
package chat.rocket.core.repositories;
import com.fernandocejas.arrow.optional.Optional;
import io.reactivex.Single;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.RoomRole;
import chat.rocket.core.models.User;
public interface RoomRoleRepository {
Single<Optional<RoomRole>> getFor(Room room, User user);
}
...@@ -37,6 +37,14 @@ public class Pair<F, S> { ...@@ -37,6 +37,14 @@ public class Pair<F, S> {
this.second = second; this.second = second;
} }
public F component1() {
return first;
}
public S component2() {
return second;
}
/** /**
* Checks the two objects for equality by delegating to their respective * Checks the two objects for equality by delegating to their respective
* {@link Object#equals(Object)} methods. * {@link Object#equals(Object)} methods.
......
package chat.rocket.core.interactors
import chat.rocket.core.PermissionsConstants
import chat.rocket.core.PublicSettingsConstants
import chat.rocket.core.models.Message
import chat.rocket.core.models.PublicSetting
import chat.rocket.core.models.Room
import chat.rocket.core.models.User
import chat.rocket.core.repositories.*
import com.fernandocejas.arrow.optional.Optional
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.observers.TestObserver
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class EditMessageInteractorTest {
@Mock
lateinit var permissionInteractor: PermissionInteractor
@Mock
lateinit var userRepository: UserRepository
@Mock
lateinit var messageRepository: MessageRepository
@Mock
lateinit var roomRepository: RoomRepository
@Mock
lateinit var publicSettingRepository: PublicSettingRepository
@Mock
lateinit var message: Message
@Mock
lateinit var user: User
@Mock
lateinit var room: Room
lateinit var editMessageInteractor: EditMessageInteractor
@Before
fun setUp() {
editMessageInteractor = EditMessageInteractor(
permissionInteractor, userRepository, messageRepository, roomRepository, publicSettingRepository
)
}
@Test
fun isAllowedReturnsFalseWhenWithoutRoom() {
val testObserver = TestObserver<Boolean>()
`when`(user.id).thenReturn("id")
`when`(message.roomId).thenReturn("roomId")
`when`(message.user).thenReturn(user)
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
.thenReturn(Single.just(Optional.of(allowEditTimeout)))
`when`(roomRepository.getById(anyString())).thenReturn(Flowable.just(Optional.absent()))
editMessageInteractor.isAllowed(message)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsFalseWhenEditNotAllowed() {
val testObserver = TestObserver<Boolean>()
`when`(user.id).thenReturn("id")
`when`(message.roomId).thenReturn("roomId")
`when`(message.user).thenReturn(user)
val allowEdit = allowEditPublicSettings(false)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
.thenReturn(Single.just(Optional.of(allowEditTimeout)))
`when`(roomRepository.getById(anyString())).thenReturn(Flowable.just(Optional.of(room)))
`when`(permissionInteractor.isAllowed(PermissionsConstants.EDIT_MESSAGE, room))
.thenReturn(Single.just(false))
editMessageInteractor.isAllowed(message)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsFalseWhenEditTimeout() {
val testObserver = TestObserver<Boolean>()
`when`(user.id).thenReturn("id")
`when`(message.roomId).thenReturn("roomId")
`when`(message.timestamp).thenReturn(120_000)
`when`(message.user).thenReturn(user)
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting(1)
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
.thenReturn(Single.just(Optional.of(allowEditTimeout)))
`when`(roomRepository.getById(anyString())).thenReturn(Flowable.just(Optional.of(room)))
`when`(permissionInteractor.isAllowed(PermissionsConstants.EDIT_MESSAGE, room))
.thenReturn(Single.just(false))
editMessageInteractor.isAllowed(message)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsFalseWhenNotTheSameUser() {
val testObserver = TestObserver<Boolean>()
`when`(user.id).thenReturn("id")
`when`(message.roomId).thenReturn("roomId")
`when`(message.user).thenReturn(user)
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
val anotherUser = mock(User::class.java)
`when`(anotherUser.id).thenReturn("another id")
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(anotherUser)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
.thenReturn(Single.just(Optional.of(allowEditTimeout)))
`when`(roomRepository.getById(anyString())).thenReturn(Flowable.just(Optional.of(room)))
`when`(permissionInteractor.isAllowed(PermissionsConstants.EDIT_MESSAGE, room))
.thenReturn(Single.just(false))
editMessageInteractor.isAllowed(message)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsTrueWhenEverythingIsOkAndNoPermission() {
val testObserver = TestObserver<Boolean>()
`when`(user.id).thenReturn("id")
`when`(message.roomId).thenReturn("roomId")
`when`(message.user).thenReturn(user)
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
.thenReturn(Single.just(Optional.of(allowEditTimeout)))
`when`(roomRepository.getById(anyString())).thenReturn(Flowable.just(Optional.of(room)))
`when`(permissionInteractor.isAllowed(PermissionsConstants.EDIT_MESSAGE, room))
.thenReturn(Single.just(false))
editMessageInteractor.isAllowed(message)
.subscribe(testObserver)
testObserver.assertResult(true)
}
@Test
fun isAllowedReturnsTrueWhenEverythingIsOkAndPermission() {
val testObserver = TestObserver<Boolean>()
`when`(user.id).thenReturn("id")
`when`(message.roomId).thenReturn("roomId")
`when`(message.user).thenReturn(user)
val allowEdit = allowEditPublicSettings(true)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
.thenReturn(Single.just(Optional.of(allowEditTimeout)))
`when`(roomRepository.getById(anyString())).thenReturn(Flowable.just(Optional.of(room)))
`when`(permissionInteractor.isAllowed(PermissionsConstants.EDIT_MESSAGE, room))
.thenReturn(Single.just(true))
editMessageInteractor.isAllowed(message)
.subscribe(testObserver)
testObserver.assertResult(true)
}
@Test
fun isAllowedReturnsTrueWhenEverythingIsNotOkAndPermission() {
val testObserver = TestObserver<Boolean>()
`when`(user.id).thenReturn("id")
`when`(message.roomId).thenReturn("roomId")
`when`(message.user).thenReturn(user)
val allowEdit = allowEditPublicSettings(false)
val allowEditTimeout = allowEditTimeLimitPublicSetting()
`when`(userRepository.current).thenReturn(Flowable.just(Optional.of(user)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING))
.thenReturn(Single.just(Optional.of(allowEdit)))
`when`(publicSettingRepository.getById(PublicSettingsConstants.Message.ALLOW_EDITING_BLOCK_TIMEOUT))
.thenReturn(Single.just(Optional.of(allowEditTimeout)))
`when`(roomRepository.getById(anyString())).thenReturn(Flowable.just(Optional.of(room)))
`when`(permissionInteractor.isAllowed(PermissionsConstants.EDIT_MESSAGE, room))
.thenReturn(Single.just(true))
editMessageInteractor.isAllowed(message)
.subscribe(testObserver)
testObserver.assertResult(true)
}
private fun allowEditPublicSettings(isAllowed: Boolean = false) = mock(PublicSetting::class.java).apply {
`when`(this.valueAsBoolean).thenReturn(isAllowed)
}
private fun allowEditTimeLimitPublicSetting(timeLimit: Long = 0) = mock(PublicSetting::class.java).apply {
`when`(this.valueAsLong).thenReturn(timeLimit)
}
}
\ No newline at end of file
package chat.rocket.core.interactors
import chat.rocket.core.models.*
import chat.rocket.core.repositories.PermissionRepository
import chat.rocket.core.repositories.RoomRoleRepository
import chat.rocket.core.repositories.UserRepository
import com.fernandocejas.arrow.optional.Optional
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.observers.TestObserver
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class PermissionInteractorTest {
@Mock
lateinit var userRepository: UserRepository
@Mock
lateinit var roomRoleRepository: RoomRoleRepository
@Mock
lateinit var permissionRepository: PermissionRepository
@Mock
lateinit var room: Room
@Mock
lateinit var user: User
@Mock
lateinit var roomRole: RoomRole
@Mock
lateinit var permission: Permission
lateinit var permissionInteractor: PermissionInteractor
@Before
fun setUp() {
permissionInteractor = PermissionInteractor(
userRepository, roomRoleRepository, permissionRepository
)
}
@Test
fun isAllowedReturnsFalseWhenWithoutCurrentUser() {
`when`(userRepository.current)
.thenReturn(Flowable.just(Optional.absent()))
val testObserver = TestObserver<Boolean>()
permissionInteractor.isAllowed("permission", room)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsFalseWhenWithoutRoomRoleAndPermission() {
val permissionId = "permission"
`when`(userRepository.current)
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
.thenReturn(Single.just(Optional.absent()))
`when`(permissionRepository.getById(permissionId))
.thenReturn(Single.just(Optional.absent()))
val testObserver = TestObserver<Boolean>()
permissionInteractor.isAllowed(permissionId, room)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsFalseWhenWithoutRoomRole() {
val permissionId = "permission"
`when`(userRepository.current)
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
.thenReturn(Single.just(Optional.absent()))
`when`(permissionRepository.getById(permissionId))
.thenReturn(Single.just(Optional.of(permission)))
val testObserver = TestObserver<Boolean>()
permissionInteractor.isAllowed(permissionId, room)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsFalseWhenWithoutPermission() {
val permissionId = "permission"
`when`(userRepository.current)
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
.thenReturn(Single.just(Optional.of(roomRole)))
`when`(permissionRepository.getById(permissionId))
.thenReturn(Single.just(Optional.absent()))
val testObserver = TestObserver<Boolean>()
permissionInteractor.isAllowed(permissionId, room)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsFalseWhenRoomRoleAndPermissionDoesNotMatchWithEmptyRoles() {
val permissionId = "permission"
`when`(userRepository.current)
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
.thenReturn(Single.just(Optional.of(roomRole)))
`when`(permissionRepository.getById(permissionId))
.thenReturn(Single.just(Optional.of(permission)))
val testObserver = TestObserver<Boolean>()
permissionInteractor.isAllowed(permissionId, room)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsFalseWhenRoomRoleAndPermissionDoesNotMatchWithRoles() {
val permissionId = "permission"
`when`(userRepository.current)
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRole.roles).thenReturn(getSomeRoles())
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
.thenReturn(Single.just(Optional.of(roomRole)))
`when`(permission.roles).thenReturn(getOtherRoles())
`when`(permissionRepository.getById(permissionId))
.thenReturn(Single.just(Optional.of(permission)))
val testObserver = TestObserver<Boolean>()
permissionInteractor.isAllowed(permissionId, room)
.subscribe(testObserver)
testObserver.assertResult(false)
}
@Test
fun isAllowedReturnsTrueWhenRoomRoleAndPermissionDoesMatch() {
val permissionId = "permission"
`when`(userRepository.current)
.thenReturn(Flowable.just(Optional.of(user)))
`when`(roomRole.roles).thenReturn(getMoreRoles())
`when`(roomRoleRepository.getFor(any(Room::class.java), any(User::class.java)))
.thenReturn(Single.just(Optional.of(roomRole)))
`when`(permission.roles).thenReturn(getOtherRoles())
`when`(permissionRepository.getById(permissionId))
.thenReturn(Single.just(Optional.of(permission)))
val testObserver = TestObserver<Boolean>()
permissionInteractor.isAllowed(permissionId, room)
.subscribe(testObserver)
testObserver.assertResult(true)
}
private fun getSomeRoles() = listOf(
Role.builder().setId("one role id").setName("one role name").build()
)
private fun getOtherRoles() = listOf(
Role.builder().setId("other role id").setName("other role name").build(),
Role.builder().setId("another role id").setName("another role name").build()
)
private fun getMoreRoles() = getSomeRoles() + listOf(
Role.builder().setId("other role id").setName("other role name").build(),
Role.builder().setId("another role id").setName("another role name").build()
)
}
\ No newline at end of file
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