Commit f4a743d4 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Add Jitsi SDK

parent fa0cc587
......@@ -2,7 +2,9 @@ def taskRequests = getGradle().getStartParameter().getTaskRequests().toString()
def isPlay = !(taskRequests.contains("Foss") || taskRequests.contains("foss"))
apply plugin: 'com.android.application'
if (isPlay) { apply plugin: 'io.fabric' }
if (isPlay) {
apply plugin: 'io.fabric'
}
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
......@@ -16,13 +18,12 @@ android {
applicationId "chat.rocket.android"
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 2057
versionName "3.2.0"
versionCode 2058
versionName "3.3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
def buildTime = new GregorianCalendar().format("MM-dd-yyyy' 'h:mm:ss a z")
buildConfigField "String", "GIT_SHA", "\"${gitSha}\""
javaCompileOptions {
......@@ -30,6 +31,17 @@ android {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
// For Jitsi
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// For Jitsi
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
signingConfigs {
......@@ -73,7 +85,7 @@ android {
dimension "type"
}
// only foss
// only FOSS
foss {
dimension "type"
}
......@@ -96,7 +108,7 @@ dependencies {
implementation project(':suggestions')
implementation libraries.kotlin
implementation libraries.coroutines
implementation libraries.coroutinesCore
implementation libraries.coroutinesAndroid
implementation libraries.appCompat
......@@ -123,6 +135,8 @@ dependencies {
implementation libraries.viewmodelKtx
implementation libraries.workmanager
implementation libraries.livedataKtx
implementation libraries.rxKotlin
implementation libraries.rxAndroid
......@@ -133,25 +147,25 @@ dependencies {
implementation libraries.timber
implementation libraries.threeTenABP
kapt libraries.kotshiCompiler
implementation libraries.kotshiApi
implementation libraries.fresco
api libraries.frescoOkHttp
implementation libraries.frescoAnimatedGif
implementation libraries.frescoWebP
implementation libraries.frescoAnimatedWebP
implementation libraries.glide
implementation libraries.glideTransformations
kapt libraries.kotshiCompiler
implementation libraries.kotshiApi
implementation libraries.frescoImageViewer
implementation libraries.markwon
implementation libraries.aVLoadingIndicatorView
implementation libraries.livedataKtx
implementation libraries.glide
implementation libraries.glideTransformations
implementation(libraries.jitsi) { transitive = true }
implementation 'com.google.code.findbugs:jsr305:3.0.2'
......@@ -159,8 +173,8 @@ dependencies {
playImplementation libraries.fcm
playImplementation libraries.firebaseAnalytics
playImplementation libraries.playServicesAuth
playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.5@aar') { transitive = true }
playImplementation('com.crashlytics.sdk.android:answers:1.4.3@aar') { transitive = true }
playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.8@aar') { transitive = true }
playImplementation('com.crashlytics.sdk.android:answers:1.4.6@aar') { transitive = true }
testImplementation libraries.junit
testImplementation libraries.truth
......@@ -168,12 +182,6 @@ dependencies {
androidTestImplementation libraries.espressoIntents
}
kotlin {
experimental {
coroutines "enable"
}
}
androidExtensions {
experimental = true
}
......@@ -181,8 +189,8 @@ androidExtensions {
// FIXME - build and install the sdk into the app/libs directory
// We were having some issues with the kapt generated files from the sdk when importing as a module
def sdk_location=project.properties['sdk_location'] ?: ""
task compileSdk(type:Exec) {
def sdk_location = project.properties['sdk_location'] ?: ""
task compileSdk(type: Exec) {
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
commandLine 'cmd', '/c', 'build-sdk.sh', sdk_location
} else {
......
......@@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:name=".app.RocketChatApplication"
......@@ -73,6 +75,11 @@
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".videoconferencing.ui.VideoConferencingActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".chatroom.ui.ChatRoomActivity"
android:theme="@style/AppTheme"
......
......@@ -3,15 +3,11 @@ package chat.rocket.android.app
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import chat.rocket.android.server.domain.GetAccountInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.UserStatus
import chat.rocket.core.internal.realtime.setTemporaryStatus
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import javax.inject.Inject
class AppLifecycleObserver @Inject constructor(
......@@ -33,7 +29,7 @@ class AppLifecycleObserver @Inject constructor(
}
private fun changeTemporaryStatus(userStatus: UserStatus) {
launch {
GlobalScope.launch {
serverInteractor.get()?.let { currentServer ->
factory.create(currentServer).setTemporaryStatus(userStatus)
}
......
......@@ -37,7 +37,8 @@ import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector
import dagger.android.HasBroadcastReceiverInjector
import dagger.android.HasServiceInjector
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.lang.ref.WeakReference
import javax.inject.Inject
......@@ -174,7 +175,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
EmojiRepository.init(this)
val currentServer = getCurrentServerInteractor.get()
currentServer?.let { server ->
launch {
GlobalScope.launch {
val client = factory.create(server)
EmojiRepository.setCurrentServerUrl(server)
val customEmojiList = mutableListOf<Emoji>()
......
......@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
@Module
class AuthenticationModule {
......
......@@ -31,7 +31,7 @@ import chat.rocket.core.internal.rest.loginWithCas
import chat.rocket.core.internal.rest.loginWithOauth
import chat.rocket.core.internal.rest.loginWithSaml
import chat.rocket.core.internal.rest.me
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit
import javax.inject.Inject
......@@ -109,11 +109,11 @@ class LoginOptionsPresenter @Inject constructor(
when (loginType) {
TYPE_LOGIN_OAUTH -> client.loginWithOauth(credentialToken, credentialSecret)
TYPE_LOGIN_CAS -> {
delay(3, TimeUnit.SECONDS)
delay(3000)
client.loginWithCas(credentialToken)
}
TYPE_LOGIN_SAML -> {
delay(3, TimeUnit.SECONDS)
delay(3000)
client.loginWithSaml(credentialToken)
}
TYPE_LOGIN_DEEP_LINK -> {
......
......@@ -10,8 +10,8 @@ import chat.rocket.android.server.domain.SaveConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.launchUI
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
class OnBoardingPresenter @Inject constructor(
......@@ -19,7 +19,7 @@ class OnBoardingPresenter @Inject constructor(
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val serverInteractor: SaveConnectingServerInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor,
refreshSettingsInteractor: RefreshSettingsInteractor,
private val getAccountsInteractor: GetAccountsInteractor,
val settingsInteractor: GetSettingsInteractor,
val factory: RocketChatClientFactory
......@@ -80,7 +80,7 @@ class OnBoardingPresenter @Inject constructor(
}
view.showLoading()
try {
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
setupConnectionInfo(serverUrl)
// preparing next fragment before showing it
......
......@@ -12,8 +12,8 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.isValidUrl
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
class ServerPresenter @Inject constructor(
......@@ -98,7 +98,7 @@ class ServerPresenter @Inject constructor(
}
view.showLoading()
try {
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
// preparing next fragment before showing it
refreshServerAccounts()
checkEnabledAccounts(serverUrl)
......
......@@ -21,7 +21,6 @@ import chat.rocket.android.chatdetails.viewmodel.ChatDetailsViewModelFactory
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
......
......@@ -5,6 +5,7 @@ import android.view.MenuItem
import chat.rocket.android.R
import chat.rocket.android.server.domain.isJitsiEnabled
import chat.rocket.android.server.domain.isJitsiEnabledForChannels
import chat.rocket.android.videoconferencing.ui.videoConferencingIntent
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
......@@ -38,7 +39,8 @@ internal fun ChatDetailsFragment.setupMenu(menu: Menu) {
MENU_ACTION_FAVORITE_REMOVE_FAVORITE,
Menu.NONE,
R.string.action_favorite
).setIcon(R.drawable.ic_star_border_white_24dp).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
).setIcon(R.drawable.ic_star_border_white_24dp)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
}
......@@ -46,6 +48,6 @@ internal fun ChatDetailsFragment.setOnMenuItemClickListener(item: MenuItem) {
if (item.itemId == MENU_ACTION_FAVORITE_REMOVE_FAVORITE) {
presenter.toggleFavoriteChatRoom(chatRoomId, isFavorite)
} else if (item.itemId == MENU_ACTION_VIDEO_CALL) {
// TODO
startActivity(activity?.videoConferencingIntent(chatRoomId))
}
}
......@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
@Module
class MessageInfoFragmentModule {
......
......@@ -24,7 +24,6 @@ fun Context.messageInformationIntent(messageId: String): Intent {
private const val INTENT_MESSAGE_ID = "message_id"
class MessageInfoActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
......
......@@ -6,12 +6,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.chatroom.uimodel.AttachmentUiModel
import chat.rocket.android.chatroom.uimodel.BaseUiModel
import chat.rocket.android.chatroom.uimodel.MessageReplyUiModel
import chat.rocket.android.chatroom.uimodel.MessageUiModel
import chat.rocket.android.chatroom.uimodel.UrlPreviewUiModel
import chat.rocket.android.chatroom.uimodel.toViewType
import chat.rocket.android.chatroom.uimodel.*
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.openTabbedUrl
......@@ -44,8 +39,10 @@ class ChatRoomAdapter(
MessageViewHolder(
view,
actionsListener,
reactionListener
) { userId -> navigator?.toUserDetails(userId) }
reactionListener,
{ userId -> navigator?.toUserDetails(userId) },
{ roomId?.let { navigator?.toVideoConferencing(it) } }
)
}
BaseUiModel.ViewType.URL_PREVIEW -> {
val view = parent.inflate(R.layout.message_url_preview)
......
......@@ -13,8 +13,8 @@ import chat.rocket.android.emoji.Emoji
import chat.rocket.android.emoji.EmojiKeyboardListener
import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.infrastructure.LocalRepository
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_reaction.view.*
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
......@@ -103,9 +103,9 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
// The view at index 1 corresponds to the one to display custom emojis which are images.
view_flipper_reaction.displayedChild = 1
val glideRequest = if (reaction.url!!.endsWith("gif", true)) {
GlideApp.with(context).asGif()
Glide.with(context).asGif()
} else {
GlideApp.with(context).asBitmap()
Glide.with(context).asBitmap()
}
glideRequest.load(reaction.url).into(image_emoji)
......
......@@ -10,6 +10,7 @@ import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.MessageUiModel
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.core.model.MessageType
import chat.rocket.core.model.isSystemMessage
import com.bumptech.glide.load.resource.gif.GifDrawable
import kotlinx.android.synthetic.main.avatar.view.*
......@@ -19,7 +20,8 @@ class MessageViewHolder(
itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null,
private val avatarListener: (String) -> Unit
private val avatarListener: (String) -> Unit,
private val joinVideoCallListener: (View) -> Unit
) : BaseViewHolder<MessageUiModel>(itemView, listener, reactionListener), Drawable.Callback {
init {
......@@ -51,6 +53,9 @@ class MessageViewHolder(
text_content.text_content.text = data.content
button_join_video_call.isVisible = data.message.type is MessageType.JitsiCallStarted
button_join_video_call.setOnClickListener { joinVideoCallListener(it) }
image_avatar.setImageURI(data.avatar)
text_content.setTextColor(if (data.isTemporary) Color.GRAY else Color.BLACK)
......
......@@ -8,10 +8,11 @@ import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.openTabbedUrl
import kotlinx.android.synthetic.main.message_url_preview.view.*
class UrlPreviewViewHolder(itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null)
: BaseViewHolder<UrlPreviewUiModel>(itemView, listener, reactionListener) {
class UrlPreviewViewHolder(
itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null
) : BaseViewHolder<UrlPreviewUiModel>(itemView, listener, reactionListener) {
init {
with(itemView) {
......
......@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
@Module
class ChatRoomModule {
......
......@@ -13,6 +13,7 @@ import chat.rocket.android.pinnedmessages.ui.TAG_PINNED_MESSAGES_FRAGMENT
import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.userdetails.ui.TAG_USER_DETAILS_FRAGMENT
import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.videoconferencing.ui.videoConferencingIntent
class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
......@@ -22,6 +23,10 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
}
}
fun toVideoConferencing(chatRoomId: String) {
activity.startActivity(activity.videoConferencingIntent(chatRoomId))
}
fun toChatRoom(
chatRoomId: String,
chatRoomName: String,
......
......@@ -53,7 +53,6 @@ import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.rest.chatRoomRoles
import chat.rocket.core.internal.rest.commands
import chat.rocket.core.internal.rest.deleteMessage
import chat.rocket.core.internal.rest.favorite
import chat.rocket.core.internal.rest.getMembers
import chat.rocket.core.internal.rest.history
import chat.rocket.core.internal.rest.joinChat
......@@ -76,12 +75,11 @@ import chat.rocket.core.model.ChatRoomRole
import chat.rocket.core.model.Command
import chat.rocket.core.model.Message
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.threeten.bp.Instant
import timber.log.Timber
import java.util.*
......@@ -134,7 +132,7 @@ class ChatRoomPresenter @Inject constructor(
draftKey = "${currentServer}_${LocalRepository.DRAFT_KEY}$roomId"
chatRoomId = roomId
chatRoomType = roomType
launch(CommonPool + strategy.jobs) {
GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
try {
chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) {
client.chatRoomRoles(roomType = roomTypeOf(roomType), roomName = roomName)
......@@ -178,7 +176,7 @@ class ChatRoomPresenter @Inject constructor(
}
private suspend fun subscribeRoomChanges() {
withContext(CommonPool + strategy.jobs) {
withContext(Dispatchers.IO + strategy.jobs) {
chatRoomId?.let {
manager.addRoomChannel(it, roomChangesChannel)
for (room in roomChangesChannel) {
......@@ -403,7 +401,7 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) {
view.showLoading()
try {
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
val fileName = uriInteractor.getFileName(uri) ?: uri.toString()
if (fileName.isEmpty()) {
view.showInvalidFileMessage()
......@@ -441,7 +439,7 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) {
view.showLoading()
try {
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
val fileName = uriInteractor.getFileName(uri) ?: uri.toString()
val fileSize = uriInteractor.getFileSize(uri)
val maxFileSizeAllowed = settings.uploadMaxFileSize()
......@@ -482,7 +480,7 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) {
view.showLoading()
try {
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
val fileName = UUID.randomUUID().toString() + ".png"
val fileSize = byteArray.size
val mimeType = "image/png"
......@@ -520,7 +518,7 @@ class ChatRoomPresenter @Inject constructor(
}
fun sendTyping() {
launch(CommonPool + strategy.jobs) {
GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
if (chatRoomId != null && currentLoggedUsername != null) {
client.setTypingStatus(chatRoomId.toString(), currentLoggedUsername, true)
}
......@@ -528,7 +526,7 @@ class ChatRoomPresenter @Inject constructor(
}
fun sendNotTyping() {
launch(CommonPool + strategy.jobs) {
GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
if (chatRoomId != null && currentLoggedUsername != null) {
client.setTypingStatus(chatRoomId.toString(), currentLoggedUsername, false)
}
......@@ -550,11 +548,11 @@ class ChatRoomPresenter @Inject constructor(
Timber.d("Subscribing to Status changes")
lastState = manager.state
manager.addStatusChannel(stateChannel)
launch(CommonPool + strategy.jobs) {
GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
for (state in stateChannel) {
Timber.d("Got new state: $state - last: $lastState")
if (state != lastState) {
launch(UI) {
launch(Dispatchers.Main) {
view.showConnectionState(state)
}
......@@ -571,7 +569,7 @@ class ChatRoomPresenter @Inject constructor(
private fun subscribeMessages(roomId: String) {
manager.subscribeRoomMessages(roomId, messagesChannel)
launch(CommonPool + strategy.jobs) {
GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
for (message in messagesChannel) {
Timber.d("New message for room ${message.roomId}")
updateMessage(message)
......@@ -580,7 +578,7 @@ class ChatRoomPresenter @Inject constructor(
}
private fun loadMissingMessages() {
launch(parent = strategy.jobs) {
GlobalScope.launch(strategy.jobs) {
chatRoomId?.let { chatRoomId ->
val roomType = roomTypeOf(chatRoomType)
val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
......@@ -924,7 +922,7 @@ class ChatRoomPresenter @Inject constructor(
}
// TODO: move this to new interactor or FetchChatRoomsInteractor?
private suspend fun getChatRoomAsync(roomId: String): ChatRoom? = withContext(CommonPool) {
private suspend fun getChatRoomAsync(roomId: String): ChatRoom? = withContext(Dispatchers.IO) {
retryDB("getRoom($roomId)") {
dbManager.chatRoomDao().getSync(roomId)?.let {
with(it.chatRoom) {
......@@ -962,7 +960,7 @@ class ChatRoomPresenter @Inject constructor(
}
// TODO: move this to new interactor or FetchChatRoomsInteractor?
private suspend fun getChatRoomsAsync(name: String? = null): List<ChatRoom> = withContext(CommonPool) {
private suspend fun getChatRoomsAsync(name: String? = null): List<ChatRoom> = withContext(Dispatchers.IO) {
retryDB("getAllSync()") {
dbManager.chatRoomDao().getAllSync().filter {
if (name == null) {
......@@ -1192,7 +1190,7 @@ class ChatRoomPresenter @Inject constructor(
}
private fun subscribeTypingStatus() {
launch(CommonPool + strategy.jobs) {
GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
client.subscribeTypingStatus(chatRoomId.toString()) { _, id ->
typingStatusSubscriptionId = id
}
......
......@@ -10,8 +10,9 @@ import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message
import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
......@@ -33,7 +34,7 @@ class MessageService : JobService() {
}
override fun onStartJob(params: JobParameters?): Boolean {
launch(CommonPool) {
GlobalScope.launch(Dispatchers.IO) {
getAccountsInteractor.get().forEach { account ->
retrySendingMessages(params, account.serverUrl)
}
......@@ -44,7 +45,8 @@ class MessageService : JobService() {
private suspend fun retrySendingMessages(params: JobParameters?, serverUrl: String) {
val dbManager = dbFactory.create(serverUrl)
val messageRepository = DatabaseMessagesRepository(dbManager, DatabaseMessageMapper(dbManager))
val messageRepository =
DatabaseMessagesRepository(dbManager, DatabaseMessageMapper(dbManager))
val temporaryMessages = messageRepository.getAllUnsent()
.sortedWith(compareBy(Message::timestamp))
if (temporaryMessages.isNotEmpty()) {
......
......@@ -15,7 +15,6 @@ import android.text.SpannableStringBuilder
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
......@@ -60,7 +59,6 @@ import chat.rocket.android.emoji.EmojiKeyboardPopup
import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.ImageHelper
......@@ -82,6 +80,7 @@ import chat.rocket.android.util.extensions.ui
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.socket.model.State
import com.bumptech.glide.Glide
import dagger.android.support.AndroidSupportInjection
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
......@@ -679,9 +678,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
view_flipper.displayedChild = if (isCustom) 1 else 0
if (isCustom && url != null) {
val glideRequest = if (url.endsWith("gif", true)) {
GlideApp.with(requireContext()).asGif()
Glide.with(requireContext()).asGif()
} else {
GlideApp.with(requireContext()).asBitmap()
Glide.with(requireContext()).asBitmap()
}
glideRequest.load(url).into(view_flipper.emoji_image_view)
......
......@@ -4,10 +4,11 @@ import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import androidx.core.view.isVisible
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.util.extensions.getFileName
import chat.rocket.android.util.extensions.getMimeType
import chat.rocket.common.util.ifNull
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
......@@ -25,18 +26,18 @@ fun ChatRoomFragment.showFileAttachmentDialog(uri: Uri) {
when {
mimeType.startsWith("image") -> {
if (mimeType.contains("gif")) {
GlideApp
Glide
.with(context)
.asGif()
.load(uri)
.fitCenter()
.apply(RequestOptions().fitCenter())
.into(imagePreview)
} else {
GlideApp
Glide
.with(context)
.asBitmap()
.load(uri)
.fitCenter()
.apply(RequestOptions().fitCenter())
.into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(
resource: Bitmap,
......
......@@ -30,5 +30,5 @@ interface BaseUiModel<out T> {
internal fun Int.toViewType(): BaseUiModel.ViewType {
return BaseUiModel.ViewType.values().firstOrNull { it.viewType == this }
?: throw InvalidParameterException("Invalid viewType: $this for BaseUiModel.ViewType")
?: throw InvalidParameterException("Invalid viewType: $this for BaseUiModel.ViewType")
}
\ No newline at end of file
......@@ -26,7 +26,6 @@ data class MessageUiModel(
) : BaseMessageUiModel<Message> {
override val viewType: Int
get() = BaseUiModel.ViewType.MESSAGE.viewType
override val layoutId: Int
get() = R.layout.item_message
}
\ No newline at end of file
......@@ -44,8 +44,8 @@ import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.Field
import chat.rocket.core.model.isSystemMessage
import chat.rocket.core.model.url.Url
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl
import java.security.InvalidParameterException
import java.util.*
......@@ -79,7 +79,7 @@ class UiModelMapper @Inject constructor(
message: Message,
roomUiModel: RoomUiModel = RoomUiModel(roles = emptyList(), isBroadcast = true)
): List<BaseUiModel<*>> =
withContext(CommonPool) {
withContext(Dispatchers.IO) {
return@withContext translate(message, roomUiModel)
}
......@@ -88,7 +88,7 @@ class UiModelMapper @Inject constructor(
roomUiModel: RoomUiModel = RoomUiModel(roles = emptyList(), isBroadcast = true),
asNotReversed: Boolean = false
): List<BaseUiModel<*>> =
withContext(CommonPool) {
withContext(Dispatchers.IO) {
val list = ArrayList<BaseUiModel<*>>(messages.size)
messages.forEach {
......@@ -102,7 +102,7 @@ class UiModelMapper @Inject constructor(
suspend fun map(
readReceipts: List<ReadReceipt>
): List<ReadReceiptViewModel> = withContext(CommonPool) {
): List<ReadReceiptViewModel> = withContext(Dispatchers.IO) {
val list = arrayListOf<ReadReceiptViewModel>()
readReceipts.forEach {
......@@ -121,7 +121,7 @@ class UiModelMapper @Inject constructor(
message: Message,
roomUiModel: RoomUiModel
): List<BaseUiModel<*>> =
withContext(CommonPool) {
withContext(Dispatchers.IO) {
val list = ArrayList<BaseUiModel<*>>()
getChatRoomAsync(message.roomId)?.let { chatRoom ->
......@@ -167,7 +167,7 @@ class UiModelMapper @Inject constructor(
}
// TODO: move this to new interactor or FetchChatRoomsInteractor?
private suspend fun getChatRoomAsync(roomId: String): ChatRoom? = withContext(CommonPool) {
private suspend fun getChatRoomAsync(roomId: String): ChatRoom? = withContext(Dispatchers.IO) {
return@withContext dbManager.getRoom(id = roomId)?.let {
with(it.chatRoom) {
ChatRoom(
......@@ -212,7 +212,7 @@ class UiModelMapper @Inject constructor(
message: Message,
roomUiModel: RoomUiModel
): List<BaseUiModel<*>> =
withContext(CommonPool) {
withContext(Dispatchers.IO) {
val list = ArrayList<BaseUiModel<*>>()
getChatRoomAsync(message.roomId)?.let { chatRoom ->
......@@ -439,7 +439,7 @@ class UiModelMapper @Inject constructor(
private suspend fun mapMessage(
message: Message,
chatRoom: ChatRoom
): MessageUiModel = withContext(CommonPool) {
): MessageUiModel = withContext(Dispatchers.IO) {
val sender = getSenderName(message)
val time = getTime(message.timestamp)
val avatar = getUserAvatar(message)
......
......@@ -22,11 +22,12 @@ import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.createDirectMessage
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.show
import kotlinx.coroutines.experimental.withTimeout
import kotlinx.coroutines.withTimeout
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
import kotlin.coroutines.experimental.suspendCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class ChatRoomsPresenter @Inject constructor(
private val view: ChatRoomsView,
......
......@@ -20,16 +20,16 @@ import chat.rocket.core.model.SpotlightResult
import com.shopify.livedataktx.distinct
import com.shopify.livedataktx.map
import com.shopify.livedataktx.nonNull
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.isActive
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.lang.IllegalArgumentException
import kotlin.coroutines.experimental.coroutineContext
import kotlin.coroutines.coroutineContext
class ChatRoomsViewModel(
private val connectionManager: ConnectionManager,
......@@ -107,7 +107,7 @@ class ChatRoomsViewModel(
}
private fun fetchRooms() {
launch {
GlobalScope.launch {
setLoadingState(LoadingState.Loading(repository.count()))
try {
interactor.refreshChatRooms()
......@@ -125,7 +125,7 @@ class ChatRoomsViewModel(
}
private suspend fun setLoadingState(state: LoadingState) {
withContext(UI) {
withContext(Dispatchers.Main) {
loadingState.value = state
}
}
......
......@@ -37,6 +37,8 @@ import chat.rocket.android.settings.di.SettingsFragmentProvider
import chat.rocket.android.settings.password.di.PasswordFragmentProvider
import chat.rocket.android.settings.password.ui.PasswordActivity
import chat.rocket.android.userdetails.di.UserDetailsFragmentProvider
import chat.rocket.android.videoconferencing.di.VideoConferencingModule
import chat.rocket.android.videoconferencing.ui.VideoConferencingActivity
import chat.rocket.android.webview.adminpanel.di.AdminPanelWebViewFragmentProvider
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -103,4 +105,8 @@ abstract class ActivityBuilder {
@PerActivity
@ContributesAndroidInjector(modules = [DrawModule::class])
abstract fun bindDrawingActivity(): DrawingActivity
@PerActivity
@ContributesAndroidInjector(modules = [VideoConferencingModule::class])
abstract fun bindVideoConferencingActivity(): VideoConferencingActivity
}
......@@ -63,7 +63,6 @@ import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date
import chat.rocket.common.model.TimestampAdapter
import chat.rocket.common.util.CalendarISO8601Converter
import chat.rocket.common.util.Logger
import chat.rocket.common.util.NoOpLogger
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.internal.AttachmentAdapterFactory
......
......@@ -13,7 +13,6 @@ import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date
import chat.rocket.common.model.TimestampAdapter
import chat.rocket.common.util.CalendarISO8601Converter
import chat.rocket.common.util.Logger
import chat.rocket.common.util.NoOpLogger
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.internal.AttachmentAdapterFactory
......
......@@ -33,11 +33,12 @@ import chat.rocket.core.model.Myself
import chat.rocket.core.model.Room
import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.userId
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.HashSet
import kotlin.system.measureTimeMillis
......@@ -69,7 +70,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
fun start() {
dbJob?.cancel()
dbJob = launch(dbContext) {
dbJob = GlobalScope.launch(dbContext) {
for (operation in writeChannel) {
doOperation(operation)
}
......@@ -115,7 +116,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
fun processUsersBatch(users: List<User>) {
launch(dbManagerContext) {
GlobalScope.launch(dbManagerContext) {
val list = ArrayList<BaseUserEntity>(users.size)
val time = measureTimeMillis {
users.forEach { user ->
......@@ -133,7 +134,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
* Creates a list of data base operations
*/
fun processChatRoomsBatch(batch: List<StreamMessage<BaseRoom>>) {
launch(dbManagerContext) {
GlobalScope.launch(dbManagerContext) {
val toRemove = HashSet<String>()
val toInsert = ArrayList<ChatRoomEntity>(batch.size / 2)
val toUpdate = ArrayList<ChatRoomEntity>(batch.size)
......@@ -165,7 +166,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
fun updateSelfUser(myself: Myself) {
launch(dbManagerContext) {
GlobalScope.launch(dbManagerContext) {
val user = retryDB("getUser(${myself.id})") { userDao().getUser(myself.id) }
val entity = user?.copy(
name = myself.name ?: user.name,
......@@ -180,14 +181,14 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
fun processRooms(rooms: List<ChatRoom>) {
launch(dbManagerContext) {
GlobalScope.launch(dbManagerContext) {
val entities = rooms.map { mapChatRoom(it) }
sendOperation(Operation.CleanInsertRooms(entities))
}
}
fun processMessagesBatch(messages: List<Message>): Job {
return launch(dbManagerContext) {
return GlobalScope.launch(dbManagerContext) {
val list = mutableListOf<Pair<MessageEntity, List<BaseMessageEntity>>>()
messages.forEach { message ->
val pair = createMessageEntities(message)
......
......@@ -11,8 +11,10 @@ object AndroidPermissionsHelper {
const val WRITE_EXTERNAL_STORAGE_CODE = 1
fun checkPermission(context: Context, permission: String): Boolean {
return ContextCompat.checkSelfPermission(context, permission) ==
PackageManager.PERMISSION_GRANTED
return ContextCompat.checkSelfPermission(
context,
permission
) == PackageManager.PERMISSION_GRANTED
}
fun requestPermission(context: Activity, permission: String, requestCode: Int) {
......
package chat.rocket.android.helper
import java.net.URL
object JitsiHelper {
/**
* Returns the [URL] for the Jitsi video conferencing.
*
* @param isSecureProtocol True if using SSL, false otherwise - from the public settings.
* @param domain The Jitsi domain - from public settings.
* @param prefix The Jitsi prefix - from public settings.
* @param uniqueIdentifier The server unique identifier - from public settings.
* @param chatRoomId The ChatRoom ID where the video conferencing was started.
*/
fun getJitsiUrl(
isSecureProtocol: Boolean,
domain: String?,
prefix: String?,
uniqueIdentifier: String?,
chatRoomId: String?
): URL =
URL(
getJitsiProtocol(isSecureProtocol) +
domain +
"/" +
prefix +
uniqueIdentifier +
chatRoomId
)
private fun getJitsiProtocol(isSecureProtocol: Boolean) =
if (isSecureProtocol) "https://" else "http://"
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.ui.MainActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
@Module
class MainModule {
......
......@@ -37,7 +37,7 @@ import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.getCustomEmojis
import chat.rocket.core.internal.rest.me
import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.channels.Channel
import timber.log.Timber
import javax.inject.Inject
......
......@@ -27,8 +27,8 @@ import chat.rocket.core.internal.rest.deleteOwnAccount
import chat.rocket.core.internal.rest.resetAvatar
import chat.rocket.core.internal.rest.setAvatar
import chat.rocket.core.internal.rest.updateProfile
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import javax.inject.Inject
......@@ -180,7 +180,7 @@ class ProfilePresenter @Inject constructor(
launchUI(strategy) {
view.showLoading()
try {
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
// REMARK: Backend API is only working with a lowercase hash.
// https://github.com/RocketChat/Rocket.Chat/issues/12573
retryIO { client.deleteOwnAccount(password.gethash().toHex().toLowerCase()) }
......
......@@ -11,8 +11,8 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.sendMessage
import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.*
import javax.inject.Inject
......@@ -36,7 +36,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
if (ACTION_REPLY == intent.action) {
val message = intent.getParcelableExtra<PushMessage>(EXTRA_PUSH_MESSAGE)
message?.let {
launch(UI) {
MainScope().launch {
val notificationId = it.notificationId.toInt()
val hostname = it.info.host
try {
......
......@@ -29,7 +29,7 @@ import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import com.squareup.moshi.Json
import com.squareup.moshi.Moshi
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
import se.ansman.kotshi.JsonSerializable
import se.ansman.kotshi.KotshiConstructor
import timber.log.Timber
......
......@@ -3,13 +3,12 @@ package chat.rocket.android.server.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.server.presentation.ChangeServerNavigator
import chat.rocket.android.server.presentation.ChangeServerView
import chat.rocket.android.server.ui.ChangeServerActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
@Module
class ChangeServerModule {
......
package chat.rocket.android.server.domain
import chat.rocket.core.model.ChatRoom
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsRepository) {
......@@ -23,15 +23,16 @@ class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsR
* @param name The name of chat room to look for or a chat room that contains this name.
* @return A list of ChatRoom objects with the given name.
*/
suspend fun getAllByName(url: String, name: String): List<ChatRoom> = withContext(CommonPool) {
val allChatRooms = repository.get(url)
if (name.isEmpty()) {
return@withContext allChatRooms
suspend fun getAllByName(url: String, name: String): List<ChatRoom> =
withContext(Dispatchers.IO) {
val allChatRooms = repository.get(url)
if (name.isEmpty()) {
return@withContext allChatRooms
}
return@withContext allChatRooms.filter {
it.name.contains(name, true)
}
}
return@withContext allChatRooms.filter {
it.name.contains(name, true)
}
}
/**
* Get a specific [ChatRoom] by its id.
......@@ -40,11 +41,12 @@ class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsR
* @param roomId The id of the room to get.
* @return The [ChatRoom] object or null if we couldn't find any.
*/
suspend fun getById(serverUrl: String, roomId: String): ChatRoom? = withContext(CommonPool) {
return@withContext repository.get(serverUrl).find {
it.id == roomId
suspend fun getById(serverUrl: String, roomId: String): ChatRoom? =
withContext(Dispatchers.IO) {
return@withContext repository.get(serverUrl).find {
it.id == roomId
}
}
}
/**
* Get a specific [ChatRoom] by its name.
......
......@@ -3,8 +3,9 @@ package chat.rocket.android.server.domain
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.permissions
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
......@@ -17,7 +18,7 @@ class RefreshPermissionsInteractor @Inject constructor(
) {
fun refreshAsync(server: String) {
launch(CommonPool) {
GlobalScope.launch(Dispatchers.IO) {
try {
factory.create(server).let { client ->
val permissions = retryIO(
......
......@@ -3,9 +3,10 @@ package chat.rocket.android.server.domain
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.settings
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
......@@ -18,6 +19,8 @@ class RefreshSettingsInteractor @Inject constructor(
) {
private var settingsFilter = arrayOf(
UNIQUE_IDENTIFIER,
LDAP_ENABLE,
CAS_ENABLE,
CAS_LOGIN_URL,
......@@ -70,7 +73,7 @@ class RefreshSettingsInteractor @Inject constructor(
)
suspend fun refresh(server: String) {
withContext(CommonPool) {
withContext(Dispatchers.IO) {
factory.create(server).let { client ->
val settings = retryIO(
description = "settings",
......@@ -86,7 +89,7 @@ class RefreshSettingsInteractor @Inject constructor(
}
fun refreshAsync(server: String) {
launch(CommonPool) {
GlobalScope.launch(Dispatchers.IO) {
try {
refresh(server)
} catch (ex: Exception) {
......
......@@ -5,6 +5,8 @@ import chat.rocket.core.model.Value
typealias PublicSettings = Map<String, Value<Any>>
const val UNIQUE_IDENTIFIER = "uniqueID"
// Authentication methods
const val LDAP_ENABLE = "LDAP_Enable"
const val CAS_ENABLE = "CAS_enabled"
......@@ -64,6 +66,8 @@ const val MESSAGE_READ_RECEIPT_STORE_USERS = "Message_Read_Receipt_Store_Users"
* RefreshSettingsInteractor.kt and a extension function to access it.
*/
fun PublicSettings.uniqueIdentifier(): String? = this[UNIQUE_IDENTIFIER]?.value as String?
// Authentication
fun PublicSettings.isLdapAuthenticationEnabled(): Boolean = this[LDAP_ENABLE]?.value == true
fun PublicSettings.isCasAuthenticationEnabled(): Boolean = this[CAS_ENABLE]?.value == true
......
......@@ -23,26 +23,31 @@ import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.channels.actor
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.selects.select
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.selects.select
import timber.log.Timber
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.coroutines.experimental.CoroutineContext
import kotlin.coroutines.CoroutineContext
class ConnectionManager(
internal val client: RocketChatClient,
private val dbManager: DatabaseManager
) {
) : CoroutineScope {
private var connectJob : Job? = null
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO
val statusLiveData = MutableLiveData<State>()
private val statusChannelList = CopyOnWriteArrayList<Channel<State>>()
private val statusChannel = Channel<State>(Channel.CONFLATED)
private var connectJob: Job? = null
private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>()
private val userDataChannels = ArrayList<Channel<Myself>>()
......@@ -60,7 +65,7 @@ class ConnectionManager(
private val messagesContext = newSingleThreadContext("messagesContext")
fun connect() {
if (connectJob?.isActive == true && (state !is State.Disconnected)) {
if (connectJob?.isActive == true && state !is State.Disconnected) {
Timber.d("Already connected, just returning...")
return
}
......@@ -78,32 +83,31 @@ class ConnectionManager(
when (status) {
is State.Connected -> {
dbManager.clearUsersStatus()
client.subscribeSubscriptions { _, id ->
Timber.d("Subscribed to subscriptions: $id")
subscriptionId = id
}
client.subscribeRooms { _, id ->
Timber.d("Subscribed to rooms: $id")
roomsId = id
}
client.subscribeUserData { _, id ->
Timber.d("Subscribed to the userData id: $id")
userDataId = id
}
client.subscribeActiveUsers { _, id ->
Timber.d("Subscribed to the activeUser id: $id")
activeUserId = id
}
resubscribeRooms()
temporaryStatus?.let { status ->
client.setTemporaryStatus(status)
}
}
is State.Waiting -> {
Timber.d("Connection in: ${status.seconds}")
temporaryStatus?.let { client.setTemporaryStatus(it) }
}
is State.Waiting -> Timber.d("Connection in: ${status.seconds}")
}
statusLiveData.postValue(status)
......@@ -116,8 +120,9 @@ class ConnectionManager(
}
var totalBatchedUsers = 0
val userActor = createBatchActor<User>(activeUsersContext, parent = connectJob,
maxSize = 500, maxTime = 1000) { users ->
val userActor = createBatchActor<User>(
activeUsersContext, parent = connectJob, maxSize = 500, maxTime = 1000
) { users ->
totalBatchedUsers += users.size
Timber.d("Processing Users batch: ${users.size} - $totalBatchedUsers")
......@@ -125,8 +130,9 @@ class ConnectionManager(
dbManager.processUsersBatch(users)
}
val roomsActor = createBatchActor<StreamMessage<BaseRoom>>(roomsContext, parent = connectJob,
maxSize = 10) { batch ->
val roomsActor = createBatchActor<StreamMessage<BaseRoom>>(
roomsContext, parent = connectJob, maxSize = 10
) { batch ->
Timber.d("processing Stream batch: ${batch.size} - $batch")
dbManager.processChatRoomsBatch(batch)
......@@ -135,16 +141,15 @@ class ConnectionManager(
if (it.type == Type.Updated) {
if (it.data is Room) {
val room = it.data as Room
roomsChannels[it.data.id]?.let { channel ->
channel.offer(room)
}
roomsChannels[it.data.id]?.offer(room)
}
}
}
}
val messagesActor = createBatchActor<Message>(messagesContext, parent = connectJob,
maxSize = 100, maxTime = 500) { messages ->
val messagesActor = createBatchActor<Message>(
messagesContext, parent = connectJob, maxSize = 100, maxTime = 500
) { messages ->
Timber.d("Processing Messages batch: ${messages.size}")
dbManager.processMessagesBatch(messages.distinctBy { it.id })
......@@ -157,7 +162,7 @@ class ConnectionManager(
}
// stream-notify-user - ${userId}/rooms-changed
launch(parent = connectJob) {
launch {
for (room in client.roomsChannel) {
Timber.d("GOT Room streamed")
roomsActor.send(room)
......@@ -170,7 +175,7 @@ class ConnectionManager(
}
// stream-notify-user - ${userId}/subscriptions-changed
launch(parent = connectJob) {
launch {
for (subscription in client.subscriptionsChannel) {
Timber.d("GOT Subscription streamed")
roomsActor.send(subscription)
......@@ -178,7 +183,7 @@ class ConnectionManager(
}
// stream-room-messages - $roomId
launch(parent = connectJob) {
launch {
for (message in client.messagesChannel) {
Timber.d("Received new Message for room ${message.roomId}")
messagesActor.send(message)
......@@ -186,7 +191,7 @@ class ConnectionManager(
}
// userData
launch(parent = connectJob) {
launch {
for (myself in client.userDataChannel) {
Timber.d("Got userData")
dbManager.updateSelfUser(myself)
......@@ -197,7 +202,7 @@ class ConnectionManager(
}
// activeUsers
launch(parent = connectJob) {
launch {
for (user in client.activeUsersChannel) {
userActor.send(user)
}
......@@ -286,16 +291,18 @@ class ConnectionManager(
}
}
private inline fun <T> createBatchActor(context: CoroutineContext = CommonPool,
parent: Job? = null,
maxSize: Int = 100,
maxTime: Int = 500,
crossinline block: (List<T>) -> Unit): SendChannel<T> {
return actor(context, parent = parent) {
private inline fun <T> createBatchActor(
context: CoroutineContext = Dispatchers.IO,
parent: Job? = null,
maxSize: Int = 100,
maxTime: Int = 500,
crossinline block: (List<T>) -> Unit
): SendChannel<T> {
return actor(context) {
val batch = ArrayList<T>(maxSize)
var deadline = 0L // deadline for sending this batch to callback block
while(true) {
while (true) {
// when deadline is reached or size is exceeded, pass the batch to the callback block
val remainingTime = deadline - System.currentTimeMillis()
if (batch.isNotEmpty() && remainingTime <= 0 || batch.size >= maxSize) {
......
......@@ -14,7 +14,6 @@ import chat.rocket.core.model.Message
import chat.rocket.core.model.Reactions
import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.Color
import chat.rocket.core.model.attachment.DEFAULT_COLOR_STR
import chat.rocket.core.model.attachment.Field
import chat.rocket.core.model.attachment.actions.Action
import chat.rocket.core.model.attachment.actions.ButtonAction
......@@ -22,8 +21,8 @@ import chat.rocket.core.model.messageTypeOf
import chat.rocket.core.model.url.Meta
import chat.rocket.core.model.url.ParsedUrl
import chat.rocket.core.model.url.Url
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
suspend fun map(message: FullMessage): Message? = map(listOf(message)).firstOrNull()
......@@ -58,7 +57,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
val attachments = this.attachments?.let { mapAttachments(it).asReversed() }
val messageType = messageTypeOf(this.message.type)
list.add(Message(
list.add(
Message(
id = this.message.id,
roomId = this.message.roomId,
message = this.message.message,
......@@ -82,7 +82,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
role = this.message.role,
synced = this.message.synced,
unread = this.message.unread
))
)
)
}
}
......@@ -106,13 +107,19 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
val parsedUrl = url.hostname?.let {
ParsedUrl(host = it)
}
val meta = if (!url.description.isNullOrEmpty() || !url.imageUrl.isNullOrEmpty() || !url.title.isNullOrEmpty()) {
val raw = HashMap<String, String>()
if (url.description != null) raw["ogDescription"] = url.description
if (url.title != null) raw["ogTitle"] = url.title
if (url.imageUrl != null) raw["ogImage"] = url.imageUrl
Meta(title = url.title,description = url.description, imageUrl = url.imageUrl, raw = raw)
} else null
val meta =
if (!url.description.isNullOrEmpty() || !url.imageUrl.isNullOrEmpty() || !url.title.isNullOrEmpty()) {
val raw = HashMap<String, String>()
if (url.description != null) raw["ogDescription"] = url.description
if (url.title != null) raw["ogTitle"] = url.title
if (url.imageUrl != null) raw["ogImage"] = url.imageUrl
Meta(
title = url.title,
description = url.description,
imageUrl = url.imageUrl,
raw = raw
)
} else null
list.add(Url(url = url.url, meta = meta, parsedUrl = parsedUrl))
}
......@@ -135,7 +142,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
attachments.forEach { attachment ->
with(attachment) {
val fields = if (hasFields) {
withContext(CommonPool) {
withContext(Dispatchers.IO) {
retryDB("getAttachmentFields(${attachment._id})") {
dbManager.messageDao().getAttachmentFields(attachment._id)
}
......@@ -144,7 +151,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
null
}
val actions = if (hasActions) {
withContext(CommonPool) {
withContext(Dispatchers.IO) {
retryDB("getAttachmentActions(${attachment._id})") {
dbManager.messageDao().getAttachmentActions(attachment._id)
}
......@@ -154,33 +161,34 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
}
val attachment = Attachment(
title = title,
type = type,
description = description,
authorName = authorName,
text = text,
thumbUrl = thumbUrl,
color = color?.let { Color.Custom(color) },
titleLink = titleLink,
titleLinkDownload = titleLinkDownload,
imageUrl = imageUrl,
imageType = imageType,
imageSize = imageSize,
videoUrl = videoUrl,
videoType = videoType,
videoSize = videoSize,
audioUrl = audioUrl,
audioType = audioType,
audioSize = audioSize,
messageLink = messageLink,
attachments = null, // HOW TO MAP THIS
timestamp = timestamp,
authorIcon = authorIcon,
authorLink = authorLink,
fields = fields,
fallback = fallback,
buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment ?: "vertical" else null,
actions = actions
title = title,
type = type,
description = description,
authorName = authorName,
text = text,
thumbUrl = thumbUrl,
color = color?.let { Color.Custom(color) },
titleLink = titleLink,
titleLinkDownload = titleLinkDownload,
imageUrl = imageUrl,
imageType = imageType,
imageSize = imageSize,
videoUrl = videoUrl,
videoType = videoType,
videoSize = videoSize,
audioUrl = audioUrl,
audioType = audioType,
audioSize = audioSize,
messageLink = messageLink,
attachments = null, // HOW TO MAP THIS
timestamp = timestamp,
authorIcon = authorIcon,
authorLink = authorLink,
fields = fields,
fallback = fallback,
buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment
?: "vertical" else null,
actions = actions
)
list.add(attachment)
}
......@@ -190,9 +198,11 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
private fun mapAction(action: AttachmentActionEntity): Action? {
return when (action.type) {
"button" -> ButtonAction(action.type, action.text, action.url, action.isWebView,
action.webViewHeightRatio, action.imageUrl, action.message,
action.isMessageInChatWindow)
"button" -> ButtonAction(
action.type, action.text, action.url, action.isWebView,
action.webViewHeightRatio, action.imageUrl, action.message,
action.isMessageInChatWindow
)
else -> null
}
}
......
......@@ -6,41 +6,42 @@ import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.util.retryDB
import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class DatabaseMessagesRepository(
private val dbManager: DatabaseManager,
private val mapper: DatabaseMessageMapper
) : MessagesRepository {
override suspend fun getById(id: String): Message? = withContext(CommonPool) {
override suspend fun getById(id: String): Message? = withContext(Dispatchers.IO) {
retryDB("getMessageById($id)") {
dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) }
}
}
override suspend fun getByRoomId(roomId: String): List<Message> = withContext(CommonPool) {
override suspend fun getByRoomId(roomId: String): List<Message> = withContext(Dispatchers.IO) {
// FIXME - investigate how to avoid this distinctBy here, since DAO is returning a lot of
// duplicate rows (something related to our JOINS and relations on Room)
retryDB("getMessagesByRoomId($roomId)") {
dbManager.messageDao().getMessagesByRoomId(roomId)
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
}
}
override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> = withContext(CommonPool) {
retryDB("getRecentMessagesByRoomId($roomId, $count)") {
dbManager.messageDao().getRecentMessagesByRoomId(roomId, count)
override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> =
withContext(Dispatchers.IO) {
retryDB("getRecentMessagesByRoomId($roomId, $count)") {
dbManager.messageDao().getRecentMessagesByRoomId(roomId, count)
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
}
}
}
override suspend fun save(message: Message) {
dbManager.processMessagesBatch(listOf(message)).join()
......@@ -51,24 +52,24 @@ class DatabaseMessagesRepository(
}
override suspend fun removeById(id: String) {
withContext(CommonPool) {
withContext(Dispatchers.IO) {
retryDB("delete($id)") { dbManager.messageDao().delete(id) }
}
}
override suspend fun removeByRoomId(roomId: String) {
withContext(CommonPool) {
withContext(Dispatchers.IO) {
retryDB("deleteByRoomId($roomId)") {
dbManager.messageDao().deleteByRoomId(roomId)
}
}
}
override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
override suspend fun getAllUnsent(): List<Message> = withContext(Dispatchers.IO) {
retryDB("getUnsentMessages") {
dbManager.messageDao().getUnsentMessages()
.distinctBy { it.message.message.id }
.let { mapper.map(it) }
.distinctBy { it.message.message.id }
.let { mapper.map(it) }
}
}
......@@ -76,7 +77,7 @@ class DatabaseMessagesRepository(
dbManager.sendOperation(Operation.SaveLastSync(MessagesSync(roomId, timeMillis)))
}
override suspend fun getLastSyncDate(roomId: String): Long? = withContext(CommonPool) {
override suspend fun getLastSyncDate(roomId: String): Long? = withContext(Dispatchers.IO) {
retryDB("getLastSync($roomId)") {
dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp }
}
......
......@@ -45,10 +45,10 @@ import chat.rocket.core.internal.rest.serverInfo
import chat.rocket.core.internal.rest.settingsOauth
import chat.rocket.core.internal.rest.unregisterPushToken
import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.withContext
import timber.log.Timber
private const val SERVICE_NAME_FACEBOOK = "facebook"
......@@ -220,7 +220,7 @@ abstract class CheckServerPresenter constructor(
}
removeAccountInteractor?.remove(currentServer)
tokenRepository?.remove(currentServer)
withContext(CommonPool) { dbManager.logout() }
withContext(Dispatchers.IO) { dbManager.logout() }
navigator?.switchOrAddNewServer()
} catch (ex: Exception) {
Timber.e(ex, "Error cleaning up the session...")
......
......@@ -26,7 +26,6 @@ fun Context.changeServerIntent(serverUrl: String? = null, chatRoomId: String? =
class ChangeServerActivity : AppCompatActivity(), ChangeServerView {
@Inject lateinit var presenter: ChangeServerPresenter
var progress: ProgressDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
......
......@@ -7,7 +7,7 @@ import chat.rocket.android.settings.presentation.SettingsView
import chat.rocket.android.settings.ui.SettingsFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
@Module
class SettingsFragmentModule {
......
......@@ -7,7 +7,7 @@ import chat.rocket.android.settings.password.presentation.PasswordView
import chat.rocket.android.settings.password.ui.PasswordFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
@Module
class PasswordFragmentModule {
......
......@@ -16,8 +16,8 @@ import chat.rocket.android.util.retryIO
import chat.rocket.common.model.RoomType
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.createDirectMessage
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
......@@ -81,7 +81,7 @@ class UserDetailsPresenter @Inject constructor(
try {
view.showLoading()
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
val directMessage = retryIO("createDirectMessage($username") {
client.createDirectMessage(username)
}
......
......@@ -2,26 +2,25 @@ package chat.rocket.android.util
import android.database.sqlite.SQLiteDatabaseLockedException
import chat.rocket.common.RocketChatNetworkErrorException
import kotlinx.coroutines.experimental.TimeoutCancellationException
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.isActive
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import timber.log.Timber
import kotlin.coroutines.experimental.coroutineContext
import kotlin.coroutines.coroutineContext
const val DEFAULT_RETRY = 3
private const val DEFAULT_DB_RETRY = 15
suspend fun <T> retryIO(
description: String = "<missing description>",
times: Int = DEFAULT_RETRY,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1000, // 1 second
factor: Double = 2.0,
block: suspend () -> T): T
{
description: String = "<missing description>",
times: Int = DEFAULT_RETRY,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1000, // 1 second
factor: Double = 2.0,
block: suspend () -> T
): T {
var currentDelay = initialDelay
repeat(times - 1) { currentTry ->
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryIO")
try {
return block()
} catch (e: RocketChatNetworkErrorException) {
......@@ -29,26 +28,26 @@ suspend fun <T> retryIO(
e.printStackTrace()
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryIO")
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryIO")
return block() // last attempt
}
suspend fun <T> retryDB(
description: String = "<missing description>",
times: Int = DEFAULT_DB_RETRY,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1500, // 1.5 second
factor: Double = 1.2,
block: suspend () -> T): T
{
description: String = "<missing description>",
times: Int = DEFAULT_DB_RETRY,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1500, // 1.5 second
factor: Double = 1.2,
block: suspend () -> T
): T {
var currentDelay = initialDelay
repeat(times - 1) { currentTry ->
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryDB")
try {
return block()
} catch (e: SQLiteDatabaseLockedException) {
......@@ -56,11 +55,11 @@ suspend fun <T> retryDB(
e.printStackTrace()
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryDB")
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryDB")
return block() // last attempt
}
\ No newline at end of file
......@@ -3,9 +3,10 @@ package chat.rocket.android.util.extensions
import android.os.Looper
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
inline fun Fragment.ui(crossinline block: (activity: FragmentActivity) -> Unit): Job? {
// Checking first for activity and view saves us from some synchronyzed and thread local checks
......@@ -16,7 +17,7 @@ inline fun Fragment.ui(crossinline block: (activity: FragmentActivity) -> Unit):
null
} else {
// Launch a Job on the UI context and check again if the activity and view are still valid
launch(UI) {
GlobalScope.launch(Dispatchers.Main) {
if (activity != null && view != null && context != null) {
block(activity!!)
}
......
......@@ -7,15 +7,15 @@ import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.registerPushToken
import chat.rocket.core.model.Message
import chat.rocket.core.model.asString
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
suspend fun RocketChatClientFactory.registerPushToken(
token: String,
accounts: List<Account>
) {
withContext(CommonPool) {
withContext(Dispatchers.IO) {
accounts.forEach { account ->
try {
retryIO(description = "register push token: ${account.serverUrl}") {
......
......@@ -2,28 +2,28 @@ package chat.rocket.android.util.livedata
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import kotlin.coroutines.experimental.CoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
class TransformedLiveData<Source, Output>(
private val runContext: CoroutineContext = CommonPool,
private val runContext: CoroutineContext = Dispatchers.IO,
private val source: LiveData<Source>,
private val transformation: (Source?) -> Output?)
: LiveData<Output>() {
private val transformation: (Source?) -> Output?
) : LiveData<Output>() {
private var job: Job? = null
private val observer = Observer<Source> { source ->
job?.cancel()
job = launch(runContext) {
job = GlobalScope.launch(runContext) {
transformation(source)?.let { transformed ->
// Could have used postValue instead, but using the UI context I can guarantee that
// a canceled job will never emit values.
withContext(UI) {
withContext(Dispatchers.Main) {
value = transformed
}
}
......@@ -41,5 +41,6 @@ class TransformedLiveData<Source, Output>(
}
fun <Source, Output> LiveData<Source>.transform(
runContext: CoroutineContext = CommonPool,
transformation: (Source?) -> Output?) = TransformedLiveData(runContext, this, transformation)
\ No newline at end of file
runContext: CoroutineContext = Dispatchers.IO,
transformation: (Source?) -> Output?
) = TransformedLiveData(runContext, this, transformation)
\ No newline at end of file
......@@ -3,21 +3,22 @@ package chat.rocket.android.util.livedata
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.launch
import kotlin.coroutines.experimental.CoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
class WrappedLiveData<Source, Output>(
private val runContext: CoroutineContext = CommonPool,
private val runContext: CoroutineContext = Dispatchers.IO,
private val source: LiveData<Source>,
private val transformation: suspend (Source?, MutableLiveData<Output>) -> Unit)
: MutableLiveData<Output>() {
private val transformation: suspend (Source?, MutableLiveData<Output>) -> Unit
) : MutableLiveData<Output>() {
private var job: Job? = null
private val observer = Observer<Source> { source ->
job?.cancel()
job = launch(runContext) {
job = GlobalScope.launch(runContext) {
transformation(source, this@WrappedLiveData)
}
}
......@@ -33,6 +34,7 @@ class WrappedLiveData<Source, Output>(
}
fun <Source, Output> LiveData<Source>.wrap(
runContext: CoroutineContext = CommonPool,
transformation: suspend (Source?, MutableLiveData<Output>) -> Unit) =
WrappedLiveData(runContext, this, transformation)
\ No newline at end of file
runContext: CoroutineContext = Dispatchers.IO,
transformation: suspend (Source?, MutableLiveData<Output>) -> Unit
) =
WrappedLiveData(runContext, this, transformation)
\ No newline at end of file
package chat.rocket.android.videoconferencing.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.videoconferencing.presenter.VideoConferencingView
import chat.rocket.android.videoconferencing.ui.VideoConferencingActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.Job
@Module
class VideoConferencingModule {
@Provides
@PerActivity
fun provideVideoConferencingView(activity: VideoConferencingActivity): VideoConferencingView {
return activity
}
@Provides
@PerActivity
fun provideJob() = Job()
@Provides
@PerActivity
fun provideLifecycleOwner(activity: VideoConferencingActivity): LifecycleOwner = activity
@Provides
@PerActivity
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy =
CancelStrategy(owner, jobs)
}
\ No newline at end of file
package chat.rocket.android.videoconferencing.presenter
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.JitsiHelper
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.realtime.updateJitsiTimeout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import javax.inject.Inject
class VideoConferencingPresenter @Inject constructor(
private val view: VideoConferencingView,
private val strategy: CancelStrategy,
private val currentServerRepository: CurrentServerRepository,
private val connectionManagerFactory: ConnectionManagerFactory,
private val settings: GetSettingsInteractor
) {
private lateinit var currentServerUrl: String
private lateinit var connectionManager: ConnectionManager
private lateinit var client: RocketChatClient
private lateinit var publicSettings: PublicSettings
private lateinit var chatRoomId: String
fun setup(chatRoomId: String) {
currentServerRepository.get()?.let {
currentServerUrl = it
connectionManager = connectionManagerFactory.create(it)
client = connectionManager.client
publicSettings = settings.get(it)
}
this.chatRoomId = chatRoomId
}
fun setupVideoConferencing() {
launchUI(strategy) {
with(publicSettings) {
view.startVideoConferencing(
JitsiHelper.getJitsiUrl(
isJitsiSSL(),
jitsiDomain(),
jitsiPrefix(),
uniqueIdentifier(),
chatRoomId
)
)
}
client.updateJitsiTimeout(chatRoomId)
}
}
private fun updateJitsiTimeout() {
GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
client.updateJitsiTimeout(chatRoomId)
}
}
}
package chat.rocket.android.videoconferencing.presenter
import java.net.URL
interface VideoConferencingView {
/**
* Starts the video conferencing.
*
* @param url The video conferencing URL to be loaded.
*/
fun startVideoConferencing(url: URL)
}
\ No newline at end of file
package chat.rocket.android.videoconferencing.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import chat.rocket.android.videoconferencing.presenter.VideoConferencingPresenter
import chat.rocket.android.videoconferencing.presenter.VideoConferencingView
import dagger.android.AndroidInjection
import org.jitsi.meet.sdk.JitsiMeetActivity
import java.net.URL
import javax.inject.Inject
fun Context.videoConferencingIntent(chatRoomId: String): Intent =
Intent(this, VideoConferencingActivity::class.java).putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
class VideoConferencingActivity : JitsiMeetActivity(), VideoConferencingView {
@Inject
lateinit var presenter: VideoConferencingPresenter
private lateinit var chatRoomId: String
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" }
presenter.setup(chatRoomId)
presenter.setupVideoConferencing()
}
override fun startVideoConferencing(url: URL) = loadURL(url)
}
......@@ -31,8 +31,8 @@ class WebViewActivity : AppCompatActivity() {
setContentView(R.layout.activity_web_view)
webPageUrl = intent.getStringExtra(INTENT_WEB_PAGE_URL)
toolbarTitle = intent.getStringExtra(TOOLBAR_TITLE)
requireNotNull(webPageUrl) { "no web_page_url provided in Intent extras" }
toolbarTitle = intent.getStringExtra(TOOLBAR_TITLE)
setupToolbar()
}
......
......@@ -159,6 +159,16 @@
app:layout_constraintTop_toBottomOf="@+id/message_header"
tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" />
<Button
android:id="@+id/button_join_video_call"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:text="@string/msg_join_video_call"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@+id/text_content"
app:layout_constraintTop_toBottomOf="@+id/text_content" />
<include
layout="@layout/layout_reactions"
android:layout_width="0dp"
......
......@@ -100,6 +100,7 @@
<string name="msg_today">Heute</string>
<string name="msg_message">Nachricht</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">Dieser Raum ist nur lesen</string>
<string name="msg_invalid_2fa_code">Falscher 2FA Code</string>
<string name="msg_invalid_file">Falsche Datei</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">Hoy</string>
<string name="msg_message">Mensaje</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">Esta sala es de solo lectura</string>
<string name="msg_invalid_2fa_code">Código 2FA no válido</string>
<string name="msg_invalid_file">Archivo inválido</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">امروز</string>
<string name="msg_message">پیام</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">این اتاق فقط خواندنی است</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string> <!-- TODO Add translation -->
<string name="msg_invalid_file">پرونده‌ی نامعتبر</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">Aujourd\'hui</string>
<string name="msg_message">Message</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">Ce salon est en lecture seule</string>
<string name="msg_invalid_2fa_code">Code 2FA non valide</string>
<string name="msg_invalid_file">Fichier non valide</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">आज</string>
<string name="msg_message">संदेश</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">यह रूम केवल पढ़ने के लिए है</string>
<string name="msg_invalid_2fa_code">अमान्य 2FA कोड</string>
<string name="msg_invalid_file">अवैध फाइल</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">Oggi</string>
<string name="msg_message">Messaggio</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">Questa stanza è di sola lettura</string>
<string name="msg_invalid_2fa_code">Invalido Codice 2FA non valido</string>
<string name="msg_invalid_file">Documento non valido</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_yesterday">昨日</string>
<string name="msg_message">メッセージ</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">この部屋は読み取り専用です</string>
<string name="msg_invalid_2fa_code">無効な 2FA コード</string>
<string name="msg_invalid_file">無効なファイル</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">Hoje</string>
<string name="msg_message">Mensagem</string>
<string name="msg_video_call">Videochamada</string>
<string name="msg_join_video_call">Entrar na videochamada</string>
<string name="msg_this_room_is_read_only">Este chat é apenas de leitura</string>
<string name="msg_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_invalid_file">Arquivo inválido</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">Сегодня</string>
<string name="msg_message">Сообщение</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">Канал только для чтения</string>
<string name="msg_invalid_2fa_code">Неверный код 2FA</string>
<string name="msg_invalid_file">Неверный файл</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">Bugün</string>
<string name="msg_message">Mesaj</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">Bu oda sadece okunabilir modundadır</string>
<string name="msg_invalid_2fa_code">Geçersiz 2FA Kodu</string>
<string name="msg_invalid_file">Geçersiz dosya</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">Сьогодні</string>
<string name="msg_message">Повідомлення</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">Канал тільки для читання</string>
<string name="msg_invalid_2fa_code">Неправильний код 2FA</string>
<string name="msg_invalid_file">Неправильний файл</string>
......
......@@ -97,6 +97,7 @@
<string name="msg_today">今天</string>
<string name="msg_message">消息</string>
<string name="msg_video_call">Video call</string> <!-- TODO Add translation -->
<string name="msg_join_video_call">Join video call</string> <!-- TODO Add translation -->
<string name="msg_this_room_is_read_only">这个频道只读</string>
<string name="msg_invalid_2fa_code">无效的2FA码</string>
<string name="msg_invalid_file">无效文件</string>
......
......@@ -110,6 +110,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_today">Today</string>
<string name="msg_message">Message</string>
<string name="msg_video_call">Video call</string>
<string name="msg_join_video_call">Join video call</string>
<string name="msg_this_room_is_read_only">This room is read only</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string>
<string name="msg_invalid_file">Invalid file</string>
......
package chat.rocket.android.extensions
import com.google.android.gms.tasks.Task
import kotlin.coroutines.experimental.suspendCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@JvmName("awaitVoid")
suspend fun Task<Void>.await() = suspendCoroutine<Unit> { continuation ->
......
......@@ -45,7 +45,7 @@ object SmartLockHelper {
.addOnCompleteListener {
when {
it.isSuccessful -> {
credential = it.result.credential
credential = it.result?.credential
}
it.exception is ResolvableApiException -> {
val resolvableApiException = (it.exception as ResolvableApiException)
......
......@@ -7,16 +7,22 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.SITE_URL
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
fun installCrashlyticsWrapper(context: Application,
currentServerInteractor: GetCurrentServerInteractor,
settingsInteractor: GetSettingsInteractor,
accountRepository: AccountsRepository,
localRepository: LocalRepository) {
fun installCrashlyticsWrapper(
context: Application,
currentServerInteractor: GetCurrentServerInteractor,
settingsInteractor: GetSettingsInteractor,
accountRepository: AccountsRepository,
localRepository: LocalRepository
) {
if (isCrashlyticsEnabled()) {
Thread.setDefaultUncaughtExceptionHandler(RocketChatUncaughtExceptionHandler(currentServerInteractor,
settingsInteractor, accountRepository, localRepository))
Thread.setDefaultUncaughtExceptionHandler(
RocketChatUncaughtExceptionHandler(
currentServerInteractor,
settingsInteractor, accountRepository, localRepository
)
)
}
}
......@@ -25,13 +31,14 @@ private fun isCrashlyticsEnabled(): Boolean {
}
private class RocketChatUncaughtExceptionHandler(
val currentServerInteractor: GetCurrentServerInteractor,
val settingsInteractor: GetSettingsInteractor,
val accountRepository: AccountsRepository,
val localRepository: LocalRepository)
: Thread.UncaughtExceptionHandler {
val currentServerInteractor: GetCurrentServerInteractor,
val settingsInteractor: GetSettingsInteractor,
val accountRepository: AccountsRepository,
val localRepository: LocalRepository
) : Thread.UncaughtExceptionHandler {
val crashlyticsHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler()
val crashlyticsHandler: Thread.UncaughtExceptionHandler? =
Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(t: Thread, e: Throwable) {
val currentServer = currentServerInteractor.get() ?: "<unknown>"
......
......@@ -9,7 +9,7 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.common.util.ifNull
import com.google.firebase.iid.FirebaseInstanceId
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import javax.inject.Inject
......
......@@ -10,12 +10,11 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.android.tools.build:gradle:3.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:4.1.0'
classpath 'io.fabric.tools:gradle:1.25.4'
classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.26.1'
classpath "com.github.ben-manes:gradle-versions-plugin:0.20.0"
}
}
......@@ -26,6 +25,7 @@ allprojects {
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://jitpack.io" }
maven { url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases" }
}
apply from: rootProject.file('dependencies.gradle')
......
......@@ -29,7 +29,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation libraries.kotlin
implementation libraries.coroutines
implementation libraries.coroutinesCore
implementation libraries.lifecycleExtensions
kapt libraries.lifecycleCompiler
......
......@@ -4,7 +4,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
class CancelStrategy(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver {
......
......@@ -2,17 +2,17 @@ ext {
versions = [
// For project configuration
java : JavaVersion.VERSION_1_8,
minSdk : 21,
compileSdk : 28,
targetSdk : 28,
minSdk : 21,
buildTools : '28.0.3',
dokka : '0.9.16',
// For app
kotlin : '1.2.71',
coroutine : '0.25.0',
kotlin : '1.3.21',
coroutine : '1.1.1',
appCompat : '1.0.0',
appCompat : '1.0.2',
recyclerview : '1.0.0',
constraintLayout : '2.0.0-alpha2',
cardview : '1.0.0',
......@@ -21,9 +21,9 @@ ext {
workmanager : '1.0.0-alpha09',
dagger : '2.16',
firebaseCloudMessage : '17.3.0',
firebaseAnalytics : '16.0.3',
playServices : '16.0.0',
firebaseCloudMessage : '17.3.4',
firebaseAnalytics : '16.0.6',
playServicesAuth : '16.0.1',
exoPlayer : '2.8.2',
flexbox : '1.1.0',
material : '1.0.0',
......@@ -36,8 +36,8 @@ ext {
rxKotlin : '2.3.0',
rxAndroid : '2.1.0',
moshi : '1.6.0',
okhttp : '3.11.0',
moshi : '1.8.0',
okhttp : '3.12.1',
timber : '4.7.1',
threeTenABP : '1.1.0',
......@@ -45,18 +45,18 @@ ext {
fresco : '1.10.0',
kotshi : '1.0.4',
kotshi : '1.0.6',
frescoImageViewer : '0.5.1',
markwon : '2.0.0',
aVLoadingIndicatorView: '2.1.3',
glide : '4.8.0',
glideTransformations : '4.0.0',
// For wearable
wear : '2.3.0',
playServicesWearable : '15.0.1',
supportWearable : '27.1.1',
jitsi : '+', // TODO Avoid using + (https://github.com/jitsi/jitsi-meet/issues/3987)
// For testing
junit : '4.12',
......@@ -66,7 +66,7 @@ ext {
]
libraries = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}",
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesCore : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
appCompat : "androidx.appcompat:appcompat:${versions.appCompat}",
......@@ -117,26 +117,20 @@ ext {
kotshiCompiler : "se.ansman.kotshi:compiler:${versions.kotshi}",
frescoImageViewer : "com.github.luciofm:FrescoImageViewer:${versions.frescoImageViewer}",
glide : "com.github.bumptech.glide:glide:${versions.glide}",
glideProcessor : "com.github.bumptech.glide:compiler:${versions.glide}",
glideTransformations : "jp.wasabeef:glide-transformations:${versions.glideTransformations}",
markwon : "ru.noties:markwon:${versions.markwon}",
aVLoadingIndicatorView: "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
glide : "com.github.bumptech.glide:glide:${versions.glide}",
glideTransformations : "jp.wasabeef:glide-transformations:${versions.glideTransformations}",
jitsi : "org.jitsi.react:jitsi-meet-sdk:${versions.jitsi}",
// Proprietary libraries
fcm : "com.google.firebase:firebase-messaging:${versions.firebaseCloudMessage}",
firebaseAnalytics : "com.google.firebase:firebase-core:${versions.firebaseAnalytics}",
playServicesAuth : "com.google.android.gms:play-services-auth:${versions.playServices}",
// For wearable
wearable : "com.google.android.support:wearable:${versions.wear}",
playServicesWearable : "com.google.android.gms:play-services-wearable:${versions.playServicesWearable}",
percentLayout : "com.android.support:percent:${versions.supportWearable}",
supportWearable : "com.android.support:support-v4:${versions.supportWearable}",
wearableRecyclerView : "com.android.support:recyclerview-v7:${versions.supportWearable}",
wearSupport : "com.android.support:wear:${versions.supportWearable}",
playServicesAuth : "com.google.android.gms:play-services-auth:${versions.playServicesAuth}",
// For testing
junit : "junit:junit:${versions.junit}",
......
......@@ -51,7 +51,7 @@ dependencies {
implementation project(':util')
implementation libraries.kotlin
implementation libraries.coroutines
implementation libraries.coroutinesCore
implementation libraries.appCompat
implementation libraries.constraintlayout
......
......@@ -6,7 +6,7 @@ import chat.rocket.android.draw.main.presenter.DrawView
import chat.rocket.android.draw.main.ui.DrawingActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.Job
@Module
class DrawModule {
......
......@@ -5,14 +5,13 @@ apply plugin: 'kotlin-kapt'
android {
compileSdkVersion versions.compileSdk
buildToolsVersion '28.0.3'
buildToolsVersion versions.buildTools
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 1
versionName "0.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
......@@ -28,28 +27,24 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation libraries.androidKtx
implementation libraries.appCompat
implementation libraries.kotlin
implementation libraries.coroutines
implementation libraries.coroutinesCore
implementation libraries.coroutinesAndroid
implementation libraries.constraintlayout
implementation libraries.appCompat
implementation libraries.recyclerview
implementation libraries.constraintlayout
implementation libraries.androidKtx
implementation libraries.material
implementation libraries.glide
kapt libraries.glideProcessor
implementation libraries.room
kapt libraries.roomProcessor
}
kotlin {
experimental {
coroutines "enable"
}
implementation libraries.glide
}
androidExtensions {
......
......@@ -22,8 +22,9 @@ import chat.rocket.android.emoji.internal.EmojiPagerAdapter
import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE
import com.google.android.material.tabs.TabLayout
import kotlinx.android.synthetic.main.dialog_skin_tone_chooser.view.*
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow(context, view) {
private lateinit var viewPager: ViewPager
......@@ -49,7 +50,7 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
}
override fun onViewCreated(view: View) {
launch(UI) {
GlobalScope.launch(Dispatchers.Main) {
setupViewPager()
setupBottomBar()
}
......
......@@ -8,12 +8,14 @@ import android.text.SpannableString
import android.text.Spanned
import android.text.style.ImageSpan
import android.util.Log
import chat.rocket.android.emoji.internal.GlideApp
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.gif.GifDrawable
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.async
import com.bumptech.glide.request.RequestOptions
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
class EmojiParser {
......@@ -85,14 +87,14 @@ class EmojiParser {
emoji.url?.let { url ->
try {
val glideRequest = if (url.endsWith("gif", true)) {
GlideApp.with(context).asGif()
Glide.with(context).asGif()
} else {
GlideApp.with(context).asBitmap()
Glide.with(context).asBitmap()
}
val futureTarget = glideRequest
.diskCacheStrategy(DiskCacheStrategy.ALL)
.load(url)
.apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL))
.submit(px, px)
val range = match.range
......@@ -120,7 +122,7 @@ class EmojiParser {
text: CharSequence,
factory: Spannable.Factory? = null
): Deferred<CharSequence> {
return async(CommonPool) { parse(context, text, factory) }
return GlobalScope.async(Dispatchers.IO) { parse(context, text, factory) }
}
}
}
......@@ -14,8 +14,9 @@ import chat.rocket.android.emoji.internal.EmojiCategory
import chat.rocket.android.emoji.internal.EmojiPagerAdapter
import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE
import kotlinx.android.synthetic.main.emoji_picker.*
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class EmojiPickerPopup(context: Context) : Dialog(context) {
......@@ -29,7 +30,7 @@ class EmojiPickerPopup(context: Context) : Dialog(context) {
setContentView(R.layout.emoji_picker)
tabs.setupWithViewPager(pager_categories)
launch(UI) {
GlobalScope.launch(Dispatchers.Main) {
setupViewPager()
setSize()
}
......
......@@ -10,22 +10,21 @@ import chat.rocket.android.emoji.internal.db.EmojiDatabase
import chat.rocket.android.emoji.internal.isCustom
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.GlideException
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.io.Reader
import java.util.*
import java.util.regex.Pattern
import kotlin.collections.ArrayList
import kotlin.coroutines.experimental.buildSequence
object EmojiRepository {
private val FITZPATRICK_REGEX = "(.*)_(tone[0-9]):".toRegex(RegexOption.IGNORE_CASE)
private val shortNameToUnicode = HashMap<String, String>()
private val SHORTNAME_PATTERN = Pattern.compile(":([-+\\w]+):")
......@@ -43,14 +42,19 @@ object EmojiRepository {
return if (::currentServerUrl.isInitialized) currentServerUrl else null
}
fun load(context: Context, customEmojis: List<Emoji> = emptyList(), path: String = "emoji.json") {
launch(CommonPool) {
fun load(
context: Context,
customEmojis: List<Emoji> = emptyList(),
path: String = "emoji.json"
) {
GlobalScope.launch(Dispatchers.IO) {
this@EmojiRepository.customEmojis = customEmojis
val allEmojis = mutableListOf<Emoji>()
db = EmojiDatabase.getInstance(context)
if (!::cachedTypeface.isInitialized) {
cachedTypeface = Typeface.createFromAsset(context.assets, "fonts/emojione-android.ttf")
cachedTypeface =
Typeface.createFromAsset(context.assets, "fonts/emojione-android.ttf")
}
preferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE)
......@@ -117,9 +121,7 @@ object EmojiRepository {
customEmojis.forEach {
try {
val future = Glide.with(context)
.load(it.url)
.submit(px, px)
val future = Glide.with(context).load(it.url).submit(px, px)
future.get()
} catch (ex: Exception) {
Log.d("EmojiRepository", "Error fetching custom emoji ${it.shortname}", ex)
......@@ -132,7 +134,7 @@ object EmojiRepository {
}
private suspend fun saveEmojisToDatabase(emojis: List<Emoji>) {
withContext(CommonPool) {
withContext(Dispatchers.IO) {
db.emojiDao().insertAllEmojis(*emojis.toTypedArray())
}
}
......@@ -146,31 +148,30 @@ object EmojiRepository {
*
* @return All emojis for all categories.
*/
suspend fun getAll(): List<Emoji> = withContext(CommonPool) {
suspend fun getAll(): List<Emoji> = withContext(Dispatchers.IO) {
return@withContext db.emojiDao().loadAllEmojis()
}
internal suspend fun getEmojiSequenceByCategory(category: EmojiCategory): Sequence<Emoji> {
val list = withContext(CommonPool) {
val list = withContext(Dispatchers.IO) {
db.emojiDao().loadEmojisByCategory(category.name)
}
return buildSequence {
list.forEach {
yield(it)
}
return sequence {
list.forEach { yield(it) }
}
}
internal suspend fun getEmojiSequenceByCategoryAndUrl(category: EmojiCategory, url: String): Sequence<Emoji> {
val list = withContext(CommonPool) {
internal suspend fun getEmojiSequenceByCategoryAndUrl(
category: EmojiCategory,
url: String
): Sequence<Emoji> {
val list = withContext(Dispatchers.IO) {
db.emojiDao().loadEmojisByCategoryAndUrl(category.name, "$url%")
}
return buildSequence {
list.forEach {
yield(it)
}
return sequence {
list.forEach { yield(it) }
}
}
......@@ -181,9 +182,10 @@ object EmojiRepository {
*
* @return Emoji given by shortname or null
*/
private suspend fun getEmojiByShortname(shortname: String): Emoji? = withContext(CommonPool) {
return@withContext db.emojiDao().loadAllCustomEmojis().firstOrNull()
}
private suspend fun getEmojiByShortname(shortname: String): Emoji? =
withContext(Dispatchers.IO) {
return@withContext db.emojiDao().loadAllCustomEmojis().firstOrNull()
}
/**
* Add an emoji to the Recents category.
......@@ -203,9 +205,9 @@ object EmojiRepository {
}
internal suspend fun getCustomEmojisAsync(): List<Emoji> {
return withContext(CommonPool) {
return withContext(Dispatchers.IO) {
db.emojiDao().loadAllCustomEmojis().also {
this.customEmojis = it
customEmojis = it
}
}
}
......@@ -217,7 +219,7 @@ object EmojiRepository {
*
* @return All recent emojis ordered by usage.
*/
internal suspend fun getRecents(): List<Emoji> = withContext(CommonPool) {
internal suspend fun getRecents(): List<Emoji> = withContext(Dispatchers.IO) {
val list = mutableListOf<Emoji>()
val recentsJson = JSONObject(preferences.getString(PREF_EMOJI_RECENTS, "{}"))
......@@ -281,11 +283,13 @@ object EmojiRepository {
if (!json.has("shortname") || !json.has("unicode")) {
return null
}
return Emoji(shortname = json.getString("shortname"),
return Emoji(
shortname = json.getString("shortname"),
unicode = json.getString("unicode"),
shortnameAlternates = buildStringListFromJsonArray(json.getJSONArray("shortnameAlternates")),
category = json.getString("category"),
keywords = buildStringListFromJsonArray(json.getJSONArray("keywords")))
keywords = buildStringListFromJsonArray(json.getJSONArray("keywords"))
)
}
private fun buildStringListFromJsonArray(array: JSONArray): List<String> {
......@@ -297,7 +301,7 @@ object EmojiRepository {
private fun inputStreamToString(stream: InputStream): String {
val sb = StringBuilder()
val isr = InputStreamReader(stream, Charsets.UTF_8)
val br = BufferedReader(isr)
val br = BufferedReader(isr as Reader?)
var read: String? = br.readLine()
while (read != null) {
sb.append(read)
......@@ -315,7 +319,7 @@ object EmojiRepository {
}
fun init(context: Context) {
launch {
GlobalScope.launch {
db = EmojiDatabase.getInstance(context)
preferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE)
cachedTypeface = Typeface.createFromAsset(context.assets, "fonts/emojione-android.ttf")
......
package chat.rocket.android.emoji.internal
import android.content.Context
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.engine.cache.ExternalPreferredCacheDiskCacheFactory
import com.bumptech.glide.module.AppGlideModule
@GlideModule
class EmojiGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setDiskCache(ExternalPreferredCacheDiskCacheFactory(context))
}
}
......@@ -14,15 +14,15 @@ import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.emoji.Fitzpatrick
import chat.rocket.android.emoji.R
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import kotlinx.android.synthetic.main.emoji_category_layout.view.*
import kotlinx.android.synthetic.main.emoji_image_row_item.view.*
import kotlinx.android.synthetic.main.emoji_row_item.view.*
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) : PagerAdapter() {
......@@ -44,7 +44,7 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
emoji_recycler_view.setRecycledViewPool(RecyclerView.RecycledViewPool())
container.addView(view)
launch(UI) {
kotlinx.coroutines.GlobalScope.launch(Dispatchers.Main) {
val currentServerUrl = EmojiRepository.getCurrentServerUrl()
val emojis = if (category != EmojiCategory.RECENTS) {
if (category == EmojiCategory.CUSTOM) {
......@@ -111,9 +111,9 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
}
suspend fun addEmojisFromSequence(emojiSequence: Sequence<Emoji>) {
withContext(CommonPool) {
withContext(Dispatchers.IO) {
emojiSequence.forEachIndexed { index, emoji ->
withContext(UI) {
withContext(Dispatchers.Main) {
allEmojis.add(emoji)
if (emoji.isDefault) {
emojis.add(emoji)
......@@ -180,9 +180,9 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
}
} else {
// Handle custom emoji.
GlideApp.with(context)
Glide.with(context)
.load(emoji.url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL))
.into(emoji_image_view)
}
......
......@@ -13,6 +13,7 @@ android {
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
......
include ':app', ':player', ':emoji', ':draw', ':util', ':core', ':suggestions' //, ':wear'
\ No newline at end of file
include ':app', ':player', ':emoji', ':draw', ':util', ':core', ':suggestions'
\ No newline at end of file
package chat.rocket.android.suggestions.strategy.trie.data
import chat.rocket.android.suggestions.model.SuggestionModel
import kotlin.coroutines.experimental.buildSequence
internal class TrieNode(
internal var data: Char,
......@@ -32,17 +31,13 @@ internal class TrieNode(
return list
}
fun getItems(): Sequence<SuggestionModel> = buildSequence {
fun getItems(): Sequence<SuggestionModel> = sequence {
if (isLeaf) {
yield(item!!)
}
children.forEach { node ->
node.value.let {
yieldAll(it.getItems())
}
}
children.forEach { node -> yieldAll(node.value.getItems()) }
}
override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data"
......
......@@ -31,7 +31,7 @@ dependencies {
implementation project(':core')
implementation libraries.kotlin
implementation libraries.coroutines
implementation libraries.coroutinesCore
implementation libraries.coroutinesAndroid
implementation libraries.appCompat
......
package chat.rocket.android.util.extension
import chat.rocket.android.core.lifecycle.CancelStrategy
import kotlinx.coroutines.experimental.CoroutineScope
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
/**
* Launches a coroutine on the UI context.
*
* @param strategy a CancelStrategy for canceling the coroutine job
*/
fun launchUI(strategy: CancelStrategy, block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = UI, parent = strategy.jobs, block = block)
}
\ No newline at end of file
fun launchUI(strategy: CancelStrategy, block: suspend CoroutineScope.() -> Unit): Job =
MainScope().launch(context = strategy.jobs, block = block)
......@@ -6,8 +6,8 @@ import android.os.Environment
import android.provider.MediaStore
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
......@@ -25,11 +25,10 @@ import java.util.*
suspend fun Bitmap.compressImageAndGetInputStream(mimeType: String): InputStream? {
var inputStream: InputStream? = null
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
val byteArrayOutputStream = ByteArrayOutputStream()
// TODO: Add an option the the app to the user be able to select the quality of the compressed image
val isCompressed =
this.compress(mimeType.getCompressFormat(), 70, byteArrayOutputStream)
val isCompressed = compress(mimeType.getCompressFormat(), 70, byteArrayOutputStream)
if (isCompressed) {
inputStream = ByteArrayInputStream(byteArrayOutputStream.toByteArray())
}
......@@ -74,10 +73,9 @@ suspend fun Bitmap.getByteArray(
suspend fun Bitmap.compressImageAndGetByteArray(mimeType: String, quality: Int = 100): ByteArray? {
var byteArray: ByteArray? = null
withContext(DefaultDispatcher) {
withContext(Dispatchers.Default) {
val byteArrayOutputStream = ByteArrayOutputStream()
val isCompressed =
this.compress(mimeType.getCompressFormat(), quality, byteArrayOutputStream)
val isCompressed = compress(mimeType.getCompressFormat(), quality, byteArrayOutputStream)
if (isCompressed) {
byteArray = byteArrayOutputStream.toByteArray()
}
......
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