Unverified Commit 4aa50996 authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #692 from RocketChat/feature-2.x/stream-room-changes

[NEW] Listen to Room and Subscription changes
parents 820abca1 c247b15a
......@@ -53,6 +53,7 @@ hs_err_pid*
# Built application files
*.apk
*.ap_
app/libs
# Files for the ART/Dalvik VM
*.dex
......
......@@ -3,6 +3,7 @@
CURRENT_DIR=`pwd`
# The SDK dir should be 2 directories up in the tree, so we use dirname 2 times
# to get the common parent dir of the SDK and the app
GIT=`which git`
tmp=`dirname $CURRENT_DIR`
tmp=`dirname $tmp`
SDK_DIR="$tmp/Rocket.Chat.Kotlin.SDK"
......@@ -13,23 +14,28 @@ echo "SDK DIR: $SDK_DIR"
# check if there are changes not commited
function git_stat {
local __resultvar=$1
cd $SDK_DIR && git diff --shortstat --exit-code
cd $SDK_DIR && $GIT diff --shortstat --exit-code
eval $__resultvar="'$?'"
}
# check for changes already on the index not commited
function git_stat_cached {
local __resultvar=$1
cd $SDK_DIR && git diff --cached --shortstat --exit-code
cd $SDK_DIR && $GIT diff --cached --shortstat --exit-code
eval $__resultvar="'$?'"
}
# get the SHA of the lastest commit
function git_sha {
temp_sha=`cd $SDK_DIR && git rev-parse --short HEAD`
temp_sha=`cd $SDK_DIR && $GIT rev-parse --short HEAD`
echo "$temp_sha"
}
function git_app_branch {
temp_branch=`cd $CURRENT_DIR && $GIT rev-parse --abbrev-ref HEAD`
echo "$temp_branch"
}
# check if the tree is dirty (has modifications not commited yet)
function check_git_dirty {
git_stat stat
......@@ -62,7 +68,17 @@ function check_last_commit {
return 0
}
function checkout_matching_branch {
app_branch=$(git_app_branch)
cd $SDK_DIR && $GIT checkout $app_branch 1>/dev/null 2>/dev/null
}
checkout_matching_branch
SHA=$(git_sha)
if [ "X$SHA" == "X" ]; then
SHA="0.1-SNAPSHOT"
fi
echo "CURRENT SHA: $SHA"
# if the tree is not dirty, there is no new commit and the .jars are still there, just skip the build
......
......@@ -54,6 +54,7 @@ dependencies {
kapt libraries.daggerAndroidApt
implementation libraries.moshi
implementation libraries.kotshi
implementation libraries.room
kapt libraries.roomProcessor
......
......@@ -7,9 +7,13 @@ import chat.rocket.android.server.domain.SaveChatRoomsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.model.Subscription
import chat.rocket.core.internal.realtime.*
import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.ChatRoom
import kotlinx.coroutines.experimental.async
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.*
import timber.log.Timber
import javax.inject.Inject
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
......@@ -17,24 +21,39 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val serverInteractor: GetCurrentServerInteractor,
private val getChatRoomsInteractor: GetChatRoomsInteractor,
private val saveChatRoomsInteractor: SaveChatRoomsInteractor,
private val factory: RocketChatClientFactory) {
lateinit var client: RocketChatClient
factory: RocketChatClientFactory) {
fun chatRooms() {
// TODO - check for current server
val currentServer = serverInteractor.get()!!
client = factory.create(currentServer)
private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
private val currentServer = serverInteractor.get()!!
private var reloadJob: Deferred<List<ChatRoom>>? = null
fun loadChatRooms() {
launchUI(strategy) {
view.showLoading()
val chatRooms = client.chatRooms().update
val openChatRooms = getOpenChatRooms(chatRooms)
val sortedOpenChatRooms = sortChatRooms(openChatRooms)
saveChatRoomsInteractor.save(currentServer, sortedOpenChatRooms)
view.showChatRooms(sortedOpenChatRooms.toMutableList())
view.updateChatRooms(loadRooms())
subscribeRoomUpdates()
view.hideLoading()
}
}
private suspend fun loadRooms(): List<ChatRoom> {
val chatRooms = client.chatRooms().update
val sortedRooms = sortRooms(chatRooms)
saveChatRoomsInteractor.save(currentServer, sortedRooms)
return sortedRooms
}
private fun sortRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
val openChatRooms = getOpenChatRooms(chatRooms)
return sortChatRooms(openChatRooms)
}
private fun updateRooms() {
launch {
view.updateChatRooms(getChatRoomsInteractor.get(currentServer))
}
}
/**
* Get a ChatRoom list from local repository. ChatRooms returned are filtered by name.
*/
......@@ -42,7 +61,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
val currentServer = serverInteractor.get()!!
launchUI(strategy) {
val roomList = getChatRoomsInteractor.getByName(currentServer, name)
view.showChatRooms(roomList.toMutableList())
view.updateChatRooms(roomList)
}
}
......@@ -55,4 +74,164 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
chatRoom -> chatRoom.lastMessage?.timestamp
}
}
// TODO - Temporary stuff, remove when adding DB support
private suspend fun subscribeRoomUpdates() {
launch(CommonPool + strategy.jobs) {
for (status in client.statusChannel) {
Timber.d("Changing status to: $status")
when (status) {
State.Authenticating -> Timber.d("Authenticating")
State.Connected -> {
Timber.d("Connected")
client.subscribeSubscriptions()
client.subscribeRooms()
}
}
}
Timber.d("Done on statusChannel")
}
when (client.state) {
State.Connected -> {
Timber.d("Already connected")
}
else -> client.connect()
}
launch(CommonPool + strategy.jobs) {
for (message in client.roomsChannel) {
Timber.d("Got message: $message")
updateRoom(message)
}
}
launch(CommonPool + strategy.jobs) {
for (message in client.subscriptionsChannel) {
Timber.d("Got message: $message")
updateSubscription(message)
}
}
}
private fun updateRoom(message: StreamMessage<Room>) {
launchUI(strategy) {
when (message.type) {
Type.Removed -> {
removeRoom(message.data.id)
}
Type.Updated -> {
updateRoom(message.data)
}
Type.Inserted -> {
// On insertion, just get all chatrooms again, since we can't create one just
// from a Room
reloadRooms()
}
}
updateRooms()
}
}
private fun updateSubscription(message: StreamMessage<Subscription>) {
launchUI(strategy) {
when (message.type) {
Type.Removed -> {
removeRoom(message.data.roomId)
}
Type.Updated -> {
updateSubscription(message.data)
}
Type.Inserted -> {
// On insertion, just get all chatrooms again, since we can't create one just
// from a Subscription
reloadRooms()
}
}
updateRooms()
}
}
private suspend fun reloadRooms() {
Timber.d("realoadRooms()")
reloadJob?.cancel()
reloadJob = async(CommonPool + strategy.jobs) {
delay(1000)
Timber.d("reloading rooms after wait")
loadRooms()
}
reloadJob?.await()
}
// Update a ChatRoom with a Room information
private fun updateRoom(room: Room) {
val chatRooms = getChatRoomsInteractor.get(currentServer).toMutableList()
val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == room.id }
chatRoom?.apply {
val newRoom = ChatRoom(room.id,
room.type,
room.user ?: user,
room.name ?: name,
room.fullName ?: fullName,
room.readonly,
room.updatedAt ?: updatedAt,
timestamp,
lastModified,
room.topic,
room.announcement,
default,
open,
alert,
unread,
userMenstions,
groupMentions,
room.lastMessage,
client)
removeRoom(room.id, chatRooms)
chatRooms.add(newRoom)
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
}
}
// Update a ChatRoom with a Subscription information
private fun updateSubscription(subscription: Subscription) {
val chatRooms = getChatRoomsInteractor.get(currentServer).toMutableList()
val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == subscription.roomId }
chatRoom?.apply {
val newRoom = ChatRoom(subscription.roomId,
subscription.type,
subscription.user ?: user,
subscription.name,
subscription.fullName ?: fullName,
subscription.readonly ?: readonly,
subscription.updatedAt ?: updatedAt,
subscription.timestamp ?: timestamp,
subscription.lastModified ?: lastModified,
topic,
announcement,
subscription.isDefault,
subscription.open,
subscription.alert,
subscription.unread,
subscription.userMentions,
subscription.groupMentions,
lastMessage,
client)
removeRoom(subscription.roomId, chatRooms)
chatRooms.add(newRoom)
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
}
}
private fun removeRoom(id: String,
chatRooms: MutableList<ChatRoom> = getChatRoomsInteractor.get(currentServer).toMutableList()) {
synchronized(this) {
chatRooms.removeAll { chatRoom -> chatRoom.id == id }
}
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
}
}
\ No newline at end of file
......@@ -11,5 +11,5 @@ interface ChatRoomsView : LoadingView, MessageView {
*
* @param dataSet The data set to show.
*/
fun showChatRooms(dataSet: MutableList<ChatRoom>)
suspend fun updateChatRooms(newDataSet: List<ChatRoom>)
}
\ No newline at end of file
......@@ -18,7 +18,14 @@ import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.item_chat.view.*
class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>, private val context: Context) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
class ChatRoomsAdapter(private val context: Context) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
var dataSet: MutableList<ChatRoom> = ArrayList()
fun updateRooms(newRooms: List<ChatRoom>) {
dataSet.clear()
dataSet.addAll(newRooms)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_chat))
......@@ -72,6 +79,10 @@ class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>, private val c
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
onBindViewHolder(holder, position)
}
override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position
......
......@@ -2,6 +2,8 @@ package chat.rocket.android.chatrooms.ui
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.util.DiffUtil
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
......@@ -14,6 +16,10 @@ import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch
import javax.inject.Inject
class ChatRoomsFragment : Fragment(), ChatRoomsView {
......@@ -32,7 +38,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.chatRooms()
floating_search_view.setOnQueryChangeListener { oldQuery, newQuery ->
floating_search_view.showProgress()
presenter.chatRoomsByName(newQuery)
......@@ -41,14 +47,32 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
floating_search_view.hideProgress()
}
}
}
override fun showChatRooms(dataSet: MutableList<ChatRoom>) {
floating_search_view.hideProgress()
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(this, 144, 32))
recycler_view.adapter = ChatRoomsAdapter(dataSet, this)
recycler_view.adapter = ChatRoomsAdapter(this)
recycler_view.itemAnimator = DefaultItemAnimator()
}
presenter.loadChatRooms()
}
/*override fun showChatRooms(dataSet: MutableList<ChatRoom>) {
floating_search_view.hideProgress()
}*/
override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) {
activity.apply {
launch(UI) {
val adapter = recycler_view.adapter as ChatRoomsAdapter
val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.dataSet, newDataSet))
}.await()
floating_search_view.hideProgress()
adapter.updateRooms(newDataSet)
diff.dispatchUpdatesTo(adapter)
}
}
}
......@@ -59,4 +83,27 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
class RoomsDiffCallback(private val oldRooms: List<ChatRoom>,
private val newRooms: List<ChatRoom>) : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldRooms[oldItemPosition].id == newRooms[newItemPosition].id
}
override fun getOldListSize(): Int {
return oldRooms.size
}
override fun getNewListSize(): Int {
return newRooms.size
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldRooms[oldItemPosition].updatedAt == newRooms[newItemPosition].updatedAt
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
return newRooms[newItemPosition]
}
}
}
\ No newline at end of file
......@@ -15,11 +15,10 @@ class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
AndroidInjection.inject(this)
addFragment("ChatRoomsFragment", R.id.fragment_container) {
ChatRoomsFragment.newInstance()
}
......
......@@ -7,20 +7,6 @@ interface SettingsRepository {
fun get(url: String): Map<String, Value<Any>>?
}
fun Map<String, Value<Any>>.googleEnabled(): Boolean = (this[ACCOUNT_GOOGLE] as Value<Boolean>?)?.value == true
fun Map<String, Value<Any>>.facebookEnabled(): Boolean = (this[ACCOUNT_FACEBOOK] as Value<Boolean>?)?.value == true
fun Map<String, Value<Any>>.githubEnabled(): Boolean = (this[ACCOUNT_GITHUB] as Value<Boolean>?)?.value == true
fun Map<String, Value<Any>>.linkedinEnabled(): Boolean = (this[ACCOUNT_LINKEDIN] as Value<Boolean>?)?.value == true
fun Map<String, Value<Any>>.meteorEnabled(): Boolean = (this[ACCOUNT_METEOR] as Value<Boolean>?)?.value == true
fun Map<String, Value<Any>>.twitterEnabled(): Boolean = (this[ACCOUNT_TWITTER] as Value<Boolean>?)?.value == true
fun Map<String, Value<Any>>.gitlabEnabled(): Boolean = (this[ACCOUNT_GITLAB] as Value<Boolean>?)?.value == true
fun Map<String, Value<Any>>.wordpressEnabled(): Boolean = (this[ACCOUNT_WORDPRESS] as Value<Boolean>?)?.value == true
fun Map<String, Value<Any>>.registrationEnabled(): Boolean {
val value = this[ACCOUNT_REGISTRATION] as Value<String>?
return value?.value == "Public"
}
const val ACCOUNT_FACEBOOK = "Accounts_OAuth_Facebook"
const val ACCOUNT_GITHUB = "Accounts_OAuth_Github"
const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab"
......@@ -45,4 +31,23 @@ const val HIDE_USER_JOIN = "Message_HideType_uj"
const val HIDE_USER_LEAVE = "Message_HideType_ul"
const val HIDE_TYPE_AU = "Message_HideType_au"
const val HIDE_TYPE_RU = "Message_HideType_ru"
const val HIDE_MUTE_UNMUTE = "Message_HideType_mute_unmute"
\ No newline at end of file
const val HIDE_MUTE_UNMUTE = "Message_HideType_mute_unmute"
/*
* Extension functions for Public Settings.
*
* If you need to access a Setting, add a const val key above, add it to the filter on
* ServerPresenter.kt and a extension function to access it
*/
fun Map<String, Value<Any>>.googleEnabled(): Boolean = this[ACCOUNT_GOOGLE]?.value == true
fun Map<String, Value<Any>>.facebookEnabled(): Boolean = this[ACCOUNT_FACEBOOK]?.value == true
fun Map<String, Value<Any>>.githubEnabled(): Boolean = this[ACCOUNT_GITHUB]?.value == true
fun Map<String, Value<Any>>.linkedinEnabled(): Boolean = this[ACCOUNT_LINKEDIN]?.value == true
fun Map<String, Value<Any>>.meteorEnabled(): Boolean = this[ACCOUNT_METEOR]?.value == true
fun Map<String, Value<Any>>.twitterEnabled(): Boolean = this[ACCOUNT_TWITTER]?.value == true
fun Map<String, Value<Any>>.gitlabEnabled(): Boolean = this[ACCOUNT_GITLAB]?.value == true
fun Map<String, Value<Any>>.wordpressEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true
fun Map<String, Value<Any>>.registrationEnabled(): Boolean {
val value = this[ACCOUNT_REGISTRATION]
return value?.value == "Public"
}
\ No newline at end of file
......@@ -4,8 +4,11 @@ import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import okhttp3.OkHttpClient
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class RocketChatClientFactory @Inject constructor(val okHttpClient: OkHttpClient,
val repository: AuthTokenRepository,
val logger: PlatformLogger) {
......@@ -13,6 +16,7 @@ class RocketChatClientFactory @Inject constructor(val okHttpClient: OkHttpClient
fun create(url: String): RocketChatClient {
cache[url]?.let {
Timber.d("Returning CACHED client for: $url")
return it
}
......@@ -23,6 +27,7 @@ class RocketChatClientFactory @Inject constructor(val okHttpClient: OkHttpClient
platformLogger = logger
}
Timber.d("Returning NEW client for: $url")
cache.put(url, client)
return client
}
......
......@@ -35,4 +35,4 @@ var TextView.hintContent: String
get() = hint.toString()
set(value) {
hint = value
}
}
\ No newline at end of file
import org.gradle.internal.jvm.Jvm
ext {
versions = [
java : JavaVersion.VERSION_1_8,
compileSdk : 27,
targetSdk : 27,
buildTools : '27.0.0',
buildTools : '27.0.3',
kotlin : '1.2.10',
coroutine : '0.21',
dokka : '0.9.15',
......@@ -65,6 +63,7 @@ ext {
moshi : "com.squareup.moshi:moshi:${versions.moshi}",
moshiKotlin : "com.squareup.moshi:moshi-kotlin:${versions.moshi}",
kotshi : "se.ansman.kotshi:api:${versions.kotshi}",
okhttp : "com.squareup.okhttp3:okhttp:${versions.okhttp}",
okhttpLogger : "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}",
......@@ -89,7 +88,6 @@ ext {
playServicesGcm : "com.google.android.gms:play-services-gcm:${versions.playServices}",
// For testing
toolsJar : files(Jvm.current().getToolsJar()),
junit : "junit:junit:$versions.junit",
expressoCore : "com.android.support.test.espresso:espresso-core:${versions.expresso}",
roomTest : "android.arch.persistence.room:testing:${versions.room}",
......
......@@ -4,6 +4,7 @@ apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
defaultConfig {
minSdkVersion 21
......@@ -30,4 +31,4 @@ dependencies {
implementation libraries.exoPlayer
testImplementation libraries.junit
}
\ No newline at end of file
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment