Commit 8b1d9112 authored by mala's avatar mala Committed by GitHub

Merge branch 'develop' into develop

parents 2bca8302 95e001cc
......@@ -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() {
......
......@@ -9,7 +9,6 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.RocketChatCache;
......@@ -196,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(
......@@ -207,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;
}
......
......@@ -9,7 +9,6 @@ import kotlinx.android.synthetic.main.activity_room.*
class RoomActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_room)
......@@ -35,8 +34,7 @@ class RoomActivity : AppCompatActivity() {
}
private fun addFragment(fragment: Fragment, tag: String) {
supportFragmentManager
.beginTransaction()
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, fragment, tag)
.commit()
}
......
......@@ -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);
......
......@@ -96,6 +96,7 @@ object RestApiHelper {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForFileList(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("sort", "{\"uploadedAt\":-1}")
?.addQueryParameter("offset", offset)
?.build()
......
......@@ -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;
}
......@@ -325,9 +316,9 @@ public class RoomFragment extends AbstractChatRoomFragment implements
case R.id.action_favorite_messages:
showRoomListFragment(R.id.action_favorite_messages);
break;
// case R.id.action_file_list:
// showRoomListFragment(R.id.action_file_list);
// break;
case R.id.action_file_list:
showRoomListFragment(R.id.action_file_list);
break;
case R.id.action_member_list:
showRoomListFragment(R.id.action_member_list);
break;
......@@ -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,6 +35,7 @@ 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,
UserRepository userRepository,
......@@ -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()) {
......
package chat.rocket.android.fragment.chatroom.list
import chat.rocket.core.models.Attachment
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
......@@ -32,7 +33,7 @@ interface RoomListContract {
* @param dataSet The file data set to show.
* @param total The total number of files.
*/
fun showFileList(dataSet: ArrayList<String>, total: String)
fun showFileList(dataSet: ArrayList<Attachment>, total: String)
/**
* Shows a list of members of a room.
......
......@@ -9,8 +9,10 @@ import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.layouthelper.chatroom.list.RoomFileListAdapter
import chat.rocket.android.layouthelper.chatroom.list.RoomMemberListAdapter
import chat.rocket.android.layouthelper.chatroom.list.RoomMessagesAdapter
import chat.rocket.core.models.Attachment
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
import kotlinx.android.synthetic.main.fragment_room_list.*
......@@ -84,6 +86,14 @@ class RoomListFragment : Fragment(), RoomListContract.View {
userId,
offset)
}
R.id.action_file_list -> {
presenter.requestFileList(roomId,
roomType,
hostname,
token,
userId,
offset)
}
R.id.action_favorite_messages -> {
presenter.requestFavoriteMessages(roomId,
roomType,
......@@ -139,8 +149,23 @@ class RoomListFragment : Fragment(), RoomListContract.View {
}
}
// TODO (after REST api fixes)
override fun showFileList(dataSet: ArrayList<String>, total: String) {}
override fun showFileList(dataSet: ArrayList<Attachment>, total: String) {
activity.title = getString(R.string.fragment_room_list_file_list_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomFileListAdapter(dataSet)
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
if (dataSet.size >= 50) {
recyclerView.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
loadNextDataFromApi(page)
}
})
}
} else {
(recyclerView.adapter as RoomFileListAdapter).addDataSet(dataSet)
}
}
override fun showMemberList(dataSet: ArrayList<User>, total: String) {
activity.title = getString(R.string.fragment_room_list_member_list_title, total)
......
......@@ -2,11 +2,13 @@ package chat.rocket.android.fragment.chatroom.list
import android.content.Context
import android.os.Handler
import android.util.Log
import chat.rocket.android.R
import chat.rocket.android.api.rest.RestApiHelper
import chat.rocket.android.helper.OkHttpHelper
import chat.rocket.android.helper.UrlHelper
import chat.rocket.core.SyncState
import chat.rocket.core.models.Attachment
import chat.rocket.core.models.AttachmentTitle
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
import okhttp3.Call
......@@ -98,13 +100,43 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
})
}
// TODO (after the REST api fixes)
override fun requestFileList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {}
offset: Int) {
view.showWaitingView(true)
OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForFileList(roomId,
roomType,
hostname,
token,
userId,
offset.toString()))
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (!call.isCanceled) {
val message = e.message
if (message != null) {
showErrorMessage(message)
}
}
}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val result = response.body()?.string()
if (result != null) {
handleFilesJson(result, hostname)
}
} else {
showErrorMessage(response.message())
}
}
})
}
override fun requestMemberList(roomId: String,
roomType: String,
......@@ -159,27 +191,13 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
val messageJsonObject = messagesJSONArray.getJSONObject(it)
val userJsonObject = messageJsonObject.getJSONObject("u")
val timestampString = messageJsonObject.optString("ts")
val timestamp = if (timestampString.isBlank()) {
0
} else {
Timestamp.valueOf(timestampString.replace("T", " ").replace("Z", "")).time
}
val editedAtString = messageJsonObject.optString("_updatedAt")
val editedAt = if (editedAtString.isBlank()) {
0
} else {
Timestamp.valueOf(editedAtString.replace("T", " ").replace("Z", "")).time
}
Message.builder()
.setId(messageJsonObject.optString("_id"))
.setRoomId(messageJsonObject.optString("rid"))
.setMessage(messageJsonObject.optString("msg"))
.setUser(getUserFromJsonObject(userJsonObject))
.setTimestamp(timestamp)
.setEditedAt(editedAt)
.setTimestamp(getLongTimestamp(messageJsonObject.optString("ts")))
.setEditedAt(getLongTimestamp(messageJsonObject.optString("_updatedAt")))
.setGroupable(messageJsonObject.optBoolean("groupable"))
.setSyncState(SyncState.SYNCED)
.build()
......@@ -202,6 +220,52 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
}
}
private fun handleFilesJson(json: String, hostname: String) {
try {
val jsonObject = JSONObject(json)
val filesJsonArray = jsonObject.getJSONArray("files")
val total = filesJsonArray.length()
val dataSet = ArrayList<Attachment>(total)
(0 until total).mapTo(dataSet) {
val fileJsonObject = filesJsonArray.getJSONObject(it)
val fileLink = UrlHelper.getAttachmentLink(hostname, fileJsonObject.optString("_id"), fileJsonObject.optString("name"))
val attachmentTitle = AttachmentTitle.builder()
.setTitle(fileJsonObject.optString("name"))
.setLink(fileLink)
.setDownloadLink(fileLink)
.build()
val attachment = Attachment.builder()
val type = fileJsonObject.optString("type")
when {
type.startsWith("image") -> attachment.setImageUrl(fileLink)
type.startsWith("audio") -> attachment.setAudioUrl(fileLink)
type.startsWith("video") -> attachment.setVideoUrl(fileLink)
}
attachment.setCollapsed(false)
.setAttachmentTitle(attachmentTitle)
.setTimestamp(getSafeTimestamp(fileJsonObject.optString("uploadedAt")))
.build()
}
if (dataSet.isEmpty() && !hasItem) {
showEmptyViewMessage(context.getString(R.string.fragment_room_list_no_file_list_to_show))
} else {
if (dataSet.isNotEmpty()) {
hasItem = true
showFileList(dataSet, jsonObject.optString("total"))
}
}
} catch (exception: JSONException) {
showInvalidRequest()
}
}
private fun handleMembersJson(json: String) {
try {
val jsonObject = JSONObject(json)
......@@ -221,7 +285,7 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
showMemberList(dataSet, jsonObject.optString("total"))
}
}
}catch (exception: JSONException) {
} catch (exception: JSONException) {
showInvalidRequest()
}
}
......@@ -236,6 +300,17 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
.build()
}
private fun getLongTimestamp(timestamp: String): Long {
return if (timestamp.isNotBlank()) {
Timestamp.valueOf(getSafeTimestamp(timestamp)).time
} else {
0
}
}
private fun getSafeTimestamp(timestamp: String): String =
timestamp.replace("T", " ").replace("Z", "")
private fun showPinnedMessageList(dataSet: ArrayList<Message>, total: String) {
mainHandler.post {
view.showWaitingView(false)
......@@ -250,6 +325,13 @@ class RoomListPresenter(val context: Context, val view: RoomListContract.View) :
}
}
private fun showFileList(dataSet: ArrayList<Attachment>, total: String) {
mainHandler.post {
view.showWaitingView(false)
view.showFileList(dataSet, total)
}
}
private fun showMemberList(dataSet: ArrayList<User>, total: String) {
mainHandler.post {
view.showWaitingView(false)
......
......@@ -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();
......
......@@ -8,7 +8,8 @@ object UrlHelper {
* @param uri The URI.
* @return The URI whit no scheme (HTTP or HTTPS)
*/
fun removeUriScheme(uri: String) = uri.replace("http://", "").replace("https://", "")
fun removeUriScheme(uri: String) =
uri.replace("http://", "").replace("https://", "")
/**
* Returns the hostname with the security protocol (scheme) HTTPS.
......@@ -17,7 +18,7 @@ object UrlHelper {
* @return The hostname with the security protocol (scheme) HTTPS.
*/
fun getSafeHostname(hostname: String): String =
"https://" + hostname.replace("http://", "").replace("https://", "")
"https://" + removeUriScheme(hostname)
/**
* Returns an URL with no spaces and inverted slashes.
......@@ -25,17 +26,17 @@ object UrlHelper {
* @param url The URL.
* @return The URL with no spaces and inverted slashes.
*/
fun getUrl(url: String) =
fun getSafeUrl(url: String) =
url.replace(" ", "%20").replace("\\", "")
/**
* Returns an URL for a file.
* Returns an attachment link.
*
* @param path The path to the file.
* @param userId The user ID.
* @param token The token.
* @return The URL for a file
* @param hostname The hostname.
* @param fileId The file ID.
* @param fileName The file name.
* @return The attachment link.
*/
fun getUrlForFile(path: String, userId: String, token: String): String =
"https://" + removeUriScheme(getUrl(path)) + "?rc_uid=$userId" + "&rc_token=$token"
fun getAttachmentLink(hostname: String, fileId: String, fileName: String): String =
getSafeUrl(getSafeHostname(hostname) + "/file-upload/" + fileId + "/" + fileName)
}
\ No newline at end of file
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);
}
}
......@@ -4,14 +4,19 @@ import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.widget.message.RocketChatMessageLayout
import chat.rocket.android.helper.DateTime
import chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
import chat.rocket.core.models.Attachment
import kotlinx.android.synthetic.main.day.view.*
import kotlinx.android.synthetic.main.item_room_file.view.*
import java.sql.Timestamp
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomFileListAdapter(private var dataSet: List<String>) : RecyclerView.Adapter<RoomFileListAdapter.ViewHolder>() {
class RoomFileListAdapter(private var dataSet: List<Attachment>) : RecyclerView.Adapter<RoomFileListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_room_file, parent, false)
......@@ -19,17 +24,22 @@ class RoomFileListAdapter(private var dataSet: List<String>) : RecyclerView.Adap
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.fileNameLink.setText(dataSet[position])
val attachment = dataSet[position]
holder.newDay.text = DateTime.fromEpocMs(Timestamp.valueOf(attachment.timestamp).time, DateTime.Format.DATE)
holder.attachment.appendAttachmentView(attachment, true, false)
}
override fun getItemCount(): Int = dataSet.size
fun setDataSet(dataSet: List<String>) {
this.dataSet = dataSet
notifyDataSetChanged()
fun addDataSet(dataSet: List<Attachment>) {
val previousDataSetSize = this.dataSet.size
this.dataSet += dataSet
notifyItemRangeInserted(previousDataSetSize, dataSet.size)
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val fileNameLink : RocketChatMessageLayout = itemView.fileLink
val newDay: TextView = itemView.day
val attachment: RocketChatMessageAttachmentsLayout = itemView.attachment
}
}
\ No newline at end of file
......@@ -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)
);
......
......@@ -3,20 +3,20 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_16">
android:paddingRight="@dimen/margin_16"
android:paddingStart="@dimen/margin_16"
android:paddingLeft="@dimen/margin_16"
android:paddingEnd="@dimen/margin_16"
android:paddingBottom="@dimen/margin_16">
<chat.rocket.android.widget.message.RocketChatMessageLayout
android:id="@+id/fileLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16" />
<include
android:id="@+id/dayLayout"
layout="@layout/day" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/colorDivider"
android:layout_marginTop="@dimen/margin_8"
app:layout_constraintTop_toBottomOf="@+id/fileLink" />
<chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
android:id="@+id/attachment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_16"
app:layout_constraintTop_toBottomOf="@+id/dayLayout" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -10,9 +10,9 @@
android:title="@string/menu_favorite_messages"
app:showAsAction="never" />
<!--<item android:id="@+id/action_file_list"-->
<!--android:title="@string/menu_file_list"-->
<!--app:showAsAction="never" />-->
<item android:id="@+id/action_file_list"
android:title="@string/menu_file_list"
app:showAsAction="never" />
<item android:id="@+id/action_member_list"
android:title="@string/menu_member_list"
......
......@@ -21,20 +21,17 @@ class UrlHelperTest {
@Test
fun getUrlTest() {
assertEquals("https://demo.rocket.chat/GENERAL/file.txt", UrlHelper.getUrl("https://demo.rocket.chat/GENERAL/file.txt"))
assertEquals("http://demo.rocket.chat/GENERAL/file.txt", UrlHelper.getUrl("http://demo.rocket.chat/GENERAL/file.txt"))
assertEquals("demo.rocket.chat/GENERAL/file.txt", UrlHelper.getUrl("demo.rocket.chat/GENERAL/file.txt"))
assertEquals("demo.rocket.chat/GENERAL/a%20sample%20file.txt", UrlHelper.getUrl("demo.rocket.chat/GENERAL/a sample file.txt"))
assertEquals("demo.rocket.chat/GENERAL/file.txt", UrlHelper.getUrl("demo.rocket.chat\\/GENERAL\\/file.txt"))
assertEquals("https://demo.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("https://demo.rocket.chat/GENERAL/file.txt"))
assertEquals("http://demo.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("http://demo.rocket.chat/GENERAL/file.txt"))
assertEquals("demo.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("demo.rocket.chat/GENERAL/file.txt"))
assertEquals("demo.rocket.chat/GENERAL/a%20sample%20file.txt", UrlHelper.getSafeUrl("demo.rocket.chat/GENERAL/a sample file.txt"))
assertEquals("demo.rocket.chat/GENERAL/file.txt", UrlHelper.getSafeUrl("demo.rocket.chat\\/GENERAL\\/file.txt"))
}
@Test
fun getUrlForFileTest() {
assertEquals("https://demo.rocket.chat/GENERAL/file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("https://demo.rocket.chat/GENERAL/file.txt","userId", "token"))
assertEquals("https://demo.rocket.chat/GENERAL/file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("http://demo.rocket.chat/GENERAL/file.txt","userId", "token"))
assertEquals("https://demo.rocket.chat/GENERAL/file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("demo.rocket.chat/GENERAL/file.txt","userId", "token"))
assertEquals("https://demo.rocket.chat/GENERAL/a%20sample%20file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("demo.rocket.chat/GENERAL/a sample file.txt","userId", "token"))
assertEquals("https://demo.rocket.chat/GENERAL/file.txt?rc_uid=userId&rc_token=token", UrlHelper.getUrlForFile("demo.rocket.chat\\/GENERAL\\/file.txt","userId", "token"))
fun getAttachmentLinkTest() {
assertEquals("https://demo.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("https://demo.rocket.chat", "aFileId", "aFileName.txt"))
assertEquals("https://demo.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("http://demo.rocket.chat", "aFileId", "aFileName.txt"))
assertEquals("https://demo.rocket.chat/file-upload/aFileId/aFileName.txt", UrlHelper.getAttachmentLink("demo.rocket.chat", "aFileId", "aFileName.txt"))
}
}
\ No newline at end of file
......@@ -6,7 +6,9 @@ import android.support.graphics.drawable.VectorDrawableCompat
import chat.rocket.android.widget.R
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.drawable.ProgressBarDrawable
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.drawee.generic.GenericDraweeHierarchy
import com.facebook.drawee.generic.RoundingParams
import com.facebook.drawee.view.SimpleDraweeView
object FrescoHelper {
......@@ -31,6 +33,8 @@ object FrescoHelper {
val hierarchy: GenericDraweeHierarchy = draweeView.hierarchy
hierarchy.setPlaceholderImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.image_dummy, null))
hierarchy.setFailureImage(VectorDrawableCompat.create(draweeView.resources, R.drawable.image_error, null))
hierarchy.roundingParams = RoundingParams().setCornersRadii(5F, 5F, 5F, 5F)
hierarchy.actualImageScaleType = ScalingUtils.ScaleType.FIT_CENTER
hierarchy.setProgressBarImage(ProgressBarDrawable())
val controller = Fresco.newDraweeControllerBuilder()
......
......@@ -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
......
......@@ -71,21 +71,21 @@ public class RocketChatMessageAttachmentsLayout extends LinearLayout {
return;
}
this.attachments = attachments;
removeAllViews();
for (int i = 0, size = attachments.size(); i < size; i++) {
appendAttachmentView(attachments.get(i), autoloadImages);
appendAttachmentView(attachments.get(i), autoloadImages, true);
}
}
private void appendAttachmentView(Attachment attachment, boolean autoloadImages) {
public void appendAttachmentView(Attachment attachment, boolean autoloadImages, boolean showAttachmentStrip) {
if (attachment == null) {
return;
}
removeAllViews();
View attachmentView = inflater.inflate(R.layout.message_inline_attachment, this, false);
colorizeAttachmentBar(attachment, attachmentView);
colorizeAttachmentBar(attachment, attachmentView, showAttachmentStrip);
showAuthorAttachment(attachment, attachmentView);
showTitleAttachment(attachment, attachmentView);
showReferenceAttachment(attachment, attachmentView);
......@@ -97,9 +97,10 @@ public class RocketChatMessageAttachmentsLayout extends LinearLayout {
addView(attachmentView);
}
private void colorizeAttachmentBar(Attachment attachment, View attachmentView) {
private void colorizeAttachmentBar(Attachment attachment, View attachmentView, boolean showAttachmentStrip) {
final View attachmentStrip = attachmentView.findViewById(R.id.attachment_strip);
if (showAttachmentStrip) {
final String colorString = attachment.getColor();
if (TextUtils.isEmpty(colorString)) {
attachmentStrip.setBackgroundResource(R.color.inline_attachment_quote_line);
......@@ -111,6 +112,9 @@ public class RocketChatMessageAttachmentsLayout extends LinearLayout {
} catch (Exception e) {
attachmentStrip.setBackgroundResource(R.color.inline_attachment_quote_line);
}
} else {
attachmentStrip.setVisibility(GONE);
}
}
private void showAuthorAttachment(Attachment attachment, View attachmentView) {
......@@ -204,8 +208,7 @@ public class RocketChatMessageAttachmentsLayout extends LinearLayout {
}
}
private void showImageAttachment(Attachment attachment, View attachmentView,
boolean autoloadImages) {
private void showImageAttachment(Attachment attachment, View attachmentView, boolean autoloadImages) {
final View imageContainer = attachmentView.findViewById(R.id.image_container);
if (attachment.getImageUrl() == null) {
imageContainer.setVisibility(GONE);
......
<!-- 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"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:color="@color/inline_attachment_box_outline"
android:width="1dp"/>
<solid android:color="@color/inline_attachment_box_background"/>
<corners android:radius="2dp"/>
android:width="1dp"
android:color="@color/inline_attachment_box_outline" />
<solid
android:color="@color/inline_attachment_box_background" />
<corners
android:radius="2dp" />
</shape>
\ 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: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:minHeight="48dp"
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:inputType="textCapSentences|textMultiLine"
android:background="@null"
android:hint="@string/message_composer_message_hint"
android:textSize="14sp"
android:minLines="1"
android:inputType="textCapSentences|textMultiLine"
android:maxLines="4"
android:background="@null"
app:layout_constraintTop_toTopOf="parent"
android:minLines="1"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/container"
app:layout_constraintBottom_toBottomOf="parent"/>
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_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toTopOf="@+id/editor"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@+id/editor"
app:layout_constraintLeft_toRightOf="@+id/editor"
app:layout_constraintBottom_toBottomOf="@+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"
android:background="?attr/selectableItemBackgroundBorderless"/>
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"
android:background="?attr/selectableItemBackgroundBorderless"/>
app:srcCompat="@drawable/ic_send_black_24dp" />
</FrameLayout>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
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:orientation="horizontal"
android:paddingTop="4dp"
android:paddingBottom="4dp">
android:paddingBottom="4dp"
android:paddingTop="4dp">
<View
android:id="@+id/attachment_strip"
android:layout_width="3dp"
android:layout_height="match_parent"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:background="@color/inline_attachment_quote_line" />
......@@ -26,16 +27,16 @@
android:id="@+id/author_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
android:orientation="horizontal">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/author_icon"
android:layout_width="16dp"
android:layout_height="16dp"
tools:src="@drawable/circle_black"
fresco:actualImageScaleType="fitCenter" />
fresco:actualImageScaleType="fitCenter"
tools:src="@drawable/circle_black" />
<android.support.v4.widget.Space
android:layout_width="8dp"
......@@ -62,27 +63,27 @@
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.RocketChat.MessageAttachment.Title"
android:layout_gravity="center"
android:background="?attr/selectableItemBackground"
android:textAppearance="@style/TextAppearance.RocketChat.MessageAttachment.Title"
tools:text="Attachment Example" />
<LinearLayout
android:id="@+id/ref_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
android:orientation="horizontal">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/thumb"
android:layout_width="32dp"
android:layout_height="32dp"
tools:src="@drawable/circle_black"
fresco:actualImageScaleType="fitCenter" />
fresco:actualImageScaleType="fitCenter"
tools:src="@drawable/circle_black" />
<android.support.v4.widget.Space
android:layout_width="8dp"
......@@ -93,33 +94,32 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Bradley Hilton" />
</LinearLayout>
<FrameLayout
android:id="@+id/image_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:padding="5dp"
android:background="@drawable/inline_attachment_background">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_marginBottom="8dp"
fresco:actualImageScaleType="fitStart" />
android:layout_width="200dp"
android:layout_height="200dp"/>
<TextView
android:id="@+id/image_load"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_width="200dp"
android:layout_height="200dp"
android:gravity="center_horizontal|bottom"
android:paddingBottom="16dp"
android:text="@string/click_to_load" />
</FrameLayout>
<!-- audio -->
<!-- video -->
</LinearLayout>
</LinearLayout>
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