Commit 9d8197a7 authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #454 from filipedelimabrito/iss424

[NEW] Room's menu (REST API calls)
parents 16052e46 1860afc3
......@@ -7,6 +7,7 @@ repositories {
}
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'com.jakewharton.hugo'
apply plugin: 'com.github.triplet.play'
......@@ -103,7 +104,10 @@ android {
manifest.srcFile 'src/release/AndroidManifest.xml'
}
test.java.srcDirs += 'src/test/kotlin'
test {
test.java.srcDirs += 'src/test/kotlin'
androidTest.java.srcDirs += 'src/androidTest/kotlin'
}
}
}
......@@ -137,6 +141,8 @@ dependencies {
compile 'com.android.support:multidex:1.0.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test:$rootProject.ext.kotlinVersion"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$rootProject.ext.kotlinVersion"
compile "com.google.firebase:firebase-core:$playLibVersion"
compile "com.google.firebase:firebase-crash:$playLibVersion"
......
......@@ -10,6 +10,13 @@ import okhttp3.OkHttpClient
object OkHttpHelper {
fun getClient(): OkHttpClient {
if (httpClient == null) {
httpClient = OkHttpClient()
}
return httpClient ?: throw AssertionError("httpClient set to null by another thread")
}
fun getClientForUploadFile(): OkHttpClient {
if (httpClientForUploadFile == null) {
httpClientForUploadFile = OkHttpClient.Builder().build()
......@@ -41,6 +48,7 @@ object OkHttpHelper {
return httpClientForWS ?: throw AssertionError("httpClientForWS set to null by another thread")
}
private var httpClient: OkHttpClient? = null
private var httpClientForUploadFile: OkHttpClient? = null
private var httpClientForDownloadFile: OkHttpClient? = null
private var httpClientForWS: OkHttpClient? = null
......
......@@ -32,6 +32,11 @@
</intent-filter>
</activity>
<activity
android:name=".activity.room.RoomActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activity.AddServerActivity"
android:configChanges="orientation|screenSize"
......
......@@ -48,7 +48,6 @@ public class MainActivity extends AbstractAuthedActivity implements MainContract
private SlidingPaneLayout pane;
private MainContract.Presenter presenter;
@Override
protected int getLayoutContainerForFragment() {
return R.id.activity_main_container;
}
......
package chat.rocket.android.activity.room
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.fragment.chatroom.list.RoomListFragment
import kotlinx.android.synthetic.main.activity_room.*
class RoomActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_room)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
val extras = intent.extras
val roomListFragment = RoomListFragment.newInstance(extras.getInt("actionId"),
extras.getString("roomId"),
extras.getString("roomType"),
extras.getString("hostname"),
extras.getString("token"),
extras.getString("userId"))
addFragment(roomListFragment, "roomListFragment")
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun addFragment(fragment: Fragment, tag: String) {
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, fragment, tag)
.commit()
}
}
\ No newline at end of file
package chat.rocket.android.api.rest
import chat.rocket.android.helper.UrlHelper
import chat.rocket.core.models.Room
import okhttp3.HttpUrl
import okhttp3.Request
/**
* Helper class for dealing with Rest API calls.
*
* @see <a href="https://rocket.chat/docs/developer-guides/rest-api">https://rocket.chat/docs/developer-guides/rest-api</a>.
*/
object RestApiHelper {
/**
* Returns an OkHttp3 request for pinned messages.
*
* @param roomId The ID of the room.
* @param roomType The type of the room.
* @param hostname The server hostname.
* @param token The token.
* @param userId The user Id.
* @param offset The offset to paging which specifies the first entry to return from a collection.
* @return An OkHttp3 request.
*/
fun getRequestForPinnedMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForMessages(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("query", "{\"pinned\":true}")
?.addQueryParameter("offset", offset)
?.build()
return Request.Builder()
.url(parsedHttpUrl)
.get()
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.build()
}
/**
* Returns an OkHttp3 request for favorite messages.
*
* @param roomId The ID of the room.
* @param roomType The type of the room.
* @param hostname The server hostname.
* @param token The token.
* @param userId The user Id.
* @param offset The offset to paging which specifies the first entry to return from a collection.
* @return An OkHttp3 request.
*/
fun getRequestForFavoriteMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForMessages(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("query", "{\"starred._id\":{\"\$in\":[\"$userId\"] } }")
?.addQueryParameter("offset", offset)
?.build()
return Request.Builder()
.url(parsedHttpUrl)
.get()
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.build()
}
/**
* Returns an OkHttp3 request for file list.
*
* @param roomId The ID of the room.
* @param roomType The type of the room.
* @param hostname The server hostname.
* @param token The token.
* @param userId The user Id.
* @param offset The offset to paging which specifies the first entry to return from a collection.
* @return An OkHttp3 request.
*/
fun getRequestForFileList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForFileList(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("offset", offset)
?.build()
return Request.Builder()
.url(parsedHttpUrl)
.get()
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.build()
}
/**
* Returns an OkHttp3 request for member list.
*
* @param roomId The ID of the room.
* @param roomType The type of the room.
* @param hostname The server hostname.
* @param token The token.
* @param userId The user Id.
* @param offset The offset to paging which specifies the first entry to return from a collection.
* @return An OkHttp3 request.
*/
fun getRequestForMemberList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: String): Request {
val parsedHttpUrl = HttpUrl.parse(getEndpointUrlForMemberList(roomType, hostname))
?.newBuilder()
?.addQueryParameter("roomId", roomId)
?.addQueryParameter("offset", offset)
?.build()
return Request.Builder()
.url(parsedHttpUrl)
.get()
.addHeader("X-Auth-Token", token)
.addHeader("X-User-Id", userId)
.build()
}
/**
* Returns a Rest API endpoint URL for favorite or pinned messages accordingly with the room type and the server hostname.
*
* @param roomType The type of the room.
* @param hostname The server hostname.
* @return A Rest API URL endpoint.
*/
fun getEndpointUrlForMessages(roomType: String, hostname: String): String =
UrlHelper.getSafeHostname(hostname) + getRestApiUrlForMessages(roomType)
/**
* Returns a Rest API endpoint URL for file list accordingly with the room type and the server hostname.
*
* @param roomType The type of the room.
* @param hostname The server hostname.
* @return A Rest API URL endpoint.
*/
fun getEndpointUrlForFileList(roomType: String, hostname: String): String =
UrlHelper.getSafeHostname(hostname) + getRestApiUrlForFileList(roomType)
/**
* Returns a Rest API endpoint URL for member list accordingly with the room type and the server hostname.
*
* @param roomType The type of the room.
* @param hostname The server hostname.
* @return A Rest API URL endpoint.
*/
fun getEndpointUrlForMemberList(roomType: String, hostname: String): String =
UrlHelper.getSafeHostname(hostname) + getRestApiUrlForMemberList(roomType)
/**
* Returns the correspondent Rest API URL accordingly with the room type to get its favorite or pinned messages.
*
* @param roomType The type of the room.
* @return A Rest API URL or null if the room type does not match.
*/
fun getRestApiUrlForMessages(roomType: String): String? {
var restApiUrl: String? = null
when (roomType) {
Room.TYPE_CHANNEL -> restApiUrl = "/api/v1/channels.messages"
Room.TYPE_PRIVATE -> restApiUrl = "/api/v1/groups.messages"
Room.TYPE_DIRECT_MESSAGE -> restApiUrl = "/api/v1/dm.messages"
}
return restApiUrl
}
/**
* Returns the correspondent Rest API URL accordingly with the room type to get its file list.
*
* @param roomType The type of the room.
* @return A Rest API URL or null if the room type does not match.
*/
fun getRestApiUrlForFileList(roomType: String): String? {
var restApiUrl: String? = null
when (roomType) {
Room.TYPE_CHANNEL -> restApiUrl = "/api/v1/channels.files"
Room.TYPE_PRIVATE -> restApiUrl = "/api/v1/groups.files"
Room.TYPE_DIRECT_MESSAGE -> restApiUrl = "/api/v1/dm.files"
}
return restApiUrl
}
/**
* Returns the correspondent Rest API URL accordingly with the room type to get its members list.
*
* @param roomType The type of the room.
* @return A Rest API URL or null if the room type does not match.
*/
fun getRestApiUrlForMemberList(roomType: String): String? {
var restApiUrl: String? = null
when (roomType) {
Room.TYPE_CHANNEL -> restApiUrl = "/api/v1/channels.members"
Room.TYPE_PRIVATE -> restApiUrl = "/api/v1/groups.members"
Room.TYPE_DIRECT_MESSAGE -> restApiUrl = "/api/v1/dm.members"
}
return restApiUrl
}
}
\ No newline at end of file
......@@ -21,4 +21,12 @@ public class RocketChatAbsoluteUrl implements AbsoluteUrl {
public String from(String url) {
return url.startsWith("/") ? baseUrl + url + "?rc_uid=" + userId + "&rc_token=" + token : url;
}
}
public String getUserId() {
return userId;
}
public String getToken() {
return token;
}
}
\ No newline at end of file
......@@ -13,8 +13,6 @@ import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.support.v4.app.DialogFragment;
import android.support.v4.os.BuildCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
......@@ -22,23 +20,20 @@ import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.hadisatrio.optional.Optional;
import com.jakewharton.rxbinding2.support.v4.widget.RxDrawerLayout;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
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.chatroom.dialog.UsersOfRoomDialogFragment;
import chat.rocket.android.fragment.sidebar.SidebarMainFragment;
import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.FileUploadHelper;
import chat.rocket.android.helper.LoadMoreScrollListener;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.helper.RecyclerViewAutoScrollManager;
import chat.rocket.android.helper.RecyclerViewScrolledToBottomListener;
......@@ -52,10 +47,8 @@ import chat.rocket.android.layouthelper.extra_action.AbstractExtraActionItem;
import chat.rocket.android.layouthelper.extra_action.MessageExtraActionBehavior;
import chat.rocket.android.layouthelper.extra_action.upload.AbstractUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.AudioUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.FileUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.ImageUploadActionItem;
import chat.rocket.android.layouthelper.extra_action.upload.VideoUploadActionItem;
import chat.rocket.android.log.RCLog;
import chat.rocket.android.renderer.RocketChatUserStatusProvider;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.android.service.temp.DeafultTempSpotlightRoomCaller;
......@@ -99,563 +92,567 @@ public class RoomFragment extends AbstractChatRoomFragment implements
ModelListAdapter.OnItemLongClickListener<PairedMessage>,
RoomContract.View {
private static final int DIALOG_ID = 1;
private static final String HOSTNAME = "hostname";
private static final String ROOM_ID = "roomId";
private static final int DIALOG_ID = 1;
private static final String HOSTNAME = "hostname";
private static final String ROOM_ID = "roomId";
private String hostname;
private String roomId;
private LoadMoreScrollListener scrollListener;
private MessageFormManager messageFormManager;
private RecyclerView messageRecyclerView;
private RecyclerViewAutoScrollManager recyclerViewAutoScrollManager;
protected AbstractNewMessageIndicatorManager newMessageIndicatorManager;
protected Snackbar unreadIndicator;
private boolean previousUnreadMessageExists;
private MessageListAdapter messageListAdapter;
private AutocompleteManager autocompleteManager;
private List<AbstractExtraActionItem> extraActionItems;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
protected RoomContract.Presenter presenter;
private RealmRoomRepository roomRepository;
private RealmUserRepository userRepository;
private MethodCallHelper methodCallHelper;
private AbsoluteUrlHelper absoluteUrlHelper;
private Message edittingMessage = null;
private SlidingPaneLayout pane;
private SidebarMainFragment sidebarFragment;
public RoomFragment() {}
/**
* create fragment with roomId.
*/
public static RoomFragment create(String hostname, String roomId) {
Bundle args = new Bundle();
args.putString(HOSTNAME, hostname);
args.putString(ROOM_ID, roomId);
RoomFragment fragment = new RoomFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
hostname = args.getString(HOSTNAME);
roomId = args.getString(ROOM_ID);
roomRepository = new RealmRoomRepository(hostname);
MessageInteractor messageInteractor = new MessageInteractor(
new RealmMessageRepository(hostname),
roomRepository
);
userRepository = new RealmUserRepository(hostname);
absoluteUrlHelper = new AbsoluteUrlHelper(
hostname,
new RealmServerInfoRepository(),
userRepository,
new SessionInteractor(new RealmSessionRepository(hostname))
);
methodCallHelper = new MethodCallHelper(getContext(), hostname);
presenter = new RoomPresenter(
roomId,
userRepository,
messageInteractor,
roomRepository,
absoluteUrlHelper,
methodCallHelper,
ConnectivityManager.getInstance(getContext())
);
if (savedInstanceState == null) {
presenter.loadMessages();
}
}
@Override
protected int getLayout() {
return R.layout.fragment_room;
}
@Override
protected void onSetupView() {
pane = getActivity().findViewById(R.id.sliding_pane);
messageRecyclerView = rootView.findViewById(R.id.messageRecyclerView);
messageListAdapter = new MessageListAdapter(getContext(), hostname);
messageRecyclerView.setAdapter(messageListAdapter);
messageListAdapter.setOnItemClickListener(this);
messageListAdapter.setOnItemLongClickListener(this);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, true);
messageRecyclerView.setLayoutManager(linearLayoutManager);
recyclerViewAutoScrollManager = new RecyclerViewAutoScrollManager(linearLayoutManager) {
@Override
protected void onAutoScrollMissed() {
if (newMessageIndicatorManager != null) {
presenter.onUnreadCount();
}
}
};
messageListAdapter.registerAdapterDataObserver(recyclerViewAutoScrollManager);
scrollListener = new LoadMoreScrollListener(linearLayoutManager, 40) {
@Override
public void requestMoreItem() {
presenter.loadMoreMessages();
}
};
messageRecyclerView.addOnScrollListener(scrollListener);
messageRecyclerView.addOnScrollListener(new RecyclerViewScrolledToBottomListener(linearLayoutManager, 1, this::markAsReadIfNeeded));
newMessageIndicatorManager = new AbstractNewMessageIndicatorManager() {
@Override
protected void onShowIndicator(int count, boolean onlyAlreadyShown) {
if ((onlyAlreadyShown && unreadIndicator != null && unreadIndicator.isShown()) || !onlyAlreadyShown) {
unreadIndicator = getUnreadCountIndicatorView(count);
unreadIndicator.show();
}
}
private String hostname;
private String token;
private String userId;
private String roomId;
private String roomType;
private LoadMoreScrollListener scrollListener;
private MessageFormManager messageFormManager;
private RecyclerView messageRecyclerView;
private RecyclerViewAutoScrollManager recyclerViewAutoScrollManager;
protected AbstractNewMessageIndicatorManager newMessageIndicatorManager;
protected Snackbar unreadIndicator;
private boolean previousUnreadMessageExists;
private MessageListAdapter messageListAdapter;
private AutocompleteManager autocompleteManager;
private List<AbstractExtraActionItem> extraActionItems;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
protected RoomContract.Presenter presenter;
private RealmRoomRepository roomRepository;
private RealmUserRepository userRepository;
private MethodCallHelper methodCallHelper;
private AbsoluteUrlHelper absoluteUrlHelper;
private Message edittingMessage = null;
private RoomToolbar toolbar;
private SlidingPaneLayout pane;
private SidebarMainFragment sidebarFragment;
public RoomFragment() {
}
/**
* create fragment with roomId.
*/
public static RoomFragment create(String hostname, String roomId) {
Bundle args = new Bundle();
args.putString(HOSTNAME, hostname);
args.putString(ROOM_ID, roomId);
RoomFragment fragment = new RoomFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
@Override
protected void onHideIndicator() {
if (unreadIndicator != null && unreadIndicator.isShown()) {
unreadIndicator.dismiss();
Bundle args = getArguments();
hostname = args.getString(HOSTNAME);
roomId = args.getString(ROOM_ID);
roomRepository = new RealmRoomRepository(hostname);
MessageInteractor messageInteractor = new MessageInteractor(
new RealmMessageRepository(hostname),
roomRepository
);
userRepository = new RealmUserRepository(hostname);
absoluteUrlHelper = new AbsoluteUrlHelper(
hostname,
new RealmServerInfoRepository(),
userRepository,
new SessionInteractor(new RealmSessionRepository(hostname))
);
methodCallHelper = new MethodCallHelper(getContext(), hostname);
presenter = new RoomPresenter(
roomId,
userRepository,
messageInteractor,
roomRepository,
absoluteUrlHelper,
methodCallHelper,
ConnectivityManager.getInstance(getContext())
);
if (savedInstanceState == null) {
presenter.loadMessages();
}
}
};
setupSidebar();
setupSideMenu();
setupMessageComposer();
setupMessageActions();
}
private void setupMessageActions() {
extraActionItems = new ArrayList<>(4); // fixed number as of now
extraActionItems.add(new ImageUploadActionItem());
extraActionItems.add(new AudioUploadActionItem());
extraActionItems.add(new VideoUploadActionItem());
extraActionItems.add(new FileUploadActionItem());
}
private void scrollToLatestMessage() {
if (messageRecyclerView != null)
messageRecyclerView.scrollToPosition(0);
}
protected Snackbar getUnreadCountIndicatorView(int count) {
// TODO: replace with another custom View widget, not to hide message composer.
final String caption = getResources().getQuantityString(
R.plurals.fmt_dialog_view_latest_message_title, count, count);
return Snackbar.make(rootView, caption, Snackbar.LENGTH_LONG)
.setAction(R.string.dialog_view_latest_message_action, view -> scrollToLatestMessage());
}
@Override
public void onDestroyView() {
RecyclerView.Adapter adapter = messageRecyclerView.getAdapter();
if (adapter != null)
adapter.unregisterAdapterDataObserver(recyclerViewAutoScrollManager);
compositeDisposable.clear();
if (autocompleteManager != null) {
autocompleteManager.dispose();
autocompleteManager = null;
}
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");
return true;
}
private void setupSideMenu() {
View sideMenu = rootView.findViewById(R.id.room_side_menu);
sideMenu.findViewById(R.id.btn_users).setOnClickListener(view -> {
UsersOfRoomDialogFragment.create(roomId, hostname)
.show(getFragmentManager(), "UsersOfRoomDialogFragment");
closeSideMenuIfNeeded();
});
DrawerLayout drawerLayout = rootView.findViewById(R.id.drawer_layout);
if (drawerLayout != null && pane != null) {
compositeDisposable.add(RxDrawerLayout.drawerOpen(drawerLayout, GravityCompat.END)
.compose(bindToLifecycle())
.subscribe(
opened -> {
try {
Field fieldSlidable = pane.getClass().getDeclaredField("mCanSlide");
fieldSlidable.setAccessible(true);
fieldSlidable.setBoolean(pane, !opened);
} catch (Exception exception) {
RCLog.w(exception);
}
@Override
protected int getLayout() {
return R.layout.fragment_room;
}
@Override
protected void onSetupView() {
pane = getActivity().findViewById(R.id.sliding_pane);
messageRecyclerView = rootView.findViewById(R.id.messageRecyclerView);
messageListAdapter = new MessageListAdapter(getContext(), hostname);
messageRecyclerView.setAdapter(messageListAdapter);
messageListAdapter.setOnItemClickListener(this);
messageListAdapter.setOnItemLongClickListener(this);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, true);
messageRecyclerView.setLayoutManager(linearLayoutManager);
recyclerViewAutoScrollManager = new RecyclerViewAutoScrollManager(linearLayoutManager) {
@Override
protected void onAutoScrollMissed() {
if (newMessageIndicatorManager != null) {
presenter.onUnreadCount();
}
},
Logger::report
)
);
}
}
private void setupSidebar() {
SlidingPaneLayout subPane = getActivity().findViewById(R.id.sub_sliding_pane);
RoomToolbar toolbar = getActivity().findViewById(R.id.activity_main_toolbar);
sidebarFragment = (SidebarMainFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.sidebar_fragment_container);
pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
messageFormManager.enableComposingText(false);
sidebarFragment.clearSearchViewFocus();
//Ref: ActionBarDrawerToggle#setProgress
toolbar.setNavigationIconProgress(v);
}
@Override
public void onPanelOpened(View view) {
toolbar.setNavigationIconVerticalMirror(true);
}
@Override
public void onPanelClosed(View view) {
messageFormManager.enableComposingText(true);
toolbar.setNavigationIconVerticalMirror(false);
subPane.closePane();
closeUserActionContainer();
}
});
toolbar.setNavigationOnClickListener(view -> {
if (pane.isSlideable() && !pane.isOpen()) {
pane.openPane();
}
});
}
public void closeUserActionContainer() {
sidebarFragment.closeUserActionContainer();
}
private boolean closeSideMenuIfNeeded() {
DrawerLayout drawerLayout = rootView.findViewById(R.id.drawer_layout);
if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.END)) {
drawerLayout.closeDrawer(GravityCompat.END);
return true;
}
return false;
}
private void setupMessageComposer() {
final MessageFormLayout messageFormLayout = rootView.findViewById(R.id.messageComposer);
messageFormManager = new MessageFormManager(messageFormLayout, this::showExtraActionSelectionDialog);
messageFormManager.setSendMessageCallback(this::sendMessage);
messageFormLayout.setEditTextCommitContentListener(this::onCommitContent);
autocompleteManager = new AutocompleteManager(rootView.findViewById(R.id.messageListRelativeLayout));
autocompleteManager.registerSource(
new ChannelSource(
new AutocompleteChannelInteractor(
roomRepository,
new RealmSpotlightRoomRepository(hostname),
new DeafultTempSpotlightRoomCaller(methodCallHelper)
),
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
)
);
Disposable disposable = Single.zip(
absoluteUrlHelper.getRocketChatAbsoluteUrl(),
roomRepository.getById(roomId).first(Optional.absent()),
Pair::create
)
.subscribe(
pair -> {
if (pair.first.isPresent() && pair.second.isPresent()) {
autocompleteManager.registerSource(
new UserSource(
new AutocompleteUserInteractor(
pair.second.get(),
userRepository,
new RealmMessageRepository(hostname),
new RealmSpotlightUserRepository(hostname),
new DefaultTempSpotlightUserCaller(methodCallHelper)
}
};
messageListAdapter.registerAdapterDataObserver(recyclerViewAutoScrollManager);
scrollListener = new LoadMoreScrollListener(linearLayoutManager, 40) {
@Override
public void requestMoreItem() {
presenter.loadMoreMessages();
}
};
messageRecyclerView.addOnScrollListener(scrollListener);
messageRecyclerView.addOnScrollListener(new RecyclerViewScrolledToBottomListener(linearLayoutManager, 1, this::markAsReadIfNeeded));
newMessageIndicatorManager = new AbstractNewMessageIndicatorManager() {
@Override
protected void onShowIndicator(int count, boolean onlyAlreadyShown) {
if ((onlyAlreadyShown && unreadIndicator != null && unreadIndicator.isShown()) || !onlyAlreadyShown) {
unreadIndicator = getUnreadCountIndicatorView(count);
unreadIndicator.show();
}
}
@Override
protected void onHideIndicator() {
if (unreadIndicator != null && unreadIndicator.isShown()) {
unreadIndicator.dismiss();
}
}
};
setupToolbar();
setupSidebar();
setupMessageComposer();
setupMessageActions();
}
private void setupMessageActions() {
extraActionItems = new ArrayList<>(3); // fixed number as of now
extraActionItems.add(new ImageUploadActionItem());
extraActionItems.add(new AudioUploadActionItem());
extraActionItems.add(new VideoUploadActionItem());
}
private void scrollToLatestMessage() {
if (messageRecyclerView != null)
messageRecyclerView.scrollToPosition(0);
}
protected Snackbar getUnreadCountIndicatorView(int count) {
// TODO: replace with another custom View widget, not to hide message composer.
final String caption = getResources().getQuantityString(
R.plurals.fmt_dialog_view_latest_message_title, count, count);
return Snackbar.make(rootView, caption, Snackbar.LENGTH_LONG)
.setAction(R.string.dialog_view_latest_message_action, view -> scrollToLatestMessage());
}
@Override
public void onDestroyView() {
RecyclerView.Adapter adapter = messageRecyclerView.getAdapter();
if (adapter != null)
adapter.unregisterAdapterDataObserver(recyclerViewAutoScrollManager);
compositeDisposable.clear();
if (autocompleteManager != null) {
autocompleteManager.dispose();
autocompleteManager = null;
}
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");
return true;
}
private void setupToolbar() {
toolbar = getActivity().findViewById(R.id.activity_main_toolbar);
toolbar.getMenu().clear();
toolbar.inflateMenu(R.menu.menu_room);
toolbar.setNavigationOnClickListener(view -> {
if (pane.isSlideable() && !pane.isOpen()) {
pane.openPane();
}
});
toolbar.setOnMenuItemClickListener(menuItem -> {
switch (menuItem.getItemId()) {
case R.id.action_pinned_messages:
showRoomListFragment(R.id.action_pinned_messages);
break;
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_member_list:
showRoomListFragment(R.id.action_member_list);
break;
default:
return super.onOptionsItemSelected(menuItem);
}
return true;
});
}
private void setupSidebar() {
SlidingPaneLayout subPane = getActivity().findViewById(R.id.sub_sliding_pane);
sidebarFragment = (SidebarMainFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.sidebar_fragment_container);
pane.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() {
@Override
public void onPanelSlide(View view, float v) {
messageFormManager.enableComposingText(false);
sidebarFragment.clearSearchViewFocus();
//Ref: ActionBarDrawerToggle#setProgress
toolbar.setNavigationIconProgress(v);
}
@Override
public void onPanelOpened(View view) {
toolbar.setNavigationIconVerticalMirror(true);
}
@Override
public void onPanelClosed(View view) {
messageFormManager.enableComposingText(true);
toolbar.setNavigationIconVerticalMirror(false);
subPane.closePane();
closeUserActionContainer();
}
});
}
public void closeUserActionContainer() {
sidebarFragment.closeUserActionContainer();
}
private void setupMessageComposer() {
final MessageFormLayout messageFormLayout = rootView.findViewById(R.id.messageComposer);
messageFormManager = new MessageFormManager(messageFormLayout, this::showExtraActionSelectionDialog);
messageFormManager.setSendMessageCallback(this::sendMessage);
messageFormLayout.setEditTextCommitContentListener(this::onCommitContent);
autocompleteManager = new AutocompleteManager(rootView.findViewById(R.id.messageListRelativeLayout));
autocompleteManager.registerSource(
new ChannelSource(
new AutocompleteChannelInteractor(
roomRepository,
new RealmSpotlightRoomRepository(hostname),
new DeafultTempSpotlightRoomCaller(methodCallHelper)
),
pair.first.get(),
RocketChatUserStatusProvider.INSTANCE,
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
)
)
);
Disposable disposable = Single.zip(
absoluteUrlHelper.getRocketChatAbsoluteUrl(),
roomRepository.getById(roomId).first(Optional.absent()),
Pair::create
)
.subscribe(
pair -> {
if (pair.first.isPresent() && pair.second.isPresent()) {
autocompleteManager.registerSource(
new UserSource(
new AutocompleteUserInteractor(
pair.second.get(),
userRepository,
new RealmMessageRepository(hostname),
new RealmSpotlightUserRepository(hostname),
new DefaultTempSpotlightUserCaller(methodCallHelper)
),
pair.first.get(),
RocketChatUserStatusProvider.INSTANCE,
AndroidSchedulers.from(BackgroundLooper.get()),
AndroidSchedulers.mainThread()
)
);
}
},
throwable -> {
}
);
}
},
Logger::report
compositeDisposable.add(disposable);
autocompleteManager.bindTo(
messageFormLayout.getEditText(),
messageFormLayout
);
}
compositeDisposable.add(disposable);
autocompleteManager.bindTo(
messageFormLayout.getEditText(),
messageFormLayout
);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != AbstractUploadActionItem.RC_UPL || resultCode != Activity.RESULT_OK) {
return;
}
if (data == null || data.getData() == null) {
return;
}
uploadFile(data.getData());
}
private void uploadFile(Uri uri) {
String uplId = new FileUploadHelper(getContext(), RealmStore.get(hostname))
.requestUploading(roomId, uri);
if (!TextUtils.isEmpty(uplId)) {
FileUploadProgressDialogFragment.create(hostname, roomId, uplId)
.show(getFragmentManager(), "FileUploadProgressDialogFragment");
} else {
// show error.
}
}
private void markAsReadIfNeeded() {
presenter.onMarkAsRead();
}
@Override
public void onResume() {
super.onResume();
presenter.bindView(this);
closeSideMenuIfNeeded();
}
@Override
public void onPause() {
presenter.release();
super.onPause();
}
private void showExtraActionSelectionDialog() {
final DialogFragment fragment = ExtraActionPickerDialogFragment
.create(new ArrayList<>(extraActionItems));
fragment.setTargetFragment(this, DIALOG_ID);
fragment.show(getFragmentManager(), "ExtraActionPickerDialogFragment");
}
@Override
public void onItemSelected(int itemId) {
for (AbstractExtraActionItem extraActionItem : extraActionItems) {
if (extraActionItem.getItemId() == itemId) {
RoomFragmentPermissionsDispatcher.onExtraActionSelectedWithCheck(RoomFragment.this, extraActionItem);
return;
}
}
}
@Override
public boolean onBackPressed() {
if (edittingMessage != null) {
edittingMessage = null;
messageFormManager.clearComposingText();
return true;
}
return closeSideMenuIfNeeded();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
RoomFragmentPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
protected void onExtraActionSelected(MessageExtraActionBehavior action) {
action.handleItemSelectedOnFragment(RoomFragment.this);
}
private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
Bundle opts, String[] supportedMimeTypes) {
boolean supported = false;
for (final String mimeType : supportedMimeTypes) {
if (inputContentInfo.getDescription().hasMimeType(mimeType)) {
supported = true;
break;
}
}
if (!supported) {
return false;
}
if (BuildCompat.isAtLeastNMR1()
&& (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
try {
inputContentInfo.requestPermission();
} catch (Exception e) {
Logger.report(e);
return false;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != AbstractUploadActionItem.RC_UPL || resultCode != Activity.RESULT_OK) {
return;
}
if (data == null || data.getData() == null) {
return;
}
uploadFile(data.getData());
}
Uri linkUri = inputContentInfo.getLinkUri();
if (linkUri == null) {
return false;
private void uploadFile(Uri uri) {
String uplId = new FileUploadHelper(getContext(), RealmStore.get(hostname))
.requestUploading(roomId, uri);
if (!TextUtils.isEmpty(uplId)) {
FileUploadProgressDialogFragment.create(hostname, roomId, uplId)
.show(getFragmentManager(), "FileUploadProgressDialogFragment");
} else {
// show error.
}
}
sendMessage(linkUri.toString());
private void markAsReadIfNeeded() {
presenter.onMarkAsRead();
}
try {
inputContentInfo.releasePermission();
} catch (Exception e) {
Logger.report(e);
@Override
public void onResume() {
super.onResume();
presenter.bindView(this);
}
return true;
}
@Override
public void onPause() {
presenter.release();
super.onPause();
}
private void sendMessage(String messageText) {
if (edittingMessage == null) {
presenter.sendMessage(messageText);
} else {
presenter.updateMessage(edittingMessage, messageText);
private void showExtraActionSelectionDialog() {
final DialogFragment fragment = ExtraActionPickerDialogFragment
.create(new ArrayList<>(extraActionItems));
fragment.setTargetFragment(this, DIALOG_ID);
fragment.show(getFragmentManager(), "ExtraActionPickerDialogFragment");
}
}
@Override
public void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl) {
messageListAdapter.setAbsoluteUrl(rocketChatAbsoluteUrl);
}
@Override
public void onItemSelected(int itemId) {
for (AbstractExtraActionItem extraActionItem : extraActionItems) {
if (extraActionItem.getItemId() == itemId) {
RoomFragmentPermissionsDispatcher.onExtraActionSelectedWithCheck(RoomFragment.this, extraActionItem);
return;
}
}
}
@Override
public void render(Room room) {
setToolbarTitle(room.getName());
@Override
public boolean onBackPressed() {
if (edittingMessage != null) {
edittingMessage = null;
messageFormManager.clearComposingText();
}
return false;
}
boolean unreadMessageExists = room.isAlert();
if (newMessageIndicatorManager != null && previousUnreadMessageExists && !unreadMessageExists) {
newMessageIndicatorManager.reset();
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
RoomFragmentPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
previousUnreadMessageExists = unreadMessageExists;
if (room.isChannel()) {
showToolbarPublicChannelIcon();
return;
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
protected void onExtraActionSelected(MessageExtraActionBehavior action) {
action.handleItemSelectedOnFragment(RoomFragment.this);
}
if (room.isPrivate()) {
showToolbarPrivateChannelIcon();
private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
Bundle opts, String[] supportedMimeTypes) {
boolean supported = false;
for (final String mimeType : supportedMimeTypes) {
if (inputContentInfo.getDescription().hasMimeType(mimeType)) {
supported = true;
break;
}
}
if (!supported) {
return false;
}
if (BuildCompat.isAtLeastNMR1()
&& (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
try {
inputContentInfo.requestPermission();
} catch (Exception e) {
return false;
}
}
Uri linkUri = inputContentInfo.getLinkUri();
if (linkUri == null) {
return false;
}
sendMessage(linkUri.toString());
try {
inputContentInfo.releasePermission();
} catch (Exception e) {
}
return true;
}
private void sendMessage(String messageText) {
if (edittingMessage == null) {
presenter.sendMessage(messageText);
} else {
presenter.updateMessage(edittingMessage, messageText);
}
}
@Override
public void setupWith(RocketChatAbsoluteUrl rocketChatAbsoluteUrl) {
token = rocketChatAbsoluteUrl.getToken();
userId = rocketChatAbsoluteUrl.getUserId();
messageListAdapter.setAbsoluteUrl(rocketChatAbsoluteUrl);
}
@Override
public void render(Room room) {
roomType = room.getType();
setToolbarTitle(room.getName());
boolean unreadMessageExists = room.isAlert();
if (newMessageIndicatorManager != null && previousUnreadMessageExists && !unreadMessageExists) {
newMessageIndicatorManager.reset();
}
previousUnreadMessageExists = unreadMessageExists;
if (room.isChannel()) {
showToolbarPublicChannelIcon();
return;
}
if (room.isPrivate()) {
showToolbarPrivateChannelIcon();
}
if (room.isLivechat()) {
showToolbarLivechatChannelIcon();
}
}
if (room.isLivechat()) {
showToolbarLivechatChannelIcon();
@Override
public void showUserStatus(User user) {
showToolbarUserStatuslIcon(user.getStatus());
}
}
@Override
public void showUserStatus(User user) {
showToolbarUserStatuslIcon(user.getStatus());
}
@Override
public void updateHistoryState(boolean hasNext, boolean isLoaded) {
if (messageRecyclerView == null || !(messageRecyclerView.getAdapter() instanceof MessageListAdapter)) {
return;
}
@Override
public void updateHistoryState(boolean hasNext, boolean isLoaded) {
if (messageRecyclerView == null || !(messageRecyclerView.getAdapter() instanceof MessageListAdapter)) {
return;
MessageListAdapter adapter = (MessageListAdapter) messageRecyclerView.getAdapter();
if (isLoaded) {
scrollListener.setLoadingDone();
}
adapter.updateFooter(hasNext, isLoaded);
}
MessageListAdapter adapter = (MessageListAdapter) messageRecyclerView.getAdapter();
if (isLoaded) {
scrollListener.setLoadingDone();
@Override
public void onMessageSendSuccessfully() {
scrollToLatestMessage();
messageFormManager.onMessageSend();
edittingMessage = null;
}
adapter.updateFooter(hasNext, isLoaded);
}
@Override
public void onMessageSendSuccessfully() {
scrollToLatestMessage();
messageFormManager.onMessageSend();
edittingMessage = null;
}
@Override
public void showUnreadCount(int count) {
newMessageIndicatorManager.updateNewMessageCount(count);
}
@Override
public void showUnreadCount(int count) {
newMessageIndicatorManager.updateNewMessageCount(count);
}
@Override
public void showMessages(List<Message> messages) {
if (messageListAdapter == null) {
return;
}
messageListAdapter.updateData(messages);
}
@Override
public void showMessages(List<Message> messages) {
if (messageListAdapter == null) {
return;
@Override
public void showMessageSendFailure(Message message) {
new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.resend,
(dialog, which) -> presenter.resendMessage(message))
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.discard,
(dialog, which) -> presenter.deleteMessage(message))
.show();
}
messageListAdapter.updateData(messages);
}
@Override
public void showMessageSendFailure(Message message) {
new AlertDialog.Builder(getContext())
.setPositiveButton(R.string.resend,
(dialog, which) -> presenter.resendMessage(message))
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.discard,
(dialog, which) -> presenter.deleteMessage(message))
.show();
}
@Override
public void autoloadImages() {
messageListAdapter.setAutoloadImages(true);
}
@Override
public void autoloadImages() {
messageListAdapter.setAutoloadImages(true);
}
@Override
public void manualLoadImages() {
messageListAdapter.setAutoloadImages(false);
}
@Override
public void manualLoadImages() {
messageListAdapter.setAutoloadImages(false);
}
private void onEditMessage(Message message) {
edittingMessage = message;
messageFormManager.setEditMessage(message.getMessage());
}
private void onEditMessage(Message message) {
edittingMessage = message;
messageFormManager.setEditMessage(message.getMessage());
}
private void showRoomListFragment(int actionId) {
Intent intent = new Intent(getActivity(), RoomActivity.class).putExtra("actionId", actionId)
.putExtra("roomId", roomId)
.putExtra("roomType", roomType)
.putExtra("hostname", hostname)
.putExtra("token", token)
.putExtra("userId", userId);
startActivity(intent);
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom.dialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
import com.hadisatrio.optional.Optional;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import chat.rocket.android.BackgroundLooper;
import chat.rocket.android.R;
import chat.rocket.android.fragment.chatroom.RocketChatAbsoluteUrl;
import chat.rocket.android.helper.AbsoluteUrlHelper;
import chat.rocket.android.helper.LogIfError;
import chat.rocket.android.helper.Logger;
import chat.rocket.android.layouthelper.chatroom.dialog.RoomUserAdapter;
import chat.rocket.android.log.RCLog;
import chat.rocket.core.SyncState;
import chat.rocket.core.interactors.SessionInteractor;
import chat.rocket.persistence.realm.models.internal.GetUsersOfRoomsProcedure;
import chat.rocket.persistence.realm.RealmObjectObserver;
import chat.rocket.android.service.ConnectivityManager;
import chat.rocket.persistence.realm.repositories.RealmServerInfoRepository;
import chat.rocket.persistence.realm.repositories.RealmSessionRepository;
import chat.rocket.persistence.realm.repositories.RealmUserRepository;
/**
* Dialog to show members in a room.
*/
public class UsersOfRoomDialogFragment extends AbstractChatRoomDialogFragment {
private String hostname;
private RealmObjectObserver<GetUsersOfRoomsProcedure> procedureObserver;
private int previousSyncState;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
public UsersOfRoomDialogFragment() {
}
/**
* create UsersOfRoomDialogFragment with required parameters.
*/
public static UsersOfRoomDialogFragment create(String roomId, String hostname) {
Bundle args = new Bundle();
args.putString("hostname", hostname);
args.putString("roomId", roomId);
UsersOfRoomDialogFragment fragment = new UsersOfRoomDialogFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
procedureObserver = realmHelper
.createObjectObserver(realm ->
realm.where(GetUsersOfRoomsProcedure.class).equalTo("roomId", roomId))
.setOnUpdateListener(this::onUpdateGetUsersOfRoomProcedure);
previousSyncState = SyncState.NOT_SYNCED;
if (savedInstanceState == null) {
requestGetUsersOfRoom();
}
}
@Override
protected void handleArgs(@NonNull Bundle args) {
super.handleArgs(args);
hostname = args.getString("hostname");
}
@Override
protected int getLayout() {
return R.layout.dialog_users_of_room;
}
@Override
protected void onSetupDialog() {
AbsoluteUrlHelper absoluteUrlHelper = new AbsoluteUrlHelper(
hostname,
new RealmServerInfoRepository(),
new RealmUserRepository(hostname),
new SessionInteractor(new RealmSessionRepository(hostname))
);
compositeDisposable.add(
absoluteUrlHelper.getRocketChatAbsoluteUrl()
.subscribeOn(AndroidSchedulers.from(BackgroundLooper.get()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::setupView,
Logger::report
)
);
}
private void setupView(Optional<RocketChatAbsoluteUrl> rocketChatAbsoluteUrlOptional) {
compositeDisposable.clear();
if (!rocketChatAbsoluteUrlOptional.isPresent()) {
return;
}
RecyclerView recyclerView = (RecyclerView) getDialog().findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
recyclerView.setAdapter(
new RoomUserAdapter(getContext(), realmHelper, rocketChatAbsoluteUrlOptional.get(), hostname));
}
private void requestGetUsersOfRoom() {
realmHelper.executeTransaction(realm -> {
realm.createOrUpdateObjectFromJson(GetUsersOfRoomsProcedure.class, new JSONObject()
.put("roomId", roomId)
.put("syncstate", SyncState.NOT_SYNCED)
.put("showAll", true));
return null;
}).onSuccessTask(task -> {
ConnectivityManager.getInstance(getContext().getApplicationContext())
.keepAliveServer();
return task;
}).continueWith(new LogIfError());
}
@Override
public void onResume() {
super.onResume();
procedureObserver.sub();
}
@Override
public void onPause() {
procedureObserver.unsub();
super.onPause();
}
private void onUpdateGetUsersOfRoomProcedure(GetUsersOfRoomsProcedure procedure) {
if (procedure == null) {
return;
}
int syncState = procedure.getSyncState();
if (previousSyncState != syncState) {
onSyncStateUpdated(syncState);
previousSyncState = syncState;
}
if (syncState == SyncState.SYNCED) {
onRenderTotalCount(procedure.getTotal());
try {
JSONArray array = new JSONArray(procedure.getRecords());
ArrayList<String> users = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
Object userObject = array.get(i);
if (userObject instanceof JSONObject) {
JSONObject user = (JSONObject) userObject;
users.add(user.getString("username"));
} else {
users.add((String) userObject);
}
}
onRenderUsers(users);
} catch (JSONException exception) {
RCLog.e(exception);
}
}
}
/**
* called only if prevSyncstate != newSyncstate.
*/
private void onSyncStateUpdated(int newSyncState) {
boolean show = newSyncState == SyncState.NOT_SYNCED || newSyncState == SyncState.SYNCING;
getDialog().findViewById(R.id.waiting).setVisibility(show ? View.VISIBLE : View.GONE);
}
/**
* called only if syncstate = SYNCED.
*/
private void onRenderTotalCount(long total) {
TextView userCount = (TextView) getDialog().findViewById(R.id.room_user_count);
userCount.setText(getResources().getQuantityString(R.plurals.fmt_room_user_count, (int) total, total));
}
/**
* called only if syncstate = SYNCED.
*/
private void onRenderUsers(List<String> usernames) {
RecyclerView recyclerView = (RecyclerView) getDialog().findViewById(R.id.recyclerview);
if (recyclerView != null && recyclerView.getAdapter() instanceof RoomUserAdapter) {
((RoomUserAdapter) recyclerView.getAdapter()).setUsernames(usernames);
}
}
}
package chat.rocket.android.fragment.chatroom.list
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
interface RoomListContract {
interface View {
/**
* Shows a pinned message list of a room.
*
* @param dataSet The pinned message data set to show.
* @param total The total number of pinned messages.
*/
fun showPinnedMessages(dataSet: ArrayList<Message>, total: String)
/**
* Shows a favorite message list of a room.
*
* @param dataSet The favorite message data set to show.
* @param total The total number of favorite messages.
*/
fun showFavoriteMessages(dataSet: ArrayList<Message>, total: String)
/**
* Shows a file list of a room.
*
* @param dataSet The file data set to show.
* @param total The total number of files.
*/
fun showFileList(dataSet: ArrayList<String>, total: String)
/**
* Shows a list of members of a room.
*
* @param dataSet The member data set to show.
* @param total The total number of members.
*/
fun showMemberList(dataSet: ArrayList<User>, total: String)
/**
* Shows a message (e.g. An error or successful message after a request).
*
* @param message The message to show.
*/
fun showMessage(message: String)
/**
* Shows a waiting view whenever a (long) process is taken.
*
* @param shouldShow The Boolean value that indicates whether the view should be showed.
*/
fun showWaitingView(shouldShow: Boolean)
}
interface Presenter {
/**
* Requests the pinned messages of a room.
*
* @param roomId The room ID to process the request.
* @param roomType The room type to process the request.
* @param hostname The server hostname to process the request.
* @param token The token to process the request.
* @param userId The user ID to process the request.
* @param offset The offset to process the request.
*/
fun requestPinnedMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int)
/**
* Requests the favorite messages of a room.
*
* @param roomId The room ID to process the request.
* @param roomType The room type to process the request.
* @param hostname The server hostname to process the request.
* @param token The token to process the request.
* @param userId The user ID to process the request.
* @param offset The offset to process the request.
*/
fun requestFavoriteMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int)
/**
* Requests the file list of a room.
*
* @param roomId The room ID to process the request.
* @param roomType The room type to process the request.
* @param hostname The server hostname to process the request.
* @param token The token to process the request.
* @param userId The user ID to process the request.
* @param offset The offset to process the request.
*/
fun requestFileList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int)
/**
* Requests the member list of a room.
*
* @param roomId The room ID to process the request.
* @param roomType The room type to process the request.
* @param hostname The server hostname to process the request.
* @param token The token to process the request.
* @param userId The user ID to process the request.
* @param offset The offset to process the request.
*/
fun requestMemberList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int)
/**
* Immediately cancels any running request.
*/
fun cancelRequest()
}
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom.list
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
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.RoomMemberListAdapter
import chat.rocket.android.layouthelper.chatroom.list.RoomMessagesAdapter
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
import kotlinx.android.synthetic.main.fragment_room_list.*
import java.util.*
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomListFragment : Fragment(), RoomListContract.View {
companion object {
fun newInstance(actionId: Int,
roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String): RoomListFragment {
val args = Bundle()
args.putInt("actionId", actionId)
args.putString("roomId", roomId)
args.putString("roomType", roomType)
args.putString("hostname", hostname)
args.putString("token", token)
args.putString("userId", userId)
val roomFileListDialogFragment = RoomListFragment()
roomFileListDialogFragment.arguments = args
return roomFileListDialogFragment
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity.title = ""
actionId = arguments.getInt("actionId")
roomId = arguments.getString("roomId")
roomType = arguments.getString("roomType")
hostname = arguments.getString("hostname")
token = arguments.getString("token")
userId = arguments.getString("userId")
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater?.inflate(R.layout.fragment_room_list, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter = RoomListPresenter(context, this)
}
override fun onResume() {
super.onResume()
if (!isDataRequested) {
requestData(0)
isDataRequested = true
}
}
override fun onPause() {
super.onPause()
presenter.cancelRequest()
}
private fun requestData(offset: Int) {
when (actionId) {
R.id.action_pinned_messages -> {
presenter.requestPinnedMessages(roomId,
roomType,
hostname,
token,
userId,
offset)
}
R.id.action_favorite_messages -> {
presenter.requestFavoriteMessages(roomId,
roomType,
hostname,
token,
userId,
offset)
}
R.id.action_member_list -> {
presenter.requestMemberList(roomId,
roomType,
hostname,
token,
userId,
offset)
}
}
}
override fun showPinnedMessages(dataSet: ArrayList<Message>, total: String) {
activity.title = getString(R.string.fragment_room_list_pinned_message_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMessagesAdapter(dataSet, hostname, context)
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 RoomMessagesAdapter).addDataSet(dataSet)
}
}
override fun showFavoriteMessages(dataSet: ArrayList<Message>, total: String) {
activity.title = getString(R.string.fragment_room_list_favorite_message_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMessagesAdapter(dataSet, hostname, context)
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 RoomMessagesAdapter).addDataSet(dataSet)
}
}
// TODO (after REST api fixes)
override fun showFileList(dataSet: ArrayList<String>, total: String) {}
override fun showMemberList(dataSet: ArrayList<User>, total: String) {
activity.title = getString(R.string.fragment_room_list_member_list_title, total)
if (recyclerView.adapter == null) {
recyclerView.adapter = RoomMemberListAdapter(dataSet, hostname, context)
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 RoomMemberListAdapter).addDataSet(dataSet)
}
}
override fun showMessage(message: String) {
messageText.text = message
messageText.visibility = View.VISIBLE
}
override fun showWaitingView(shouldShow: Boolean) {
if (shouldShow) {
waitingView.visibility = View.VISIBLE
} else {
waitingView.visibility = View.GONE
}
}
private fun loadNextDataFromApi(page: Int) {
requestData(page * 50)
}
private var actionId: Int = 0
private lateinit var roomId: String
private lateinit var roomType: String
private lateinit var hostname: String
private lateinit var token: String
private lateinit var userId: String
private lateinit var presenter: RoomListContract.Presenter
private var isDataRequested: Boolean = false
}
\ No newline at end of file
package chat.rocket.android.fragment.chatroom.list
import android.content.Context
import android.os.Handler
import chat.rocket.android.R
import chat.rocket.android.api.rest.RestApiHelper
import chat.rocket.android.helper.OkHttpHelper
import chat.rocket.core.SyncState
import chat.rocket.core.models.Message
import chat.rocket.core.models.User
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import org.json.JSONObject
import java.io.IOException
import java.sql.Timestamp
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomListPresenter(val context: Context, val view: RoomListContract.View) : RoomListContract.Presenter {
override fun requestPinnedMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {
view.showWaitingView(true)
OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForPinnedMessages(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) {
handleMessagesJson(result, true)
}
} else {
showErrorMessage(response.message())
}
}
})
}
override fun requestFavoriteMessages(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {
view.showWaitingView(true)
OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForFavoriteMessages(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) {
handleMessagesJson(result, false)
}
} else {
showErrorMessage(response.message())
}
}
})
}
// TODO (after the REST api fixes)
override fun requestFileList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {}
override fun requestMemberList(roomId: String,
roomType: String,
hostname: String,
token: String,
userId: String,
offset: Int) {
view.showWaitingView(true)
OkHttpHelper.getClient()
.newCall(RestApiHelper.getRequestForMemberList(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) {
handleMembersJson(result)
}
} else {
showErrorMessage(response.message())
}
}
})
}
override fun cancelRequest() {
OkHttpHelper.getClient().dispatcher().cancelAll()
}
private fun handleMessagesJson(json: String, isPinnedMessage: Boolean) {
val jSONObject = JSONObject(json)
val messagesJSONArray = jSONObject.getJSONArray("messages")
val total = messagesJSONArray.length()
val dataSet = ArrayList<Message>(total)
(0 until total).mapTo(dataSet) {
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)
.setGroupable(messageJsonObject.optBoolean("groupable"))
.setSyncState(SyncState.SYNCED)
.build()
}
if (dataSet.isEmpty() && !hasItem) {
showEmptyViewMessage(context.getString(R.string.fragment_room_list_no_favorite_message_to_show))
} else {
if (dataSet.isNotEmpty()) {
hasItem = true
if (isPinnedMessage) {
showPinnedMessageList(dataSet, jSONObject.optString("total"))
} else {
showFavoriteMessageList(dataSet, jSONObject.optString("total"))
}
}
}
}
private fun handleMembersJson(json: String) {
val jsonObject = JSONObject(json)
val membersJsonArray = jsonObject.getJSONArray("members")
val total = membersJsonArray.length()
val dataSet = ArrayList<User>(total)
(0 until total).mapTo(dataSet) {
getUserFromJsonObject(membersJsonArray.getJSONObject(it))
}
if (dataSet.isEmpty() && !hasItem) {
showEmptyViewMessage(context.getString(R.string.fragment_room_list_no_member_list_to_show))
} else {
if (dataSet.isNotEmpty()) {
hasItem = true
showMemberList(dataSet, jsonObject.optString("total"))
}
}
}
private fun getUserFromJsonObject(jsonObject: JSONObject): User {
return User.builder()
.setId(jsonObject.optString("_id"))
.setName(jsonObject.optString("name"))
.setUsername(jsonObject.optString("username"))
.setStatus(jsonObject.optString("status"))
.setUtcOffset(jsonObject.optLong("utcOffset").toDouble())
.build()
}
private fun showPinnedMessageList(dataSet: ArrayList<Message>, total: String) {
mainHandler.post {
view.showWaitingView(false)
view.showPinnedMessages(dataSet, total)
}
}
private fun showFavoriteMessageList(dataSet: ArrayList<Message>, total: String) {
mainHandler.post {
view.showWaitingView(false)
view.showFavoriteMessages(dataSet, total)
}
}
private fun showMemberList(dataSet: ArrayList<User>, total: String) {
mainHandler.post {
view.showWaitingView(false)
view.showMemberList(dataSet, total)
}
}
private fun showEmptyViewMessage(message: String) {
mainHandler.post {
view.showWaitingView(false)
view.showMessage(message)
}
}
private fun showErrorMessage(message: String) {
mainHandler.post {
view.showWaitingView(false)
view.showMessage(context.getString(R.string.fragment_room_list_could_not_load_your_request, message))
}
}
private val mainHandler = Handler(context.mainLooper)
private var hasItem: Boolean = false
}
\ No newline at end of file
package chat.rocket.android.helper
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/21/17.
* Source info: https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
*/
abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener {
private var visibleThreshold = 5 // The minimum amount of items to have below of the current scroll position before loading more.
private var currentPage = 0 // The current offset index of data loaded
private var previousTotalItemCount = 0 // The total number of items in the dataset after the last load
private var loading = true // True if we are still waiting for the last set of data to load.
private val startingPageIndex = 0 // Sets the starting page index
private var layoutManager: RecyclerView.LayoutManager
constructor(layoutManager: LinearLayoutManager) {
this.layoutManager = layoutManager
}
constructor(layoutManager: GridLayoutManager) {
this.layoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
constructor(layoutManager: StaggeredGridLayoutManager) {
this.layoutManager = layoutManager
visibleThreshold *= layoutManager.spanCount
}
private fun getLastVisibleItem(lastVisibleItemPositions: IntArray): Int {
var maxSize = 0
for (i in lastVisibleItemPositions.indices) {
if (i == 0) {
maxSize = lastVisibleItemPositions[i]
} else if (lastVisibleItemPositions[i] > maxSize) {
maxSize = lastVisibleItemPositions[i]
}
}
return maxSize
}
// This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish.
override fun onScrolled(view: RecyclerView?, dx: Int, dy: Int) {
var lastVisibleItemPosition = 0
val totalItemCount = layoutManager.itemCount
when (layoutManager) {
is StaggeredGridLayoutManager -> {
val lastVisibleItemPositions = (layoutManager as StaggeredGridLayoutManager).findLastVisibleItemPositions(null)
// get maximum element within the list
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions)
}
is GridLayoutManager -> lastVisibleItemPosition = (layoutManager as GridLayoutManager).findLastVisibleItemPosition()
is LinearLayoutManager -> lastVisibleItemPosition = (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
}
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex
this.previousTotalItemCount = totalItemCount
if (totalItemCount == 0) {
this.loading = true
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && totalItemCount > previousTotalItemCount) {
loading = false
previousTotalItemCount = totalItemCount
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
// threshold should reflect how many total columns there are too
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
currentPage++
onLoadMore(currentPage, totalItemCount, view)
loading = true
}
}
// Call this method whenever performing new searches
fun resetState() {
this.currentPage = this.startingPageIndex
this.previousTotalItemCount = 0
this.loading = true
}
// Defines the process for actually loading more data based on page
abstract fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?)
}
\ No newline at end of file
package chat.rocket.android.helper;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@SuppressWarnings("PMD.AbstractNaming")
public abstract class LoadMoreScrollListener extends RecyclerView.OnScrollListener {
private final LinearLayoutManager layoutManager;
private final int loadThreshold;
private boolean isLoading;
/**
* constructor. loadThreshold is better to set to 0.4 * total.
*/
public LoadMoreScrollListener(LinearLayoutManager layoutManager, int loadThreshold) {
this.layoutManager = layoutManager;
this.loadThreshold = loadThreshold;
setLoadingDone();
}
@Override
public void onScrolled(RecyclerView recyclerView, int deltaX, int deltaY) {
super.onScrolled(recyclerView, deltaX, deltaY);
final int visibleItemCount = recyclerView.getChildCount();
final int totalItemCount = layoutManager.getItemCount();
final int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
if (!isLoading
&& firstVisibleItem + visibleItemCount >= totalItemCount - loadThreshold
&& visibleItemCount < totalItemCount
&& deltaY < 0) {
isLoading = true;
requestMoreItem();
}
}
public void setLoadingDone() {
isLoading = false;
}
public abstract void requestMoreItem();
}
\ No newline at end of file
package chat.rocket.android.helper
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
abstract class LoadMoreScrollListener(private val layoutManager: LinearLayoutManager, private val visibleThreshold: Int) : RecyclerView.OnScrollListener() {
private var isLoading: Boolean = false
override fun onScrolled(recyclerView: RecyclerView, deltaX: Int, deltaY: Int) {
super.onScrolled(recyclerView, deltaX, deltaY)
val visibleItemCount = recyclerView.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
if (!isLoading
&& (firstVisibleItem + visibleItemCount) >= (totalItemCount - visibleThreshold)
&& visibleItemCount < totalItemCount
&& deltaY < 0) {
isLoading = true
requestMoreItem()
}
}
fun setLoadingDone() {
isLoading = false
}
abstract fun requestMoreItem()
}
\ No newline at end of file
package chat.rocket.android.helper
object UrlHelper {
/**
* Returns an URI whit no scheme (HTTP or HTTPS)
*
* @param uri The URI.
* @return The URI whit no scheme (HTTP or HTTPS)
*/
fun removeUriScheme(uri: String) = uri.replace("http://", "").replace("https://", "")
/**
* Returns the hostname with the security protocol (scheme) HTTPS.
*
* @param hostname The hostname.
* @return The hostname with the security protocol (scheme) HTTPS.
*/
fun getSafeHostname(hostname: String): String =
"https://" + hostname.replace("http://", "").replace("https://", "")
/**
* Returns an URL with no spaces and inverted slashes.
*
* @param url The URL.
* @return The URL with no spaces and inverted slashes.
*/
fun getUrl(url: String) =
url.replace(" ", "%20").replace("\\", "")
/**
* Returns an URL for a file.
*
* @param path The path to the file.
* @param userId The user ID.
* @param token The token.
* @return The URL for a file
*/
fun getUrlForFile(path: String, userId: String, token: String): String =
"https://" + removeUriScheme(getUrl(path)) + "?rc_uid=$userId" + "&rc_token=$token"
}
\ No newline at end of file
......@@ -34,8 +34,8 @@ public abstract class AbstractMessageViewHolder extends ModelViewHolder<PairedMe
subUsername = itemView.findViewById(R.id.sub_username);
timestamp = itemView.findViewById(R.id.timestamp);
userAndTimeContainer = itemView.findViewById(R.id.user_and_timestamp_container);
newDayContainer = itemView.findViewById(R.id.newday_container);
newDayText = itemView.findViewById(R.id.newday_text);
newDayContainer = itemView.findViewById(R.id.dayContainer);
newDayText = itemView.findViewById(R.id.day);
this.absoluteUrl = absoluteUrl;
this.hostname = hostname;
}
......
package chat.rocket.android.layouthelper.chatroom.list
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.widget.message.RocketChatMessageLayout
import kotlinx.android.synthetic.main.item_room_file.view.*
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomFileListAdapter(private var dataSet: List<String>) : 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)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.fileNameLink.setText(dataSet[position])
}
override fun getItemCount(): Int = dataSet.size
fun setDataSet(dataSet: List<String>) {
this.dataSet = dataSet
notifyDataSetChanged()
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val fileNameLink : RocketChatMessageLayout = itemView.fileLink
}
}
\ No newline at end of file
package chat.rocket.android.layouthelper.chatroom.list
import android.content.Context
import android.graphics.drawable.Drawable
import android.support.graphics.drawable.VectorDrawableCompat
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.widget.RocketChatAvatar
import chat.rocket.android.widget.helper.AvatarHelper
import chat.rocket.android.widget.helper.DrawableHelper
import chat.rocket.core.models.User
import kotlinx.android.synthetic.main.item_room_member.view.*
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomMemberListAdapter(private var dataSet: List<User>, private val hostname: String, private val context: Context) : RecyclerView.Adapter<RoomMemberListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_room_member, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val user = dataSet[position]
holder.name.text = user.name
val userStatusDrawable: Drawable? = VectorDrawableCompat.create(context.resources, chat.rocket.android.widget.R.drawable.ic_user_status_black_24dp, null)
DrawableHelper.wrapDrawable(userStatusDrawable)
when (user.status) {
User.STATUS_ONLINE -> DrawableHelper.tintDrawable(userStatusDrawable, context, chat.rocket.android.widget.R.color.color_user_status_online)
User.STATUS_BUSY -> DrawableHelper.tintDrawable(userStatusDrawable, context, chat.rocket.android.widget.R.color.color_user_status_busy)
User.STATUS_AWAY -> DrawableHelper.tintDrawable(userStatusDrawable, context, chat.rocket.android.widget.R.color.color_user_status_away)
User.STATUS_OFFLINE -> DrawableHelper.tintDrawable(userStatusDrawable, context, chat.rocket.android.widget.R.color.color_user_status_offline)
}
holder.status.setImageDrawable(userStatusDrawable)
val username = user.username
if (username != null) {
holder.username.text = context.getString(R.string.username, username)
val placeholderDrawable = AvatarHelper.getTextDrawable(username, holder.userAvatar.context)
holder.userAvatar.loadImage(AvatarHelper.getUri(hostname, username), placeholderDrawable)
} else {
holder.userAvatar.visibility = View.GONE
holder.username.visibility = View.GONE
}
}
override fun getItemCount(): Int = dataSet.size
fun addDataSet(dataSet: List<User>) {
val previousDataSetSize = this.dataSet.size
this.dataSet += dataSet
notifyItemRangeInserted(previousDataSetSize, dataSet.size)
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userAvatar: RocketChatAvatar = itemView.userAvatar
val name: TextView = itemView.name
val status: ImageView = itemView.status
val username: TextView = itemView.username
}
}
\ No newline at end of file
package chat.rocket.android.layouthelper.chatroom.list
import android.content.Context
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.helper.DateTime
import chat.rocket.android.widget.RocketChatAvatar
import chat.rocket.android.widget.helper.AvatarHelper
import chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
import chat.rocket.android.widget.message.RocketChatMessageLayout
import chat.rocket.android.widget.message.RocketChatMessageUrlsLayout
import chat.rocket.core.models.Message
import kotlinx.android.synthetic.main.day.view.*
import kotlinx.android.synthetic.main.item_room_message.view.*
/**
* Created by Filipe de Lima Brito (filipedelimabrito@gmail.com) on 9/22/17.
*/
class RoomMessagesAdapter(private var dataSet: List<Message>, private val hostname: String, private val context: Context) : RecyclerView.Adapter<RoomMessagesAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_room_message, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val message = dataSet[position]
holder.newDay.text = DateTime.fromEpocMs(message.timestamp, DateTime.Format.DATE)
val user = message.user
if (user != null) {
if (user.name.isNullOrBlank()) {
holder.name.visibility = View.GONE
} else {
holder.name.text = message.user?.name
}
val username = user.username
if (username != null) {
val placeholderDrawable = AvatarHelper.getTextDrawable(username, holder.userAvatar.context)
holder.userAvatar.loadImage(AvatarHelper.getUri(hostname, username), placeholderDrawable)
holder.username.text = context.getString(R.string.username, username)
} else {
holder.userAvatar.visibility = View.GONE
holder.username.visibility = View.GONE
}
}
holder.messageBody.setText(message.message)
val webContents = message.webContents
if (webContents == null || webContents.isEmpty()) {
holder.messageUrl.visibility = View.GONE
} else {
holder.messageUrl.setUrls(message.webContents, true)
}
val attachments = message.attachments
if (attachments == null || attachments.isEmpty()) {
holder.messageAttachment.visibility = View.GONE
} else {
// holder.messageAttachment.setAbsoluteUrl(absoluteUrl)
// holder.messageAttachment.setAttachments(attachments, true)
// holder.messageAttachment.visibility = View.VISIBLE
}
}
override fun getItemCount(): Int = dataSet.size
fun addDataSet(dataSet: List<Message>) {
val previousDataSetSize = this.dataSet.size
this.dataSet += dataSet
notifyItemRangeInserted(previousDataSetSize, dataSet.size)
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val newDay: TextView = itemView.day
val userAvatar: RocketChatAvatar = itemView.userAvatar
val username: TextView = itemView.username
val name: TextView = itemView.name
val messageBody: RocketChatMessageLayout = itemView.messageBody
val messageUrl: RocketChatMessageUrlsLayout = itemView.messageUrl
val messageAttachment: RocketChatMessageAttachmentsLayout = itemView.messageAttachment
}
}
\ No newline at end of file
package chat.rocket.android.layouthelper.chatroom.dialog;
package chat.rocket.android.layouthelper.chatroom.list;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
......
package chat.rocket.android.layouthelper.chatroom.dialog;
package chat.rocket.android.layouthelper.chatroom.list;
import android.support.v7.widget.RecyclerView;
import android.view.View;
......
<!-- drawable/close_circle_outline.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z" />
</vector>
\ No newline at end of file
......@@ -17,7 +17,9 @@
<chat.rocket.android.widget.RoomToolbar
android:id="@+id/activity_main_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="chat.rocket.android.fragment.chatroom.RoomFragment">
<include layout="@layout/fragment_room_main" />
<include layout="@layout/room_side_menu" />
</LinearLayout>
\ No newline at end of file
......@@ -18,7 +18,9 @@
<chat.rocket.android.widget.RoomToolbar
android:id="@+id/activity_main_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="match_parent"
android:orientation="vertical"
tools:context="chat.rocket.android.activity.room.RoomActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
\ 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"
android:id="@+id/dayContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/colorDivider" />
<TextView
android:id="@+id/day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Day"
tools:text="2016/01/23" />
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/colorDivider" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:id="@+id/messageListRelativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context="chat.rocket.android.fragment.chatroom.RoomFragment">
<include layout="@layout/fragment_room_main" />
<FrameLayout
<android.support.v7.widget.RecyclerView
android:id="@+id/messageRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:clickable="true"
android:theme="@style/AppTheme.Dark">
android:scrollbars="vertical"
android:layout_above="@+id/messageComposer" />
<include layout="@layout/room_side_menu" />
</FrameLayout>
</android.support.v4.widget.DrawerLayout>
\ No newline at end of file
<chat.rocket.android.widget.message.MessageFormLayout
android:id="@+id/messageComposer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:theme="@style/Theme.AppCompat.Light" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<chat.rocket.android.widget.WaitingView
android:id="@+id/waitingView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<TextView
android:id="@+id/messageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/messageListRelativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<android.support.v7.widget.RecyclerView
android:id="@+id/messageRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:layout_above="@+id/messageComposer" />
<chat.rocket.android.widget.message.MessageFormLayout
android:id="@+id/messageComposer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:theme="@style/Theme.AppCompat.Light" />
</RelativeLayout>
\ 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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@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" />
<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" />
</android.support.constraint.ConstraintLayout>
\ 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:paddingTop="@dimen/margin_16"
android:paddingRight="@dimen/margin_16"
android:paddingStart="@dimen/margin_16"
android:paddingLeft="@dimen/margin_16"
android:paddingEnd="@dimen/margin_16">
<chat.rocket.android.widget.RocketChatAvatar
android:id="@+id/userAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="4dp"
app:layout_constraintTop_toTopOf="parent"/>
<android.support.constraint.ConstraintLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16"
app:layout_constraintLeft_toRightOf="@+id/userAvatar" >
<ImageView
android:id="@+id/status"
android:layout_width="8dp"
android:layout_height="8dp"
app:layout_constraintTop_toTopOf="@+id/name"
app:layout_constraintBottom_toBottomOf="@+id/name"
app:srcCompat="@drawable/ic_user_status_black_24dp" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Name"
app:layout_constraintLeft_toRightOf="@+id/status"
tools:text="John Doe" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Username"
app:layout_constraintTop_toBottomOf="@+id/name"
app:layout_constraintLeft_toLeftOf="@+id/name"
tools:text="\@john.doe" />
</android.support.constraint.ConstraintLayout>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/colorDivider"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@+id/container" />
</android.support.constraint.ConstraintLayout>
\ 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:paddingRight="@dimen/margin_16"
android:paddingStart="@dimen/margin_16"
android:paddingLeft="@dimen/margin_16"
android:paddingEnd="@dimen/margin_16"
android:paddingBottom="@dimen/margin_16">
<include
android:id="@+id/dayLayout"
layout="@layout/day" />
<chat.rocket.android.widget.RocketChatAvatar
android:id="@+id/userAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@+id/dayLayout" />
<android.support.constraint.ConstraintLayout
android:id="@+id/userContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_16"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16"
app:layout_constraintTop_toBottomOf="@+id/dayLayout"
app:layout_constraintLeft_toRightOf="@+id/userAvatar">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Name"
tools:text="John Doe" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.RocketChat.Message.Username"
app:layout_constraintLeft_toLeftOf="@+id/name"
app:layout_constraintTop_toBottomOf="@+id/name"
tools:text="\@john.doe" />
</android.support.constraint.ConstraintLayout>
<android.support.constraint.ConstraintLayout
android:id="@+id/messageContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="@dimen/margin_16"
android:layout_marginStart="@dimen/margin_16"
app:layout_constraintTop_toBottomOf="@+id/userContainer"
app:layout_constraintLeft_toRightOf="@+id/userAvatar"
app:layout_constraintRight_toRightOf="parent">
<chat.rocket.android.widget.message.RocketChatMessageLayout
android:id="@+id/messageBody"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<chat.rocket.android.widget.message.RocketChatMessageUrlsLayout
android:id="@+id/messageUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="@+id/messageBody"
app:layout_constraintTop_toBottomOf="@+id/messageBody" />
<chat.rocket.android.widget.message.RocketChatMessageAttachmentsLayout
android:id="@+id/messageAttachment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="@+id/messageUrl"
app:layout_constraintTop_toBottomOf="@+id/messageUrl" />
</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"
android:id="@+id/newday_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/newday_color" />
<TextView
android:id="@+id/newday_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:textColor="@color/newday_text_color"
android:textSize="8sp"
android:textStyle="bold"
tools:text="2016/01/23" />
<View
android:layout_width="0px"
android:layout_height="1dp"
android:layout_weight="1"
android:background="@color/newday_color" />
</LinearLayout>
\ No newline at end of file
......@@ -7,7 +7,7 @@
android:orientation="vertical"
android:theme="@style/AppTheme">
<include layout="@layout/list_item_message_newday" />
<include layout="@layout/day" />
<FrameLayout
android:layout_width="match_parent"
......
......@@ -7,7 +7,7 @@
android:orientation="vertical"
android:theme="@style/AppTheme">
<include layout="@layout/list_item_message_newday" />
<include layout="@layout/day" />
<FrameLayout
android:layout_width="match_parent"
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/room_side_menu"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:orientation="vertical">
<io.github.yusukeiwaki.android.widget.FontAwesomeButton
android:layout_width="48dp"
android:layout_height="48dp"
android:enabled="false"
android:text="@string/fa_search"
android:textSize="24dp" />
<io.github.yusukeiwaki.android.widget.FontAwesomeButton
android:id="@+id/btn_users"
android:layout_width="48dp"
android:layout_height="48dp"
android:text="@string/fa_users"
android:textSize="24dp" />
<io.github.yusukeiwaki.android.widget.FontAwesomeButton
android:layout_width="48dp"
android:layout_height="48dp"
android:enabled="false"
android:text="@string/fa_at"
android:textSize="24dp" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_pinned_messages"
android:title="@string/menu_pinned_messages"
app:showAsAction="never" />
<item android:id="@+id/action_favorite_messages"
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_member_list"
android:title="@string/menu_member_list"
app:showAsAction="never" />
</menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#044b76</color>
<color name="colorPrimaryDark">#FF04436a</color>
<color name="colorAccent">#FF2D91FA</color>
<color name="colorAccentLight">#FF6CB1FA</color>
<color name="colorAccentDark">#FF287DD7</color>
<color name="colorAccent_a40">#662D91FA</color>
<color name="textColorLink">#008ce3</color>
<color name="colorRed400">#FFEF5350</color>
<color name="colorPrimary">#044b76</color>
<color name="colorPrimaryDark">#FF04436a</color>
<color name="colorAccent">#FF2D91FA</color>
<color name="colorAccentLight">#FF6CB1FA</color>
<color name="colorAccentDark">#FF287DD7</color>
<color name="colorAccent_a40">#662D91FA</color>
<color name="textColorLink">#008ce3</color>
<color name="colorRed400">#FFEF5350</color>
<color name="colorPrimaryTextDark">#DE000000</color>
<color name="colorSecondaryTextDark">#8A000000</color>
<color name="colorDivider">#1F000000</color>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="fa_chevron_down" translatable="false">&#xf078;</string>
<string name="fa_twitter" translatable="false">&#xf099;</string>
<string name="fa_github" translatable="false">&#xf09b;</string>
<string name="fa_google" translatable="false">&#xf1a0;</string>
<string name="fa_facebook_official" translatable="false">&#xf230;</string>
<string name="fa_plus" translatable="false">&#xf067;</string>
<string name="fa_sign_out" translatable="false">&#xf08b;</string>
<string name="fa_search" translatable="false">&#xf002;</string>
<string name="fa_users" translatable="false">&#xf0c0;</string>
<string name="fa_at" translatable="false">&#xf1fa;</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextAppearance.RocketChat.Message.Day" parent="TextAppearance.AppCompat">
<item name="android:textSize">12sp</item>
<item name="android:textColor">@color/colorPrimaryTextDark</item>
</style>
<style name="TextAppearance.RocketChat.Message.SubUsername" parent="TextAppearance.AppCompat.Body1">
<item name="android:textColor">#5f000000</item>
</style>
<style name="TextAppearance.RocketChat.Message.Name" parent="TextAppearance.AppCompat.Body1">
<item name="android:textColor">@color/colorPrimaryTextDark</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textSize">16sp</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
</style>
<style name="TextAppearance.RocketChat.Message.Username" parent="TextAppearance.AppCompat.Body2">
<item name="android:textColor">@color/colorSecondaryTextDark</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:textSize">14sp</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="newday_color">#e0e0e0</color>
<color name="newday_text_color">#444444</color>
<style name="TextAppearance.RocketChat.Message.Username" parent="TextAppearance.AppCompat.Body2">
<item name="android:textColor">#bb000000</item>
</style>
<style name="TextAppearance.RocketChat.Message.SubUsername" parent="TextAppearance.AppCompat.Body1">
<item name="android:textColor">#5f000000</item>
</style>
</resources>
\ No newline at end of file
......@@ -13,6 +13,16 @@
<string name="dialog_add_channel_private">private</string>
<string name="dialog_add_channel_read_only">read only</string>
<string name="fragment_room_list_pinned_message_title">Pinned Messages (%s)</string>
<string name="fragment_room_list_favorite_message_title">Favorite Messages (%s)</string>
<string name="fragment_room_list_file_list_title">File list (%s)</string>
<string name="fragment_room_list_member_list_title">Member list (%s)</string>
<string name="fragment_room_list_could_not_load_your_request">Could not load your request.\nResponse was: %s</string>
<string name="fragment_room_list_no_pinned_message_to_show">No pinned message to show</string>
<string name="fragment_room_list_no_favorite_message_to_show">No favorite message to show</string>
<string name="fragment_room_list_no_file_list_to_show">No file list to show</string>
<string name="fragment_room_list_no_member_list_to_show">No member list to show</string>
<string name="start_of_conversation">Start of conversation</string>
<string name="users_of_room_title">Members List</string>
<plurals name="fmt_room_user_count">
......@@ -36,6 +46,7 @@
<string name="dialog_user_registration_email">Email</string>
<string name="dialog_user_registration_username">Username</string>
<string name="sub_username">\@%s</string>
<string name="username">\@%s</string>
<string name="dialog_user_registration_password">Password</string>
<string name="fragment_home_welcome_message">Welcome to Rocket.Chat.Android\nSelect a channel from the drawer.</string>
<string name="fragment_input_hostname_hostname">Hostname</string>
......@@ -68,5 +79,11 @@
<string name="edit_message">Edit message</string>
<string name="message_options_no_message_info">Ooops. Something\'s up!</string>
<string name="message_options_no_permissions_info">You have no permissions</string>
<string name="menu_pinned_messages">Pinned messages</string>
<string name="menu_favorite_messages">Favorite messages</string>
<string name="menu_file_list">File list</string>
<string name="menu_member_list">Member list</string>
<string name="add_new_team">Add new Team</string>
</resources>
......@@ -9,6 +9,13 @@ import okhttp3.OkHttpClient
object OkHttpHelper {
fun getClient(): OkHttpClient {
if (httpClient == null) {
httpClient = OkHttpClient()
}
return httpClient ?: throw AssertionError("httpClient set to null by another thread")
}
fun getClientForUploadFile(): OkHttpClient {
if (httpClientForUploadFile == null) {
httpClientForUploadFile = OkHttpClient.Builder().build()
......@@ -19,6 +26,8 @@ object OkHttpHelper {
fun getClientForDownloadFile(context: Context): OkHttpClient {
if(httpClientForDownloadFile == null) {
httpClientForDownloadFile = OkHttpClient.Builder()
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(CookieInterceptor(DefaultCookieProvider(RocketChatCache(context))))
.build()
}
......@@ -37,7 +46,8 @@ object OkHttpHelper {
return httpClientForWS ?: throw AssertionError("httpClientForWS set to null by another thread")
}
private var httpClient: OkHttpClient? = null
private var httpClientForUploadFile: OkHttpClient? = null
private var httpClientForDownloadFile: OkHttpClient? = null
private var httpClientForWS: OkHttpClient? = null
}
\ No newline at end of file
}
package chat.rocket.android.api.rest
import chat.rocket.core.models.Room
import org.junit.Test
import kotlin.test.assertEquals
class RestApiHelperTest {
@Test
fun getEndpointUrlForMessagesTest() {
assertEquals("https://demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_PRIVATE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_PRIVATE, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_PRIVATE, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "http://demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_PRIVATE, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_PRIVATE, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_CHANNEL, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_PRIVATE, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.messages", RestApiHelper.getEndpointUrlForMessages(Room.TYPE_DIRECT_MESSAGE, "http://www.demo.rocket.chat"))
}
@Test
fun getEndpointUrlForFileListTest() {
assertEquals("https://demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_PRIVATE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_PRIVATE, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_PRIVATE, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "http://demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_PRIVATE, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_PRIVATE, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_CHANNEL, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_PRIVATE, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.files", RestApiHelper.getEndpointUrlForFileList(Room.TYPE_DIRECT_MESSAGE, "http://www.demo.rocket.chat"))
}
@Test
fun getEndpointUrlForMemberListTest() {
assertEquals("https://demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_PRIVATE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_PRIVATE, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_PRIVATE, "http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "http://demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_PRIVATE, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_PRIVATE, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "https://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/channels.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_CHANNEL, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/groups.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_PRIVATE, "http://www.demo.rocket.chat"))
assertEquals("https://www.demo.rocket.chat/api/v1/dm.members", RestApiHelper.getEndpointUrlForMemberList(Room.TYPE_DIRECT_MESSAGE, "http://www.demo.rocket.chat"))
}
@Test
fun getRestApiUrlForMessagesTest() {
assertEquals("/api/v1/channels.messages", RestApiHelper.getRestApiUrlForMessages(Room.TYPE_CHANNEL))
assertEquals("/api/v1/groups.messages", RestApiHelper.getRestApiUrlForMessages(Room.TYPE_PRIVATE))
assertEquals("/api/v1/dm.messages", RestApiHelper.getRestApiUrlForMessages(Room.TYPE_DIRECT_MESSAGE))
}
@Test
fun getRestApiUrlForFileListTest() {
assertEquals("/api/v1/channels.files", RestApiHelper.getRestApiUrlForFileList(Room.TYPE_CHANNEL))
assertEquals("/api/v1/groups.files", RestApiHelper.getRestApiUrlForFileList(Room.TYPE_PRIVATE))
assertEquals("/api/v1/dm.files", RestApiHelper.getRestApiUrlForFileList(Room.TYPE_DIRECT_MESSAGE))
}
@Test
fun getRestApiUrlForMemberListTest() {
assertEquals("/api/v1/channels.members", RestApiHelper.getRestApiUrlForMemberList(Room.TYPE_CHANNEL))
assertEquals("/api/v1/groups.members", RestApiHelper.getRestApiUrlForMemberList(Room.TYPE_PRIVATE))
assertEquals("/api/v1/dm.members", RestApiHelper.getRestApiUrlForMemberList(Room.TYPE_DIRECT_MESSAGE))
}
}
\ No newline at end of file
package chat.rocket.android.helper
import org.junit.Test
import kotlin.test.assertEquals
class UrlHelperTest {
@Test
fun removeUriSchemeTest() {
assertEquals("demo.rocket.chat", UrlHelper.removeUriScheme("https://demo.rocket.chat"))
assertEquals("demo.rocket.chat", UrlHelper.removeUriScheme("http://demo.rocket.chat"))
assertEquals("demo.rocket.chat", UrlHelper.removeUriScheme("demo.rocket.chat"))
}
@Test
fun getSafeHostnameTest() {
assertEquals("https://demo.rocket.chat", UrlHelper.getSafeHostname("https://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat", UrlHelper.getSafeHostname("http://demo.rocket.chat"))
assertEquals("https://demo.rocket.chat", UrlHelper.getSafeHostname("demo.rocket.chat"))
}
@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"))
}
@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"))
}
}
\ No newline at end of file
......@@ -22,7 +22,7 @@ ext {
buildToolsVersion = "26.0.0"
supportLibraryVersion = "25.4.0"
constraintLayoutVersion = "1.0.2"
kotlinVersion = "1.1.4-2"
kotlinVersion = "1.1.4-3"
okHttpVersion = "3.9.0"
}
......
......@@ -15,6 +15,9 @@ public abstract class User {
public abstract String getId();
@Nullable
public abstract String getName();
@Nullable
public abstract String getUsername();
......@@ -38,6 +41,8 @@ public abstract class User {
public abstract Builder setId(String id);
public abstract Builder setName(String name);
public abstract Builder setUsername(String username);
public abstract Builder setStatus(String status);
......
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