Commit 49ba5fbb authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Merge branch 'feature/show-message-list-using-sdk' into feature/support-sending-message-using-sdk

parents bebb5a92 2d03370c
...@@ -55,6 +55,7 @@ hs_err_pid* ...@@ -55,6 +55,7 @@ hs_err_pid*
# Built application files # Built application files
*.apk *.apk
*.ap_ *.ap_
app/libs
# Files for the ART/Dalvik VM # Files for the ART/Dalvik VM
*.dex *.dex
......
...@@ -52,8 +52,7 @@ dependencies { ...@@ -52,8 +52,7 @@ dependencies {
implementation libraries.daggerSupport implementation libraries.daggerSupport
kapt libraries.daggerProcessor kapt libraries.daggerProcessor
kapt libraries.daggerAndroidApt kapt libraries.daggerAndroidApt
implementation libraries.playServicesGcm
implementation libraries.moshi
implementation libraries.room implementation libraries.room
kapt libraries.roomProcessor kapt libraries.roomProcessor
...@@ -62,6 +61,7 @@ dependencies { ...@@ -62,6 +61,7 @@ dependencies {
implementation libraries.rxjava implementation libraries.rxjava
implementation libraries.rxandroid implementation libraries.rxandroid
implementation libraries.moshi
implementation libraries.okhttp implementation libraries.okhttp
implementation libraries.okhttpLogger implementation libraries.okhttpLogger
...@@ -75,6 +75,8 @@ dependencies { ...@@ -75,6 +75,8 @@ dependencies {
implementation libraries.frescoImageViewer implementation libraries.frescoImageViewer
implementation libraries.kotshi
implementation libraries.floatingSearchView implementation libraries.floatingSearchView
implementation libraries.androidSvg implementation libraries.androidSvg
...@@ -101,3 +103,5 @@ task compileSdk(type:Exec) { ...@@ -101,3 +103,5 @@ task compileSdk(type:Exec) {
commandLine './build-sdk.sh' commandLine './build-sdk.sh'
} }
preBuild.dependsOn compileSdk preBuild.dependsOn compileSdk
apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
{
"project_info": {
"project_number": "1020987621558",
"firebase_url": "https://rocketchatnative.firebaseio.com",
"project_id": "rocketchatnative",
"storage_bucket": "rocketchatnative.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1020987621558:android:16da2e50aff9f0c9",
"android_client_info": {
"package_name": "chat.rocket.android"
}
},
"oauth_client": [
{
"client_id": "1020987621558-trk61fjrahho0ujtjap095p1jmi48pfq.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDc7VYUdU6kRkoRTToiCn1rh-W0wJvhLWk"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:1020987621558:android:1551054db195f705",
"android_client_info": {
"package_name": "chat.rocket.android.dev"
}
},
"oauth_client": [
{
"client_id": "1020987621558-trk61fjrahho0ujtjap095p1jmi48pfq.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDc7VYUdU6kRkoRTToiCn1rh-W0wJvhLWk"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"
}
\ No newline at end of file
...@@ -5,6 +5,13 @@ ...@@ -5,6 +5,13 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<application <application
android:name=".app.RocketChatApplication" android:name=".app.RocketChatApplication"
...@@ -29,6 +36,10 @@ ...@@ -29,6 +36,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".webview.WebViewActivity"
android:theme="@style/AppTheme" />
<activity <activity
android:name=".chatrooms.ui.MainActivity" android:name=".chatrooms.ui.MainActivity"
android:theme="@style/ChatListTheme" /> android:theme="@style/ChatListTheme" />
...@@ -37,9 +48,33 @@ ...@@ -37,9 +48,33 @@
android:name=".chatroom.ui.ChatRoomActivity" android:name=".chatroom.ui.ChatRoomActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity <receiver
android:name=".webview.WebViewActivity" android:name="com.google.android.gms.gcm.GcmReceiver"
android:theme="@style/AppTheme" /> android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
<service
android:name=".push.FirebaseTokenService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service
android:name=".push.GcmListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -2,24 +2,29 @@ package chat.rocket.android.app ...@@ -2,24 +2,29 @@ package chat.rocket.android.app
import android.app.Activity import android.app.Activity
import android.app.Application import android.app.Application
import android.app.Service
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.app.utils.CustomImageFormatConfigurator import chat.rocket.android.app.utils.CustomImageFormatConfigurator
import com.facebook.drawee.backends.pipeline.DraweeConfig
import chat.rocket.android.dagger.DaggerAppComponent import chat.rocket.android.dagger.DaggerAppComponent
import com.facebook.drawee.backends.pipeline.DraweeConfig
import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector import dagger.android.HasActivityInjector
import dagger.android.HasServiceInjector
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class RocketChatApplication : Application(), HasActivityInjector { class RocketChatApplication : Application(), HasActivityInjector, HasServiceInjector {
@Inject @Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity> lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
@Inject
lateinit var serviceDispatchingAndroidInjector: DispatchingAndroidInjector<Service>
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
...@@ -52,4 +57,8 @@ class RocketChatApplication : Application(), HasActivityInjector { ...@@ -52,4 +57,8 @@ class RocketChatApplication : Application(), HasActivityInjector {
override fun activityInjector(): AndroidInjector<Activity> { override fun activityInjector(): AndroidInjector<Activity> {
return activityDispatchingAndroidInjector return activityDispatchingAndroidInjector
} }
override fun serviceInjector(): AndroidInjector<Service> {
return serviceDispatchingAndroidInjector
}
} }
\ No newline at end of file
...@@ -3,6 +3,7 @@ package chat.rocket.android.authentication.login.presentation ...@@ -3,6 +3,7 @@ package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
...@@ -11,11 +12,13 @@ import chat.rocket.common.RocketChatTwoFactorException ...@@ -11,11 +12,13 @@ import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.registerPushToken
import javax.inject.Inject import javax.inject.Inject
class LoginPresenter @Inject constructor(private val view: LoginView, class LoginPresenter @Inject constructor(private val view: LoginView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val localRepository: LocalRepository,
private val settingsInteractor: GetSettingsInteractor, private val settingsInteractor: GetSettingsInteractor,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory) { factory: RocketChatClientFactory) {
...@@ -88,6 +91,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -88,6 +91,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
try { try {
client.login(usernameOrEmail, password) // TODO This function returns a user token so should we save it? client.login(usernameOrEmail, password) // TODO This function returns a user token so should we save it?
registerPushToken()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
when (exception) { when (exception) {
...@@ -114,4 +118,11 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -114,4 +118,11 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
} }
fun signup() = navigator.toSignUp() fun signup() = navigator.toSignUp()
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it)
}
// TODO: Schedule push token registering when it comes up null
}
} }
\ No newline at end of file
...@@ -4,20 +4,25 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator ...@@ -4,20 +4,25 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.registerPushToken
import chat.rocket.core.internal.rest.signup import chat.rocket.core.internal.rest.signup
import javax.inject.Inject import javax.inject.Inject
class SignupPresenter @Inject constructor(private val view: SignupView, class SignupPresenter @Inject constructor(private val view: SignupView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val localRepository: LocalRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val factory: RocketChatClientFactory) { private val factory: RocketChatClientFactory) {
private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
fun signup(name: String, username: String, password: String, email: String) { fun signup(name: String, username: String, password: String, email: String) {
val server = serverInteractor.get() val server = serverInteractor.get()
...@@ -46,6 +51,7 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -46,6 +51,7 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
try { try {
client.signup(email, name, username, password) // TODO This function returns a user so should we save it? client.signup(email, name, username, password) // TODO This function returns a user so should we save it?
client.login(username, password) // TODO This function returns a user token so should we save it? client.login(username, password) // TODO This function returns a user token so should we save it?
registerPushToken()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { exception.message?.let {
...@@ -76,4 +82,11 @@ class SignupPresenter @Inject constructor(private val view: SignupView, ...@@ -76,4 +82,11 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
navigator.toWebPage(UrlHelper.getPrivacyPolicyUrl(it)) navigator.toWebPage(UrlHelper.getPrivacyPolicyUrl(it))
} }
} }
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it)
}
// TODO: Schedule push token registering when it comes up null
}
} }
\ No newline at end of file
...@@ -2,21 +2,26 @@ package chat.rocket.android.authentication.twofactor.presentation ...@@ -2,21 +2,26 @@ package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.util.launchUI import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatAuthException import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.registerPushToken
import javax.inject.Inject import javax.inject.Inject
class TwoFAPresenter @Inject constructor(private val view: TwoFAView, class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val localRepository: LocalRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val factory: RocketChatClientFactory) { private val factory: RocketChatClientFactory) {
private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
// TODO: If the usernameOrEmail and password was informed by the user on the previous screen, then we should pass only the pin, like this: fun authenticate(pin: EditText) // TODO: If the usernameOrEmail and password was informed by the user on the previous screen, then we should pass only the pin, like this: fun authenticate(pin: EditText)
fun authenticate(usernameOrEmail: String, password: String, twoFactorAuthenticationCode: String) { fun authenticate(usernameOrEmail: String, password: String, twoFactorAuthenticationCode: String) {
...@@ -30,14 +35,13 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -30,14 +35,13 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
} }
else -> { else -> {
launchUI(strategy) { launchUI(strategy) {
val client = factory.create(server) val client = factory.create(server)
if (NetworkHelper.hasInternetAccess()) { if (NetworkHelper.hasInternetAccess()) {
view.showLoading() view.showLoading()
try { try {
// The token is saved via the client TokenProvider // The token is saved via the client TokenProvider
client.login(usernameOrEmail, password, twoFactorAuthenticationCode) client.login(usernameOrEmail, password, twoFactorAuthenticationCode)
registerPushToken()
navigator.toChatList() navigator.toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
if (exception is RocketChatAuthException) { if (exception is RocketChatAuthException) {
...@@ -61,4 +65,11 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView, ...@@ -61,4 +65,11 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
} }
fun signup() = navigator.toSignUp() fun signup() = navigator.toSignUp()
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it)
}
// TODO: Schedule push token registering when it comes up null
}
} }
\ No newline at end of file
package chat.rocket.android.chatrooms.presentation package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetChatRoomsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SaveChatRoomsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI 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.internal.rest.chatRooms
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.*
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: ChatRoomsNavigator, private val navigator: ChatRoomsNavigator,
serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val getChatRoomsInteractor: GetChatRoomsInteractor,
private val saveChatRoomsInteractor: SaveChatRoomsInteractor,
factory: RocketChatClientFactory) { factory: RocketChatClientFactory) {
private val client = factory.create(serverInteractor.get()!!) private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
private val currentServer = serverInteractor.get()!!
private var reloadJob: Deferred<List<ChatRoom>>? = null
fun loadChatRooms() { fun loadChatRooms() {
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { view.updateChatRooms(loadRooms())
val chatRooms = client.chatRooms().update subscribeRoomUpdates()
val openChatRooms = getOpenChatRooms(chatRooms)
val sortedOpenChatRooms = sortChatRooms(openChatRooms)
view.showChatRooms(sortedOpenChatRooms.toMutableList())
} catch (ex: Exception) {
view.showMessage(ex.message!!)
} finally {
view.hideLoading() view.hideLoading()
} }
} }
fun loadChatRoom(chatRoom: ChatRoom) = navigator.toChatRoom(chatRoom.id, chatRoom.name, chatRoom.type.name, chatRoom.readonly ?: false)
/**
* Gets a [ChatRoom] list from local repository.
* ChatRooms returned are filtered by name.
*/
fun chatRoomsByName(name: String) {
val currentServer = serverInteractor.get()!!
launchUI(strategy) {
val roomList = getChatRoomsInteractor.getByName(currentServer, name)
view.updateChatRooms(roomList)
}
} }
fun loadChatRoom(chatRoom: ChatRoom) { private suspend fun loadRooms(): List<ChatRoom> {
navigator.toChatRoom(chatRoom.id, chatRoom.name, chatRoom.type.name, chatRoom.readonly ?: false) 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))
}
} }
private fun getOpenChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> { private fun getOpenChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
...@@ -44,4 +77,164 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -44,4 +77,164 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
chatRoom.lastMessage?.timestamp 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 { ...@@ -11,5 +11,5 @@ interface ChatRoomsView : LoadingView, MessageView {
* *
* @param dataSet The data set to show. * @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,9 +18,9 @@ import chat.rocket.core.model.ChatRoom ...@@ -18,9 +18,9 @@ import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.item_chat.view.* import kotlinx.android.synthetic.main.item_chat.view.*
class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>, class ChatRoomsAdapter(private val context: Context,
private val context: Context,
private val listener: (ChatRoom) -> Unit) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() { private val listener: (ChatRoom) -> Unit) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
var dataSet: MutableList<ChatRoom> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_chat)) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_chat))
...@@ -30,6 +30,11 @@ class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>, ...@@ -30,6 +30,11 @@ class ChatRoomsAdapter(private var dataSet: MutableList<ChatRoom>,
override fun getItemViewType(position: Int): Int = position override fun getItemViewType(position: Int): Int = position
fun updateRooms(newRooms: List<ChatRoom>) {
dataSet.clear()
dataSet.addAll(newRooms)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(chatRoom: ChatRoom) = with(itemView) { fun bind(chatRoom: ChatRoom) = with(itemView) {
......
...@@ -2,6 +2,8 @@ package chat.rocket.android.chatrooms.ui ...@@ -2,6 +2,8 @@ package chat.rocket.android.chatrooms.ui
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment 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.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
...@@ -15,6 +17,10 @@ import chat.rocket.android.widget.DividerItemDecoration ...@@ -15,6 +17,10 @@ import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_rooms.* 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 import javax.inject.Inject
class ChatRoomsFragment : Fragment(), ChatRoomsView { class ChatRoomsFragment : Fragment(), ChatRoomsView {
...@@ -33,17 +39,41 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -33,17 +39,41 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
presenter.loadChatRooms()
floating_search_view.setOnQueryChangeListener { oldQuery, newQuery ->
floating_search_view.showProgress()
presenter.chatRoomsByName(newQuery)
if (oldQuery.isNotEmpty() && newQuery.isEmpty()) {
floating_search_view.clearSuggestions()
floating_search_view.hideProgress()
}
} }
override fun showChatRooms(dataSet: MutableList<ChatRoom>) {
activity?.apply { activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(this, 144, 32)) recycler_view.addItemDecoration(DividerItemDecoration(this, 144, 32))
recycler_view.adapter = ChatRoomsAdapter(dataSet, this) { chatRoom -> recycler_view.itemAnimator = DefaultItemAnimator()
recycler_view.adapter = ChatRoomsAdapter(this) { chatRoom ->
presenter.loadChatRoom(chatRoom) presenter.loadChatRoom(chatRoom)
} }
} }
presenter.loadChatRooms()
}
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)
}
}
} }
override fun showLoading() = view_loading.setVisibility(true) override fun showLoading() = view_loading.setVisibility(true)
...@@ -53,4 +83,28 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -53,4 +83,28 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show() override fun showMessage(message: String) = Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error)) 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 { ...@@ -15,11 +15,10 @@ class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment> @Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
AndroidInjection.inject(this)
addFragment("ChatRoomsFragment", R.id.fragment_container) { addFragment("ChatRoomsFragment", R.id.fragment_container) {
ChatRoomsFragment.newInstance() ChatRoomsFragment.newInstance()
} }
......
...@@ -4,13 +4,15 @@ import android.app.Application ...@@ -4,13 +4,15 @@ import android.app.Application
import chat.rocket.android.app.RocketChatApplication import chat.rocket.android.app.RocketChatApplication
import chat.rocket.android.dagger.module.ActivityBuilder import chat.rocket.android.dagger.module.ActivityBuilder
import chat.rocket.android.dagger.module.AppModule import chat.rocket.android.dagger.module.AppModule
import chat.rocket.android.dagger.module.ServiceBuilder
import chat.rocket.android.push.FirebaseTokenService
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Component import dagger.Component
import dagger.android.support.AndroidSupportInjectionModule import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Component(modules = [AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class]) @Component(modules = [AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class, ServiceBuilder::class])
interface AppComponent { interface AppComponent {
@Component.Builder @Component.Builder
...@@ -23,6 +25,8 @@ interface AppComponent { ...@@ -23,6 +25,8 @@ interface AppComponent {
fun inject(app: RocketChatApplication) fun inject(app: RocketChatApplication)
fun inject(service: FirebaseTokenService)
/*@Component.Builder /*@Component.Builder
abstract class Builder : AndroidInjector.Builder<RocketChatApplication>()*/ abstract class Builder : AndroidInjector.Builder<RocketChatApplication>()*/
} }
...@@ -7,8 +7,12 @@ import android.content.SharedPreferences ...@@ -7,8 +7,12 @@ import android.content.SharedPreferences
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.app.RocketChatDatabase import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPrefsLocalRepository
import chat.rocket.android.server.domain.ChatRoomsRepository
import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infraestructure.MemorySettingsRepository import chat.rocket.android.server.infraestructure.MemorySettingsRepository
import chat.rocket.android.server.infraestructure.ServerDao import chat.rocket.android.server.infraestructure.ServerDao
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
...@@ -99,6 +103,12 @@ class AppModule { ...@@ -99,6 +103,12 @@ class AppModule {
return context.getSharedPreferences("rocket.chat", Context.MODE_PRIVATE) return context.getSharedPreferences("rocket.chat", Context.MODE_PRIVATE)
} }
@Provides
@Singleton
fun provideSharedPreferencesRepository(prefs: SharedPreferences): LocalRepository {
return SharedPrefsLocalRepository(prefs)
}
@Provides @Provides
@Singleton @Singleton
fun provideCurrentServerRepository(prefs: SharedPreferences): CurrentServerRepository { fun provideCurrentServerRepository(prefs: SharedPreferences): CurrentServerRepository {
...@@ -110,4 +120,10 @@ class AppModule { ...@@ -110,4 +120,10 @@ class AppModule {
fun provideSettingsRepository(): SettingsRepository { fun provideSettingsRepository(): SettingsRepository {
return MemorySettingsRepository() return MemorySettingsRepository()
} }
@Provides
@Singleton
fun provideChatRoomsRepository(): ChatRoomsRepository {
return MemoryChatRoomsRepository()
}
} }
\ No newline at end of file
package chat.rocket.android.dagger.module
import chat.rocket.android.push.FirebaseTokenService
import chat.rocket.android.push.di.FirebaseTokenServiceProvider
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class ServiceBuilder {
@ContributesAndroidInjector(modules = [FirebaseTokenServiceProvider::class])
abstract fun bindFirebaseTokenService(): FirebaseTokenService
}
\ No newline at end of file
package chat.rocket.android.infrastructure
interface LocalRepository {
companion object {
val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN"
}
fun save(key: String, value: String?)
fun get(key: String): String?
}
\ No newline at end of file
package chat.rocket.android.infrastructure
import android.content.SharedPreferences
class SharedPrefsLocalRepository(private val preferences: SharedPreferences) : LocalRepository {
override fun save(key: String, value: String?) {
preferences.edit().putString(key, value).apply()
}
override fun get(key: String): String? {
return preferences.getString(key, null)
}
}
\ No newline at end of file
package chat.rocket.android.push
import chat.rocket.android.R
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.common.RocketChatException
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.registerPushToken
import com.google.android.gms.gcm.GoogleCloudMessaging
import com.google.android.gms.iid.InstanceID
import com.google.firebase.iid.FirebaseInstanceIdService
import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject
class FirebaseTokenService : FirebaseInstanceIdService() {
@Inject
lateinit var client: RocketChatClient
@Inject
lateinit var localRepository: LocalRepository
override fun onCreate() {
super.onCreate()
AndroidInjection.inject(this);
}
override fun onTokenRefresh() {
//TODO: We need to use the Cordova Project gcm_sender_id since it's the one configured on RC
// default push gateway. We should register this project's own project sender id into it.
val gcmToken = InstanceID.getInstance(this)
.getToken(getString(R.string.gcm_sender_id), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null)
gcmToken?.let {
localRepository.save(LocalRepository.KEY_PUSH_TOKEN, gcmToken)
launch {
try {
client.registerPushToken(gcmToken)
} catch (ex: RocketChatException) {
Timber.e(ex)
}
}
}
}
}
\ No newline at end of file
package chat.rocket.android.push
import android.os.Bundle
import com.google.android.gms.gcm.GcmListenerService
class GcmListenerService : GcmListenerService() {
override fun onMessageReceived(from: String?, data: Bundle?) {
data?.let {
PushManager.handle(this, data)
}
}
}
\ No newline at end of file
package chat.rocket.android.push
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
import android.support.v4.app.RemoteInput
import android.text.Html
import android.text.Spanned
import android.util.Log
import chat.rocket.android.BuildConfig
import chat.rocket.android.R
import chat.rocket.android.chatrooms.ui.MainActivity
import org.json.JSONObject
import java.io.Serializable
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.HashMap
typealias TupleGroupIdMessageCount = Pair<Int, AtomicInteger>
/**
* Refer to: https://github.com/RocketChat/Rocket.Chat.Android/blob/9e846b7fde8fe0c74b9e0117c37ce49293308db5/app/src/main/java/chat/rocket/android/push/PushManager.kt
* for old source code.
*/
object PushManager {
const val EXTRA_NOT_ID = "chat.rocket.android.EXTRA_NOT_ID"
const val EXTRA_HOSTNAME = "chat.rocket.android.EXTRA_HOSTNAME"
const val EXTRA_PUSH_MESSAGE = "chat.rocket.android.EXTRA_PUSH_MESSAGE"
const val EXTRA_ROOM_ID = "chat.rocket.android.EXTRA_ROOM_ID"
private const val REPLY_LABEL = "REPLY"
private const val REMOTE_INPUT_REPLY = "REMOTE_INPUT_REPLY"
// Notifications received from the same server are grouped in a single bundled notification.
// This map associates a host to a group id.
private val groupMap = HashMap<String, TupleGroupIdMessageCount>()
// Map a hostname to a list of push messages that pertain to it.
private val hostToPushMessageList = HashMap<String, MutableList<PushMessage>>()
private val randomizer = Random()
/**
* Handles a receiving push by creating and displaying an appropriate notification based
* on the *data* param bundle received.
*/
@Synchronized
fun handle(context: Context, data: Bundle) {
val appContext = context.applicationContext
val message = data["message"] as String?
val image = data["image"] as String?
val ejson = data["ejson"] as String?
val notId = data["notId"] as String? ?: randomizer.nextInt().toString()
val style = data["style"] as String?
val summaryText = data["summaryText"] as String?
val count = data["count"] as String?
val title = data["title"] as String?
if (ejson == null || message == null || title == null) {
return
}
val lastPushMessage = PushMessage(title, message, image, ejson, count, notId, summaryText, style)
// We should use Timber here
if (BuildConfig.DEBUG) {
Log.d(PushMessage::class.java.simpleName, lastPushMessage.toString())
}
showNotification(appContext, lastPushMessage)
}
/**
* Clear all messages received to a given host the user is signed-in.
*/
fun clearNotificationsByHost(host: String) {
hostToPushMessageList.remove(host)
}
/**
* Remove a notification solely by it's unique id.
*/
fun clearNotificationsByNotificationId(notificationId: Int) {
if (hostToPushMessageList.isNotEmpty()) {
for (entry in hostToPushMessageList.entries) {
entry.value.removeAll {
it.notificationId.toInt() == notificationId
}
}
}
}
/**
* Clear notifications by the host they belong to and its unique id.
*/
fun clearNotificationsByHostAndNotificationId(host: String?, notificationId: Int?) {
if (host == null || notificationId == null) {
return
}
if (hostToPushMessageList.isNotEmpty()) {
val notifications = hostToPushMessageList[host]
notifications?.let {
notifications.removeAll {
it.notificationId.toInt() == notificationId
}
}
}
}
private fun getGroupForHost(host: String): TupleGroupIdMessageCount {
val size = groupMap.size
var group = groupMap.get(host)
if (group == null) {
group = TupleGroupIdMessageCount(size + 1, AtomicInteger(0))
groupMap.put(host, group)
}
return group
}
@SuppressLint("NewApi")
internal fun showNotification(context: Context, lastPushMessage: PushMessage) {
if (lastPushMessage.host == null || lastPushMessage.message == null || lastPushMessage.title == null) {
return
}
val manager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notId = lastPushMessage.notificationId.toInt()
val host = lastPushMessage.host
val groupTuple = getGroupForHost(host)
groupTuple.second.incrementAndGet()
val notIdListForHostname: MutableList<PushMessage>? = hostToPushMessageList.get(host)
if (notIdListForHostname == null) {
hostToPushMessageList.put(host, arrayListOf(lastPushMessage))
} else {
notIdListForHostname.add(0, lastPushMessage)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val notification = createSingleNotificationForNougatAndAbove(context, lastPushMessage)
val groupNotification = createGroupNotificationForNougatAndAbove(context, lastPushMessage)
notification?.let {
manager.notify(notId, notification)
}
groupNotification?.let {
manager.notify(groupTuple.first, groupNotification)
}
} else {
val notification = createSingleNotification(context, lastPushMessage)
val pushMessageList = hostToPushMessageList.get(host)
notification?.let {
NotificationManagerCompat.from(context).notify(notId, notification)
}
pushMessageList?.let {
if (pushMessageList.size > 1) {
val groupNotification = createGroupNotification(context, lastPushMessage)
groupNotification?.let {
NotificationManagerCompat.from(context).notify(groupTuple.first, groupNotification)
}
}
}
}
}
internal fun createGroupNotification(context: Context, lastPushMessage: PushMessage): Notification? {
with(lastPushMessage) {
if (host == null || message == null || title == null) {
return null
}
val id = lastPushMessage.notificationId.toInt()
val contentIntent = getContentIntent(context, id, lastPushMessage)
val deleteIntent = getDismissIntent(context, lastPushMessage)
val builder = NotificationCompat.Builder(context)
.setWhen(createdAt)
.setContentTitle(title.fromHtml())
.setContentText(message.fromHtml())
.setGroup(host)
.setGroupSummary(true)
.setContentIntent(contentIntent)
.setDeleteIntent(deleteIntent)
.setMessageNotification()
//TODO: Get Site_Name PublicSetting from cache
val subText = "Rocket.Chat"
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
if (style == null || style == "inbox") {
val pushMessageList = hostToPushMessageList.get(host)
pushMessageList?.let {
val messageCount = pushMessageList.size
val summary = summaryText?.replace("%n%", messageCount.toString())
?.fromHtml() ?: "$messageCount new messages"
builder.setNumber(messageCount)
if (messageCount > 1) {
val firstPush = pushMessageList[0]
val singleConversation = pushMessageList.filter {
firstPush.sender?.username != it.sender?.username
}.isEmpty()
val inbox = NotificationCompat.InboxStyle()
.setBigContentTitle(if (singleConversation) title else summary)
for (push in pushMessageList) {
if (singleConversation) {
inbox.addLine(push.message)
} else {
inbox.addLine("<font color='black'>${push.title}</font> <font color='gray'>${push.message}</font>".fromHtml())
}
}
builder.setStyle(inbox)
} else {
val firstMsg = pushMessageList[0]
if (firstMsg.host == null || firstMsg.message == null || firstMsg.title == null) {
return null
}
val bigText = NotificationCompat.BigTextStyle()
.bigText(firstMsg.message.fromHtml())
.setBigContentTitle(firstMsg.title.fromHtml())
builder.setStyle(bigText)
}
}
} else {
val bigText = NotificationCompat.BigTextStyle()
.bigText(message.fromHtml())
.setBigContentTitle(title.fromHtml())
builder.setStyle(bigText)
}
return builder.build()
}
}
@SuppressLint("NewApi")
@RequiresApi(Build.VERSION_CODES.N)
internal fun createGroupNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? {
with(lastPushMessage) {
if (host == null || message == null || title == null) {
return null
}
val manager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val id = notificationId.toInt()
val contentIntent = getContentIntent(context, id, lastPushMessage, grouped = true)
val deleteIntent = getDismissIntent(context, lastPushMessage)
val builder = Notification.Builder(context)
.setWhen(createdAt)
.setContentTitle(title.fromHtml())
.setContentText(message.fromHtml())
.setGroup(host)
.setGroupSummary(true)
.setContentIntent(contentIntent)
.setDeleteIntent(deleteIntent)
.setMessageNotification(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(host)
val groupChannel = NotificationChannel(host, host, NotificationManager.IMPORTANCE_HIGH)
groupChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
groupChannel.enableLights(false)
groupChannel.enableVibration(true)
groupChannel.setShowBadge(true)
manager.createNotificationChannel(groupChannel)
}
//TODO: Get Site_Name PublicSetting from cache
val subText = "Rocket.Chat"
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
if (style == null || style == "inbox") {
val pushMessageList = hostToPushMessageList.get(host)
pushMessageList?.let {
val count = pushMessageList.filter {
it.title == title
}.size
builder.setContentTitle(getTitle(count, title))
val inbox = Notification.InboxStyle()
.setBigContentTitle(getTitle(count, title))
for (push in pushMessageList) {
inbox.addLine(push.message)
}
builder.setStyle(inbox)
}
} else {
val bigText = Notification.BigTextStyle()
.bigText(message.fromHtml())
.setBigContentTitle(title.fromHtml())
builder.setStyle(bigText)
}
return builder.build()
}
}
internal fun createSingleNotification(context: Context, lastPushMessage: PushMessage): Notification? {
with(lastPushMessage) {
if (host == null || message == null || title == null) {
return null
}
val id = notificationId.toInt()
val contentIntent = getContentIntent(context, id, lastPushMessage)
val deleteIntent = getDismissIntent(context, lastPushMessage)
val builder = NotificationCompat.Builder(context)
.setWhen(createdAt)
.setContentTitle(title.fromHtml())
.setContentText(message.fromHtml())
.setGroupSummary(false)
.setGroup(host)
.setDeleteIntent(deleteIntent)
.setContentIntent(contentIntent)
.setMessageNotification()
//TODO: Get Site_Name PublicSetting from cache
val subText = "Rocket.Chat"
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
val pushMessageList = hostToPushMessageList.get(host)
pushMessageList?.let {
val lastPushMsg = pushMessageList.last()
if (lastPushMsg.host == null || lastPushMsg.message == null || lastPushMsg.title == null) {
return null
}
if (pushMessageList.isNotEmpty()) {
val messageCount = pushMessageList.size
val bigText = NotificationCompat.BigTextStyle()
.bigText(lastPushMsg.message.fromHtml())
.setBigContentTitle(lastPushMsg.title.fromHtml())
builder.setStyle(bigText).setNumber(messageCount)
}
}
return builder.build()
}
}
@SuppressLint("NewApi")
@RequiresApi(Build.VERSION_CODES.N)
internal fun createSingleNotificationForNougatAndAbove(context: Context, lastPushMessage: PushMessage): Notification? {
val manager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
with(lastPushMessage) {
if (host == null || message == null || title == null) {
return null
}
val id = notificationId.toInt()
val contentIntent = getContentIntent(context, id, lastPushMessage)
val deleteIntent = getDismissIntent(context, lastPushMessage)
val builder = Notification.Builder(context)
.setWhen(createdAt)
.setContentTitle(title.fromHtml())
.setContentText(message.fromHtml())
.setGroup(host)
.setGroupSummary(false)
.setDeleteIntent(deleteIntent)
.setContentIntent(contentIntent)
.setMessageNotification(context)
.addReplyAction(context, lastPushMessage)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(host)
val channel = NotificationChannel(host, host, NotificationManager.IMPORTANCE_HIGH)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.enableLights(false)
channel.enableVibration(true)
channel.setShowBadge(true)
manager.createNotificationChannel(channel)
}
//TODO: Get Site_Name PublicSetting from cache
val subText = "Rocket.Chat"
if (subText.isNotEmpty()) {
builder.setSubText(subText)
}
if (style == null || "inbox" == style) {
val pushMessageList = hostToPushMessageList.get(host)
pushMessageList?.let {
val userMessages = pushMessageList.filter {
it.notificationId == lastPushMessage.notificationId
}
val count = pushMessageList.filter {
it.title == title
}.size
builder.setContentTitle(getTitle(count, title))
if (count > 1) {
val inbox = Notification.InboxStyle()
inbox.setBigContentTitle(getTitle(count, title))
for (push in userMessages) {
inbox.addLine(push.message)
}
builder.setStyle(inbox)
} else {
val bigTextStyle = Notification.BigTextStyle()
.bigText(message.fromHtml())
builder.setStyle(bigTextStyle)
}
}
} else {
val bigTextStyle = Notification.BigTextStyle()
.bigText(message.fromHtml())
builder.setStyle(bigTextStyle)
}
return builder.build()
}
}
private fun getTitle(messageCount: Int, title: String): CharSequence {
return if (messageCount > 1) "($messageCount) ${title.fromHtml()}" else title.fromHtml()
}
private fun getDismissIntent(context: Context, pushMessage: PushMessage): PendingIntent {
val deleteIntent = Intent(context, DeleteReceiver::class.java)
.putExtra(EXTRA_NOT_ID, pushMessage.notificationId.toInt())
.putExtra(EXTRA_HOSTNAME, pushMessage.host)
return PendingIntent.getBroadcast(context, pushMessage.notificationId.toInt(), deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun getContentIntent(context: Context, notificationId: Int, pushMessage: PushMessage, grouped: Boolean = false): PendingIntent {
val notificationIntent = Intent(context, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(EXTRA_NOT_ID, notificationId)
.putExtra(EXTRA_HOSTNAME, pushMessage.host)
if (!grouped) {
notificationIntent.putExtra(EXTRA_ROOM_ID, pushMessage.rid)
}
return PendingIntent.getActivity(context, randomizer.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// CharSequence extensions
private fun CharSequence.fromHtml(): Spanned {
return Html.fromHtml(this as String)
}
//Notification.Builder extensions
@RequiresApi(Build.VERSION_CODES.N)
private fun Notification.Builder.addReplyAction(ctx: Context, pushMessage: PushMessage): Notification.Builder {
val replyRemoteInput = android.app.RemoteInput.Builder(REMOTE_INPUT_REPLY)
.setLabel(REPLY_LABEL)
.build()
//TODO: Implement this when we have sendMessage call
// val replyIntent = Intent(ctx, ReplyReceiver::class.java)
// replyIntent.putExtra(EXTRA_PUSH_MESSAGE, pushMessage as Serializable)
// val pendingIntent = PendingIntent.getBroadcast(
// ctx, randomizer.nextInt(), replyIntent, 0)
// val replyAction =
// Notification.Action.Builder(
// Icon.createWithResource(ctx, R.drawable.ic_reply), REPLY_LABEL, pendingIntent)
// .addRemoteInput(replyRemoteInput)
// .setAllowGeneratedReplies(true)
// .build()
// this.addAction(replyAction)
return this
}
@RequiresApi(Build.VERSION_CODES.N)
private fun Notification.Builder.setMessageNotification(ctx: Context): Notification.Builder {
val alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val res = ctx.resources
val smallIcon = res.getIdentifier(
"rocket_chat_notification", "drawable", ctx.packageName)
with(this, {
setAutoCancel(true)
setShowWhen(true)
setColor(res.getColor(R.color.colorPrimary, ctx.theme))
setSmallIcon(smallIcon)
setSound(alarmSound)
})
return this
}
// NotificationCompat.Builder extensions
private fun NotificationCompat.Builder.addReplyAction(pushMessage: PushMessage): NotificationCompat.Builder {
val context = this.mContext
val replyRemoteInput = RemoteInput.Builder(REMOTE_INPUT_REPLY)
.setLabel(REPLY_LABEL)
.build()
//TODO: Implement when we have sendMessage call
// val replyIntent = Intent(context, ReplyReceiver::class.java)
// replyIntent.putExtra(EXTRA_PUSH_MESSAGE, pushMessage as Serializable)
// val pendingIntent = PendingIntent.getBroadcast(
// context, randomizer.nextInt(), replyIntent, 0)
// val replyAction = NotificationCompat.Action.Builder(R.drawable.ic_reply, REPLY_LABEL, pendingIntent)
// .addRemoteInput(replyRemoteInput)
// .setAllowGeneratedReplies(true)
// .build()
//
// this.addAction(replyAction)
return this
}
private fun NotificationCompat.Builder.setMessageNotification(): NotificationCompat.Builder {
val alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val ctx = this.mContext
val res = ctx.resources
val smallIcon = res.getIdentifier(
"rocket_chat_notification", "drawable", ctx.packageName)
with(this, {
setAutoCancel(true)
setShowWhen(true)
color = ctx.resources.getColor(R.color.colorPrimary)
setDefaults(Notification.DEFAULT_ALL)
setSmallIcon(smallIcon)
setSound(alarmSound)
})
return this
}
internal data class PushMessage(
val title: String? = null,
val message: String? = null,
val image: String? = null,
val ejson: String? = null,
val count: String? = null,
val notificationId: String,
val summaryText: String? = null,
val style: String? = null) : Serializable {
val host: String?
val rid: String?
val type: String?
val channelName: String?
val sender: Sender?
val createdAt: Long
init {
val json = if (ejson == null) JSONObject() else JSONObject(ejson)
host = json.optString("host", null)
rid = json.optString("rid", null)
type = json.optString("type", null)
channelName = json.optString("name", null)
val senderJson = json.optString("sender", null)
if (senderJson != null && senderJson != "null") {
sender = Sender(senderJson)
} else {
sender = null
}
createdAt = System.currentTimeMillis()
}
data class Sender(val sender: String) : Serializable {
val _id: String?
val username: String?
val name: String?
init {
val json = JSONObject(sender)
_id = json.optString("_id", null)
username = json.optString("username", null)
name = json.optString("name", null)
}
}
}
/**
* BroadcastReceiver for dismissed notifications.
*/
class DeleteReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val notId = intent?.extras?.getInt(EXTRA_NOT_ID)
val host = intent?.extras?.getString(EXTRA_HOSTNAME)
if (host != null && notId != null) {
clearNotificationsByHostAndNotificationId(host, notId)
}
}
}
}
\ No newline at end of file
package chat.rocket.android.push.di
import chat.rocket.android.dagger.module.AppModule
import chat.rocket.android.push.FirebaseTokenService
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class FirebaseTokenServiceProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideFirebaseTokenService(): FirebaseTokenService
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.core.model.ChatRoom
interface ChatRoomsRepository {
fun save(url: String, chatRooms: List<ChatRoom>)
fun get(url: String): List<ChatRoom>
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.core.model.ChatRoom
import kotlinx.coroutines.experimental.async
import javax.inject.Inject
class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsRepository) {
fun get(url: String) = repository.get(url)
suspend fun getByName(url: String, name: String): List<ChatRoom> {
val chatRooms = async {
val allChatRooms = repository.get(url)
if (name.isEmpty()) {
return@async allChatRooms
}
return@async allChatRooms.filter {
it.name.contains(name, true)
}
}
return chatRooms.await()
}
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.core.model.ChatRoom
import javax.inject.Inject
class SaveChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsRepository) {
fun save(url: String, chatRooms: List<ChatRoom>) = repository.save(url, chatRooms)
}
\ No newline at end of file
...@@ -7,20 +7,6 @@ interface SettingsRepository { ...@@ -7,20 +7,6 @@ interface SettingsRepository {
fun get(url: String): Map<String, Value<Any>>? 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_FACEBOOK = "Accounts_OAuth_Facebook"
const val ACCOUNT_GITHUB = "Accounts_OAuth_Github" const val ACCOUNT_GITHUB = "Accounts_OAuth_Github"
const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab" const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab"
...@@ -46,3 +32,22 @@ const val HIDE_USER_LEAVE = "Message_HideType_ul" ...@@ -46,3 +32,22 @@ const val HIDE_USER_LEAVE = "Message_HideType_ul"
const val HIDE_TYPE_AU = "Message_HideType_au" const val HIDE_TYPE_AU = "Message_HideType_au"
const val HIDE_TYPE_RU = "Message_HideType_ru" const val HIDE_TYPE_RU = "Message_HideType_ru"
const val HIDE_MUTE_UNMUTE = "Message_HideType_mute_unmute" 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
package chat.rocket.android.server.infraestructure
import chat.rocket.android.server.domain.ChatRoomsRepository
import chat.rocket.core.model.ChatRoom
class MemoryChatRoomsRepository : ChatRoomsRepository {
val cache = HashMap<String, List<ChatRoom>>()
override fun save(url: String, chatRooms: List<ChatRoom>) {
//TODO: should diff the existing chatrooms and new chatroom dataset
cache[url] = chatRooms
}
override fun get(url: String): List<ChatRoom> = cache[url] ?: emptyList()
}
\ No newline at end of file
...@@ -4,15 +4,19 @@ import chat.rocket.android.authentication.infraestructure.AuthTokenRepository ...@@ -4,15 +4,19 @@ import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.common.util.PlatformLogger import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
class RocketChatClientFactory @Inject constructor(private val okHttpClient: OkHttpClient, @Singleton
private val repository: AuthTokenRepository, class RocketChatClientFactory @Inject constructor(val okHttpClient: OkHttpClient,
private val logger: PlatformLogger) { val repository: AuthTokenRepository,
val logger: PlatformLogger) {
private val cache = HashMap<String, RocketChatClient>() private val cache = HashMap<String, RocketChatClient>()
fun create(url: String): RocketChatClient { fun create(url: String): RocketChatClient {
cache[url]?.let { cache[url]?.let {
Timber.d("Returning CACHED client for: $url")
return it return it
} }
...@@ -23,6 +27,7 @@ class RocketChatClientFactory @Inject constructor(private val okHttpClient: OkHt ...@@ -23,6 +27,7 @@ class RocketChatClientFactory @Inject constructor(private val okHttpClient: OkHt
platformLogger = logger platformLogger = logger
} }
Timber.d("Returning NEW client for: $url")
cache.put(url, client) cache.put(url, client)
return client return client
} }
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M44.99,23.47C44.99,21.42 44.38,19.45 43.16,17.62C42.07,15.97 40.54,14.52 38.62,13.29C34.91,10.92 30.03,9.62 24.88,9.62C23.16,9.62 21.47,9.77 19.82,10.05C18.8,9.1 17.61,8.24 16.35,7.57C9.6,4.3 4,7.49 4,7.49C4,7.49 9.21,11.76 8.36,15.49C6.03,17.8 4.77,20.58 4.77,23.47C4.77,23.48 4.77,23.49 4.77,23.5C4.77,23.51 4.77,23.51 4.77,23.52C4.77,26.42 6.03,29.2 8.36,31.5C9.21,35.24 4,39.5 4,39.5C4,39.5 9.6,42.69 16.35,39.43C17.61,38.75 18.8,37.89 19.82,36.94C21.47,37.23 23.16,37.37 24.88,37.37C30.03,37.37 34.91,36.07 38.62,33.7C40.54,32.48 42.07,31.02 43.16,29.38C44.38,27.55 44.99,25.58 44.99,23.53C44.99,23.52 44.99,23.51 44.99,23.5L44.99,23.47ZM24.88,12.53C34.41,12.53 42.14,17.45 42.14,23.52C42.14,29.6 34.41,34.52 24.88,34.52C22.76,34.52 20.73,34.28 18.85,33.83C16.94,36.12 12.74,39.31 8.67,38.28C9.99,36.86 11.96,34.45 11.54,30.5C9.09,28.6 7.63,26.17 7.63,23.52C7.63,17.45 15.35,12.53 24.88,12.53Z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M24.88,26.17C26.15,26.17 27.17,25.14 27.17,23.88C27.17,22.61 26.15,21.59 24.88,21.59C23.62,21.59 22.59,22.61 22.59,23.88C22.59,25.14 23.62,26.17 24.88,26.17ZM32.85,26.17C34.12,26.17 35.14,25.14 35.14,23.88C35.14,22.61 34.12,21.59 32.85,21.59C31.59,21.59 30.56,22.61 30.56,23.88C30.56,25.14 31.59,26.17 32.85,26.17ZM16.91,26.17C18.18,26.17 19.2,25.14 19.2,23.88C19.2,22.62 18.18,21.59 16.91,21.59C15.65,21.59 14.62,22.62 14.62,23.88C14.62,25.14 15.65,26.17 16.91,26.17L16.91,26.17Z" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- This is the Cordova GCM sender id-->
<string name="gcm_sender_id" translatable="false">673693445664</string>
</resources>
\ No newline at end of file
...@@ -12,6 +12,7 @@ buildscript { ...@@ -12,6 +12,7 @@ buildscript {
classpath "com.android.tools.build:gradle:3.0.1" classpath "com.android.tools.build:gradle:3.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.1.2'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
......
import org.gradle.internal.jvm.Jvm
ext { ext {
versions = [ versions = [
java : JavaVersion.VERSION_1_8, java : JavaVersion.VERSION_1_8,
compileSdk : 27, compileSdk : 27,
targetSdk : 27, targetSdk : 27,
buildTools : '27.0.0', buildTools : '27.0.3',
kotlin : '1.2.10', kotlin : '1.2.10',
coroutine : '0.21', coroutine : '0.21',
dokka : '0.9.15', dokka : '0.9.15',
kotshi : '0.3.0-beta2',
// Main dependencies // Main dependencies
support : '27.0.2', support : '27.0.2',
constraintLayout : '1.0.2', constraintLayout : '1.0.2',
dagger : '2.13', dagger : '2.13',
exoPlayer : '2.6.0', exoPlayer : '2.6.0',
playServices : '11.8.0',
room : '1.0.0', room : '1.0.0',
rxjava : '2.1.4', rxjava : '2.1.4',
rxandroid : '2.0.1', rxandroid : '2.0.1',
...@@ -25,6 +23,7 @@ ext { ...@@ -25,6 +23,7 @@ ext {
threeTenABP : '1.0.5', threeTenABP : '1.0.5',
fresco : '1.7.1', fresco : '1.7.1',
frescoImageViewer : '0.5.0', frescoImageViewer : '0.5.0',
kotshi : '0.3.0',
floatingSearchView : '2.1.1', floatingSearchView : '2.1.1',
androidSvg : '1.2.1', androidSvg : '1.2.1',
aVLoadingIndicatorView : '2.1.3', aVLoadingIndicatorView : '2.1.3',
...@@ -53,6 +52,7 @@ ext { ...@@ -53,6 +52,7 @@ ext {
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}", daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}", daggerProcessor : "com.google.dagger:dagger-compiler:${versions.dagger}",
daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}", daggerAndroidApt : "com.google.dagger:dagger-android-processor:${versions.dagger}",
playServicesGcm : "com.google.android.gms:play-services-gcm:${versions.playServices}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}", exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
room : "android.arch.persistence.room:runtime:${versions.room}", room : "android.arch.persistence.room:runtime:${versions.room}",
...@@ -77,6 +77,8 @@ ext { ...@@ -77,6 +77,8 @@ ext {
frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}", frescoImageViewer : "com.github.stfalcon:frescoimageviewer:${versions.frescoImageViewer}",
kotshi : "se.ansman.kotshi:api:${versions.kotshi}",
floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}", floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}",
androidSvg : "com.caverock:androidsvg:${versions.androidSvg}", androidSvg : "com.caverock:androidsvg:${versions.androidSvg}",
...@@ -86,7 +88,6 @@ ext { ...@@ -86,7 +88,6 @@ ext {
textDrawable : "com.github.rocketchat:textdrawable:${versions.textDrawable}", textDrawable : "com.github.rocketchat:textdrawable:${versions.textDrawable}",
// For testing // For testing
toolsJar : files(Jvm.current().getToolsJar()),
junit : "junit:junit:$versions.junit", junit : "junit:junit:$versions.junit",
expressoCore : "com.android.support.test.espresso:espresso-core:${versions.expresso}", expressoCore : "com.android.support.test.espresso:espresso-core:${versions.expresso}",
roomTest : "android.arch.persistence.room:testing:${versions.room}", roomTest : "android.arch.persistence.room:testing:${versions.room}",
......
...@@ -4,6 +4,7 @@ apply plugin: 'kotlin-android-extensions' ...@@ -4,6 +4,7 @@ apply plugin: 'kotlin-android-extensions'
android { android {
compileSdkVersion versions.compileSdk compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
......
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