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 {
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
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.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
......@@ -8,4 +8,9 @@
<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,6 +46,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
......
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