Commit 9f44e9b3 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Implement Reply feature available at context popup

parent a2acf159
......@@ -172,7 +172,10 @@ public class RocketChatCache {
}
public Flowable<Optional<String>> getSelectedRoomIdPublisher() {
return getValuePublisher(KEY_SELECTED_ROOM_ID);
return getValuePublisher(KEY_SELECTED_ROOM_ID)
.filter(Optional::isPresent)
.map(Optional::get)
.map(roomValue -> Optional.ofNullable(new JSONObject(roomValue).optString(getSelectedServerHostname(), null)));
}
private SharedPreferences getSharedPreferences() {
......
......@@ -6,9 +6,6 @@ import android.support.annotation.Nullable;
import com.hadisatrio.optional.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import chat.rocket.android.LaunchUtil;
......@@ -198,9 +195,8 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
compositeDisposable.add(
rocketChatCache.getSelectedRoomIdPublisher()
.filter(Optional::isPresent)
.map(Optional::get)
.map(this::convertStringToJsonObject)
.map(jsonObject -> jsonObject.optString(rocketChatCache.getSelectedServerHostname(), null))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
......@@ -209,11 +205,4 @@ abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
)
);
}
private JSONObject convertStringToJsonObject(String json) throws JSONException {
if (json == null) {
return new JSONObject();
}
return new JSONObject(json);
}
}
......@@ -48,7 +48,8 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
private SlidingPaneLayout pane;
private MainContract.Presenter presenter;
protected int getLayoutContainerForFragment() {
@Override
public int getLayoutContainerForFragment() {
return R.id.activity_main_container;
}
......
......@@ -30,7 +30,7 @@ public class DDPClientWrapper {
}
/**
* create new API client instance.
* build new API client instance.
*/
public static DDPClientWrapper create(String hostname) {
return new DDPClientWrapper(hostname);
......
......@@ -29,4 +29,8 @@ public class RocketChatAbsoluteUrl implements AbsoluteUrl {
public String getToken() {
return token;
}
public String getBaseUrl() {
return baseUrl;
}
}
\ No newline at end of file
......@@ -2,11 +2,12 @@ package chat.rocket.android.fragment.chatroom;
import android.support.annotation.Nullable;
import chat.rocket.core.models.User;
import java.util.List;
import chat.rocket.android.shared.BaseContract;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.User;
public interface RoomContract {
......@@ -35,6 +36,12 @@ public interface RoomContract {
void autoloadImages();
void manualLoadImages();
void onReply(String message);
void onCopy(String message);
void showMessageActions(Message message);
}
interface Presenter extends BaseContract.Presenter<View> {
......@@ -58,5 +65,9 @@ public interface RoomContract {
void onMarkAsRead();
void refreshRoom();
void replyMessage(Message message);
void copyMessage(Message message);
}
}
......@@ -2,6 +2,9 @@ package chat.rocket.android.fragment.chatroom;
import android.Manifest;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
......@@ -18,6 +21,7 @@ import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Toast;
import com.hadisatrio.optional.Optional;
......@@ -26,6 +30,8 @@ import java.util.List;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatApplication;
import chat.rocket.android.activity.MainActivity;
import chat.rocket.android.activity.room.RoomActivity;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.fragment.chatroom.dialog.FileUploadProgressDialogFragment;
......@@ -42,6 +48,7 @@ import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.layouthelper.chatroom.AbstractNewMessageIndicatorManager;
import chat.rocket.android.layouthelper.chatroom.MessageFormManager;
import chat.rocket.android.layouthelper.chatroom.MessageListAdapter;
import chat.rocket.android.layouthelper.chatroom.MessagePopup;
import chat.rocket.android.layouthelper.chatroom.ModelListAdapter;
import chat.rocket.android.layouthelper.chatroom.PairedMessage;
import chat.rocket.android.layouthelper.extra_action.AbstractExtraActionItem;
......@@ -135,7 +142,7 @@ public class RoomFragment extends AbstractChatRoomFragment implements
}
/**
* create fragment with roomId.
* build fragment with roomId.
*/
public static RoomFragment create(String hostname, String roomId) {
Bundle args = new Bundle();
......@@ -659,6 +666,32 @@ public class RoomFragment extends AbstractChatRoomFragment implements
messageListAdapter.setAutoloadImages(false);
}
@Override
public void onReply(String message) {
messageFormManager.setEditMessage(message);
}
@Override
public void onCopy(String message) {
RocketChatApplication context = RocketChatApplication.getInstance();
ClipboardManager clipboardManager =
(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText("message", message));
}
@Override
public void showMessageActions(Message message) {
Activity context = getActivity();
if (context != null && context instanceof MainActivity) {
MessagePopup.with(message)
.setReplyAction(presenter::replyMessage)
.setEditAction(this::onEditMessage)
.setCopyAction(msg -> onCopy(message.getMessage()))
.addAction("Test", message1 -> Toast.makeText(context, "Teste", Toast.LENGTH_SHORT).show())
.show(context);
}
}
private void onEditMessage(Message message) {
edittingMessage = message;
messageFormManager.setEditMessage(message.getMessage());
......
......@@ -6,15 +6,12 @@ import android.support.v4.util.Pair;
import com.hadisatrio.optional.Optional;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.api.MethodCallHelper;
import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.shared.BasePresenter;
import chat.rocket.core.SyncState;
import chat.rocket.core.interactors.MessageInteractor;
......@@ -24,7 +21,10 @@ import chat.rocket.core.models.Settings;
import chat.rocket.core.models.User;
import chat.rocket.core.repositories.RoomRepository;
import chat.rocket.core.repositories.UserRepository;
import chat.rocket.android.service.ConnectivityManagerApi;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class RoomPresenter extends BasePresenter<RoomContract.View>
implements RoomContract.Presenter {
......@@ -36,8 +36,9 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
private final AbsoluteUrlHelper absoluteUrlHelper;
private final MethodCallHelper methodCallHelper;
private final ConnectivityManagerApi connectivityManagerApi;
private Room currentRoom;
public RoomPresenter(String roomId,
public RoomPresenter(String roomId,
UserRepository userRepository,
MessageInteractor messageInteractor,
RoomRepository roomRepository,
......@@ -115,6 +116,50 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
if (message.getSyncState() == SyncState.FAILED) {
view.showMessageSendFailure(message);
}
if (message.getType() == null) {
// If message is not a system message show applicable actions.
view.showMessageActions(message);
}
}
@Override
public void replyMessage(Message message) {
this.absoluteUrlHelper.getRocketChatAbsoluteUrl()
.cache()
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(
serverUrl -> {
if (serverUrl.isPresent()) {
String baseUrl = serverUrl.get().getBaseUrl();
view.onReply(buildReplyMessage(baseUrl, message));
}
},
Logger::report
);
}
@Override
public void copyMessage(Message message) {
view.onCopy(message.getMessage());
}
private String buildReplyMessage(String baseUrl, Message message) {
if (currentRoom == null || message.getUser() == null) {
return "";
}
if (currentRoom.isDirectMessage()) {
return String.format("[ ](%s/direct/%s?msg=%s) ", baseUrl,
message.getUser().getUsername(),
message.getId());
} else {
return String.format("[ ](%s/channel/%s?msg=%s) @%s ", baseUrl,
currentRoom.getName(),
message.getId(),
message.getUser().getUsername());
}
}
@Override
......@@ -233,6 +278,7 @@ public class RoomPresenter extends BasePresenter<RoomContract.View>
}
private void processRoom(Room room) {
this.currentRoom = room;
view.render(room);
if (room.isDirectMessage()) {
......
......@@ -100,7 +100,7 @@ public class LoginFragment extends AbstractServerConfigFragment implements Login
try {
fragment = info.fragmentClass.newInstance();
} catch (Exception exception) {
RCLog.w(exception, "failed to create new Fragment");
RCLog.w(exception, "failed to build new Fragment");
}
if (fragment != null) {
Bundle args = new Bundle();
......
......@@ -30,7 +30,7 @@ public class UserRegistrationDialogFragment extends DialogFragment {
}
/**
* create UserRegistrationDialogFragment with auto-detect email/username.
* build UserRegistrationDialogFragment with auto-detect email/username.
*/
public static UserRegistrationDialogFragment create(String hostname,
String usernameOrEmail, String password) {
......@@ -42,7 +42,7 @@ public class UserRegistrationDialogFragment extends DialogFragment {
}
/**
* create UserRegistrationDialogFragment.
* build UserRegistrationDialogFragment.
*/
public static UserRegistrationDialogFragment create(String hostname,
String username, String email,
......
......@@ -67,7 +67,7 @@ public class SidebarMainFragment extends AbstractFragment implements SidebarMain
public SidebarMainFragment() {}
/**
* create SidebarMainFragment with hostname.
* build SidebarMainFragment with hostname.
*/
public static SidebarMainFragment create(String hostname) {
Bundle args = new Bundle();
......
package chat.rocket.android.layouthelper.chatroom;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
import android.support.v7.app.AlertDialog;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.RocketChatApplication;
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;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
public class MessagePopup {
private static volatile MessagePopup singleton = null;
private static final Action REPLY_ACTION_INFO = new Action("Reply", null, true);
private static final Action EDIT_ACTION_INFO = new Action("Edit", null, true);
private static final Action COPY_ACTION_INFO = new Action("Copy", null, true);
private final List<Action> defaultActions = new ArrayList<>(3);
private final List<Action> otherActions = new ArrayList<>();
private Message message;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private MessagePopup(Message message) {
this.message = message;
}
private void showAvailableActionsOnly(Context context) {
RocketChatCache cache = new RocketChatCache(RocketChatApplication.getInstance());
String hostname = cache.getSelectedServerHostname();
EditMessageInteractor editMessageInteractor = getEditMessageInteractor(hostname);
MessageRepository messageRepository = new RealmMessageRepository(hostname);
Disposable disposable = messageRepository.getById(singleton.message.getId())
.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 -> {
EDIT_ACTION_INFO.allowed = pair.second;
List<Action> allActions = singleton.defaultActions;
List<Action> allowedActions = new ArrayList<>(3);
for (int i = 0; i < allActions.size(); i++) {
Action action = allActions.get(i);
if (action.allowed) {
allowedActions.add(action);
}
}
CharSequence[] items = new CharSequence[allowedActions.size()];
for (int j = 0; j < items.length; j++) {
items[j] = allowedActions.get(j).actionName;
}
new AlertDialog.Builder(context)
.setItems(items, (dialog, index) -> {
Action action = allowedActions.get(index);
ActionListener actionListener = action.actionListener;
if (actionListener != null) {
actionListener.execute(singleton.message);
}
})
.setOnCancelListener(dialog -> compositeDisposable.clear())
.setOnDismissListener(dialog1 -> compositeDisposable.clear())
.setTitle("Message")
.create()
.show();
},
Logger::report
);
compositeDisposable.add(disposable);
}
private void addDefaultActions() {
singleton.defaultActions.add(REPLY_ACTION_INFO);
singleton.defaultActions.add(EDIT_ACTION_INFO);
singleton.defaultActions.add(COPY_ACTION_INFO);
}
public static MessagePopup with(Message message) {
if (singleton == null) {
synchronized (MessagePopup.class) {
if (singleton == null) {
singleton = new Builder(message).build();
singleton.addDefaultActions();
}
}
}
singleton.message = message;
singleton.otherActions.clear();
return singleton;
}
private Action getActionIfExists(Action action) {
if (singleton.otherActions.contains(action)) {
return singleton.otherActions.get(singleton.otherActions.indexOf(action));
}
if (singleton.defaultActions.contains(action)) {
return singleton.defaultActions.get(singleton.defaultActions.indexOf(action));
}
return null;
}
public MessagePopup addAction(@NonNull CharSequence actionName, ActionListener actionListener) {
List<Action> actions = singleton.otherActions;
Action newAction = new Action(actionName, actionListener, true);
Action existingAction = getActionIfExists(newAction);
if (existingAction != null) {
existingAction.actionListener = actionListener;
} else {
actions.add(newAction);
}
return singleton;
}
public MessagePopup setReplyAction(ActionListener actionListener) {
REPLY_ACTION_INFO.actionListener= actionListener;
return singleton;
}
public MessagePopup setEditAction(ActionListener actionListener) {
EDIT_ACTION_INFO.actionListener= actionListener;
return singleton;
}
public MessagePopup setCopyAction(ActionListener actionListener) {
COPY_ACTION_INFO.actionListener= actionListener;
return singleton;
}
public void show(Context context) {
showAvailableActionsOnly(context);
}
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
);
}
private static class Builder {
final Message message;
Builder(Message message) {
if (message == null) {
throw new IllegalArgumentException("Message must not be null");
}
this.message = message;
}
public MessagePopup build() {
Message message = this.message;
return new MessagePopup(message);
}
}
public static class Action {
CharSequence actionName;
ActionListener actionListener;
boolean allowed;
public Action(CharSequence actionName, ActionListener actionListener, boolean allowed) {
this.actionName = actionName;
this.actionListener = actionListener;
this.allowed = allowed;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Action that = (Action) o;
return actionName.equals(that.actionName);
}
@Override
public int hashCode() {
return actionName.hashCode();
}
}
public interface ActionListener {
void execute(Message message);
}
}
......@@ -88,7 +88,7 @@ public class PushNotificationHandler implements PushConstants {
if ((message != null && message.length() != 0) ||
(title != null && title.length() != 0)) {
Log.d(LOG_TAG, "create notification");
Log.d(LOG_TAG, "build notification");
if (title == null || title.isEmpty()) {
extras.putString(TITLE, getAppName(context));
......@@ -191,7 +191,7 @@ public class PushNotificationHandler implements PushConstants {
private void createActions(Context context, Bundle extras, NotificationCompat.Builder builder,
Resources resources, String packageName, int notId) {
Log.d(LOG_TAG, "create actions: with in-line");
Log.d(LOG_TAG, "build actions: with in-line");
String actions = extras.getString(ACTIONS);
if (actions == null) {
return;
......@@ -256,7 +256,7 @@ public class PushNotificationHandler implements PushConstants {
RemoteInput remoteInput;
if (inline) {
Log.d(LOG_TAG, "create remote input");
Log.d(LOG_TAG, "build remote input");
String replyLabel = "Enter your reply here";
remoteInput = new RemoteInput.Builder(INLINE_REPLY)
.setLabel(replyLabel)
......@@ -287,7 +287,7 @@ public class PushNotificationHandler implements PushConstants {
@RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH)
private void createActions(Context context, Bundle extras, Notification.Builder builder,
Resources resources, String packageName, int notId) {
Log.d(LOG_TAG, "create actions: with in-line");
Log.d(LOG_TAG, "build actions: with in-line");
String actions = extras.getString(ACTIONS);
if (actions == null) {
return;
......@@ -352,7 +352,7 @@ public class PushNotificationHandler implements PushConstants {
android.app.RemoteInput remoteInput;
if (inline) {
Log.d(LOG_TAG, "create remote input");
Log.d(LOG_TAG, "build remote input");
String replyLabel = "Enter your reply here";
remoteInput = new android.app.RemoteInput.Builder(INLINE_REPLY)
.setLabel(replyLabel)
......
......@@ -109,7 +109,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
}
/**
* create new Thread.
* build new Thread.
*/
@DebugLog
public static Single<RocketChatWebSocketThread> getStarted(Context appContext, String hostname) {
......@@ -241,11 +241,11 @@ public class RocketChatWebSocketThread extends HandlerThread {
private Single<Boolean> prepareDDPClient() {
// TODO: temporarily replaced checkIfConnectionAlive() call for this single checking if ddpClient is
// null or not. In case it is, create a new client, otherwise just keep connecting with existing one.
// null or not. In case it is, build a new client, otherwise just keep connecting with existing one.
return Single.just(ddpClient != null)
.doOnSuccess(alive -> {
if (!alive) {
RCLog.d("DDPClient#create");
RCLog.d("DDPClient#build");
ddpClient = DDPClientWrapper.create(hostname);
}
});
......@@ -392,7 +392,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
)
);
} else {
// if we don't have any session then just create the observers and register normally
// if we don't have any session then just build the observers and register normally
createObserversAndRegister();
}
}
......
......@@ -4,14 +4,13 @@ import android.content.Context;
import com.hadisatrio.optional.Optional;
import chat.rocket.android.log.RCLog;
import io.reactivex.disposables.CompositeDisposable;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.Registrable;
import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.models.ddp.RealmRoom;
import io.reactivex.disposables.CompositeDisposable;
public abstract class AbstractRocketChatCacheObserver implements Registrable {
private final Context context;
......@@ -50,6 +49,7 @@ public abstract class AbstractRocketChatCacheObserver implements Registrable {
compositeDisposable.add(
new RocketChatCache(context)
.getSelectedRoomIdPublisher()
.filter(Optional::isPresent)
.map(Optional::get)
.subscribe(this::updateRoomIdWith, RCLog::e)
);
......
......@@ -51,7 +51,7 @@ public class MarkDown {
private static final Pattern LINK_PATTERN = Pattern.compile(
"\\[([^\\]]+)\\]\\(((?:http|https):\\/\\/[^\\)]+)\\)", Pattern.MULTILINE);
"\\[(.*?)\\]\\(((https?):\\/\\/[-a-zA-Z0-9+&@#\\/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#\\/%=~_|]?)\\)", Pattern.MULTILINE);
private static void highlightLink1(SpannableString inputText) {
final Matcher matcher = LINK_PATTERN.matcher(inputText);
while (matcher.find()) {
......
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