Commit 95e001cc authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #512 from RocketChat/feature/message-actions

[NEW] Add Reply feature
parents 12247fd5 fe3ac9e8
......@@ -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,13 @@ 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.android.widget.AbsoluteUrl;
import chat.rocket.core.models.Message;
import chat.rocket.core.models.Room;
import chat.rocket.core.models.User;
public interface RoomContract {
......@@ -35,6 +37,12 @@ public interface RoomContract {
void autoloadImages();
void manualLoadImages();
void onReply(AbsoluteUrl absoluteUrl, String markdown, Message message);
void onCopy(String message);
void showMessageActions(Message message);
}
interface Presenter extends BaseContract.Presenter<View> {
......@@ -58,5 +66,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;
......@@ -26,10 +29,11 @@ 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;
import chat.rocket.android.fragment.chatroom.dialog.MessageOptionsDialogFragment;
import chat.rocket.android.fragment.sidebar.SidebarMainFragment;
import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.FileUploadHelper;
......@@ -42,6 +46,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;
......@@ -55,6 +60,7 @@ import chat.rocket.android.renderer.RocketChatUserStatusProvider;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.temp.DeafultTempSpotlightRoomCaller;
import chat.rocket.android.service.temp.DefaultTempSpotlightUserCaller;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.RoomToolbar;
import chat.rocket.android.widget.internal.ExtraActionPickerDialogFragment;
import chat.rocket.android.widget.message.MessageFormLayout;
......@@ -90,7 +96,6 @@ import permissions.dispatcher.RuntimePermissions;
public class RoomFragment extends AbstractChatRoomFragment implements
OnBackPressListener,
ExtraActionPickerDialogFragment.Callback,
ModelListAdapter.OnItemClickListener<PairedMessage>,
ModelListAdapter.OnItemLongClickListener<PairedMessage>,
RoomContract.View {
......@@ -135,7 +140,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();
......@@ -202,7 +207,6 @@ public class RoomFragment extends AbstractChatRoomFragment implements
messageListAdapter = new MessageListAdapter(getContext(), hostname);
messageRecyclerView.setAdapter(messageListAdapter);
messageListAdapter.setOnItemClickListener(this);
messageListAdapter.setOnItemLongClickListener(this);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, true);
......@@ -287,22 +291,9 @@ public class RoomFragment extends AbstractChatRoomFragment implements
super.onDestroyView();
}
@Override
public void onItemClick(PairedMessage pairedMessage) {
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");
presenter.onMessageSelected(pairedMessage.target);
return true;
}
......@@ -659,6 +650,31 @@ public class RoomFragment extends AbstractChatRoomFragment implements
messageListAdapter.setAutoloadImages(false);
}
@Override
public void onReply(AbsoluteUrl absoluteUrl, String markdown, Message message) {
messageFormManager.setReply(absoluteUrl, markdown, 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.take(message)
.setReplyAction(presenter::replyMessage)
.setEditAction(this::onEditMessage)
.setCopyAction(msg -> onCopy(message.getMessage()))
.showWith(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,9 @@ 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;
public class RoomPresenter extends BasePresenter<RoomContract.View>
implements RoomContract.Presenter {
......@@ -36,8 +35,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 +115,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()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
serverUrl -> {
if (serverUrl.isPresent()) {
String baseUrl = serverUrl.get().getBaseUrl();
view.onReply(serverUrl.get(), buildReplyMarkDown(baseUrl, message), message);
}
},
Logger::report
);
}
@Override
public void copyMessage(Message message) {
view.onCopy(message.getMessage());
}
private String buildReplyMarkDown(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 +277,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 chat.rocket.android.widget.AbsoluteUrl
import chat.rocket.android.widget.message.MessageFormLayout
import chat.rocket.core.models.Message
class MessageFormManager(private val messageFormLayout: MessageFormLayout, val callback: MessageFormLayout.ExtraActionSelectionClickListener) {
private var sendMessageCallback: SendMessageCallback? = null
private var replyMarkDown: String = ""
init {
messageFormLayout.setExtraActionSelectionClickListener(callback)
......@@ -31,8 +34,20 @@ class MessageFormManager(private val messageFormLayout: MessageFormLayout, val c
messageFormLayout.isEnabled = enable
}
fun setReply(absoluteUrl: AbsoluteUrl, replyMarkDown: String, message: Message) {
this.replyMarkDown = replyMarkDown
messageFormLayout.setReplyContent(absoluteUrl, message)
messageFormLayout.setReplyCancelListener({
this.replyMarkDown = ""
messageFormLayout.clearReplyContent()
messageFormLayout.hideKeyboard()
})
}
private fun sendMessage(message: String) {
sendMessageCallback?.onSubmitText(message)
val finalMessage = if (replyMarkDown.isNotEmpty()) "$replyMarkDown $message" else message
replyMarkDown = ""
sendMessageCallback?.onSubmitText(finalMessage)
}
interface SendMessageCallback {
......
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);
}
}
allowedActions.addAll(singleton.otherActions);
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 take(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 showWith(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 {
private 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 {
private CharSequence actionName;
private ActionListener actionListener;
private 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()) {
......
......@@ -4,6 +4,7 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.text.Editable;
import android.text.TextUtils;
......@@ -15,10 +16,20 @@ import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.facebook.drawee.view.SimpleDraweeView;
import chat.rocket.android.widget.AbsoluteUrl;
import chat.rocket.android.widget.R;
import chat.rocket.android.widget.helper.DebouncingOnClickListener;
import chat.rocket.android.widget.helper.FrescoHelper;
import chat.rocket.core.models.Attachment;
import chat.rocket.core.models.AttachmentTitle;
import chat.rocket.core.models.Message;
public class MessageFormLayout extends LinearLayout {
......@@ -27,6 +38,12 @@ public class MessageFormLayout extends LinearLayout {
private ImageButton attachButton;
private ImageButton sendButton;
private RelativeLayout replyBar;
private ImageView replyCancelButton;
private SimpleDraweeView replyThumb;
private TextView replyMessageText;
private TextView replyUsernameText;
private ExtraActionSelectionClickListener extraActionSelectionClickListener;
private SubmitTextListener submitTextListener;
private ImageKeyboardEditText.OnCommitContentListener listener;
......@@ -65,6 +82,12 @@ public class MessageFormLayout extends LinearLayout {
}
});
replyCancelButton = composer.findViewById(R.id.reply_cancel);
replyMessageText = composer.findViewById(R.id.reply_message);
replyUsernameText = composer.findViewById(R.id.reply_username);
replyThumb = composer.findViewById(R.id.reply_thumb);
replyBar = composer.findViewById(R.id.reply_bar);
sendButton = composer.findViewById(R.id.button_send);
sendButton.setOnClickListener(new DebouncingOnClickListener() {
......@@ -73,6 +96,7 @@ public class MessageFormLayout extends LinearLayout {
String messageText = getText();
if (messageText.length() > 0 && submitTextListener != null) {
submitTextListener.onSubmitText(messageText);
clearReplyContent();
}
}
});
......@@ -118,6 +142,20 @@ public class MessageFormLayout extends LinearLayout {
addView(composer);
}
public void clearReplyContent() {
replyBar.setVisibility(View.GONE);
replyThumb.setVisibility(View.GONE);
replyMessageText.setText("");
replyUsernameText.setText("");
}
public void showReplyThumb() {
replyThumb.setVisibility(View.VISIBLE);
}
public void setReplyCancelListener(OnClickListener onClickListener) {
replyCancelButton.setOnClickListener(onClickListener);
}
public EditText getEditText() {
return (EditText) composer.findViewById(R.id.editor);
}
......@@ -154,10 +192,7 @@ public class MessageFormLayout extends LinearLayout {
if (text.length() > 0) {
editor.setSelection(text.length());
InputMethodManager inputMethodManager = (InputMethodManager) editor.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
editor.requestFocus();
inputMethodManager.showSoftInput(editor, 0);
requestFocusAndShowKeyboard();
}
}
});
......@@ -173,6 +208,61 @@ public class MessageFormLayout extends LinearLayout {
this.listener = listener;
}
public void setReplyContent(@NonNull AbsoluteUrl absoluteUrl, @NonNull Message message) {
String text = message.getMessage();
replyUsernameText.setText(message.getUser().getUsername());
if (!TextUtils.isEmpty(text)) {
replyMessageText.setText(text);
} else {
if (message.getAttachments() != null && message.getAttachments().size() > 0) {
Attachment attachment = message.getAttachments().get(0);
AttachmentTitle attachmentTitle = attachment.getAttachmentTitle();
String imageUrl = null;
if (attachment.getImageUrl() != null) {
imageUrl = absoluteUrl.from(attachment.getImageUrl());
}
if (attachmentTitle != null) {
text = attachmentTitle.getTitle();
}
if (TextUtils.isEmpty(text)) {
text = "Unknown";
}
if (imageUrl != null) {
FrescoHelper.INSTANCE.loadImageWithCustomization(replyThumb, imageUrl);
showReplyThumb();
}
replyMessageText.setText(text);
}
}
replyBar.setVisibility(View.VISIBLE);
requestFocusAndShowKeyboard();
}
public void hideKeyboard() {
final EditText editor = getEditor();
editor.post(new Runnable() {
@Override
public void run() {
InputMethodManager inputMethodManager = (InputMethodManager) editor.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(editor.getWindowToken(), 0);
}
});
}
private void requestFocusAndShowKeyboard() {
final EditText editor = getEditor();
editor.post(new Runnable() {
@Override
public void run() {
InputMethodManager inputMethodManager = (InputMethodManager) editor.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
editor.requestFocus();
inputMethodManager.showSoftInput(editor, 0);
}
});
}
private void animateHide(final View view) {
view.animate().scaleX(0).scaleY(0).setDuration(150).withEndAction(new Runnable() {
@Override
......
<!-- drawable/close.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</vector>
\ No newline at end of file
<!-- drawable/reply.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M10,9V5L3,12L10,19V14.9C15,14.9 18.5,16.5 21,20C20,15 17,10 10,9Z" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:background="@drawable/top_shadow"
tools:context="chat.rocket.android.widget.message.MessageFormLayout">
<chat.rocket.android.widget.message.ImageKeyboardEditText
android:id="@+id/editor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:inputType="textCapSentences|textMultiLine"
android:hint="@string/message_composer_message_hint"
android:textSize="14sp"
android:minLines="1"
android:maxLines="4"
android:background="@null"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/container"
app:layout_constraintBottom_toBottomOf="parent"/>
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toTopOf="@+id/editor"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="@+id/editor"
app:layout_constraintBottom_toBottomOf="@+id/editor">
<ImageButton
android:id="@+id/button_attach"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/color_icon_composer"
app:srcCompat="@drawable/ic_attach_file_black_24dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
<ImageButton
android:id="@+id/button_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/color_accent"
app:srcCompat="@drawable/ic_send_black_24dp"
android:background="?attr/selectableItemBackgroundBorderless"/>
</FrameLayout>
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/top_shadow"
android:minHeight="48dp"
tools:context="chat.rocket.android.widget.message.MessageFormLayout">
<RelativeLayout
android:id="@+id/reply_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/keyboard_container"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="8dp"
tools:visibility="visible">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/reply_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_reply"
app:tint="@color/color_accent" />
<android.support.v7.widget.AppCompatImageView
android:id="@+id/reply_cancel"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_close"
app:tint="@color/color_icon_composer" />
<TextView
android:id="@+id/reply_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/reply_username"
android:layout_toLeftOf="@+id/reply_cancel"
android:layout_toStartOf="@id/reply_cancel"
android:layout_toRightOf="@+id/reply_thumb"
android:layout_toEndOf="@+id/reply_thumb"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/color_accent"
android:textStyle="bold"
tools:text="jane.doe" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/reply_thumb"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:layout_marginRight="4dp"
android:layout_marginEnd="4dp"
android:layout_toRightOf="@+id/reply_icon"
android:layout_toEndOf="@+id/reply_icon"
android:layout_alignBottom="@+id/reply_message"
android:layout_alignTop="@+id/reply_username"
android:layout_centerVertical="true"
android:visibility="gone"
fresco:actualImageScaleType="fitCenter" />
<TextView
android:id="@+id/reply_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/reply_username"
android:layout_toLeftOf="@+id/reply_cancel"
android:layout_toStartOf="@id/reply_cancel"
android:layout_toRightOf="@+id/reply_thumb"
android:layout_toEndOf="@+id/reply_thumb"
android:ellipsize="end"
android:maxLines="1"
tools:text="Message" />
</RelativeLayout>
<android.support.constraint.ConstraintLayout
android:id="@+id/keyboard_container"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="@drawable/top_shadow"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/reply_bar">
<chat.rocket.android.widget.message.ImageKeyboardEditText
android:id="@+id/editor"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:background="@null"
android:hint="@string/message_composer_message_hint"
android:inputType="textCapSentences|textMultiLine"
android:maxLines="4"
android:minLines="1"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/container"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@+id/editor"
app:layout_constraintLeft_toRightOf="@+id/editor"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/editor">
<ImageButton
android:id="@+id/button_attach"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:tint="@color/color_icon_composer"
app:srcCompat="@drawable/ic_attach_file_black_24dp" />
<ImageButton
android:id="@+id/button_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:tint="@color/color_accent"
app:srcCompat="@drawable/ic_send_black_24dp" />
</FrameLayout>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
\ 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