Unverified Commit c8cd83d1 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito Committed by GitHub

Merge branch 'beta' into fix-2133

parents 699f18fe 8d63b2e7
...@@ -2,7 +2,9 @@ def taskRequests = getGradle().getStartParameter().getTaskRequests().toString() ...@@ -2,7 +2,9 @@ def taskRequests = getGradle().getStartParameter().getTaskRequests().toString()
def isPlay = !(taskRequests.contains("Foss") || taskRequests.contains("foss")) def isPlay = !(taskRequests.contains("Foss") || taskRequests.contains("foss"))
apply plugin: 'com.android.application' 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'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
...@@ -16,13 +18,12 @@ android { ...@@ -16,13 +18,12 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion versions.minSdk minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 2057 versionCode 2058
versionName "3.2.0" versionName "3.3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim() 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}\"" buildConfigField "String", "GIT_SHA", "\"${gitSha}\""
javaCompileOptions { javaCompileOptions {
...@@ -30,6 +31,17 @@ android { ...@@ -30,6 +31,17 @@ android {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] 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 { signingConfigs {
...@@ -73,7 +85,7 @@ android { ...@@ -73,7 +85,7 @@ android {
dimension "type" dimension "type"
} }
// only foss // only FOSS
foss { foss {
dimension "type" dimension "type"
} }
...@@ -83,6 +95,10 @@ android { ...@@ -83,6 +95,10 @@ android {
exclude 'META-INF/core.kotlin_module' exclude 'META-INF/core.kotlin_module'
exclude 'META-INF/main.kotlin_module' exclude 'META-INF/main.kotlin_module'
} }
lintOptions {
lintConfig file("src/main/res/xml/lint.xml")
}
} }
dependencies { dependencies {
...@@ -96,7 +112,7 @@ dependencies { ...@@ -96,7 +112,7 @@ dependencies {
implementation project(':suggestions') implementation project(':suggestions')
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutinesCore
implementation libraries.coroutinesAndroid implementation libraries.coroutinesAndroid
implementation libraries.appCompat implementation libraries.appCompat
...@@ -123,6 +139,8 @@ dependencies { ...@@ -123,6 +139,8 @@ dependencies {
implementation libraries.viewmodelKtx implementation libraries.viewmodelKtx
implementation libraries.workmanager implementation libraries.workmanager
implementation libraries.livedataKtx
implementation libraries.rxKotlin implementation libraries.rxKotlin
implementation libraries.rxAndroid implementation libraries.rxAndroid
...@@ -133,25 +151,25 @@ dependencies { ...@@ -133,25 +151,25 @@ dependencies {
implementation libraries.timber implementation libraries.timber
implementation libraries.threeTenABP implementation libraries.threeTenABP
kapt libraries.kotshiCompiler
implementation libraries.kotshiApi
implementation libraries.fresco implementation libraries.fresco
api libraries.frescoOkHttp api libraries.frescoOkHttp
implementation libraries.frescoAnimatedGif implementation libraries.frescoAnimatedGif
implementation libraries.frescoWebP implementation libraries.frescoWebP
implementation libraries.frescoAnimatedWebP implementation libraries.frescoAnimatedWebP
implementation libraries.glide
implementation libraries.glideTransformations
kapt libraries.kotshiCompiler
implementation libraries.kotshiApi
implementation libraries.frescoImageViewer implementation libraries.frescoImageViewer
implementation libraries.markwon implementation libraries.markwon
implementation libraries.aVLoadingIndicatorView 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' implementation 'com.google.code.findbugs:jsr305:3.0.2'
...@@ -159,8 +177,8 @@ dependencies { ...@@ -159,8 +177,8 @@ dependencies {
playImplementation libraries.fcm playImplementation libraries.fcm
playImplementation libraries.firebaseAnalytics playImplementation libraries.firebaseAnalytics
playImplementation libraries.playServicesAuth playImplementation libraries.playServicesAuth
playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.5@aar') { transitive = true } playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.8@aar') { transitive = true }
playImplementation('com.crashlytics.sdk.android:answers:1.4.3@aar') { transitive = true } playImplementation('com.crashlytics.sdk.android:answers:1.4.6@aar') { transitive = true }
testImplementation libraries.junit testImplementation libraries.junit
testImplementation libraries.truth testImplementation libraries.truth
...@@ -168,12 +186,6 @@ dependencies { ...@@ -168,12 +186,6 @@ dependencies {
androidTestImplementation libraries.espressoIntents androidTestImplementation libraries.espressoIntents
} }
kotlin {
experimental {
coroutines "enable"
}
}
androidExtensions { androidExtensions {
experimental = true experimental = true
} }
...@@ -181,8 +193,8 @@ androidExtensions { ...@@ -181,8 +193,8 @@ androidExtensions {
// FIXME - build and install the sdk into the app/libs directory // 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 // 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'] ?: "" def sdk_location = project.properties['sdk_location'] ?: ""
task compileSdk(type:Exec) { task compileSdk(type: Exec) {
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) { if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
commandLine 'cmd', '/c', 'build-sdk.sh', sdk_location commandLine 'cmd', '/c', 'build-sdk.sh', sdk_location
} else { } else {
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <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 <application
android:name=".app.RocketChatApplication" android:name=".app.RocketChatApplication"
...@@ -73,6 +75,11 @@ ...@@ -73,6 +75,11 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" /> android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity
android:name=".videoconference.ui.VideoConferenceActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<activity <activity
android:name=".chatroom.ui.ChatRoomActivity" android:name=".chatroom.ui.ChatRoomActivity"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
......
...@@ -71,4 +71,13 @@ interface Analytics { ...@@ -71,4 +71,13 @@ interface Analytics {
* @param resetPasswordSucceeded True if successful reset password, false otherwise. * @param resetPasswordSucceeded True if successful reset password, false otherwise.
*/ */
fun logResetPassword(resetPasswordSucceeded: Boolean) {} fun logResetPassword(resetPasswordSucceeded: Boolean) {}
/**
* Logs the video conference event.
*
* @param event The [SubscriptionTypeEvent] to log.
* @param serverUrl The server URL to log.
*/
fun logVideoConference(event: SubscriptionTypeEvent, serverUrl: String) {}
} }
...@@ -76,4 +76,12 @@ class AnalyticsManager @Inject constructor( ...@@ -76,4 +76,12 @@ class AnalyticsManager @Inject constructor(
analytics.forEach { it.logResetPassword(resetPasswordSucceeded) } analytics.forEach { it.logResetPassword(resetPasswordSucceeded) }
} }
} }
fun logVideoConference(event: SubscriptionTypeEvent) {
if (analyticsTrackingInteractor.get() && serverUrl != null) {
analytics.forEach { it.logVideoConference(event, serverUrl) }
}
}
} }
...@@ -6,7 +6,8 @@ import androidx.lifecycle.OnLifecycleEvent ...@@ -6,7 +6,8 @@ import androidx.lifecycle.OnLifecycleEvent
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class AppLifecycleObserver @Inject constructor( class AppLifecycleObserver @Inject constructor(
...@@ -28,7 +29,7 @@ class AppLifecycleObserver @Inject constructor( ...@@ -28,7 +29,7 @@ class AppLifecycleObserver @Inject constructor(
} }
private fun changeTemporaryStatus(userStatus: UserStatus) { private fun changeTemporaryStatus(userStatus: UserStatus) {
launch { GlobalScope.launch {
serverInteractor.get()?.let { currentServer -> serverInteractor.get()?.let { currentServer ->
factory.create(currentServer).setTemporaryStatus(userStatus) factory.create(currentServer).setTemporaryStatus(userStatus)
} }
......
...@@ -91,7 +91,7 @@ object DrawableHelper { ...@@ -91,7 +91,7 @@ object DrawableHelper {
} }
/** /**
* Compounds a Drawable (to appear to the left of the text) into a TextView. * Compounds a Drawable (to appear on the left side of a text) into a TextView.
* *
* @param textView The TextView. * @param textView The TextView.
* @param drawable The Drawable. * @param drawable The Drawable.
...@@ -100,6 +100,16 @@ object DrawableHelper { ...@@ -100,6 +100,16 @@ object DrawableHelper {
fun compoundDrawable(textView: TextView, drawable: Drawable) = fun compoundDrawable(textView: TextView, drawable: Drawable) =
textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
/**
* Compounds a Drawable (to appear on the right side of a text) into a TextView.
*
* @param textView The TextView.
* @param drawable The Drawable.
* @see compoundDrawable
*/
fun compoundRightDrawable(textView: TextView, drawable: Drawable) =
textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null)
/** /**
* Returns the user status drawable. * Returns the user status drawable.
* *
......
...@@ -37,7 +37,8 @@ import dagger.android.DispatchingAndroidInjector ...@@ -37,7 +37,8 @@ import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector import dagger.android.HasActivityInjector
import dagger.android.HasBroadcastReceiverInjector import dagger.android.HasBroadcastReceiverInjector
import dagger.android.HasServiceInjector import dagger.android.HasServiceInjector
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import javax.inject.Inject import javax.inject.Inject
...@@ -174,7 +175,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -174,7 +175,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
EmojiRepository.init(this) EmojiRepository.init(this)
val currentServer = getCurrentServerInteractor.get() val currentServer = getCurrentServerInteractor.get()
currentServer?.let { server -> currentServer?.let { server ->
launch { GlobalScope.launch {
val client = factory.create(server) val client = factory.create(server)
EmojiRepository.setCurrentServerUrl(server) EmojiRepository.setCurrentServerUrl(server)
val customEmojiList = mutableListOf<Emoji>() val customEmojiList = mutableListOf<Emoji>()
......
...@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
@Module @Module
class AuthenticationModule { class AuthenticationModule {
......
...@@ -90,7 +90,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -90,7 +90,7 @@ class LoginFragment : Fragment(), LoginView {
text_username_or_email.setText(credential.first) text_username_or_email.setText(credential.first)
text_password.setText(credential.second) text_password.setText(credential.second)
} }
REQUEST_CODE_FOR_SAVE_RESOLUTION -> showMessage(getString(R.string.message_credentials_saved_successfully)) REQUEST_CODE_FOR_SAVE_RESOLUTION -> showMessage(getString(R.string.msg_credentials_saved_successfully))
} }
} }
} }
......
...@@ -31,8 +31,7 @@ import chat.rocket.core.internal.rest.loginWithCas ...@@ -31,8 +31,7 @@ import chat.rocket.core.internal.rest.loginWithCas
import chat.rocket.core.internal.rest.loginWithOauth import chat.rocket.core.internal.rest.loginWithOauth
import chat.rocket.core.internal.rest.loginWithSaml import chat.rocket.core.internal.rest.loginWithSaml
import chat.rocket.core.internal.rest.me 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 import javax.inject.Inject
private const val TYPE_LOGIN_OAUTH = 1 private const val TYPE_LOGIN_OAUTH = 1
...@@ -109,11 +108,11 @@ class LoginOptionsPresenter @Inject constructor( ...@@ -109,11 +108,11 @@ class LoginOptionsPresenter @Inject constructor(
when (loginType) { when (loginType) {
TYPE_LOGIN_OAUTH -> client.loginWithOauth(credentialToken, credentialSecret) TYPE_LOGIN_OAUTH -> client.loginWithOauth(credentialToken, credentialSecret)
TYPE_LOGIN_CAS -> { TYPE_LOGIN_CAS -> {
delay(3, TimeUnit.SECONDS) delay(3000)
client.loginWithCas(credentialToken) client.loginWithCas(credentialToken)
} }
TYPE_LOGIN_SAML -> { TYPE_LOGIN_SAML -> {
delay(3, TimeUnit.SECONDS) delay(3000)
client.loginWithSaml(credentialToken) client.loginWithSaml(credentialToken)
} }
TYPE_LOGIN_DEEP_LINK -> { TYPE_LOGIN_DEEP_LINK -> {
......
...@@ -10,8 +10,8 @@ import chat.rocket.android.server.domain.SaveConnectingServerInteractor ...@@ -10,8 +10,8 @@ import chat.rocket.android.server.domain.SaveConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import kotlinx.coroutines.experimental.DefaultDispatcher import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
class OnBoardingPresenter @Inject constructor( class OnBoardingPresenter @Inject constructor(
...@@ -19,7 +19,7 @@ class OnBoardingPresenter @Inject constructor( ...@@ -19,7 +19,7 @@ class OnBoardingPresenter @Inject constructor(
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val serverInteractor: SaveConnectingServerInteractor, private val serverInteractor: SaveConnectingServerInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor, refreshSettingsInteractor: RefreshSettingsInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
val settingsInteractor: GetSettingsInteractor, val settingsInteractor: GetSettingsInteractor,
val factory: RocketChatClientFactory val factory: RocketChatClientFactory
...@@ -80,7 +80,7 @@ class OnBoardingPresenter @Inject constructor( ...@@ -80,7 +80,7 @@ class OnBoardingPresenter @Inject constructor(
} }
view.showLoading() view.showLoading()
try { try {
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
setupConnectionInfo(serverUrl) setupConnectionInfo(serverUrl)
// preparing next fragment before showing it // preparing next fragment before showing it
......
...@@ -12,8 +12,8 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory ...@@ -12,8 +12,8 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.isValidUrl import chat.rocket.android.util.extensions.isValidUrl
import kotlinx.coroutines.experimental.DefaultDispatcher import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
class ServerPresenter @Inject constructor( class ServerPresenter @Inject constructor(
...@@ -98,7 +98,7 @@ class ServerPresenter @Inject constructor( ...@@ -98,7 +98,7 @@ class ServerPresenter @Inject constructor(
} }
view.showLoading() view.showLoading()
try { try {
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
// preparing next fragment before showing it // preparing next fragment before showing it
refreshServerAccounts() refreshServerAccounts()
checkEnabledAccounts(serverUrl) checkEnabledAccounts(serverUrl)
......
...@@ -63,7 +63,7 @@ class SignupFragment : Fragment(), SignupView { ...@@ -63,7 +63,7 @@ class SignupFragment : Fragment(), SignupView {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
if (data != null) { if (data != null) {
if (requestCode == SAVE_CREDENTIALS) { if (requestCode == SAVE_CREDENTIALS) {
showMessage(getString(R.string.message_credentials_saved_successfully)) showMessage(getString(R.string.msg_credentials_saved_successfully))
} }
} }
} }
......
...@@ -7,8 +7,10 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor ...@@ -7,8 +7,10 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.favorite
import chat.rocket.core.internal.rest.getInfo import chat.rocket.core.internal.rest.getInfo
import chat.rocket.core.model.Room import chat.rocket.core.model.Room
import timber.log.Timber import timber.log.Timber
...@@ -25,6 +27,31 @@ class ChatDetailsPresenter @Inject constructor( ...@@ -25,6 +27,31 @@ class ChatDetailsPresenter @Inject constructor(
private val manager = factory.create(currentServer) private val manager = factory.create(currentServer)
private val client = manager.client private val client = manager.client
fun toggleFavoriteChatRoom(roomId: String, isFavorite: Boolean) {
launchUI(strategy) {
try {
// Note: If it is favorite then the user wants to remove the favorite - and vice versa.
retryIO("favorite($roomId, ${!isFavorite})") {
client.favorite(roomId, !isFavorite)
}
view.showFavoriteIcon(!isFavorite)
} catch (e: RocketChatException) {
Timber.e(
e,
"Error while trying to favorite or removing the favorite of a chat room."
)
e.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
}
}
fun toVideoConference(roomId: String, chatRoomType: String) =
navigator.toVideoConference(roomId, chatRoomType)
fun getDetails(chatRoomId: String, chatRoomType: String) { fun getDetails(chatRoomId: String, chatRoomType: String) {
launchUI(strategy) { launchUI(strategy) {
try { try {
...@@ -32,7 +59,7 @@ class ChatDetailsPresenter @Inject constructor( ...@@ -32,7 +59,7 @@ class ChatDetailsPresenter @Inject constructor(
client.getInfo(chatRoomId, null, roomTypeOf(chatRoomType)) client.getInfo(chatRoomId, null, roomTypeOf(chatRoomType))
} }
view.displayDetails(roomToChatDetails(room)) view.displayDetails(roomToChatDetails(room))
} catch(exception: Exception) { } catch (exception: Exception) {
Timber.e(exception) Timber.e(exception)
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
......
...@@ -5,5 +5,16 @@ import chat.rocket.android.core.behaviours.LoadingView ...@@ -5,5 +5,16 @@ import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
interface ChatDetailsView: MessageView { interface ChatDetailsView: MessageView {
/**
* Shows the corresponding favorite icon for a favorite or non-favorite chat room.
*
* @param isFavorite True if a chat room is favorite, false otherwise.
*/
fun showFavoriteIcon(isFavorite: Boolean)
/**
* Shows the details of a chat room.
*/
fun displayDetails(room: ChatDetails) fun displayDetails(room: ChatDetails)
} }
\ No newline at end of file
...@@ -3,6 +3,8 @@ package chat.rocket.android.chatdetails.ui ...@@ -3,6 +3,8 @@ package chat.rocket.android.chatdetails.ui
import DrawableHelper import DrawableHelper
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
...@@ -17,6 +19,8 @@ import chat.rocket.android.chatdetails.presentation.ChatDetailsView ...@@ -17,6 +19,8 @@ import chat.rocket.android.chatdetails.presentation.ChatDetailsView
import chat.rocket.android.chatdetails.viewmodel.ChatDetailsViewModel import chat.rocket.android.chatdetails.viewmodel.ChatDetailsViewModel
import chat.rocket.android.chatdetails.viewmodel.ChatDetailsViewModelFactory import chat.rocket.android.chatdetails.viewmodel.ChatDetailsViewModelFactory
import chat.rocket.android.chatroom.ui.ChatRoomActivity 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.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui import chat.rocket.android.util.extensions.ui
...@@ -31,21 +35,28 @@ fun newInstance( ...@@ -31,21 +35,28 @@ fun newInstance(
chatRoomId: String, chatRoomId: String,
chatRoomType: String, chatRoomType: String,
isSubscribed: Boolean, isSubscribed: Boolean,
isFavorite: Boolean,
disableMenu: Boolean disableMenu: Boolean
): ChatDetailsFragment = ChatDetailsFragment().apply { ): ChatDetailsFragment {
arguments = Bundle(4).apply { return ChatDetailsFragment().apply {
arguments = Bundle(5).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
putString(BUNDLE_CHAT_ROOM_TYPE, chatRoomType) putString(BUNDLE_CHAT_ROOM_TYPE, chatRoomType)
putBoolean(BUNDLE_IS_SUBSCRIBED, isSubscribed) putBoolean(BUNDLE_IS_SUBSCRIBED, isSubscribed)
putBoolean(BUNDLE_IS_FAVORITE, isFavorite)
putBoolean(BUNDLE_DISABLE_MENU, disableMenu) putBoolean(BUNDLE_DISABLE_MENU, disableMenu)
} }
}
} }
internal const val TAG_CHAT_DETAILS_FRAGMENT = "ChatDetailsFragment" internal const val TAG_CHAT_DETAILS_FRAGMENT = "ChatDetailsFragment"
internal const val MENU_ACTION_FAVORITE_REMOVE_FAVORITE = 1
internal const val MENU_ACTION_VIDEO_CALL = 2
private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID" private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID"
private const val BUNDLE_CHAT_ROOM_TYPE = "BUNDLE_CHAT_ROOM_TYPE" private const val BUNDLE_CHAT_ROOM_TYPE = "BUNDLE_CHAT_ROOM_TYPE"
private const val BUNDLE_IS_SUBSCRIBED = "BUNDLE_IS_SUBSCRIBED" private const val BUNDLE_IS_SUBSCRIBED = "BUNDLE_IS_SUBSCRIBED"
private const val BUNDLE_IS_FAVORITE = "BUNDLE_IS_FAVORITE"
private const val BUNDLE_DISABLE_MENU = "BUNDLE_DISABLE_MENU" private const val BUNDLE_DISABLE_MENU = "BUNDLE_DISABLE_MENU"
class ChatDetailsFragment : Fragment(), ChatDetailsView { class ChatDetailsFragment : Fragment(), ChatDetailsView {
...@@ -53,12 +64,17 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { ...@@ -53,12 +64,17 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView {
lateinit var presenter: ChatDetailsPresenter lateinit var presenter: ChatDetailsPresenter
@Inject @Inject
lateinit var factory: ChatDetailsViewModelFactory lateinit var factory: ChatDetailsViewModelFactory
@Inject
lateinit var serverUrl: CurrentServerRepository
@Inject
lateinit var settings: GetSettingsInteractor
private var adapter: ChatDetailsAdapter? = null private var adapter: ChatDetailsAdapter? = null
private lateinit var viewModel: ChatDetailsViewModel private lateinit var viewModel: ChatDetailsViewModel
private var chatRoomId: String? = null internal lateinit var chatRoomId: String
private var chatRoomType: String? = null internal lateinit var chatRoomType: String
private var isSubscribed: Boolean = true private var isSubscribed: Boolean = true
internal var isFavorite: Boolean = false
private var disableMenu: Boolean = false private var disableMenu: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
...@@ -69,8 +85,11 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { ...@@ -69,8 +85,11 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID) chatRoomId = getString(BUNDLE_CHAT_ROOM_ID)
chatRoomType = getString(BUNDLE_CHAT_ROOM_TYPE) chatRoomType = getString(BUNDLE_CHAT_ROOM_TYPE)
isSubscribed = getBoolean(BUNDLE_IS_SUBSCRIBED) isSubscribed = getBoolean(BUNDLE_IS_SUBSCRIBED)
isFavorite = getBoolean(BUNDLE_IS_FAVORITE)
disableMenu = getBoolean(BUNDLE_DISABLE_MENU) disableMenu = getBoolean(BUNDLE_DISABLE_MENU)
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
setHasOptionsMenu(true)
} }
override fun onCreateView( override fun onCreateView(
...@@ -87,11 +106,27 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { ...@@ -87,11 +106,27 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView {
getDetails() getDetails()
} }
override fun onPrepareOptionsMenu(menu: Menu) {
menu.clear()
setupMenu(menu)
super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
setOnMenuItemClickListener(item)
return true
}
override fun showFavoriteIcon(isFavorite: Boolean) {
this.isFavorite = isFavorite
activity?.invalidateOptionsMenu()
}
override fun displayDetails(room: ChatDetails) { override fun displayDetails(room: ChatDetails) {
ui { ui {
val text = room.name val text = room.name
name.text = text name.text = text
bindImage(chatRoomType!!) bindImage(chatRoomType)
content_topic.text = content_topic.text =
if (room.topic.isNullOrEmpty()) getString(R.string.msg_no_topic) else room.topic if (room.topic.isNullOrEmpty()) getString(R.string.msg_no_topic) else room.topic
content_announcement.text = content_announcement.text =
...@@ -203,8 +238,8 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { ...@@ -203,8 +238,8 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView {
private fun setupToolbar() { private fun setupToolbar() {
with((activity as ChatRoomActivity)) { with((activity as ChatRoomActivity)) {
hideToolbarChatRoomIcon() hideExpandMoreForToolbar()
showToolbarTitle(getString(R.string.title_channel_details)) setupToolbarTitle(getString(R.string.title_channel_details))
} }
} }
} }
\ No newline at end of file
package chat.rocket.android.chatdetails.ui
import android.view.Menu
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.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
internal fun ChatDetailsFragment.setupMenu(menu: Menu) {
serverUrl.get()?.let {
with(settings.get(it)) {
if (isJitsiEnabled()) {
if (roomTypeOf(chatRoomType) !is RoomType.DirectMessage && !isJitsiEnabledForChannels()) {
return
}
menu.add(
Menu.NONE,
MENU_ACTION_VIDEO_CALL,
Menu.NONE,
R.string.msg_video_call
).setIcon(R.drawable.ic_video_24dp).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
}
}
if (isFavorite) {
menu.add(
Menu.NONE,
MENU_ACTION_FAVORITE_REMOVE_FAVORITE,
Menu.NONE,
R.string.action_remove_favorite
).setIcon(R.drawable.ic_star_yellow_24dp).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
} else {
menu.add(
Menu.NONE,
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)
}
}
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) {
presenter.toVideoConference(chatRoomId, chatRoomType)
}
}
...@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
@Module @Module
class MessageInfoFragmentModule { class MessageInfoFragmentModule {
......
...@@ -24,7 +24,6 @@ fun Context.messageInformationIntent(messageId: String): Intent { ...@@ -24,7 +24,6 @@ fun Context.messageInformationIntent(messageId: String): Intent {
private const val INTENT_MESSAGE_ID = "message_id" private const val INTENT_MESSAGE_ID = "message_id"
class MessageInfoActivity : AppCompatActivity(), HasSupportFragmentInjector { class MessageInfoActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject @Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment> lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
...@@ -45,7 +44,7 @@ class MessageInfoActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -45,7 +44,7 @@ class MessageInfoActivity : AppCompatActivity(), HasSupportFragmentInjector {
} }
private fun setupToolbar() { private fun setupToolbar() {
text_room_name.textContent = getString(R.string.message_information_title) text_toolbar_title.textContent = getString(R.string.message_information_title)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false) supportActionBar?.setDisplayShowTitleEnabled(false)
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
......
...@@ -44,8 +44,14 @@ class ChatRoomAdapter( ...@@ -44,8 +44,14 @@ class ChatRoomAdapter(
MessageViewHolder( MessageViewHolder(
view, view,
actionsListener, actionsListener,
reactionListener reactionListener,
) { userId -> navigator?.toUserDetails(userId) } { userId -> navigator?.toUserDetails(userId) },
{
if (roomId != null && roomType != null) {
navigator?.toVideoConference(roomId, roomType)
}
}
)
} }
BaseUiModel.ViewType.URL_PREVIEW -> { BaseUiModel.ViewType.URL_PREVIEW -> {
val view = parent.inflate(R.layout.message_url_preview) val view = parent.inflate(R.layout.message_url_preview)
...@@ -101,8 +107,9 @@ class ChatRoomAdapter( ...@@ -101,8 +107,9 @@ class ChatRoomAdapter(
when (holder) { when (holder) {
is MessageViewHolder -> is MessageViewHolder ->
holder.bind(dataSet[position] as MessageUiModel) holder.bind(dataSet[position] as MessageUiModel)
is UrlPreviewViewHolder -> is UrlPreviewViewHolder -> {
holder.bind(dataSet[position] as UrlPreviewUiModel) holder.bind(dataSet[position] as UrlPreviewUiModel)
}
is MessageReplyViewHolder -> is MessageReplyViewHolder ->
holder.bind(dataSet[position] as MessageReplyUiModel) holder.bind(dataSet[position] as MessageReplyUiModel)
is AttachmentViewHolder -> is AttachmentViewHolder ->
......
...@@ -13,8 +13,8 @@ import chat.rocket.android.emoji.Emoji ...@@ -13,8 +13,8 @@ import chat.rocket.android.emoji.Emoji
import chat.rocket.android.emoji.EmojiKeyboardListener import chat.rocket.android.emoji.EmojiKeyboardListener
import chat.rocket.android.emoji.EmojiPickerPopup import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_reaction.view.* import kotlinx.android.synthetic.main.item_reaction.view.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject import javax.inject.Inject
...@@ -103,9 +103,9 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -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. // The view at index 1 corresponds to the one to display custom emojis which are images.
view_flipper_reaction.displayedChild = 1 view_flipper_reaction.displayedChild = 1
val glideRequest = if (reaction.url!!.endsWith("gif", true)) { val glideRequest = if (reaction.url!!.endsWith("gif", true)) {
GlideApp.with(context).asGif() Glide.with(context).asGif()
} else { } else {
GlideApp.with(context).asBitmap() Glide.with(context).asBitmap()
} }
glideRequest.load(reaction.url).into(image_emoji) glideRequest.load(reaction.url).into(image_emoji)
......
...@@ -10,6 +10,7 @@ import androidx.core.view.isVisible ...@@ -10,6 +10,7 @@ import androidx.core.view.isVisible
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.MessageUiModel import chat.rocket.android.chatroom.uimodel.MessageUiModel
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.core.model.MessageType
import chat.rocket.core.model.isSystemMessage import chat.rocket.core.model.isSystemMessage
import com.bumptech.glide.load.resource.gif.GifDrawable import com.bumptech.glide.load.resource.gif.GifDrawable
import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.avatar.view.*
...@@ -19,7 +20,8 @@ class MessageViewHolder( ...@@ -19,7 +20,8 @@ class MessageViewHolder(
itemView: View, itemView: View,
listener: ActionsListener, listener: ActionsListener,
reactionListener: EmojiReactionListener? = null, 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 { ) : BaseViewHolder<MessageUiModel>(itemView, listener, reactionListener), Drawable.Callback {
init { init {
...@@ -51,6 +53,9 @@ class MessageViewHolder( ...@@ -51,6 +53,9 @@ class MessageViewHolder(
text_content.text_content.text = data.content 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) image_avatar.setImageURI(data.avatar)
text_content.setTextColor(if (data.isTemporary) Color.GRAY else Color.BLACK) text_content.setTextColor(if (data.isTemporary) Color.GRAY else Color.BLACK)
......
...@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -7,7 +7,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
@Module @Module
class ChatRoomModule { class ChatRoomModule {
......
package chat.rocket.android.chatroom.presentation package chat.rocket.android.chatroom.presentation
import android.os.Build
import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatdetails.ui.TAG_CHAT_DETAILS_FRAGMENT import chat.rocket.android.chatdetails.ui.TAG_CHAT_DETAILS_FRAGMENT
import chat.rocket.android.chatinformation.ui.messageInformationIntent import chat.rocket.android.chatinformation.ui.messageInformationIntent
...@@ -13,6 +15,7 @@ import chat.rocket.android.pinnedmessages.ui.TAG_PINNED_MESSAGES_FRAGMENT ...@@ -13,6 +15,7 @@ import chat.rocket.android.pinnedmessages.ui.TAG_PINNED_MESSAGES_FRAGMENT
import chat.rocket.android.server.ui.changeServerIntent import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.userdetails.ui.TAG_USER_DETAILS_FRAGMENT import chat.rocket.android.userdetails.ui.TAG_USER_DETAILS_FRAGMENT
import chat.rocket.android.util.extensions.addFragmentBackStack import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.videoconference.ui.videoConferenceIntent
class ChatRoomNavigator(internal val activity: ChatRoomActivity) { class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
...@@ -22,6 +25,19 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) { ...@@ -22,6 +25,19 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
} }
} }
fun toVideoConference(chatRoomId: String, chatRoomType: String) {
// TODO: Jitsi isn't working with Android M- version. We need to remove the condition bellow after it's solved. (https://github.com/jitsi/jitsi-meet/pull/3967)/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.startActivity(activity.videoConferenceIntent(chatRoomId, chatRoomType))
} else {
Toast.makeText(
activity,
"Sorry, unable to open the video conference due to device configuration",
Toast.LENGTH_SHORT
).show()
}
}
fun toChatRoom( fun toChatRoom(
chatRoomId: String, chatRoomId: String,
chatRoomName: String, chatRoomName: String,
...@@ -51,6 +67,7 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) { ...@@ -51,6 +67,7 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
chatRoomId: String, chatRoomId: String,
chatRoomType: String, chatRoomType: String,
isChatRoomSubscribed: Boolean, isChatRoomSubscribed: Boolean,
isChatRoomFavorite: Boolean,
isMenuDisabled: Boolean isMenuDisabled: Boolean
) { ) {
activity.addFragmentBackStack(TAG_CHAT_DETAILS_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(TAG_CHAT_DETAILS_FRAGMENT, R.id.fragment_container) {
...@@ -58,6 +75,7 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) { ...@@ -58,6 +75,7 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
chatRoomId, chatRoomId,
chatRoomType, chatRoomType,
isChatRoomSubscribed, isChatRoomSubscribed,
isChatRoomFavorite,
isMenuDisabled isMenuDisabled
) )
} }
......
...@@ -53,7 +53,6 @@ import chat.rocket.core.internal.realtime.unsubscribe ...@@ -53,7 +53,6 @@ import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.rest.chatRoomRoles import chat.rocket.core.internal.rest.chatRoomRoles
import chat.rocket.core.internal.rest.commands import chat.rocket.core.internal.rest.commands
import chat.rocket.core.internal.rest.deleteMessage 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.getMembers
import chat.rocket.core.internal.rest.history import chat.rocket.core.internal.rest.history
import chat.rocket.core.internal.rest.joinChat import chat.rocket.core.internal.rest.joinChat
...@@ -76,12 +75,11 @@ import chat.rocket.core.model.ChatRoomRole ...@@ -76,12 +75,11 @@ import chat.rocket.core.model.ChatRoomRole
import chat.rocket.core.model.Command import chat.rocket.core.model.Command
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Room import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.DefaultDispatcher import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.launch
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.withContext
import kotlinx.coroutines.experimental.withContext
import org.threeten.bp.Instant import org.threeten.bp.Instant
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
...@@ -134,7 +132,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -134,7 +132,7 @@ class ChatRoomPresenter @Inject constructor(
draftKey = "${currentServer}_${LocalRepository.DRAFT_KEY}$roomId" draftKey = "${currentServer}_${LocalRepository.DRAFT_KEY}$roomId"
chatRoomId = roomId chatRoomId = roomId
chatRoomType = roomType chatRoomType = roomType
launch(CommonPool + strategy.jobs) { GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
try { try {
chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) { chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) {
client.chatRoomRoles(roomType = roomTypeOf(roomType), roomName = roomName) client.chatRoomRoles(roomType = roomTypeOf(roomType), roomName = roomName)
...@@ -178,7 +176,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -178,7 +176,7 @@ class ChatRoomPresenter @Inject constructor(
} }
private suspend fun subscribeRoomChanges() { private suspend fun subscribeRoomChanges() {
withContext(CommonPool + strategy.jobs) { withContext(Dispatchers.IO + strategy.jobs) {
chatRoomId?.let { chatRoomId?.let {
manager.addRoomChannel(it, roomChangesChannel) manager.addRoomChannel(it, roomChangesChannel)
for (room in roomChangesChannel) { for (room in roomChangesChannel) {
...@@ -405,7 +403,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -405,7 +403,7 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
val fileName = uriInteractor.getFileName(uri) ?: uri.toString() val fileName = uriInteractor.getFileName(uri) ?: uri.toString()
if (fileName.isEmpty()) { if (fileName.isEmpty()) {
view.showInvalidFileMessage() view.showInvalidFileMessage()
...@@ -443,7 +441,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -443,7 +441,7 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
val fileName = uriInteractor.getFileName(uri) ?: uri.toString() val fileName = uriInteractor.getFileName(uri) ?: uri.toString()
val fileSize = uriInteractor.getFileSize(uri) val fileSize = uriInteractor.getFileSize(uri)
val maxFileSizeAllowed = settings.uploadMaxFileSize() val maxFileSizeAllowed = settings.uploadMaxFileSize()
...@@ -484,7 +482,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -484,7 +482,7 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
val fileName = UUID.randomUUID().toString() + ".png" val fileName = UUID.randomUUID().toString() + ".png"
val fileSize = byteArray.size val fileSize = byteArray.size
val mimeType = "image/png" val mimeType = "image/png"
...@@ -522,7 +520,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -522,7 +520,7 @@ class ChatRoomPresenter @Inject constructor(
} }
fun sendTyping() { fun sendTyping() {
launch(CommonPool + strategy.jobs) { GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
if (chatRoomId != null && currentLoggedUsername != null) { if (chatRoomId != null && currentLoggedUsername != null) {
client.setTypingStatus(chatRoomId.toString(), currentLoggedUsername, true) client.setTypingStatus(chatRoomId.toString(), currentLoggedUsername, true)
} }
...@@ -530,7 +528,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -530,7 +528,7 @@ class ChatRoomPresenter @Inject constructor(
} }
fun sendNotTyping() { fun sendNotTyping() {
launch(CommonPool + strategy.jobs) { GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
if (chatRoomId != null && currentLoggedUsername != null) { if (chatRoomId != null && currentLoggedUsername != null) {
client.setTypingStatus(chatRoomId.toString(), currentLoggedUsername, false) client.setTypingStatus(chatRoomId.toString(), currentLoggedUsername, false)
} }
...@@ -552,11 +550,11 @@ class ChatRoomPresenter @Inject constructor( ...@@ -552,11 +550,11 @@ class ChatRoomPresenter @Inject constructor(
Timber.d("Subscribing to Status changes") Timber.d("Subscribing to Status changes")
lastState = manager.state lastState = manager.state
manager.addStatusChannel(stateChannel) manager.addStatusChannel(stateChannel)
launch(CommonPool + strategy.jobs) { GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
for (state in stateChannel) { for (state in stateChannel) {
Timber.d("Got new state: $state - last: $lastState") Timber.d("Got new state: $state - last: $lastState")
if (state != lastState) { if (state != lastState) {
launch(UI) { launch(Dispatchers.Main) {
view.showConnectionState(state) view.showConnectionState(state)
} }
...@@ -573,7 +571,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -573,7 +571,7 @@ class ChatRoomPresenter @Inject constructor(
private fun subscribeMessages(roomId: String) { private fun subscribeMessages(roomId: String) {
manager.subscribeRoomMessages(roomId, messagesChannel) manager.subscribeRoomMessages(roomId, messagesChannel)
launch(CommonPool + strategy.jobs) { GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
for (message in messagesChannel) { for (message in messagesChannel) {
Timber.d("New message for room ${message.roomId}") Timber.d("New message for room ${message.roomId}")
updateMessage(message) updateMessage(message)
...@@ -582,7 +580,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -582,7 +580,7 @@ class ChatRoomPresenter @Inject constructor(
} }
private fun loadMissingMessages() { private fun loadMissingMessages() {
launch(parent = strategy.jobs) { GlobalScope.launch(strategy.jobs) {
chatRoomId?.let { chatRoomId -> chatRoomId?.let { chatRoomId ->
val roomType = roomTypeOf(chatRoomType) val roomType = roomTypeOf(chatRoomType)
val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId) val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
...@@ -891,32 +889,14 @@ class ChatRoomPresenter @Inject constructor( ...@@ -891,32 +889,14 @@ class ChatRoomPresenter @Inject constructor(
} }
} }
fun toggleFavoriteChatRoom(roomId: String, isFavorite: Boolean) {
launchUI(strategy) {
try {
// Note that if it is favorite then the user wants to unfavorite - and vice versa.
retryIO("favorite($roomId, $isFavorite)") {
client.favorite(roomId, !isFavorite)
}
view.showFavoriteIcon(!isFavorite)
} catch (e: RocketChatException) {
Timber.e(e, "Error while trying to favorite/unfavorite chat room.")
e.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
}
}
fun toChatDetails( fun toChatDetails(
chatRoomId: String, chatRoomId: String,
chatRoomType: String, chatRoomType: String,
isSubscribed: Boolean, isSubscribed: Boolean,
isFavorite: Boolean,
isMenuDisabled: Boolean isMenuDisabled: Boolean
) { ) {
navigator.toChatDetails(chatRoomId, chatRoomType, isSubscribed, isMenuDisabled) navigator.toChatDetails(chatRoomId, chatRoomType, isSubscribed, isFavorite, isMenuDisabled)
} }
fun loadChatRoomsSuggestions() { fun loadChatRoomsSuggestions() {
...@@ -944,7 +924,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -944,7 +924,7 @@ class ChatRoomPresenter @Inject constructor(
} }
// TODO: move this to new interactor or FetchChatRoomsInteractor? // 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)") { retryDB("getRoom($roomId)") {
dbManager.chatRoomDao().getSync(roomId)?.let { dbManager.chatRoomDao().getSync(roomId)?.let {
with(it.chatRoom) { with(it.chatRoom) {
...@@ -982,7 +962,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -982,7 +962,7 @@ class ChatRoomPresenter @Inject constructor(
} }
// TODO: move this to new interactor or FetchChatRoomsInteractor? // 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()") { retryDB("getAllSync()") {
dbManager.chatRoomDao().getAllSync().filter { dbManager.chatRoomDao().getAllSync().filter {
if (name == null) { if (name == null) {
...@@ -1212,7 +1192,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -1212,7 +1192,7 @@ class ChatRoomPresenter @Inject constructor(
} }
private fun subscribeTypingStatus() { private fun subscribeTypingStatus() {
launch(CommonPool + strategy.jobs) { GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
client.subscribeTypingStatus(chatRoomId.toString()) { _, id -> client.subscribeTypingStatus(chatRoomId.toString()) { _, id ->
typingStatusSubscriptionId = id typingStatusSubscriptionId = id
} }
......
...@@ -13,13 +13,6 @@ import chat.rocket.core.model.ChatRoom ...@@ -13,13 +13,6 @@ import chat.rocket.core.model.ChatRoom
interface ChatRoomView : LoadingView, MessageView { interface ChatRoomView : LoadingView, MessageView {
/**
* Shows the Favorite/Unfavorite chat room icon.
*
* @param isFavorite Shows the favorite icon if true, otherwise shows the unfavorite icon.
*/
fun showFavoriteIcon(isFavorite: Boolean)
/** /**
* Shows the chat room messages. * Shows the chat room messages.
* *
......
...@@ -10,8 +10,9 @@ import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository ...@@ -10,8 +10,9 @@ import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.core.internal.rest.sendMessage import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -33,7 +34,7 @@ class MessageService : JobService() { ...@@ -33,7 +34,7 @@ class MessageService : JobService() {
} }
override fun onStartJob(params: JobParameters?): Boolean { override fun onStartJob(params: JobParameters?): Boolean {
launch(CommonPool) { GlobalScope.launch(Dispatchers.IO) {
getAccountsInteractor.get().forEach { account -> getAccountsInteractor.get().forEach { account ->
retrySendingMessages(params, account.serverUrl) retrySendingMessages(params, account.serverUrl)
} }
...@@ -44,7 +45,8 @@ class MessageService : JobService() { ...@@ -44,7 +45,8 @@ class MessageService : JobService() {
private suspend fun retrySendingMessages(params: JobParameters?, serverUrl: String) { private suspend fun retrySendingMessages(params: JobParameters?, serverUrl: String) {
val dbManager = dbFactory.create(serverUrl) val dbManager = dbFactory.create(serverUrl)
val messageRepository = DatabaseMessagesRepository(dbManager, DatabaseMessageMapper(dbManager)) val messageRepository =
DatabaseMessagesRepository(dbManager, DatabaseMessageMapper(dbManager))
val temporaryMessages = messageRepository.getAllUnsent() val temporaryMessages = messageRepository.getAllUnsent()
.sortedWith(compareBy(Message::timestamp)) .sortedWith(compareBy(Message::timestamp))
if (temporaryMessages.isNotEmpty()) { if (temporaryMessages.isNotEmpty()) {
......
...@@ -4,16 +4,15 @@ import DrawableHelper ...@@ -4,16 +4,15 @@ import DrawableHelper
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extensions.addFragment import chat.rocket.android.util.extensions.addFragment
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
...@@ -56,7 +55,6 @@ private const val INTENT_CHAT_ROOM_MESSAGE = "chat_room_message" ...@@ -56,7 +55,6 @@ private const val INTENT_CHAT_ROOM_MESSAGE = "chat_room_message"
class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject @Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment> lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
// TODO - workaround for now... We will move to a single activity // TODO - workaround for now... We will move to a single activity
@Inject @Inject
lateinit var serverInteractor: GetCurrentServerInteractor lateinit var serverInteractor: GetCurrentServerInteractor
...@@ -136,31 +134,21 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -136,31 +134,21 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
toolbar.setNavigationOnClickListener { finishActivity() } toolbar.setNavigationOnClickListener { finishActivity() }
} }
fun showToolbarTitle(title: String) { fun setupToolbarTitle(title: String) {
text_room_name.textContent = title text_toolbar_title.textContent = title
} }
fun showToolbarChatRoomIcon(chatRoomType: String) { fun setupExpandMoreForToolbar(listener: (View) -> Unit) {
val drawable = when (roomTypeOf(chatRoomType)) { DrawableHelper.compoundRightDrawable(
is RoomType.Channel -> { text_toolbar_title,
DrawableHelper.getDrawableFromId(R.drawable.ic_hashtag_black_12dp, this) DrawableHelper.getDrawableFromId(R.drawable.ic_chatroom_toolbar_expand_more_20dp, this)
} )
is RoomType.PrivateGroup -> { text_toolbar_title.setOnClickListener { listener(it) }
DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_12_dp, this)
}
else -> null
}
drawable?.let {
val wrappedDrawable = DrawableHelper.wrapDrawable(it)
val mutableDrawable = wrappedDrawable.mutate()
DrawableHelper.tintDrawable(mutableDrawable, this, R.color.colorWhite)
DrawableHelper.compoundDrawable(text_room_name, mutableDrawable)
}
} }
fun hideToolbarChatRoomIcon() { fun hideExpandMoreForToolbar() {
text_room_name.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) text_toolbar_title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
text_toolbar_title.setOnClickListener(null)
} }
private fun finishActivity() { private fun finishActivity() {
......
package chat.rocket.android.chatroom.ui package chat.rocket.android.chatroom.ui
import android.app.Activity import android.app.Activity
import androidx.appcompat.app.AlertDialog
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
...@@ -15,7 +14,6 @@ import android.text.SpannableStringBuilder ...@@ -15,7 +14,6 @@ import android.text.SpannableStringBuilder
import android.view.KeyEvent import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
...@@ -23,6 +21,7 @@ import android.widget.FrameLayout ...@@ -23,6 +21,7 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.text.bold import androidx.core.text.bold
import androidx.core.view.isVisible import androidx.core.view.isVisible
...@@ -60,7 +59,6 @@ import chat.rocket.android.emoji.EmojiKeyboardPopup ...@@ -60,7 +59,6 @@ import chat.rocket.android.emoji.EmojiKeyboardPopup
import chat.rocket.android.emoji.EmojiParser import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiPickerPopup import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.emoji.internal.isCustom import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.ImageHelper import chat.rocket.android.helper.ImageHelper
...@@ -82,6 +80,7 @@ import chat.rocket.android.util.extensions.ui ...@@ -82,6 +80,7 @@ import chat.rocket.android.util.extensions.ui
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.socket.model.State import chat.rocket.core.internal.realtime.socket.model.State
import com.bumptech.glide.Glide
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
...@@ -140,9 +139,6 @@ private const val BUNDLE_CHAT_ROOM_IS_CREATOR = "chat_room_is_creator" ...@@ -140,9 +139,6 @@ private const val BUNDLE_CHAT_ROOM_IS_CREATOR = "chat_room_is_creator"
private const val BUNDLE_CHAT_ROOM_IS_FAVORITE = "chat_room_is_favorite" private const val BUNDLE_CHAT_ROOM_IS_FAVORITE = "chat_room_is_favorite"
private const val BUNDLE_CHAT_ROOM_MESSAGE = "chat_room_message" private const val BUNDLE_CHAT_ROOM_MESSAGE = "chat_room_message"
internal const val MENU_ACTION_FAVORITE_UNFAVOURITE_CHAT = 1
internal const val MENU_ACTION_SHOW_DETAILS = 2
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener, class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener,
ChatRoomAdapter.OnActionSelected, Drawable.Callback { ChatRoomAdapter.OnActionSelected, Drawable.Callback {
@Inject @Inject
...@@ -159,7 +155,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -159,7 +155,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
internal lateinit var chatRoomType: String internal lateinit var chatRoomType: String
private var newMessageCount: Int = 0 private var newMessageCount: Int = 0
private var chatRoomMessage: String? = null private var chatRoomMessage: String? = null
internal var isSubscribed: Boolean = true private var isSubscribed: Boolean = true
private var isReadOnly: Boolean = false private var isReadOnly: Boolean = false
private var isCreator: Boolean = false private var isCreator: Boolean = false
internal var isFavorite: Boolean = false internal var isFavorite: Boolean = false
...@@ -169,7 +165,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -169,7 +165,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private lateinit var actionSnackbar: ActionSnackbar private lateinit var actionSnackbar: ActionSnackbar
internal var citation: String? = null internal var citation: String? = null
private var editingMessageId: String? = null private var editingMessageId: String? = null
internal var disableMenu: Boolean = false private var disableMenu: Boolean = false
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
private var playComposeMessageButtonsAnimation = true private var playComposeMessageButtonsAnimation = true
...@@ -197,7 +193,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -197,7 +193,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private var verticalScrollOffset = AtomicInteger(0) private var verticalScrollOffset = AtomicInteger(0)
private val dialogView by lazy { View.inflate(context, R.layout.file_attachments_dialog, null) } private val dialogView by lazy { View.inflate(context, R.layout.file_attachments_dialog, null) }
internal val alertDialog by lazy { activity?.let { AlertDialog.Builder(it).setView(dialogView).create() } } internal val alertDialog by lazy {
activity?.let {
AlertDialog.Builder(it).setView(dialogView).create()
}
}
internal val imagePreview by lazy { dialogView.findViewById<ImageView>(R.id.image_preview) } internal val imagePreview by lazy { dialogView.findViewById<ImageView>(R.id.image_preview) }
internal val sendButton by lazy { dialogView.findViewById<android.widget.Button>(R.id.button_send) } internal val sendButton by lazy { dialogView.findViewById<android.widget.Button>(R.id.button_send) }
internal val cancelButton by lazy { dialogView.findViewById<android.widget.Button>(R.id.button_cancel) } internal val cancelButton by lazy { dialogView.findViewById<android.widget.Button>(R.id.button_cancel) }
...@@ -280,9 +280,17 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -280,9 +280,17 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
isCreator = getBoolean(BUNDLE_CHAT_ROOM_IS_CREATOR) isCreator = getBoolean(BUNDLE_CHAT_ROOM_IS_CREATOR)
isFavorite = getBoolean(BUNDLE_CHAT_ROOM_IS_FAVORITE) isFavorite = getBoolean(BUNDLE_CHAT_ROOM_IS_FAVORITE)
chatRoomMessage = getString(BUNDLE_CHAT_ROOM_MESSAGE) chatRoomMessage = getString(BUNDLE_CHAT_ROOM_MESSAGE)
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } }
?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
adapter = ChatRoomAdapter(chatRoomId, chatRoomType, chatRoomName, this, reactionListener = this, navigator = navigator) adapter = ChatRoomAdapter(
chatRoomId,
chatRoomType,
chatRoomName,
this,
reactionListener = this,
navigator = navigator
)
} }
override fun onCreateView( override fun onCreateView(
...@@ -302,8 +310,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -302,8 +310,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
setupSuggestionsView() setupSuggestionsView()
setupActionSnackbar() setupActionSnackbar()
with(activity as ChatRoomActivity) { with(activity as ChatRoomActivity) {
showToolbarTitle(chatRoomName) setupToolbarTitle(chatRoomName)
showToolbarChatRoomIcon(chatRoomType) setupExpandMoreForToolbar {
presenter.toChatDetails(
chatRoomId,
chatRoomType,
isSubscribed,
isFavorite,
disableMenu
)
}
} }
getDraftMessage() getDraftMessage()
subscribeComposeTextMessage() subscribeComposeTextMessage()
...@@ -359,15 +375,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -359,15 +375,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
super.onPrepareOptionsMenu(menu) super.onPrepareOptionsMenu(menu)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean {
setOnMenuItemClickListener(item)
return true
}
override fun showFavoriteIcon(isFavorite: Boolean) {
this.isFavorite = isFavorite
activity?.invalidateOptionsMenu()
}
override fun showMessages(dataSet: List<BaseUiModel<*>>, clearDataSet: Boolean) { override fun showMessages(dataSet: List<BaseUiModel<*>>, clearDataSet: Boolean) {
ui { ui {
...@@ -534,9 +541,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -534,9 +541,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
text_count.text = "99+" text_count.text = "99+"
} }
text_count.isVisible = true text_count.isVisible = true
} } else if (!button_fab.isVisible) {
else if (!button_fab.isVisible) {
recycler_view.scrollToPosition(0) recycler_view.scrollToPosition(0)
} }
verticalScrollOffset.set(0) verticalScrollOffset.set(0)
...@@ -567,7 +572,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -567,7 +572,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
} }
override fun showReplyingAction(username: String, replyMarkdown: String, quotedMessage: String) { override fun showReplyingAction(
username: String,
replyMarkdown: String,
quotedMessage: String
) {
ui { ui {
citation = replyMarkdown citation = replyMarkdown
actionSnackbar.title = username actionSnackbar.title = username
...@@ -650,7 +659,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -650,7 +659,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if (cursorPosition > -1) { if (cursorPosition > -1) {
context?.let { context?.let {
val offset = if (!emoji.isCustom()) emoji.unicode.length else emoji.shortname.length val offset = if (!emoji.isCustom()) emoji.unicode.length else emoji.shortname.length
val parsed = if (emoji.isCustom()) emoji.shortname else EmojiParser.parse(it, emoji.shortname) val parsed = if (emoji.isCustom()) emoji.shortname else EmojiParser.parse(
it,
emoji.shortname
)
text_message.text?.insert(cursorPosition, parsed) text_message.text?.insert(cursorPosition, parsed)
text_message.setSelection(cursorPosition + offset) text_message.setSelection(cursorPosition + offset)
} }
...@@ -674,8 +686,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -674,8 +686,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.react(messageId, emoji.shortname) presenter.react(messageId, emoji.shortname)
} }
override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List<String>) { override fun onReactionLongClicked(
val layout = LayoutInflater.from(requireContext()).inflate(R.layout.reaction_praises_list_item, null) shortname: String,
isCustom: Boolean,
url: String?,
usernames: List<String>
) {
val layout =
LayoutInflater.from(requireContext()).inflate(R.layout.reaction_praises_list_item, null)
val dialog = AlertDialog.Builder(requireContext()) val dialog = AlertDialog.Builder(requireContext())
.setView(layout) .setView(layout)
.setCancelable(true) .setCancelable(true)
...@@ -684,9 +702,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -684,9 +702,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
view_flipper.displayedChild = if (isCustom) 1 else 0 view_flipper.displayedChild = if (isCustom) 1 else 0
if (isCustom && url != null) { if (isCustom && url != null) {
val glideRequest = if (url.endsWith("gif", true)) { val glideRequest = if (url.endsWith("gif", true)) {
GlideApp.with(requireContext()).asGif() Glide.with(requireContext()).asGif()
} else { } else {
GlideApp.with(requireContext()).asBitmap() Glide.with(requireContext()).asBitmap()
} }
glideRequest.load(url).into(view_flipper.emoji_image_view) glideRequest.load(url).into(view_flipper.emoji_image_view)
...@@ -702,11 +720,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -702,11 +720,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
listing += if (index == usernames.size - 1) "|$username" else "$username, " listing += if (index == usernames.size - 1) "|$username" else "$username, "
} }
listing = listing.replace(", |", " ${requireContext().getString(R.string.msg_and)} ") listing =
listing.replace(", |", " ${requireContext().getString(R.string.msg_and)} ")
} }
text_view_usernames.text = requireContext().resources.getQuantityString( text_view_usernames.text = requireContext().resources.getQuantityString(
R.plurals.msg_reacted_with_, usernames.size, listing, shortname) R.plurals.msg_reacted_with_, usernames.size, listing, shortname
)
dialog.show() dialog.show()
} }
...@@ -840,7 +860,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -840,7 +860,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
true true
) )
emojiKeyboardPopup = EmojiKeyboardPopup(activity!!, activity!!.findViewById(R.id.fragment_container)) emojiKeyboardPopup =
EmojiKeyboardPopup(activity!!, activity!!.findViewById(R.id.fragment_container))
emojiKeyboardPopup.listener = this emojiKeyboardPopup.listener = this
...@@ -1054,7 +1075,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -1054,7 +1075,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupToolbar(toolbarTitle: String) { private fun setupToolbar(toolbarTitle: String) {
with(activity as ChatRoomActivity) { with(activity as ChatRoomActivity) {
this.clearLightStatusBar() this.clearLightStatusBar()
this.showToolbarTitle(toolbarTitle) this.setupToolbarTitle(toolbarTitle)
toolbar.isVisible = true toolbar.isVisible = true
} }
} }
...@@ -1141,8 +1162,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -1141,8 +1162,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
override fun reportMessage(id: String) { override fun reportMessage(id: String) {
presenter.reportMessage(messageId = id, presenter.reportMessage(
description = "This message was reported by a user from the Android app") messageId = id,
description = "This message was reported by a user from the Android app"
)
} }
fun openEmojiKeyboard() { fun openEmojiKeyboard() {
......
...@@ -4,10 +4,11 @@ import android.graphics.Bitmap ...@@ -4,10 +4,11 @@ import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import androidx.core.view.isVisible 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.getFileName
import chat.rocket.android.util.extensions.getMimeType import chat.rocket.android.util.extensions.getMimeType
import chat.rocket.common.util.ifNull 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.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
...@@ -25,18 +26,18 @@ fun ChatRoomFragment.showFileAttachmentDialog(uri: Uri) { ...@@ -25,18 +26,18 @@ fun ChatRoomFragment.showFileAttachmentDialog(uri: Uri) {
when { when {
mimeType.startsWith("image") -> { mimeType.startsWith("image") -> {
if (mimeType.contains("gif")) { if (mimeType.contains("gif")) {
GlideApp Glide
.with(context) .with(context)
.asGif() .asGif()
.load(uri) .load(uri)
.fitCenter() .apply(RequestOptions().fitCenter())
.into(imagePreview) .into(imagePreview)
} else { } else {
GlideApp Glide
.with(context) .with(context)
.asBitmap() .asBitmap()
.load(uri) .load(uri)
.fitCenter() .apply(RequestOptions().fitCenter())
.into(object : SimpleTarget<Bitmap>() { .into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady( override fun onResourceReady(
resource: Bitmap, resource: Bitmap,
......
...@@ -11,23 +11,6 @@ import chat.rocket.android.util.extension.onQueryTextListener ...@@ -11,23 +11,6 @@ import chat.rocket.android.util.extension.onQueryTextListener
internal fun ChatRoomFragment.setupMenu(menu: Menu) { internal fun ChatRoomFragment.setupMenu(menu: Menu) {
setupSearchMessageMenuItem(menu, requireContext()) setupSearchMessageMenuItem(menu, requireContext())
setupFavoriteMenuItem(menu)
setupDetailsMenuItem(menu)
}
internal fun ChatRoomFragment.setOnMenuItemClickListener(item: MenuItem) {
when (item.itemId) {
MENU_ACTION_FAVORITE_UNFAVOURITE_CHAT -> presenter.toggleFavoriteChatRoom(
chatRoomId,
isFavorite
)
MENU_ACTION_SHOW_DETAILS -> presenter.toChatDetails(
chatRoomId,
chatRoomType,
isSubscribed,
disableMenu
)
}
} }
private fun ChatRoomFragment.setupSearchMessageMenuItem(menu: Menu, context: Context) { private fun ChatRoomFragment.setupSearchMessageMenuItem(menu: Menu, context: Context) {
...@@ -37,27 +20,23 @@ private fun ChatRoomFragment.setupSearchMessageMenuItem(menu: Menu, context: Con ...@@ -37,27 +20,23 @@ private fun ChatRoomFragment.setupSearchMessageMenuItem(menu: Menu, context: Con
Menu.NONE, Menu.NONE,
R.string.title_search_message R.string.title_search_message
).setActionView(SearchView(context)) ).setActionView(SearchView(context))
.setIcon(R.drawable.ic_search_white_24dp) .setIcon(R.drawable.ic_chatroom_toolbar_magnifier_20dp)
.setShowAsActionFlags( .setShowAsActionFlags(
MenuItem.SHOW_AS_ACTION_IF_ROOM or MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW MenuItem.SHOW_AS_ACTION_IF_ROOM or MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
) )
.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { .setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean { override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
dismissEmojiKeyboard() dismissEmojiKeyboard()
removeFavoriteMenuItem(menu)
removeDetailMenuItem(menu)
return true return true
} }
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
dismissEmojiKeyboard() dismissEmojiKeyboard()
setupFavoriteMenuItem(menu)
setupDetailsMenuItem(menu)
return true return true
} }
}) })
(searchItem?.actionView as? SearchView)?.let { (searchItem.actionView as? SearchView)?.let {
// TODO: Check why we need to stylize the search text programmatically instead of by defining it in the styles.xml (ChatRoom.SearchView) // TODO: Check why we need to stylize the search text programmatically instead of by defining it in the styles.xml (ChatRoom.SearchView)
it.maxWidth = Integer.MAX_VALUE it.maxWidth = Integer.MAX_VALUE
stylizeSearchView(it, context) stylizeSearchView(it, context)
...@@ -78,6 +57,7 @@ private fun stylizeSearchView(searchView: SearchView, context: Context) { ...@@ -78,6 +57,7 @@ private fun stylizeSearchView(searchView: SearchView, context: Context) {
private fun ChatRoomFragment.setupSearchViewTextListener(searchView: SearchView) { private fun ChatRoomFragment.setupSearchViewTextListener(searchView: SearchView) {
searchView.onQueryTextListener { searchView.onQueryTextListener {
// TODO: We use isSearchTermQueried to avoid querying when the search view is expanded but the user doesn't start typing. Check for a native solution.
if (it.isEmpty() && isSearchTermQueried) { if (it.isEmpty() && isSearchTermQueried) {
presenter.loadMessages(chatRoomId, chatRoomType, clearDataSet = true) presenter.loadMessages(chatRoomId, chatRoomType, clearDataSet = true)
} else if (it.isNotEmpty()) { } else if (it.isNotEmpty()) {
...@@ -86,41 +66,3 @@ private fun ChatRoomFragment.setupSearchViewTextListener(searchView: SearchView) ...@@ -86,41 +66,3 @@ private fun ChatRoomFragment.setupSearchViewTextListener(searchView: SearchView)
} }
} }
} }
\ No newline at end of file
private fun ChatRoomFragment.setupFavoriteMenuItem(menu: Menu) {
if (isFavorite) {
menu.add(
Menu.NONE,
MENU_ACTION_FAVORITE_UNFAVOURITE_CHAT,
Menu.NONE,
R.string.title_unfavorite_chat
).setIcon(R.drawable.ic_star_yellow_24dp)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
} else {
menu.add(
Menu.NONE,
MENU_ACTION_FAVORITE_UNFAVOURITE_CHAT,
Menu.NONE,
R.string.title_favorite_chat
).setIcon(R.drawable.ic_star_border_white_24dp)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
}
private fun ChatRoomFragment.setupDetailsMenuItem(menu: Menu) {
menu.add(
Menu.NONE,
MENU_ACTION_SHOW_DETAILS,
Menu.NONE,
R.string.title_channel_details
).setIcon(R.drawable.ic_info_outline_white_24dp)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
private fun removeFavoriteMenuItem(menu: Menu) {
menu.removeItem(MENU_ACTION_FAVORITE_UNFAVOURITE_CHAT)
}
private fun removeDetailMenuItem(menu: Menu) {
menu.removeItem(MENU_ACTION_SHOW_DETAILS)
}
\ No newline at end of file
...@@ -26,7 +26,6 @@ data class MessageUiModel( ...@@ -26,7 +26,6 @@ data class MessageUiModel(
) : BaseMessageUiModel<Message> { ) : BaseMessageUiModel<Message> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.MESSAGE.viewType get() = BaseUiModel.ViewType.MESSAGE.viewType
override val layoutId: Int override val layoutId: Int
get() = R.layout.item_message get() = R.layout.item_message
} }
\ No newline at end of file
...@@ -44,8 +44,8 @@ import chat.rocket.core.model.attachment.Attachment ...@@ -44,8 +44,8 @@ import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.Field import chat.rocket.core.model.attachment.Field
import chat.rocket.core.model.isSystemMessage import chat.rocket.core.model.isSystemMessage
import chat.rocket.core.model.url.Url import chat.rocket.core.model.url.Url
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import okhttp3.HttpUrl import okhttp3.HttpUrl
import java.security.InvalidParameterException import java.security.InvalidParameterException
import java.util.* import java.util.*
...@@ -79,7 +79,7 @@ class UiModelMapper @Inject constructor( ...@@ -79,7 +79,7 @@ class UiModelMapper @Inject constructor(
message: Message, message: Message,
roomUiModel: RoomUiModel = RoomUiModel(roles = emptyList(), isBroadcast = true) roomUiModel: RoomUiModel = RoomUiModel(roles = emptyList(), isBroadcast = true)
): List<BaseUiModel<*>> = ): List<BaseUiModel<*>> =
withContext(CommonPool) { withContext(Dispatchers.IO) {
return@withContext translate(message, roomUiModel) return@withContext translate(message, roomUiModel)
} }
...@@ -88,7 +88,7 @@ class UiModelMapper @Inject constructor( ...@@ -88,7 +88,7 @@ class UiModelMapper @Inject constructor(
roomUiModel: RoomUiModel = RoomUiModel(roles = emptyList(), isBroadcast = true), roomUiModel: RoomUiModel = RoomUiModel(roles = emptyList(), isBroadcast = true),
asNotReversed: Boolean = false asNotReversed: Boolean = false
): List<BaseUiModel<*>> = ): List<BaseUiModel<*>> =
withContext(CommonPool) { withContext(Dispatchers.IO) {
val list = ArrayList<BaseUiModel<*>>(messages.size) val list = ArrayList<BaseUiModel<*>>(messages.size)
messages.forEach { messages.forEach {
...@@ -102,7 +102,7 @@ class UiModelMapper @Inject constructor( ...@@ -102,7 +102,7 @@ class UiModelMapper @Inject constructor(
suspend fun map( suspend fun map(
readReceipts: List<ReadReceipt> readReceipts: List<ReadReceipt>
): List<ReadReceiptViewModel> = withContext(CommonPool) { ): List<ReadReceiptViewModel> = withContext(Dispatchers.IO) {
val list = arrayListOf<ReadReceiptViewModel>() val list = arrayListOf<ReadReceiptViewModel>()
readReceipts.forEach { readReceipts.forEach {
...@@ -121,7 +121,7 @@ class UiModelMapper @Inject constructor( ...@@ -121,7 +121,7 @@ class UiModelMapper @Inject constructor(
message: Message, message: Message,
roomUiModel: RoomUiModel roomUiModel: RoomUiModel
): List<BaseUiModel<*>> = ): List<BaseUiModel<*>> =
withContext(CommonPool) { withContext(Dispatchers.IO) {
val list = ArrayList<BaseUiModel<*>>() val list = ArrayList<BaseUiModel<*>>()
getChatRoomAsync(message.roomId)?.let { chatRoom -> getChatRoomAsync(message.roomId)?.let { chatRoom ->
...@@ -167,7 +167,7 @@ class UiModelMapper @Inject constructor( ...@@ -167,7 +167,7 @@ class UiModelMapper @Inject constructor(
} }
// TODO: move this to new interactor or FetchChatRoomsInteractor? // 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 { return@withContext dbManager.getRoom(id = roomId)?.let {
with(it.chatRoom) { with(it.chatRoom) {
ChatRoom( ChatRoom(
...@@ -212,7 +212,7 @@ class UiModelMapper @Inject constructor( ...@@ -212,7 +212,7 @@ class UiModelMapper @Inject constructor(
message: Message, message: Message,
roomUiModel: RoomUiModel roomUiModel: RoomUiModel
): List<BaseUiModel<*>> = ): List<BaseUiModel<*>> =
withContext(CommonPool) { withContext(Dispatchers.IO) {
val list = ArrayList<BaseUiModel<*>>() val list = ArrayList<BaseUiModel<*>>()
getChatRoomAsync(message.roomId)?.let { chatRoom -> getChatRoomAsync(message.roomId)?.let { chatRoom ->
...@@ -437,7 +437,7 @@ class UiModelMapper @Inject constructor( ...@@ -437,7 +437,7 @@ class UiModelMapper @Inject constructor(
private suspend fun mapMessage( private suspend fun mapMessage(
message: Message, message: Message,
chatRoom: ChatRoom chatRoom: ChatRoom
): MessageUiModel = withContext(CommonPool) { ): MessageUiModel = withContext(Dispatchers.IO) {
val sender = getSenderName(message) val sender = getSenderName(message)
val time = getTime(message.timestamp) val time = getTime(message.timestamp)
val avatar = getUserAvatar(message) val avatar = getUserAvatar(message)
...@@ -552,13 +552,15 @@ class UiModelMapper @Inject constructor( ...@@ -552,13 +552,15 @@ class UiModelMapper @Inject constructor(
is MessageType.SubscriptionRoleAdded -> getString(R.string.message_role_add, message.message, message.role, message.sender?.username) is MessageType.SubscriptionRoleAdded -> getString(R.string.message_role_add, message.message, message.role, message.sender?.username)
is MessageType.SubscriptionRoleRemoved -> getString(R.string.message_role_removed, message.message, message.role, message.sender?.username) is MessageType.SubscriptionRoleRemoved -> getString(R.string.message_role_removed, message.message, message.role, message.sender?.username)
is MessageType.RoomChangedPrivacy -> getString(R.string.message_room_changed_privacy, message.message, message.sender?.username) is MessageType.RoomChangedPrivacy -> getString(R.string.message_room_changed_privacy, message.message, message.sender?.username)
is MessageType.JitsiCallStarted -> context.getString(
R.string.message_video_call_started, message.sender?.username
)
else -> throw InvalidParameterException("Invalid message type: ${message.type}") else -> throw InvalidParameterException("Invalid message type: ${message.type}")
} }
} }
val spannableMsg = SpannableStringBuilder(content) val spannableMsg = SpannableStringBuilder(content)
spannableMsg.setSpan(StyleSpan(Typeface.ITALIC), 0, spannableMsg.length, 0) spannableMsg.setSpan(StyleSpan(Typeface.ITALIC), 0, spannableMsg.length, 0)
spannableMsg.setSpan(ForegroundColorSpan(Color.GRAY), 0, spannableMsg.length, 0) spannableMsg.setSpan(ForegroundColorSpan(Color.GRAY), 0, spannableMsg.length, 0)
return spannableMsg return spannableMsg
} }
} }
\ No newline at end of file
...@@ -23,11 +23,12 @@ import chat.rocket.common.model.roomTypeOf ...@@ -23,11 +23,12 @@ import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.createDirectMessage import chat.rocket.core.internal.realtime.createDirectMessage
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.show import chat.rocket.core.internal.rest.show
import kotlinx.coroutines.experimental.withTimeout import kotlinx.coroutines.withTimeout
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
import kotlin.coroutines.experimental.suspendCoroutine import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class ChatRoomsPresenter @Inject constructor( class ChatRoomsPresenter @Inject constructor(
private val view: ChatRoomsView, private val view: ChatRoomsView,
......
...@@ -20,16 +20,16 @@ import chat.rocket.core.model.SpotlightResult ...@@ -20,16 +20,16 @@ import chat.rocket.core.model.SpotlightResult
import com.shopify.livedataktx.distinct import com.shopify.livedataktx.distinct
import com.shopify.livedataktx.map import com.shopify.livedataktx.map
import com.shopify.livedataktx.nonNull import com.shopify.livedataktx.nonNull
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.isActive import kotlinx.coroutines.delay
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.isActive
import kotlinx.coroutines.experimental.newSingleThreadContext import kotlinx.coroutines.launch
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
import kotlin.coroutines.experimental.coroutineContext import kotlin.coroutines.coroutineContext
class ChatRoomsViewModel( class ChatRoomsViewModel(
private val connectionManager: ConnectionManager, private val connectionManager: ConnectionManager,
...@@ -107,7 +107,7 @@ class ChatRoomsViewModel( ...@@ -107,7 +107,7 @@ class ChatRoomsViewModel(
} }
private fun fetchRooms() { private fun fetchRooms() {
launch { GlobalScope.launch {
setLoadingState(LoadingState.Loading(repository.count())) setLoadingState(LoadingState.Loading(repository.count()))
try { try {
interactor.refreshChatRooms() interactor.refreshChatRooms()
...@@ -125,7 +125,7 @@ class ChatRoomsViewModel( ...@@ -125,7 +125,7 @@ class ChatRoomsViewModel(
} }
private suspend fun setLoadingState(state: LoadingState) { private suspend fun setLoadingState(state: LoadingState) {
withContext(UI) { withContext(Dispatchers.Main) {
loadingState.value = state loadingState.value = state
} }
} }
......
...@@ -37,6 +37,8 @@ import chat.rocket.android.settings.di.SettingsFragmentProvider ...@@ -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.di.PasswordFragmentProvider
import chat.rocket.android.settings.password.ui.PasswordActivity import chat.rocket.android.settings.password.ui.PasswordActivity
import chat.rocket.android.userdetails.di.UserDetailsFragmentProvider import chat.rocket.android.userdetails.di.UserDetailsFragmentProvider
import chat.rocket.android.videoconference.di.VideoConferenceModule
import chat.rocket.android.videoconference.ui.VideoConferenceActivity
import chat.rocket.android.webview.adminpanel.di.AdminPanelWebViewFragmentProvider import chat.rocket.android.webview.adminpanel.di.AdminPanelWebViewFragmentProvider
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -103,4 +105,8 @@ abstract class ActivityBuilder { ...@@ -103,4 +105,8 @@ abstract class ActivityBuilder {
@PerActivity @PerActivity
@ContributesAndroidInjector(modules = [DrawModule::class]) @ContributesAndroidInjector(modules = [DrawModule::class])
abstract fun bindDrawingActivity(): DrawingActivity abstract fun bindDrawingActivity(): DrawingActivity
@PerActivity
@ContributesAndroidInjector(modules = [VideoConferenceModule::class])
abstract fun bindVideoConferenceActivity(): VideoConferenceActivity
} }
...@@ -63,7 +63,6 @@ import chat.rocket.common.internal.FallbackSealedClassJsonAdapter ...@@ -63,7 +63,6 @@ import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date import chat.rocket.common.internal.ISO8601Date
import chat.rocket.common.model.TimestampAdapter import chat.rocket.common.model.TimestampAdapter
import chat.rocket.common.util.CalendarISO8601Converter import chat.rocket.common.util.CalendarISO8601Converter
import chat.rocket.common.util.Logger
import chat.rocket.common.util.NoOpLogger import chat.rocket.common.util.NoOpLogger
import chat.rocket.common.util.PlatformLogger import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.internal.AttachmentAdapterFactory import chat.rocket.core.internal.AttachmentAdapterFactory
......
...@@ -13,7 +13,6 @@ import chat.rocket.common.internal.FallbackSealedClassJsonAdapter ...@@ -13,7 +13,6 @@ import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date import chat.rocket.common.internal.ISO8601Date
import chat.rocket.common.model.TimestampAdapter import chat.rocket.common.model.TimestampAdapter
import chat.rocket.common.util.CalendarISO8601Converter import chat.rocket.common.util.CalendarISO8601Converter
import chat.rocket.common.util.Logger
import chat.rocket.common.util.NoOpLogger import chat.rocket.common.util.NoOpLogger
import chat.rocket.common.util.PlatformLogger import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.internal.AttachmentAdapterFactory import chat.rocket.core.internal.AttachmentAdapterFactory
......
...@@ -33,11 +33,12 @@ import chat.rocket.core.model.Myself ...@@ -33,11 +33,12 @@ import chat.rocket.core.model.Myself
import chat.rocket.core.model.Room import chat.rocket.core.model.Room
import chat.rocket.core.model.attachment.Attachment import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.userId import chat.rocket.core.model.userId
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.Job
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.experimental.newSingleThreadContext import kotlinx.coroutines.launch
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.util.HashSet import java.util.HashSet
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
...@@ -50,8 +51,10 @@ import kotlin.system.measureTimeMillis ...@@ -50,8 +51,10 @@ import kotlin.system.measureTimeMillis
class DatabaseManager(val context: Application, val serverUrl: String) { class DatabaseManager(val context: Application, val serverUrl: String) {
private val database: RCDatabase = androidx.room.Room.databaseBuilder(context, private val database: RCDatabase = androidx.room.Room.databaseBuilder(
RCDatabase::class.java, serverUrl.databaseName()) context,
RCDatabase::class.java, serverUrl.databaseName()
)
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.build() .build()
private val dbContext = newSingleThreadContext("$serverUrl-db-context") private val dbContext = newSingleThreadContext("$serverUrl-db-context")
...@@ -75,7 +78,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -75,7 +78,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
fun start() { fun start() {
dbJob?.cancel() dbJob?.cancel()
dbJob = launch(dbContext) { dbJob = GlobalScope.launch(dbContext) {
for (operation in writeChannel) { for (operation in writeChannel) {
doOperation(operation) doOperation(operation)
} }
...@@ -121,7 +124,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -121,7 +124,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
fun processUsersBatch(users: List<User>) { fun processUsersBatch(users: List<User>) {
launch(dbManagerContext) { GlobalScope.launch(dbManagerContext) {
val list = ArrayList<BaseUserEntity>(users.size) val list = ArrayList<BaseUserEntity>(users.size)
val time = measureTimeMillis { val time = measureTimeMillis {
users.forEach { user -> users.forEach { user ->
...@@ -139,7 +142,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -139,7 +142,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
* Creates a list of data base operations * Creates a list of data base operations
*/ */
fun processChatRoomsBatch(batch: List<StreamMessage<BaseRoom>>) { fun processChatRoomsBatch(batch: List<StreamMessage<BaseRoom>>) {
launch(dbManagerContext) { GlobalScope.launch(dbManagerContext) {
val toRemove = HashSet<String>() val toRemove = HashSet<String>()
val toInsert = ArrayList<ChatRoomEntity>(batch.size / 2) val toInsert = ArrayList<ChatRoomEntity>(batch.size / 2)
val toUpdate = ArrayList<ChatRoomEntity>(batch.size) val toUpdate = ArrayList<ChatRoomEntity>(batch.size)
...@@ -149,7 +152,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -149,7 +152,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
is Type.Inserted -> insertChatRoom(it.data)?.let { room -> toInsert.add(room) } is Type.Inserted -> insertChatRoom(it.data)?.let { room -> toInsert.add(room) }
is Type.Updated -> { is Type.Updated -> {
when (it.data) { when (it.data) {
is Subscription -> updateSubs[(it.data as Subscription).roomId] = it.data as Subscription is Subscription -> updateSubs[(it.data as Subscription).roomId] =
it.data as Subscription
is Room -> updateRooms[(it.data as Room).id] = it.data as Room is Room -> updateRooms[(it.data as Room).id] = it.data as Room
} }
} }
...@@ -163,7 +167,13 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -163,7 +167,13 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
val filteredUpdate = toUpdate.filterNot { toRemove.contains(it.id) } val filteredUpdate = toUpdate.filterNot { toRemove.contains(it.id) }
val filteredInsert = toInsert.filterNot { toRemove.contains(it.id) } val filteredInsert = toInsert.filterNot { toRemove.contains(it.id) }
sendOperation(Operation.UpdateRooms(filteredInsert, filteredUpdate, toRemove.toList())) sendOperation(
Operation.UpdateRooms(
filteredInsert,
filteredUpdate,
toRemove.toList()
)
)
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error updating chatrooms") Timber.d(ex, "Error updating chatrooms")
} }
...@@ -171,7 +181,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -171,7 +181,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
fun updateSelfUser(myself: Myself) { fun updateSelfUser(myself: Myself) {
launch(dbManagerContext) { GlobalScope.launch(dbManagerContext) {
val user = retryDB("getUser(${myself.id})") { userDao().getUser(myself.id) } val user = retryDB("getUser(${myself.id})") { userDao().getUser(myself.id) }
val entity = user?.copy( val entity = user?.copy(
name = myself.name ?: user.name, name = myself.name ?: user.name,
...@@ -186,19 +196,18 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -186,19 +196,18 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
fun processRooms(rooms: List<ChatRoom>) { fun processRooms(rooms: List<ChatRoom>) {
launch(dbManagerContext) { GlobalScope.launch(dbManagerContext) {
val entities = rooms.map { mapChatRoom(it) } val entities = rooms.map { mapChatRoom(it) }
sendOperation(Operation.CleanInsertRooms(entities)) sendOperation(Operation.CleanInsertRooms(entities))
} }
} }
fun processMessagesBatch(messages: List<Message>): Job = launch(dbManagerContext) { fun processMessagesBatch(messages: List<Message>): Job = GlobalScope.launch(dbManagerContext) {
val list = mutableListOf<Pair<MessageEntity, List<BaseMessageEntity>>>() val list = mutableListOf<Pair<MessageEntity, List<BaseMessageEntity>>>()
messages.forEach { message -> messages.forEach { message ->
val pair = createMessageEntities(message) val pair = createMessageEntities(message)
list.add(pair) list.add(pair)
} }
sendOperation(Operation.InsertMessages(list)) sendOperation(Operation.InsertMessages(list))
} }
...@@ -217,12 +226,20 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -217,12 +226,20 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
return Pair(messageEntity, list) return Pair(messageEntity, list)
} }
private fun createReactions(message: Message): List<BaseMessageEntity>? = message.reactions?.run { private fun createReactions(message: Message): List<BaseMessageEntity>? =
message.reactions?.run {
if (isNotEmpty()) { if (isNotEmpty()) {
val list = mutableListOf<BaseMessageEntity>() val list = mutableListOf<BaseMessageEntity>()
keys.forEach { reaction -> keys.forEach { reaction ->
get(reaction)?.let { reactionValue -> get(reaction)?.let { reactionValue ->
list.add(ReactionEntity(reaction, message.id, size, reactionValue.joinToString())) list.add(
ReactionEntity(
reaction,
message.id,
size,
reactionValue.joinToString()
)
)
} }
} }
list list
...@@ -233,14 +250,19 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -233,14 +250,19 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
if (isNotEmpty()) { if (isNotEmpty()) {
val list = mutableListOf<UrlEntity>() val list = mutableListOf<UrlEntity>()
forEach { url -> forEach { url ->
list.add(UrlEntity(message.id, url.url, url.parsedUrl?.host, url.meta?.title, list.add(
url.meta?.description, url.meta?.imageUrl)) UrlEntity(
message.id, url.url, url.parsedUrl?.host, url.meta?.title,
url.meta?.description, url.meta?.imageUrl
)
)
} }
list list
} else null } else null
} }
private fun createChannelRelations(message: Message): List<BaseMessageEntity>? = message.channels?.run { private fun createChannelRelations(message: Message): List<BaseMessageEntity>? =
message.channels?.run {
if (isNotEmpty()) { if (isNotEmpty()) {
val list = mutableListOf<MessageChannels>() val list = mutableListOf<MessageChannels>()
forEach { channel -> forEach { channel ->
...@@ -250,7 +272,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -250,7 +272,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} else null } else null
} }
private suspend fun createMentionRelations(message: Message): List<BaseMessageEntity>? = message.mentions?.run { private suspend fun createMentionRelations(message: Message): List<BaseMessageEntity>? =
message.mentions?.run {
if (isNotEmpty()) { if (isNotEmpty()) {
val list = mutableListOf<MessageMentionsRelation>() val list = mutableListOf<MessageMentionsRelation>()
filterNot { user -> user.id.isNullOrEmpty() }.forEach { mention -> filterNot { user -> user.id.isNullOrEmpty() }.forEach { mention ->
...@@ -261,7 +284,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -261,7 +284,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} else null } else null
} }
private suspend fun createFavoriteRelations(message: Message): List<BaseMessageEntity>? = message.starred?.run { private suspend fun createFavoriteRelations(message: Message): List<BaseMessageEntity>? =
message.starred?.run {
if (isNotEmpty()) { if (isNotEmpty()) {
val list = mutableListOf<MessageFavoritesRelation>() val list = mutableListOf<MessageFavoritesRelation>()
filterNot { user -> user.id.isNullOrEmpty() }.forEach { userId -> filterNot { user -> user.id.isNullOrEmpty() }.forEach { userId ->
...@@ -273,7 +297,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -273,7 +297,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
private fun createAttachments(message: Message): List<BaseMessageEntity>? = message.attachments?.run { private fun createAttachments(message: Message): List<BaseMessageEntity>? =
message.attachments?.run {
if (isNotEmpty()) { if (isNotEmpty()) {
val list = ArrayList<BaseMessageEntity>(size) val list = ArrayList<BaseMessageEntity>(size)
forEach { attachment -> forEach { attachment ->
...@@ -366,7 +391,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -366,7 +391,8 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
} }
private fun mapAttachmentText(attachment: Attachment): String = context.getString(R.string.msg_sent_attachment) private fun mapAttachmentText(attachment: Attachment): String =
context.getString(R.string.msg_sent_attachment)
private suspend fun updateSubscription(data: Subscription): ChatRoomEntity? { private suspend fun updateSubscription(data: Subscription): ChatRoomEntity? {
return retryDB("getRoom(${data.roomId}") { chatRoomDao().getSync(data.roomId) }?.let { current -> return retryDB("getRoom(${data.roomId}") { chatRoomDao().getSync(data.roomId) }?.let { current ->
...@@ -551,31 +577,18 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -551,31 +577,18 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
is Operation.ClearStatus -> userDao().clearStatus() is Operation.ClearStatus -> userDao().clearStatus()
is Operation.UpdateRooms -> { is Operation.UpdateRooms -> {
Timber.d("Running ChatRooms transaction: remove: ${operation.toRemove} - insert: ${operation.toInsert} - update: ${operation.toUpdate}") Timber.d("Running ChatRooms transaction: remove: ${operation.toRemove} - insert: ${operation.toInsert} - update: ${operation.toUpdate}")
chatRoomDao().update(operation.toInsert, operation.toUpdate, operation.toRemove) chatRoomDao().update(operation.toInsert, operation.toUpdate, operation.toRemove)
} }
is Operation.InsertRooms -> { is Operation.InsertRooms -> chatRoomDao().insertOrReplace(operation.chatRooms)
chatRoomDao().insertOrReplace(operation.chatRooms) is Operation.CleanInsertRooms -> chatRoomDao().cleanInsert(operation.chatRooms)
}
is Operation.CleanInsertRooms -> {
chatRoomDao().cleanInsert(operation.chatRooms)
}
is Operation.InsertUsers -> { is Operation.InsertUsers -> {
val time = measureTimeMillis { userDao().upsert(operation.users) } val time = measureTimeMillis { userDao().upsert(operation.users) }
Timber.d("Upserted users batch(${operation.users.size}) in $time MS") Timber.d("Upserted users batch(${operation.users.size}) in $time MS")
} }
is Operation.InsertUser -> { is Operation.InsertUser -> userDao().insert(operation.user)
userDao().insert(operation.user) is Operation.UpsertUser -> userDao().upsert(operation.user)
} is Operation.InsertMessages -> messageDao().insert(operation.list)
is Operation.UpsertUser -> { is Operation.SaveLastSync -> messageDao().saveLastSync(operation.sync)
userDao().upsert(operation.user)
}
is Operation.InsertMessages -> {
messageDao().insert(operation.list)
}
is Operation.SaveLastSync -> {
messageDao().saveLastSync(operation.sync)
}
}.exhaustive }.exhaustive
} }
} }
...@@ -597,7 +610,8 @@ sealed class Operation { ...@@ -597,7 +610,8 @@ sealed class Operation {
data class UpsertUser(val user: BaseUserEntity) : Operation() data class UpsertUser(val user: BaseUserEntity) : Operation()
data class InsertUser(val user: UserEntity) : Operation() data class InsertUser(val user: UserEntity) : Operation()
data class InsertMessages(val list: List<Pair<MessageEntity, List<BaseMessageEntity>>>) : Operation() data class InsertMessages(val list: List<Pair<MessageEntity, List<BaseMessageEntity>>>) :
Operation()
data class SaveLastSync(val sync: MessagesSync) : Operation() data class SaveLastSync(val sync: MessagesSync) : Operation()
} }
......
...@@ -110,6 +110,6 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { ...@@ -110,6 +110,6 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
} }
private fun setupToolbar() { private fun setupToolbar() {
(activity as ChatRoomActivity).showToolbarTitle(getString(R.string.title_favorite_messages)) (activity as ChatRoomActivity).setupToolbarTitle(getString(R.string.title_favorite_messages))
} }
} }
\ No newline at end of file
...@@ -147,7 +147,7 @@ class FilesFragment : Fragment(), FilesView { ...@@ -147,7 +147,7 @@ class FilesFragment : Fragment(), FilesView {
} }
private fun setupToolbar(totalFiles: Long) { private fun setupToolbar(totalFiles: Long) {
(activity as ChatRoomActivity).showToolbarTitle( (activity as ChatRoomActivity).setupToolbarTitle(
(getString( (getString(
R.string.title_files_total, R.string.title_files_total,
totalFiles totalFiles
......
...@@ -11,7 +11,10 @@ object AndroidPermissionsHelper { ...@@ -11,7 +11,10 @@ object AndroidPermissionsHelper {
const val WRITE_EXTERNAL_STORAGE_CODE = 1 const val WRITE_EXTERNAL_STORAGE_CODE = 1
fun checkPermission(context: Context, permission: String): Boolean { 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) { fun requestPermission(context: Activity, permission: String, requestCode: Int) {
......
package chat.rocket.android.helper
object JitsiHelper {
/**
* Returns the for the Jitsi video conferencing URL.
*
* @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?
): String =
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 ...@@ -8,7 +8,7 @@ import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
@Module @Module
class MainModule { class MainModule {
......
...@@ -37,7 +37,7 @@ import chat.rocket.core.RocketChatClient ...@@ -37,7 +37,7 @@ import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.getCustomEmojis import chat.rocket.core.internal.rest.getCustomEmojis
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.channels.Channel
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
......
...@@ -3,6 +3,7 @@ package chat.rocket.android.members.presentation ...@@ -3,6 +3,7 @@ package chat.rocket.android.members.presentation
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.members.uimodel.MemberUiModel import chat.rocket.android.members.uimodel.MemberUiModel
import chat.rocket.android.members.uimodel.MemberUiModelMapper import chat.rocket.android.members.uimodel.MemberUiModelMapper
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
...@@ -23,7 +24,8 @@ class MembersPresenter @Inject constructor( ...@@ -23,7 +24,8 @@ class MembersPresenter @Inject constructor(
@Named("currentServer") private val currentServer: String, @Named("currentServer") private val currentServer: String,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val mapper: MemberUiModelMapper, private val mapper: MemberUiModelMapper,
val factory: RocketChatClientFactory val factory: RocketChatClientFactory,
private val userHelper: UserHelper
) { ) {
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.create(currentServer)
private var offset: Long = 0 private var offset: Long = 0
...@@ -59,6 +61,10 @@ class MembersPresenter @Inject constructor( ...@@ -59,6 +61,10 @@ class MembersPresenter @Inject constructor(
} }
fun toMemberDetails(memberUiModel: MemberUiModel) { fun toMemberDetails(memberUiModel: MemberUiModel) {
navigator.toMemberDetails(memberUiModel.userId) with(memberUiModel) {
if (userId != userHelper.user()?.id) {
navigator.toMemberDetails(userId)
}
}
} }
} }
...@@ -131,9 +131,9 @@ class MembersFragment : Fragment(), MembersView { ...@@ -131,9 +131,9 @@ class MembersFragment : Fragment(), MembersView {
private fun setupToolbar(totalMembers: Long? = null) { private fun setupToolbar(totalMembers: Long? = null) {
with((activity as ChatRoomActivity)) { with((activity as ChatRoomActivity)) {
if (totalMembers != null) { if (totalMembers != null) {
showToolbarTitle((getString(R.string.title_counted_members, totalMembers))) setupToolbarTitle((getString(R.string.title_counted_members, totalMembers)))
} else { } else {
showToolbarTitle((getString(R.string.title_members))) setupToolbarTitle((getString(R.string.title_members)))
} }
this.clearLightStatusBar() this.clearLightStatusBar()
toolbar.isVisible = true toolbar.isVisible = true
......
...@@ -116,6 +116,6 @@ class MentionsFragment : Fragment(), MentionsView { ...@@ -116,6 +116,6 @@ class MentionsFragment : Fragment(), MentionsView {
} }
private fun setupToolbar() { private fun setupToolbar() {
(activity as ChatRoomActivity).showToolbarTitle((getString(R.string.msg_mentions))) (activity as ChatRoomActivity).setupToolbarTitle((getString(R.string.msg_mentions)))
} }
} }
\ No newline at end of file
...@@ -116,6 +116,6 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { ...@@ -116,6 +116,6 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
} }
private fun setupToolbar() { private fun setupToolbar() {
(activity as ChatRoomActivity).showToolbarTitle((getString(R.string.title_pinned_messages))) (activity as ChatRoomActivity).setupToolbarTitle((getString(R.string.title_pinned_messages)))
} }
} }
\ No newline at end of file
...@@ -27,8 +27,8 @@ import chat.rocket.core.internal.rest.deleteOwnAccount ...@@ -27,8 +27,8 @@ import chat.rocket.core.internal.rest.deleteOwnAccount
import chat.rocket.core.internal.rest.resetAvatar import chat.rocket.core.internal.rest.resetAvatar
import chat.rocket.core.internal.rest.setAvatar import chat.rocket.core.internal.rest.setAvatar
import chat.rocket.core.internal.rest.updateProfile import chat.rocket.core.internal.rest.updateProfile
import kotlinx.coroutines.experimental.DefaultDispatcher import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
...@@ -180,7 +180,7 @@ class ProfilePresenter @Inject constructor( ...@@ -180,7 +180,7 @@ class ProfilePresenter @Inject constructor(
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
// REMARK: Backend API is only working with a lowercase hash. // REMARK: Backend API is only working with a lowercase hash.
// https://github.com/RocketChat/Rocket.Chat/issues/12573 // https://github.com/RocketChat/Rocket.Chat/issues/12573
retryIO { client.deleteOwnAccount(password.gethash().toHex().toLowerCase()) } retryIO { client.deleteOwnAccount(password.gethash().toHex().toLowerCase()) }
......
...@@ -11,8 +11,8 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory ...@@ -11,8 +11,8 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.sendMessage import chat.rocket.core.internal.rest.sendMessage
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.MainScope
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
...@@ -36,7 +36,7 @@ class DirectReplyReceiver : BroadcastReceiver() { ...@@ -36,7 +36,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
if (ACTION_REPLY == intent.action) { if (ACTION_REPLY == intent.action) {
val message = intent.getParcelableExtra<PushMessage>(EXTRA_PUSH_MESSAGE) val message = intent.getParcelableExtra<PushMessage>(EXTRA_PUSH_MESSAGE)
message?.let { message?.let {
launch(UI) { MainScope().launch {
val notificationId = it.notificationId.toInt() val notificationId = it.notificationId.toInt()
val hostname = it.info.host val hostname = it.info.host
try { try {
......
...@@ -30,7 +30,7 @@ import chat.rocket.common.model.RoomType ...@@ -30,7 +30,7 @@ import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.runBlocking
import se.ansman.kotshi.JsonSerializable import se.ansman.kotshi.JsonSerializable
import se.ansman.kotshi.KotshiConstructor import se.ansman.kotshi.KotshiConstructor
import timber.log.Timber import timber.log.Timber
......
...@@ -3,13 +3,12 @@ package chat.rocket.android.server.di ...@@ -3,13 +3,12 @@ package chat.rocket.android.server.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity 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.ChangeServerNavigator
import chat.rocket.android.server.presentation.ChangeServerView import chat.rocket.android.server.presentation.ChangeServerView
import chat.rocket.android.server.ui.ChangeServerActivity import chat.rocket.android.server.ui.ChangeServerActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
@Module @Module
class ChangeServerModule { class ChangeServerModule {
......
package chat.rocket.android.server.domain package chat.rocket.android.server.domain
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsRepository) { class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsRepository) {
...@@ -23,7 +23,8 @@ class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsR ...@@ -23,7 +23,8 @@ 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. * @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. * @return A list of ChatRoom objects with the given name.
*/ */
suspend fun getAllByName(url: String, name: String): List<ChatRoom> = withContext(CommonPool) { suspend fun getAllByName(url: String, name: String): List<ChatRoom> =
withContext(Dispatchers.IO) {
val allChatRooms = repository.get(url) val allChatRooms = repository.get(url)
if (name.isEmpty()) { if (name.isEmpty()) {
return@withContext allChatRooms return@withContext allChatRooms
...@@ -40,7 +41,8 @@ class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsR ...@@ -40,7 +41,8 @@ class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsR
* @param roomId The id of the room to get. * @param roomId The id of the room to get.
* @return The [ChatRoom] object or null if we couldn't find any. * @return The [ChatRoom] object or null if we couldn't find any.
*/ */
suspend fun getById(serverUrl: String, roomId: String): ChatRoom? = withContext(CommonPool) { suspend fun getById(serverUrl: String, roomId: String): ChatRoom? =
withContext(Dispatchers.IO) {
return@withContext repository.get(serverUrl).find { return@withContext repository.get(serverUrl).find {
it.id == roomId it.id == roomId
} }
......
...@@ -3,8 +3,9 @@ package chat.rocket.android.server.domain ...@@ -3,8 +3,9 @@ package chat.rocket.android.server.domain
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.permissions import chat.rocket.core.internal.rest.permissions
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -17,7 +18,7 @@ class RefreshPermissionsInteractor @Inject constructor( ...@@ -17,7 +18,7 @@ class RefreshPermissionsInteractor @Inject constructor(
) { ) {
fun refreshAsync(server: String) { fun refreshAsync(server: String) {
launch(CommonPool) { GlobalScope.launch(Dispatchers.IO) {
try { try {
factory.create(server).let { client -> factory.create(server).let { client ->
val permissions = retryIO( val permissions = retryIO(
......
...@@ -3,9 +3,10 @@ package chat.rocket.android.server.domain ...@@ -3,9 +3,10 @@ package chat.rocket.android.server.domain
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.settings import chat.rocket.core.internal.rest.settings
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -18,10 +19,11 @@ class RefreshSettingsInteractor @Inject constructor( ...@@ -18,10 +19,11 @@ class RefreshSettingsInteractor @Inject constructor(
) { ) {
private var settingsFilter = arrayOf( private var settingsFilter = arrayOf(
UNIQUE_IDENTIFIER,
LDAP_ENABLE, LDAP_ENABLE,
CAS_ENABLE, CAS_ENABLE,
CAS_LOGIN_URL, CAS_LOGIN_URL,
ACCOUNT_REGISTRATION, ACCOUNT_REGISTRATION,
ACCOUNT_LOGIN_FORM, ACCOUNT_LOGIN_FORM,
ACCOUNT_PASSWORD_RESET, ACCOUNT_PASSWORD_RESET,
...@@ -37,6 +39,12 @@ class RefreshSettingsInteractor @Inject constructor( ...@@ -37,6 +39,12 @@ class RefreshSettingsInteractor @Inject constructor(
ACCOUNT_WORDPRESS, ACCOUNT_WORDPRESS,
ACCOUNT_WORDPRESS_URL, ACCOUNT_WORDPRESS_URL,
JITSI_ENABLED,
JISTI_ENABLE_CHANNELS,
JITSI_SSL,
JITSI_DOMAIN,
JITSI_URL_ROOM_PREFIX,
SITE_URL, SITE_URL,
SITE_NAME, SITE_NAME,
FAVICON_512, FAVICON_512,
...@@ -65,7 +73,7 @@ class RefreshSettingsInteractor @Inject constructor( ...@@ -65,7 +73,7 @@ class RefreshSettingsInteractor @Inject constructor(
) )
suspend fun refresh(server: String) { suspend fun refresh(server: String) {
withContext(CommonPool) { withContext(Dispatchers.IO) {
factory.create(server).let { client -> factory.create(server).let { client ->
val settings = retryIO( val settings = retryIO(
description = "settings", description = "settings",
...@@ -81,7 +89,7 @@ class RefreshSettingsInteractor @Inject constructor( ...@@ -81,7 +89,7 @@ class RefreshSettingsInteractor @Inject constructor(
} }
fun refreshAsync(server: String) { fun refreshAsync(server: String) {
launch(CommonPool) { GlobalScope.launch(Dispatchers.IO) {
try { try {
refresh(server) refresh(server)
} catch (ex: Exception) { } catch (ex: Exception) {
......
...@@ -5,7 +5,9 @@ import chat.rocket.core.model.Value ...@@ -5,7 +5,9 @@ import chat.rocket.core.model.Value
typealias PublicSettings = Map<String, Value<Any>> typealias PublicSettings = Map<String, Value<Any>>
// Authentication methods. const val UNIQUE_IDENTIFIER = "uniqueID"
// Authentication methods
const val LDAP_ENABLE = "LDAP_Enable" const val LDAP_ENABLE = "LDAP_Enable"
const val CAS_ENABLE = "CAS_enabled" const val CAS_ENABLE = "CAS_enabled"
const val CAS_LOGIN_URL = "CAS_login_url" const val CAS_LOGIN_URL = "CAS_login_url"
...@@ -24,6 +26,13 @@ const val ACCOUNT_GITLAB_URL = "API_Gitlab_URL" ...@@ -24,6 +26,13 @@ const val ACCOUNT_GITLAB_URL = "API_Gitlab_URL"
const val ACCOUNT_WORDPRESS = "Accounts_OAuth_Wordpress" const val ACCOUNT_WORDPRESS = "Accounts_OAuth_Wordpress"
const val ACCOUNT_WORDPRESS_URL = "API_Wordpress_URL" const val ACCOUNT_WORDPRESS_URL = "API_Wordpress_URL"
// Video call
const val JITSI_ENABLED = "Jitsi_Enabled"
const val JISTI_ENABLE_CHANNELS = "Jisti_Enable_Channels"
const val JITSI_SSL = "Jitsi_SSL"
const val JITSI_DOMAIN = "Jitsi_Domain"
const val JITSI_URL_ROOM_PREFIX = "Jitsi_URL_Room_Prefix"
const val SITE_URL = "Site_Url" const val SITE_URL = "Site_Url"
const val SITE_NAME = "Site_Name" const val SITE_NAME = "Site_Name"
const val FAVICON_196 = "Assets_favicon_192" const val FAVICON_196 = "Assets_favicon_192"
...@@ -54,10 +63,13 @@ const val MESSAGE_READ_RECEIPT_STORE_USERS = "Message_Read_Receipt_Store_Users" ...@@ -54,10 +63,13 @@ const val MESSAGE_READ_RECEIPT_STORE_USERS = "Message_Read_Receipt_Store_Users"
* Extension functions for Public Settings. * Extension functions for Public Settings.
* *
* If you need to access a Setting, add a const val key above, add it to the filter on * If you need to access a Setting, add a const val key above, add it to the filter on
* ServerPresenter.kt and a extension function to access it * RefreshSettingsInteractor.kt and a extension function to access it.
*/ */
fun PublicSettings.isLdapAuthenticationEnabled(): Boolean = this[LDAP_ENABLE]?.value == true
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 fun PublicSettings.isCasAuthenticationEnabled(): Boolean = this[CAS_ENABLE]?.value == true
fun PublicSettings.casLoginUrl(): String = this[CAS_LOGIN_URL]?.value.toString() fun PublicSettings.casLoginUrl(): String = this[CAS_LOGIN_URL]?.value.toString()
fun PublicSettings.isRegistrationEnabledForNewUsers(): Boolean = this[ACCOUNT_REGISTRATION]?.value == "Public" fun PublicSettings.isRegistrationEnabledForNewUsers(): Boolean = this[ACCOUNT_REGISTRATION]?.value == "Public"
...@@ -74,6 +86,13 @@ fun PublicSettings.gitlabUrl(): String? = this[ACCOUNT_GITLAB_URL]?.value as Str ...@@ -74,6 +86,13 @@ fun PublicSettings.gitlabUrl(): String? = this[ACCOUNT_GITLAB_URL]?.value as Str
fun PublicSettings.isWordpressAuthenticationEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true fun PublicSettings.isWordpressAuthenticationEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true
fun PublicSettings.wordpressUrl(): String? = this[ACCOUNT_WORDPRESS_URL]?.value as String? fun PublicSettings.wordpressUrl(): String? = this[ACCOUNT_WORDPRESS_URL]?.value as String?
// Video call
fun PublicSettings.isJitsiEnabled(): Boolean = this[JITSI_ENABLED]?.value == true
fun PublicSettings.isJitsiEnabledForChannels(): Boolean = this[JISTI_ENABLE_CHANNELS]?.value == true
fun PublicSettings.isJitsiSSL(): Boolean = this[JITSI_SSL]?.value == true
fun PublicSettings.jitsiDomain(): String? = this[JITSI_DOMAIN]?.value as String?
fun PublicSettings.jitsiPrefix(): String? = this[JITSI_URL_ROOM_PREFIX]?.value as String?
fun PublicSettings.useRealName(): Boolean = this[USE_REALNAME]?.value == true fun PublicSettings.useRealName(): Boolean = this[USE_REALNAME]?.value == true
fun PublicSettings.useSpecialCharsOnRoom(): Boolean = this[ALLOW_ROOM_NAME_SPECIAL_CHARS]?.value == true fun PublicSettings.useSpecialCharsOnRoom(): Boolean = this[ALLOW_ROOM_NAME_SPECIAL_CHARS]?.value == true
fun PublicSettings.faviconLarge(): String? = this[FAVICON_512]?.value as String? fun PublicSettings.faviconLarge(): String? = this[FAVICON_512]?.value as String?
......
...@@ -23,26 +23,31 @@ import chat.rocket.core.internal.rest.chatRooms ...@@ -23,26 +23,31 @@ import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import chat.rocket.core.model.Room import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.Job
import kotlinx.coroutines.experimental.channels.SendChannel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.experimental.channels.actor import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.experimental.newSingleThreadContext import kotlinx.coroutines.isActive
import kotlinx.coroutines.experimental.selects.select import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.selects.select
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import kotlin.coroutines.experimental.CoroutineContext import kotlin.coroutines.CoroutineContext
class ConnectionManager( class ConnectionManager(
internal val client: RocketChatClient, internal val client: RocketChatClient,
private val dbManager: DatabaseManager private val dbManager: DatabaseManager
) { ) : CoroutineScope {
private var connectJob : Job? = null
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO
val statusLiveData = MutableLiveData<State>() val statusLiveData = MutableLiveData<State>()
private val statusChannelList = CopyOnWriteArrayList<Channel<State>>() private val statusChannelList = CopyOnWriteArrayList<Channel<State>>()
private val statusChannel = Channel<State>(Channel.CONFLATED) private val statusChannel = Channel<State>(Channel.CONFLATED)
private var connectJob: Job? = null
private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>() private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>()
private val userDataChannels = ArrayList<Channel<Myself>>() private val userDataChannels = ArrayList<Channel<Myself>>()
...@@ -60,7 +65,7 @@ class ConnectionManager( ...@@ -60,7 +65,7 @@ class ConnectionManager(
private val messagesContext = newSingleThreadContext("messagesContext") private val messagesContext = newSingleThreadContext("messagesContext")
fun connect() { fun connect() {
if (connectJob?.isActive == true && (state !is State.Disconnected)) { if (connectJob?.isActive == true && state !is State.Disconnected) {
Timber.d("Already connected, just returning...") Timber.d("Already connected, just returning...")
return return
} }
...@@ -78,32 +83,32 @@ class ConnectionManager( ...@@ -78,32 +83,32 @@ class ConnectionManager(
when (status) { when (status) {
is State.Connected -> { is State.Connected -> {
dbManager.clearUsersStatus() dbManager.clearUsersStatus()
client.subscribeSubscriptions { _, id -> client.subscribeSubscriptions { _, id ->
Timber.d("Subscribed to subscriptions: $id") Timber.d("Subscribed to subscriptions: $id")
subscriptionId = id subscriptionId = id
} }
client.subscribeRooms { _, id -> client.subscribeRooms { _, id ->
Timber.d("Subscribed to rooms: $id") Timber.d("Subscribed to rooms: $id")
roomsId = id roomsId = id
} }
client.subscribeUserData { _, id -> client.subscribeUserData { _, id ->
Timber.d("Subscribed to the userData id: $id") Timber.d("Subscribed to the userData id: $id")
userDataId = id userDataId = id
} }
client.subscribeActiveUsers { _, id -> client.subscribeActiveUsers { _, id ->
Timber.d("Subscribed to the activeUser id: $id") Timber.d("Subscribed to the activeUser id: $id")
activeUserId = id activeUserId = id
} }
resubscribeRooms() resubscribeRooms()
temporaryStatus?.let { client.setTemporaryStatus(it) }
temporaryStatus?.let { tempStatus ->
client.setTemporaryStatus(tempStatus)
}
}
is State.Waiting -> {
Timber.d("Connection in: ${status.seconds}")
} }
is State.Waiting -> Timber.d("Connection in: ${status.seconds}")
} }
statusLiveData.postValue(status) statusLiveData.postValue(status)
...@@ -116,8 +121,9 @@ class ConnectionManager( ...@@ -116,8 +121,9 @@ class ConnectionManager(
} }
var totalBatchedUsers = 0 var totalBatchedUsers = 0
val userActor = createBatchActor<User>(activeUsersContext, parent = connectJob, val userActor = createBatchActor<User>(
maxSize = 500, maxTime = 1000) { users -> activeUsersContext, parent = connectJob, maxSize = 500, maxTime = 1000
) { users ->
totalBatchedUsers += users.size totalBatchedUsers += users.size
Timber.d("Processing Users batch: ${users.size} - $totalBatchedUsers") Timber.d("Processing Users batch: ${users.size} - $totalBatchedUsers")
...@@ -125,8 +131,9 @@ class ConnectionManager( ...@@ -125,8 +131,9 @@ class ConnectionManager(
dbManager.processUsersBatch(users) dbManager.processUsersBatch(users)
} }
val roomsActor = createBatchActor<StreamMessage<BaseRoom>>(roomsContext, parent = connectJob, val roomsActor = createBatchActor<StreamMessage<BaseRoom>>(
maxSize = 10) { batch -> roomsContext, parent = connectJob, maxSize = 10
) { batch ->
Timber.d("processing Stream batch: ${batch.size} - $batch") Timber.d("processing Stream batch: ${batch.size} - $batch")
dbManager.processChatRoomsBatch(batch) dbManager.processChatRoomsBatch(batch)
...@@ -141,8 +148,9 @@ class ConnectionManager( ...@@ -141,8 +148,9 @@ class ConnectionManager(
} }
} }
val messagesActor = createBatchActor<Message>(messagesContext, parent = connectJob, val messagesActor = createBatchActor<Message>(
maxSize = 100, maxTime = 500) { messages -> messagesContext, parent = connectJob, maxSize = 100, maxTime = 500
) { messages ->
Timber.d("Processing Messages batch: ${messages.size}") Timber.d("Processing Messages batch: ${messages.size}")
dbManager.processMessagesBatch(messages.distinctBy { it.id }) dbManager.processMessagesBatch(messages.distinctBy { it.id })
...@@ -155,7 +163,7 @@ class ConnectionManager( ...@@ -155,7 +163,7 @@ class ConnectionManager(
} }
// stream-notify-user - ${userId}/rooms-changed // stream-notify-user - ${userId}/rooms-changed
launch(parent = connectJob) { launch {
for (room in client.roomsChannel) { for (room in client.roomsChannel) {
Timber.d("GOT Room streamed") Timber.d("GOT Room streamed")
roomsActor.send(room) roomsActor.send(room)
...@@ -168,7 +176,7 @@ class ConnectionManager( ...@@ -168,7 +176,7 @@ class ConnectionManager(
} }
// stream-notify-user - ${userId}/subscriptions-changed // stream-notify-user - ${userId}/subscriptions-changed
launch(parent = connectJob) { launch {
for (subscription in client.subscriptionsChannel) { for (subscription in client.subscriptionsChannel) {
Timber.d("GOT Subscription streamed") Timber.d("GOT Subscription streamed")
roomsActor.send(subscription) roomsActor.send(subscription)
...@@ -176,7 +184,7 @@ class ConnectionManager( ...@@ -176,7 +184,7 @@ class ConnectionManager(
} }
// stream-room-messages - $roomId // stream-room-messages - $roomId
launch(parent = connectJob) { launch {
for (message in client.messagesChannel) { for (message in client.messagesChannel) {
Timber.d("Received new Message for room ${message.roomId}") Timber.d("Received new Message for room ${message.roomId}")
messagesActor.send(message) messagesActor.send(message)
...@@ -184,7 +192,7 @@ class ConnectionManager( ...@@ -184,7 +192,7 @@ class ConnectionManager(
} }
// userData // userData
launch(parent = connectJob) { launch {
for (myself in client.userDataChannel) { for (myself in client.userDataChannel) {
Timber.d("Got userData") Timber.d("Got userData")
dbManager.updateSelfUser(myself) dbManager.updateSelfUser(myself)
...@@ -195,7 +203,7 @@ class ConnectionManager( ...@@ -195,7 +203,7 @@ class ConnectionManager(
} }
// activeUsers // activeUsers
launch(parent = connectJob) { launch {
for (user in client.activeUsersChannel) { for (user in client.activeUsersChannel) {
userActor.send(user) userActor.send(user)
} }
...@@ -284,16 +292,18 @@ class ConnectionManager( ...@@ -284,16 +292,18 @@ class ConnectionManager(
} }
} }
private inline fun <T> createBatchActor(context: CoroutineContext = CommonPool, private inline fun <T> createBatchActor(
context: CoroutineContext = Dispatchers.IO,
parent: Job? = null, parent: Job? = null,
maxSize: Int = 100, maxSize: Int = 100,
maxTime: Int = 500, maxTime: Int = 500,
crossinline block: (List<T>) -> Unit): SendChannel<T> { crossinline block: (List<T>) -> Unit
return actor(context, parent = parent) { ): SendChannel<T> {
return actor(context) {
val batch = ArrayList<T>(maxSize) val batch = ArrayList<T>(maxSize)
var deadline = 0L // deadline for sending this batch to callback block 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 // when deadline is reached or size is exceeded, pass the batch to the callback block
val remainingTime = deadline - System.currentTimeMillis() val remainingTime = deadline - System.currentTimeMillis()
if (batch.isNotEmpty() && remainingTime <= 0 || batch.size >= maxSize) { if (batch.isNotEmpty() && remainingTime <= 0 || batch.size >= maxSize) {
......
package chat.rocket.android.server.infraestructure package chat.rocket.android.server.infraestructure
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.AttachmentActionEntity import chat.rocket.android.db.model.*
import chat.rocket.android.db.model.AttachmentEntity
import chat.rocket.android.db.model.FullMessage
import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.util.retryDB import chat.rocket.android.util.retryDB
import chat.rocket.common.model.SimpleRoom import chat.rocket.common.model.SimpleRoom
import chat.rocket.common.model.SimpleUser import chat.rocket.common.model.SimpleUser
...@@ -21,8 +16,8 @@ import chat.rocket.core.model.messageTypeOf ...@@ -21,8 +16,8 @@ import chat.rocket.core.model.messageTypeOf
import chat.rocket.core.model.url.Meta import chat.rocket.core.model.url.Meta
import chat.rocket.core.model.url.ParsedUrl import chat.rocket.core.model.url.ParsedUrl
import chat.rocket.core.model.url.Url import chat.rocket.core.model.url.Url
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
class DatabaseMessageMapper(private val dbManager: DatabaseManager) { class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
suspend fun map(message: FullMessage): Message? = map(listOf(message)).firstOrNull() suspend fun map(message: FullMessage): Message? = map(listOf(message)).firstOrNull()
...@@ -57,7 +52,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -57,7 +52,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
val attachments = this.attachments?.let { mapAttachments(it).asReversed() } val attachments = this.attachments?.let { mapAttachments(it).asReversed() }
val messageType = messageTypeOf(this.message.type) val messageType = messageTypeOf(this.message.type)
list.add(Message( list.add(
Message(
id = this.message.id, id = this.message.id,
roomId = this.message.roomId, roomId = this.message.roomId,
message = this.message.message, message = this.message.message,
...@@ -81,7 +77,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -81,7 +77,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
role = this.message.role, role = this.message.role,
synced = this.message.synced, synced = this.message.synced,
unread = this.message.unread unread = this.message.unread
)) )
)
} }
} }
...@@ -105,12 +102,18 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -105,12 +102,18 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
val parsedUrl = url.hostname?.let { val parsedUrl = url.hostname?.let {
ParsedUrl(host = it) ParsedUrl(host = it)
} }
val meta = if (!url.description.isNullOrEmpty() || !url.imageUrl.isNullOrEmpty() || !url.title.isNullOrEmpty()) { val meta =
if (!url.description.isNullOrEmpty() || !url.imageUrl.isNullOrEmpty() || !url.title.isNullOrEmpty()) {
val raw = HashMap<String, String>() val raw = HashMap<String, String>()
if (url.description != null) raw["ogDescription"] = url.description if (url.description != null) raw["ogDescription"] = url.description
if (url.title != null) raw["ogTitle"] = url.title if (url.title != null) raw["ogTitle"] = url.title
if (url.imageUrl != null) raw["ogImage"] = url.imageUrl if (url.imageUrl != null) raw["ogImage"] = url.imageUrl
Meta(title = url.title,description = url.description, imageUrl = url.imageUrl, raw = raw) Meta(
title = url.title,
description = url.description,
imageUrl = url.imageUrl,
raw = raw
)
} else null } else null
list.add(Url(url = url.url, meta = meta, parsedUrl = parsedUrl)) list.add(Url(url = url.url, meta = meta, parsedUrl = parsedUrl))
...@@ -134,7 +137,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -134,7 +137,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
attachments.forEach { attachment -> attachments.forEach { attachment ->
with(attachment) { with(attachment) {
val fields = if (hasFields) { val fields = if (hasFields) {
withContext(CommonPool) { withContext(Dispatchers.IO) {
retryDB("getAttachmentFields(${attachment._id})") { retryDB("getAttachmentFields(${attachment._id})") {
dbManager.messageDao().getAttachmentFields(attachment._id) dbManager.messageDao().getAttachmentFields(attachment._id)
} }
...@@ -143,7 +146,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -143,7 +146,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
null null
} }
val actions = if (hasActions) { val actions = if (hasActions) {
withContext(CommonPool) { withContext(Dispatchers.IO) {
retryDB("getAttachmentActions(${attachment._id})") { retryDB("getAttachmentActions(${attachment._id})") {
dbManager.messageDao().getAttachmentActions(attachment._id) dbManager.messageDao().getAttachmentActions(attachment._id)
} }
...@@ -151,8 +154,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -151,8 +154,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
} else { } else {
null null
} }
list.add(
list.add(Attachment( Attachment(
title = title, title = title,
type = type, type = type,
description = description, description = description,
...@@ -181,7 +184,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -181,7 +184,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment
?: "vertical" else null, ?: "vertical" else null,
actions = actions actions = actions
)) )
)
} }
} }
return list return list
...@@ -189,9 +193,11 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -189,9 +193,11 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
private fun mapAction(action: AttachmentActionEntity): Action? { private fun mapAction(action: AttachmentActionEntity): Action? {
return when (action.type) { return when (action.type) {
"button" -> ButtonAction(action.type, action.text, action.url, action.isWebView, "button" -> ButtonAction(
action.type, action.text, action.url, action.isWebView,
action.webViewHeightRatio, action.imageUrl, action.message, action.webViewHeightRatio, action.imageUrl, action.message,
action.isMessageInChatWindow) action.isMessageInChatWindow
)
else -> null else -> null
} }
} }
......
...@@ -6,21 +6,21 @@ import chat.rocket.android.db.model.MessagesSync ...@@ -6,21 +6,21 @@ import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.util.retryDB import chat.rocket.android.util.retryDB
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
class DatabaseMessagesRepository( class DatabaseMessagesRepository(
private val dbManager: DatabaseManager, private val dbManager: DatabaseManager,
private val mapper: DatabaseMessageMapper private val mapper: DatabaseMessageMapper
) : MessagesRepository { ) : MessagesRepository {
override suspend fun getById(id: String): Message? = withContext(CommonPool) { override suspend fun getById(id: String): Message? = withContext(Dispatchers.IO) {
retryDB("getMessageById($id)") { retryDB("getMessageById($id)") {
dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) } 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 // 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) // duplicate rows (something related to our JOINS and relations on Room)
retryDB("getMessagesByRoomId($roomId)") { retryDB("getMessagesByRoomId($roomId)") {
...@@ -32,7 +32,8 @@ class DatabaseMessagesRepository( ...@@ -32,7 +32,8 @@ class DatabaseMessagesRepository(
} }
} }
override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> = withContext(CommonPool) { override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> =
withContext(Dispatchers.IO) {
retryDB("getRecentMessagesByRoomId($roomId, $count)") { retryDB("getRecentMessagesByRoomId($roomId, $count)") {
dbManager.messageDao().getRecentMessagesByRoomId(roomId, count) dbManager.messageDao().getRecentMessagesByRoomId(roomId, count)
.distinctBy { it.message.message.id } .distinctBy { it.message.message.id }
...@@ -51,20 +52,20 @@ class DatabaseMessagesRepository( ...@@ -51,20 +52,20 @@ class DatabaseMessagesRepository(
} }
override suspend fun removeById(id: String) { override suspend fun removeById(id: String) {
withContext(CommonPool) { withContext(Dispatchers.IO) {
retryDB("delete($id)") { dbManager.messageDao().delete(id) } retryDB("delete($id)") { dbManager.messageDao().delete(id) }
} }
} }
override suspend fun removeByRoomId(roomId: String) { override suspend fun removeByRoomId(roomId: String) {
withContext(CommonPool) { withContext(Dispatchers.IO) {
retryDB("deleteByRoomId($roomId)") { retryDB("deleteByRoomId($roomId)") {
dbManager.messageDao().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") { retryDB("getUnsentMessages") {
dbManager.messageDao().getUnsentMessages() dbManager.messageDao().getUnsentMessages()
.distinctBy { it.message.message.id } .distinctBy { it.message.message.id }
...@@ -76,7 +77,7 @@ class DatabaseMessagesRepository( ...@@ -76,7 +77,7 @@ class DatabaseMessagesRepository(
dbManager.sendOperation(Operation.SaveLastSync(MessagesSync(roomId, timeMillis))) 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)") { retryDB("getLastSync($roomId)") {
dbManager.messageDao().getLastSync(roomId)?.timestamp dbManager.messageDao().getLastSync(roomId)?.timestamp
} }
......
...@@ -45,10 +45,10 @@ import chat.rocket.core.internal.rest.serverInfo ...@@ -45,10 +45,10 @@ import chat.rocket.core.internal.rest.serverInfo
import chat.rocket.core.internal.rest.settingsOauth import chat.rocket.core.internal.rest.settingsOauth
import chat.rocket.core.internal.rest.unregisterPushToken import chat.rocket.core.internal.rest.unregisterPushToken
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
private const val SERVICE_NAME_FACEBOOK = "facebook" private const val SERVICE_NAME_FACEBOOK = "facebook"
...@@ -220,7 +220,7 @@ abstract class CheckServerPresenter constructor( ...@@ -220,7 +220,7 @@ abstract class CheckServerPresenter constructor(
} }
removeAccountInteractor?.remove(currentServer) removeAccountInteractor?.remove(currentServer)
tokenRepository?.remove(currentServer) tokenRepository?.remove(currentServer)
withContext(CommonPool) { dbManager.logout() } withContext(Dispatchers.IO) { dbManager.logout() }
navigator?.switchOrAddNewServer() navigator?.switchOrAddNewServer()
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.e(ex, "Error cleaning up the session...") Timber.e(ex, "Error cleaning up the session...")
......
...@@ -26,7 +26,6 @@ fun Context.changeServerIntent(serverUrl: String? = null, chatRoomId: String? = ...@@ -26,7 +26,6 @@ fun Context.changeServerIntent(serverUrl: String? = null, chatRoomId: String? =
class ChangeServerActivity : AppCompatActivity(), ChangeServerView { class ChangeServerActivity : AppCompatActivity(), ChangeServerView {
@Inject lateinit var presenter: ChangeServerPresenter @Inject lateinit var presenter: ChangeServerPresenter
var progress: ProgressDialog? = null var progress: ProgressDialog? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
......
...@@ -7,7 +7,7 @@ import chat.rocket.android.settings.presentation.SettingsView ...@@ -7,7 +7,7 @@ import chat.rocket.android.settings.presentation.SettingsView
import chat.rocket.android.settings.ui.SettingsFragment import chat.rocket.android.settings.ui.SettingsFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
@Module @Module
class SettingsFragmentModule { class SettingsFragmentModule {
......
...@@ -7,7 +7,7 @@ import chat.rocket.android.settings.password.presentation.PasswordView ...@@ -7,7 +7,7 @@ import chat.rocket.android.settings.password.presentation.PasswordView
import chat.rocket.android.settings.password.ui.PasswordFragment import chat.rocket.android.settings.password.ui.PasswordFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
@Module @Module
class PasswordFragmentModule { class PasswordFragmentModule {
......
...@@ -6,7 +6,9 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -6,7 +6,9 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.ChatRoomEntity import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.UserEntity import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.server.domain.GetConnectingServerInteractor import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.isJitsiEnabled
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
...@@ -14,8 +16,8 @@ import chat.rocket.android.util.retryIO ...@@ -14,8 +16,8 @@ import chat.rocket.android.util.retryIO
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.createDirectMessage import chat.rocket.core.internal.rest.createDirectMessage
import kotlinx.coroutines.experimental.DefaultDispatcher import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -24,13 +26,15 @@ class UserDetailsPresenter @Inject constructor( ...@@ -24,13 +26,15 @@ class UserDetailsPresenter @Inject constructor(
private val dbManager: DatabaseManager, private val dbManager: DatabaseManager,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: ChatRoomNavigator, private val navigator: ChatRoomNavigator,
serverInteractor: GetConnectingServerInteractor, settingsInteractor: GetSettingsInteractor,
serverInteractor: CurrentServerRepository,
factory: ConnectionManagerFactory factory: ConnectionManagerFactory
) { ) {
private var currentServer = serverInteractor.get()!! private var currentServer = serverInteractor.get()!!
private val manager = factory.create(currentServer) private val manager = factory.create(currentServer)
private val client = manager.client private val client = manager.client
private val interactor = FetchChatRoomsInteractor(client, dbManager) private val interactor = FetchChatRoomsInteractor(client, dbManager)
private val settings = settingsInteractor.get(currentServer)
private lateinit var userEntity: UserEntity private lateinit var userEntity: UserEntity
fun loadUserDetails(userId: String) { fun loadUserDetails(userId: String) {
...@@ -47,12 +51,13 @@ class UserDetailsPresenter @Inject constructor( ...@@ -47,12 +51,13 @@ class UserDetailsPresenter @Inject constructor(
userEntity.utcOffset // TODO Convert UTC and display like the mockup userEntity.utcOffset // TODO Convert UTC and display like the mockup
if (avatarUrl != null && username != null && name != null && utcOffset != null) { if (avatarUrl != null && username != null && name != null && utcOffset != null) {
view.showUserDetails( view.showUserDetailsAndActions(
avatarUrl = avatarUrl, avatarUrl = avatarUrl,
name = name, name = name,
username = username, username = username,
status = userEntity.status, status = userEntity.status,
utcOffset = utcOffset.toString() utcOffset = utcOffset.toString(),
isVideoCallAllowed = settings.isJitsiEnabled()
) )
} else { } else {
throw Exception() throw Exception()
...@@ -76,7 +81,7 @@ class UserDetailsPresenter @Inject constructor( ...@@ -76,7 +81,7 @@ class UserDetailsPresenter @Inject constructor(
try { try {
view.showLoading() view.showLoading()
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
val directMessage = retryIO("createDirectMessage($username") { val directMessage = retryIO("createDirectMessage($username") {
client.createDirectMessage(username) client.createDirectMessage(username)
} }
...@@ -118,4 +123,25 @@ class UserDetailsPresenter @Inject constructor( ...@@ -118,4 +123,25 @@ class UserDetailsPresenter @Inject constructor(
} }
} }
} }
fun toVideoConference(username: String) {
launchUI(strategy) {
try {
withContext(Dispatchers.Default) {
val directMessage = retryIO("createDirectMessage($username") {
client.createDirectMessage(username)
}
navigator.toVideoConference(directMessage.id, RoomType.DIRECT_MESSAGE)
}
} catch (ex: Exception) {
Timber.e(ex)
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
}
}
} }
...@@ -13,12 +13,14 @@ interface UserDetailsView : LoadingView, MessageView { ...@@ -13,12 +13,14 @@ interface UserDetailsView : LoadingView, MessageView {
* @param username The user's username. * @param username The user's username.
* @param status The user's status. * @param status The user's status.
* @param utcOffset The user's UTC offset. * @param utcOffset The user's UTC offset.
* @param isVideoCallAllowed True if the video call is allowed, false otherwise.
*/ */
fun showUserDetails( fun showUserDetailsAndActions(
avatarUrl: String, avatarUrl: String,
name: String, name: String,
username: String, username: String,
status: String, status: String,
utcOffset: String utcOffset: String,
isVideoCallAllowed: Boolean
) )
} }
...@@ -52,7 +52,8 @@ class UserDetailsFragment : Fragment(), UserDetailsView { ...@@ -52,7 +52,8 @@ class UserDetailsFragment : Fragment(), UserDetailsView {
arguments?.run { arguments?.run {
userId = getString(BUNDLE_USER_ID, "") userId = getString(BUNDLE_USER_ID, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } }
?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} }
override fun onCreateView( override fun onCreateView(
...@@ -76,12 +77,13 @@ class UserDetailsFragment : Fragment(), UserDetailsView { ...@@ -76,12 +77,13 @@ class UserDetailsFragment : Fragment(), UserDetailsView {
super.onDestroyView() super.onDestroyView()
} }
override fun showUserDetails( override fun showUserDetailsAndActions(
avatarUrl: String, avatarUrl: String,
name: String, name: String,
username: String, username: String,
status: String, status: String,
utcOffset: String utcOffset: String,
isVideoCallAllowed: Boolean
) { ) {
val requestBuilder = Glide.with(this).load(avatarUrl) val requestBuilder = Glide.with(this).load(avatarUrl)
...@@ -99,6 +101,13 @@ class UserDetailsFragment : Fragment(), UserDetailsView { ...@@ -99,6 +101,13 @@ class UserDetailsFragment : Fragment(), UserDetailsView {
// We should also setup the user details listeners. // We should also setup the user details listeners.
text_message.setOnClickListener { presenter.createDirectMessage(username) } text_message.setOnClickListener { presenter.createDirectMessage(username) }
if (isVideoCallAllowed) {
text_video_call.isVisible = true
text_video_call.setOnClickListener { presenter.toVideoConference(username) }
} else {
text_video_call.isVisible = false
}
} }
override fun showLoading() { override fun showLoading() {
......
...@@ -2,11 +2,10 @@ package chat.rocket.android.util ...@@ -2,11 +2,10 @@ package chat.rocket.android.util
import android.database.sqlite.SQLiteDatabaseLockedException import android.database.sqlite.SQLiteDatabaseLockedException
import chat.rocket.common.RocketChatNetworkErrorException import chat.rocket.common.RocketChatNetworkErrorException
import kotlinx.coroutines.experimental.TimeoutCancellationException import kotlinx.coroutines.delay
import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.isActive
import kotlinx.coroutines.experimental.isActive
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.experimental.coroutineContext import kotlin.coroutines.coroutineContext
const val DEFAULT_RETRY = 3 const val DEFAULT_RETRY = 3
private const val DEFAULT_DB_RETRY = 15 private const val DEFAULT_DB_RETRY = 15
...@@ -17,11 +16,11 @@ suspend fun <T> retryIO( ...@@ -17,11 +16,11 @@ suspend fun <T> retryIO(
initialDelay: Long = 100, // 0.1 second initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1000, // 1 second maxDelay: Long = 1000, // 1 second
factor: Double = 2.0, factor: Double = 2.0,
block: suspend () -> T): T block: suspend () -> T
{ ): T {
var currentDelay = initialDelay var currentDelay = initialDelay
repeat(times - 1) { currentTry -> repeat(times - 1) { currentTry ->
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled") if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryIO")
try { try {
return block() return block()
} catch (e: RocketChatNetworkErrorException) { } catch (e: RocketChatNetworkErrorException) {
...@@ -29,12 +28,12 @@ suspend fun <T> retryIO( ...@@ -29,12 +28,12 @@ suspend fun <T> retryIO(
e.printStackTrace() e.printStackTrace()
} }
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled") if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryIO")
delay(currentDelay) delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) 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 return block() // last attempt
} }
...@@ -44,11 +43,11 @@ suspend fun <T> retryDB( ...@@ -44,11 +43,11 @@ suspend fun <T> retryDB(
initialDelay: Long = 100, // 0.1 second initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1500, // 1.5 second maxDelay: Long = 1500, // 1.5 second
factor: Double = 1.2, factor: Double = 1.2,
block: suspend () -> T): T block: suspend () -> T
{ ): T {
var currentDelay = initialDelay var currentDelay = initialDelay
repeat(times - 1) { currentTry -> repeat(times - 1) { currentTry ->
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled") if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryDB")
try { try {
return block() return block()
} catch (e: SQLiteDatabaseLockedException) { } catch (e: SQLiteDatabaseLockedException) {
...@@ -56,11 +55,11 @@ suspend fun <T> retryDB( ...@@ -56,11 +55,11 @@ suspend fun <T> retryDB(
e.printStackTrace() e.printStackTrace()
} }
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled") if (!coroutineContext.isActive) throw Exception("Job canceled when trying to execute retryDB")
delay(currentDelay) delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) 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 return block() // last attempt
} }
\ No newline at end of file
...@@ -3,9 +3,10 @@ package chat.rocket.android.util.extensions ...@@ -3,9 +3,10 @@ package chat.rocket.android.util.extensions
import android.os.Looper import android.os.Looper
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
inline fun Fragment.ui(crossinline block: (activity: FragmentActivity) -> Unit): Job? { 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 // 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): ...@@ -16,7 +17,7 @@ inline fun Fragment.ui(crossinline block: (activity: FragmentActivity) -> Unit):
null null
} else { } else {
// Launch a Job on the UI context and check again if the activity and view are still valid // 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) { if (activity != null && view != null && context != null) {
block(activity!!) block(activity!!)
} }
......
...@@ -7,15 +7,15 @@ import chat.rocket.android.util.retryIO ...@@ -7,15 +7,15 @@ import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.internal.rest.registerPushToken
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import chat.rocket.core.model.asString import chat.rocket.core.model.asString
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
suspend fun RocketChatClientFactory.registerPushToken( suspend fun RocketChatClientFactory.registerPushToken(
token: String, token: String,
accounts: List<Account> accounts: List<Account>
) { ) {
withContext(CommonPool) { withContext(Dispatchers.IO) {
accounts.forEach { account -> accounts.forEach { account ->
try { try {
retryIO(description = "register push token: ${account.serverUrl}") { retryIO(description = "register push token: ${account.serverUrl}") {
......
...@@ -2,28 +2,28 @@ package chat.rocket.android.util.livedata ...@@ -2,28 +2,28 @@ package chat.rocket.android.util.livedata
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.Job
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import kotlin.coroutines.experimental.CoroutineContext import kotlin.coroutines.CoroutineContext
class TransformedLiveData<Source, Output>( class TransformedLiveData<Source, Output>(
private val runContext: CoroutineContext = CommonPool, private val runContext: CoroutineContext = Dispatchers.IO,
private val source: LiveData<Source>, private val source: LiveData<Source>,
private val transformation: (Source?) -> Output?) private val transformation: (Source?) -> Output?
: LiveData<Output>() { ) : LiveData<Output>() {
private var job: Job? = null private var job: Job? = null
private val observer = Observer<Source> { source -> private val observer = Observer<Source> { source ->
job?.cancel() job?.cancel()
job = launch(runContext) { job = GlobalScope.launch(runContext) {
transformation(source)?.let { transformed -> transformation(source)?.let { transformed ->
// Could have used postValue instead, but using the UI context I can guarantee that // Could have used postValue instead, but using the UI context I can guarantee that
// a canceled job will never emit values. // a canceled job will never emit values.
withContext(UI) { withContext(Dispatchers.Main) {
value = transformed value = transformed
} }
} }
...@@ -41,5 +41,6 @@ class TransformedLiveData<Source, Output>( ...@@ -41,5 +41,6 @@ class TransformedLiveData<Source, Output>(
} }
fun <Source, Output> LiveData<Source>.transform( fun <Source, Output> LiveData<Source>.transform(
runContext: CoroutineContext = CommonPool, runContext: CoroutineContext = Dispatchers.IO,
transformation: (Source?) -> Output?) = TransformedLiveData(runContext, this, transformation) transformation: (Source?) -> Output?
\ No newline at end of file ) = TransformedLiveData(runContext, this, transformation)
\ No newline at end of file
...@@ -3,21 +3,22 @@ package chat.rocket.android.util.livedata ...@@ -3,21 +3,22 @@ package chat.rocket.android.util.livedata
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.Job
import kotlin.coroutines.experimental.CoroutineContext import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
class WrappedLiveData<Source, Output>( class WrappedLiveData<Source, Output>(
private val runContext: CoroutineContext = CommonPool, private val runContext: CoroutineContext = Dispatchers.IO,
private val source: LiveData<Source>, private val source: LiveData<Source>,
private val transformation: suspend (Source?, MutableLiveData<Output>) -> Unit) private val transformation: suspend (Source?, MutableLiveData<Output>) -> Unit
: MutableLiveData<Output>() { ) : MutableLiveData<Output>() {
private var job: Job? = null private var job: Job? = null
private val observer = Observer<Source> { source -> private val observer = Observer<Source> { source ->
job?.cancel() job?.cancel()
job = launch(runContext) { job = GlobalScope.launch(runContext) {
transformation(source, this@WrappedLiveData) transformation(source, this@WrappedLiveData)
} }
} }
...@@ -33,6 +34,7 @@ class WrappedLiveData<Source, Output>( ...@@ -33,6 +34,7 @@ class WrappedLiveData<Source, Output>(
} }
fun <Source, Output> LiveData<Source>.wrap( fun <Source, Output> LiveData<Source>.wrap(
runContext: CoroutineContext = CommonPool, runContext: CoroutineContext = Dispatchers.IO,
transformation: suspend (Source?, MutableLiveData<Output>) -> Unit) = transformation: suspend (Source?, MutableLiveData<Output>) -> Unit
) =
WrappedLiveData(runContext, this, transformation) WrappedLiveData(runContext, this, transformation)
\ No newline at end of file
package chat.rocket.android.videoconference.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.videoconference.presenter.JitsiVideoConferenceView
import chat.rocket.android.videoconference.ui.VideoConferenceActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.Job
@Module
class VideoConferenceModule {
@Provides
@PerActivity
fun provideVideoConferenceView(activity: VideoConferenceActivity): JitsiVideoConferenceView {
return activity
}
@Provides
@PerActivity
fun provideJob() = Job()
@Provides
@PerActivity
fun provideLifecycleOwner(activity: VideoConferenceActivity): LifecycleOwner = activity
@Provides
@PerActivity
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy =
CancelStrategy(owner, jobs)
}
\ No newline at end of file
package chat.rocket.android.videoconference.presenter
interface JitsiVideoConferenceView {
/**
* Starts the Jitsi video conference.
*
* @param url The video conference URL to be loaded.
* @param name The user name to be show on the video conference.
*/
fun startJitsiVideoConference(url: String, name: String?)
/**
* Finishes the Jitsi video conference.
*/
fun finishJitsiVideoConference()
/**
* Logs the state of the Jitsi Meet conference displayed in a JitsiMeetView.
*
* @param message The message to log.
* @param map the map information by Jitsi
*/
fun logJitsiMeetViewState(message: String, map: MutableMap<String, Any>?)
}
\ No newline at end of file
package chat.rocket.android.videoconference.presenter
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.SubscriptionTypeEvent
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.JitsiHelper
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.updateJitsiTimeout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.*
import javax.inject.Inject
import kotlin.concurrent.timer
class VideoConferencePresenter @Inject constructor(
private val view: JitsiVideoConferenceView,
private val strategy: CancelStrategy,
private val currentServerRepository: CurrentServerRepository,
private val connectionManagerFactory: ConnectionManagerFactory,
private val settings: GetSettingsInteractor,
private val userHelp: UserHelper,
private val analyticsManager: AnalyticsManager
) {
private lateinit var client: RocketChatClient
private lateinit var publicSettings: PublicSettings
private lateinit var chatRoomId: String
private lateinit var chatRoomType: String
private lateinit var timer: Timer
fun setup(chatRoomId: String, chatRoomType: String) {
currentServerRepository.get()?.let {
client = connectionManagerFactory.create(it).client
publicSettings = settings.get(it)
}
this.chatRoomId = chatRoomId
this.chatRoomType = chatRoomType
}
fun initVideoConference() {
launchUI(strategy) {
try {
with(publicSettings) {
view.startJitsiVideoConference(
JitsiHelper.getJitsiUrl(
isJitsiSSL(),
jitsiDomain(),
jitsiPrefix(),
uniqueIdentifier(),
chatRoomId
),
userHelp.user()?.username
)
updateJitsiTimeout()
logVideoConferenceEvent()
}
} catch (ex: Exception) {
Timber.e(ex)
view.finishJitsiVideoConference()
}
}
}
fun invalidateTimer() = timer.cancel()
// Jitsi update call needs to be called every 10 seconds to make sure call is not ended and is available to web users.
private fun updateJitsiTimeout() {
timer = timer(daemon = false, initialDelay = 0L, period = 10000) {
GlobalScope.launch(Dispatchers.IO + strategy.jobs) {
client.updateJitsiTimeout(chatRoomId)
}
}
}
private fun logVideoConferenceEvent() = when {
roomTypeOf(chatRoomType) is RoomType.DirectMessage ->
analyticsManager.logVideoConference(SubscriptionTypeEvent.DirectMessage)
roomTypeOf(chatRoomType) is RoomType.Channel ->
analyticsManager.logVideoConference(SubscriptionTypeEvent.Channel)
else -> analyticsManager.logVideoConference(SubscriptionTypeEvent.Group)
}
}
package chat.rocket.android.videoconference.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.os.bundleOf
import chat.rocket.android.videoconference.presenter.JitsiVideoConferenceView
import chat.rocket.android.videoconference.presenter.VideoConferencePresenter
import dagger.android.AndroidInjection
import org.jitsi.meet.sdk.JitsiMeetActivity
import org.jitsi.meet.sdk.JitsiMeetView
import org.jitsi.meet.sdk.JitsiMeetViewListener
import timber.log.Timber
import javax.inject.Inject
fun Context.videoConferenceIntent(chatRoomId: String, chatRoomType: String): Intent =
Intent(this, VideoConferenceActivity::class.java)
.putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
.putExtra(INTENT_CHAT_ROOM_TYPE, chatRoomType)
private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
private const val INTENT_CHAT_ROOM_TYPE = "chat_room_type"
class VideoConferenceActivity : JitsiMeetActivity(), JitsiVideoConferenceView,
JitsiMeetViewListener {
@Inject
lateinit var presenter: VideoConferencePresenter
private lateinit var chatRoomId: String
private lateinit var chatRoomType: String
private var view: JitsiMeetView? = null
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" }
chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE)
requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" }
view = JitsiMeetView(this)
view?.listener = this
setContentView(view)
presenter.setup(chatRoomId, chatRoomType)
presenter.initVideoConference()
}
override fun onConferenceWillJoin(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Joining video conferencing", map)
override fun onConferenceJoined(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Joined video conferencing", map)
override fun onConferenceWillLeave(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Leaving video conferencing", map)
override fun onConferenceLeft(map: MutableMap<String, Any>?) {
logJitsiMeetViewState("Left video conferencing", map)
finishJitsiVideoConference()
}
override fun onLoadConfigError(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Error loading video conference config", map)
override fun onConferenceFailed(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Video conference failed", map)
override fun startJitsiVideoConference(url: String, name: String?) {
view?.loadURLObject(
bundleOf(
"config" to bundleOf(
"startWithAudioMuted" to true,
"startWithVideoMuted" to true
),
"context" to bundleOf(
"user" to bundleOf("name" to name),
"iss" to "rocketchat-android"
),
"url" to url
)
)
}
override fun finishJitsiVideoConference() {
presenter.invalidateTimer()
view?.dispose()
view = null
finish()
}
override fun logJitsiMeetViewState(message: String, map: MutableMap<String, Any>?) =
Timber.i("$message: $map")
}
...@@ -31,8 +31,8 @@ class WebViewActivity : AppCompatActivity() { ...@@ -31,8 +31,8 @@ class WebViewActivity : AppCompatActivity() {
setContentView(R.layout.activity_web_view) setContentView(R.layout.activity_web_view)
webPageUrl = intent.getStringExtra(INTENT_WEB_PAGE_URL) webPageUrl = intent.getStringExtra(INTENT_WEB_PAGE_URL)
toolbarTitle = intent.getStringExtra(TOOLBAR_TITLE)
requireNotNull(webPageUrl) { "no web_page_url provided in Intent extras" } requireNotNull(webPageUrl) { "no web_page_url provided in Intent extras" }
toolbarTitle = intent.getStringExtra(TOOLBAR_TITLE)
setupToolbar() setupToolbar()
} }
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#FFFFFFFF"
android:fillType="nonZero"
android:pathData="M14.748,13.572l3.752,3.752 -1.176,1.176 -3.752,-3.752a7.435,7.435 0,1 1,1.176 -1.176zM8.935,14.952a6.017,6.017 0,1 0,0 -12.034,6.017 6.017,0 0,0 0,12.035z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:viewportWidth="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="#FF000000"
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#1d74f5"
android:fillType="evenOdd"
android:pathData="M16.538,13.564l-3.388,-1.09v-2.07l3.394,-1.085 -0.006,4.245zM11.696,14.56L3.454,14.56L3.454,8.32h8.242v6.24zM17.428,8.107c0.362,0.261 0.57,0.69 0.57,1.176v4.312c0,0.487 -0.209,0.914 -0.57,1.175a1.37,1.37 0,0 1,-0.808 0.254c-0.164,0 -0.331,-0.026 -0.498,-0.08l-2.972,-0.956L13.15,16L2,16L2,6.88h11.15v2.01l2.973,-0.956c0.468,-0.15 0.943,-0.087 1.305,0.173zM4.424,5.44L4.424,4h6.302v1.44L4.424,5.44z" />
</vector>
...@@ -17,16 +17,11 @@ ...@@ -17,16 +17,11 @@
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<TextView <TextView
android:id="@+id/text_room_name" android:id="@+id/text_toolbar_title"
style="@style/ChatRoom.ChatName.TextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawablePadding="@dimen/text_view_drawable_padding" tools:text="Product Team" />
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/colorWhite"
android:textSize="18sp"
android:textStyle="bold"
tools:text="general" />
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:background="@color/whitesmoke"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="@color/whitesmoke">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<RelativeLayout <RelativeLayout
android:background="@color/colorWhite"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="15dp"> android:background="@color/colorWhite"
android:padding="16dp">
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
style="@style/ChatDetails.Title.TextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="#important" />
<TextView
android:id="@+id/title_description"
style="@style/ChatDetails.Title.TextView" style="@style/ChatDetails.Title.TextView"
android:drawablePadding="@dimen/text_view_drawable_padding" android:layout_width="match_parent"
tools:text="#important"/> android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_marginTop="20dp"
android:text="@string/title_description" />
<TextView
android:id="@+id/content_description"
style="@style/ChatDetails.Content.TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/title_description"
android:text="@string/msg_no_description" />
<TextView <TextView
android:id="@+id/title_topic" android:id="@+id/title_topic"
style="@style/ChatDetails.Title.TextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/name" android:layout_below="@+id/content_description"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
style="@style/ChatDetails.Title.TextView"
android:text="@string/title_topic" /> android:text="@string/title_topic" />
<TextView <TextView
android:id="@+id/content_topic" android:id="@+id/content_topic"
style="@style/ChatDetails.Content.TextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/title_topic" android:layout_below="@+id/title_topic"
style="@style/ChatDetails.Content.TextView"
android:text="@string/msg_no_topic" /> android:text="@string/msg_no_topic" />
<TextView <TextView
android:id="@+id/title_announcement" android:id="@+id/title_announcement"
style="@style/ChatDetails.Title.TextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/content_topic" android:layout_below="@+id/content_topic"
android:layout_marginTop="15dp" android:layout_marginTop="20dp"
style="@style/ChatDetails.Title.TextView"
android:text="@string/title_announcement" /> android:text="@string/title_announcement" />
<TextView <TextView
android:id="@+id/content_announcement" android:id="@+id/content_announcement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title_announcement"
style="@style/ChatDetails.Content.TextView" style="@style/ChatDetails.Content.TextView"
android:text="@string/msg_no_announcement" />
<TextView
android:id="@+id/title_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/content_announcement" android:layout_below="@+id/title_announcement"
android:layout_marginTop="15dp" android:text="@string/msg_no_announcement" />
style="@style/ChatDetails.Title.TextView"
android:text="@string/title_description" />
<TextView
android:id="@+id/content_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title_description"
android:layout_marginBottom="10dp"
style="@style/ChatDetails.Content.TextView"
android:text="@string/msg_no_description" />
</RelativeLayout> </RelativeLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/options" android:id="@+id/options"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
\ No newline at end of file
...@@ -8,12 +8,16 @@ ...@@ -8,12 +8,16 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@android:color/white"> android:background="@android:color/white"
android:paddingBottom="@dimen/screen_edge_left_and_right_margins">
<ImageView <ImageView
android:id="@+id/image_blur" android:id="@+id/image_blur"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="120dp" /> android:layout_height="120dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
android:id="@+id/image_arrow_back" android:id="@+id/image_arrow_back"
...@@ -66,10 +70,24 @@ ...@@ -66,10 +70,24 @@
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:drawableTop="@drawable/ic_message_24dp" android:drawableTop="@drawable/ic_message_24dp"
android:text="@string/msg_message" android:text="@string/msg_message"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@+id/text_video_call"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_username" /> app:layout_constraintTop_toBottomOf="@+id/text_username" />
<TextView
android:id="@+id/text_video_call"
style="@style/UserDetails.TextView.Actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:drawableTop="@drawable/ic_video_24dp"
android:text="@string/msg_video_call"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/text_message"
app:layout_constraintTop_toBottomOf="@+id/text_username"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/text_title_status" android:id="@+id/text_title_status"
style="@style/UserDetails.TextView.Title" style="@style/UserDetails.TextView.Title"
......
...@@ -158,6 +158,16 @@ ...@@ -158,6 +158,16 @@
app:layout_constraintTop_toBottomOf="@+id/message_header" 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!" /> 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 <include
layout="@layout/layout_reactions" layout="@layout/layout_reactions"
android:layout_width="0dp" android:layout_width="0dp"
......
...@@ -29,8 +29,9 @@ ...@@ -29,8 +29,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent"
tools:text="Filename.png" tools:text="Filename.png"
tools:visibility="visible"/> tools:visibility="visible" />
<TextView <TextView
android:id="@+id/file_description" android:id="@+id/file_description"
...@@ -40,31 +41,31 @@ ...@@ -40,31 +41,31 @@
app:layout_constraintStart_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/file_name" app:layout_constraintTop_toBottomOf="@id/file_name"
tools:text="Some description" tools:text="Some description"
tools:visibility="visible"/> tools:visibility="visible" />
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_attachment" android:id="@+id/image_attachment"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="150dp" android:layout_height="150dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/file_description" app:layout_constraintTop_toBottomOf="@id/file_description"
fresco:actualImageScaleType="centerCrop" fresco:actualImageScaleType="centerCrop"
fresco:placeholderImage="@drawable/image_dummy" fresco:placeholderImage="@drawable/image_dummy"
tools:background="@drawable/image_dummy" tools:background="@drawable/image_dummy"
tools:visibility="visible"/> tools:visibility="visible" />
<FrameLayout <FrameLayout
android:id="@+id/audio_video_attachment" android:id="@+id/audio_video_attachment"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="150dp" android:layout_height="150dp"
android:background="@color/colorBlack" android:background="@color/colorBlack"
android:visibility="gone"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:layout_constraintStart_toStartOf="@id/guideline" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/image_attachment" app:layout_constraintTop_toBottomOf="@id/image_attachment"
tools:visibility="gone"> tools:visibility="gone">
...@@ -85,21 +86,21 @@ ...@@ -85,21 +86,21 @@
app:layout_constraintStart_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/audio_video_attachment" app:layout_constraintTop_toBottomOf="@id/audio_video_attachment"
tools:text="Some text" tools:text="Some text"
tools:visibility="visible"/> tools:visibility="visible" />
<TextView <TextView
android:id="@+id/text_file_name" android:id="@+id/text_file_name"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:drawableStart="@drawable/ic_files_24dp" android:drawableStart="@drawable/ic_files_24dp"
android:drawablePadding="6dp" android:drawablePadding="6dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="@color/colorAccent"
android:textDirection="locale"
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/file_text" app:layout_constraintTop_toBottomOf="@id/file_text"
android:textDirection="locale"
tools:text="This is a very, very, very long filename, to test how the layout will work on very very very long filenames.pdf" tools:text="This is a very, very, very long filename, to test how the layout will work on very very very long filenames.pdf"
tools:visibility="visible" /> tools:visibility="visible" />
<!-- END File attachments --> <!-- END File attachments -->
...@@ -108,13 +109,13 @@ ...@@ -108,13 +109,13 @@
android:id="@+id/quote_bar" android:id="@+id/quote_bar"
android:layout_width="4dp" android:layout_width="4dp"
android:layout_height="0dp" android:layout_height="0dp"
android:src="@drawable/quote_vertical_gray_bar"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/quote_vertical_gray_bar"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/actions_list" app:layout_constraintBottom_toBottomOf="@+id/actions_list"
app:layout_constraintStart_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/text_file_name" app:layout_constraintTop_toBottomOf="@id/text_file_name"
tools:visibility="visible"/> tools:visibility="visible" />
<!-- Message attachment --> <!-- Message attachment -->
<TextView <TextView
...@@ -173,27 +174,27 @@ ...@@ -173,27 +174,27 @@
android:id="@+id/author_icon" android:id="@+id/author_icon"
android:layout_width="8dp" android:layout_width="8dp"
android:layout_height="8dp" android:layout_height="8dp"
android:layout_marginTop="6dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="6dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/text_view_more"
app:layout_constraintStart_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/text_view_more"
tools:src="@tools:sample/avatars" tools:src="@tools:sample/avatars"
tools:visibility="visible"/> tools:visibility="visible" />
<TextView <TextView
android:id="@+id/text_author_name" android:id="@+id/text_author_name"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="@color/colorAccent"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/text_view_more"
app:layout_constraintStart_toEndOf="@id/author_icon"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/author_icon"
app:layout_constraintTop_toBottomOf="@id/text_view_more"
tools:text="#5571 - User profile from SSO must not have password change option" tools:text="#5571 - User profile from SSO must not have password change option"
tools:visibility="visible"/> tools:visibility="visible" />
<!-- END author --> <!-- END author -->
<!-- TEXT --> <!-- TEXT -->
...@@ -202,15 +203,16 @@ ...@@ -202,15 +203,16 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:textColor="@color/colorAccent"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="@color/colorAccent"
android:textDirection="locale"
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/text_author_name" app:layout_constraintTop_toBottomOf="@id/text_author_name"
android:textDirection="locale"
tools:text="This is a very, very, very long filename, to test how the layout will work on very very very long filenames.pdf" tools:text="This is a very, very, very long filename, to test how the layout will work on very very very long filenames.pdf"
tools:visibility="visible" /> tools:visibility="visible" />
<TextView <TextView
android:id="@+id/attachment_text" android:id="@+id/attachment_text"
android:layout_width="0dp" android:layout_width="0dp"
...@@ -218,11 +220,11 @@ ...@@ -218,11 +220,11 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/file_name_not_file_type"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/file_name_not_file_type"
tools:text="#5571 - User profile from SSO must not have password change option" tools:text="#5571 - User profile from SSO must not have password change option"
tools:visibility="visible"/> tools:visibility="visible" />
<!-- END TEXT --> <!-- END TEXT -->
<!-- Fields --> <!-- Fields -->
...@@ -230,14 +232,14 @@ ...@@ -230,14 +232,14 @@
android:id="@+id/text_fields" android:id="@+id/text_fields"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
app:layout_constraintTop_toBottomOf="@id/attachment_text" android:visibility="gone"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/attachment_text"
tools:text="line1\nline2\n\nline3\nline4" tools:text="line1\nline2\n\nline3\nline4"
tools:visibility="visible"/> tools:visibility="visible" />
<!-- END Fields --> <!-- END Fields -->
<!-- Actions --> <!-- Actions -->
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:foregroundGravity="center" android:foregroundGravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<include layout="@layout/emoji_row_item" /> <include layout="@layout/emoji_row_item" />
......
...@@ -62,12 +62,14 @@ ...@@ -62,12 +62,14 @@
<string name="action_register">Registrieren</string> <string name="action_register">Registrieren</string>
<string name="action_confirm">Bestätigen</string> <string name="action_confirm">Bestätigen</string>
<string name="action_delete_account">Konto löschen</string> <string name="action_delete_account">Konto löschen</string>
<string name="action_favorite">Favoriten</string>
<string name="action_remove_favorite">Favoriten entfernen</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Eigenschaften</item> <item name="item_preferences">Eigenschaften</item>
<item name="item_password">Passwort ändern</item> <item name="item_password">Passwort ändern</item>
<item name="change_language">Change language</item> <!-- TODO Add translation --> <item name="change_language">Sprache ändern</item>
<item name="item_share_app">Link zur App teilen</item> <item name="item_share_app">Link zur App teilen</item>
<item name="item_rate_us">Bewerten Sie uns</item> <item name="item_rate_us">Bewerten Sie uns</item>
<item name="item_contact_us">Kontaktieren Sie uns</item> <item name="item_contact_us">Kontaktieren Sie uns</item>
...@@ -97,6 +99,8 @@ ...@@ -97,6 +99,8 @@
<string name="msg_yesterday">Gestern</string> <string name="msg_yesterday">Gestern</string>
<string name="msg_today">Heute</string> <string name="msg_today">Heute</string>
<string name="msg_message">Nachricht</string> <string name="msg_message">Nachricht</string>
<string name="msg_video_call">Videoanruf</string>
<string name="msg_join_video_call">Videoanruf beitreten</string>
<string name="msg_this_room_is_read_only">Dieser Raum ist nur lesen</string> <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_2fa_code">Falscher 2FA Code</string>
<string name="msg_invalid_file">Falsche Datei</string> <string name="msg_invalid_file">Falsche Datei</string>
...@@ -128,7 +132,7 @@ ...@@ -128,7 +132,7 @@
<string name="msg_preview_photo">Bild</string> <string name="msg_preview_photo">Bild</string>
<string name="msg_preview_file">Datei</string> <string name="msg_preview_file">Datei</string>
<string name="msg_no_messages_yet">Noch keine Nachrichten</string> <string name="msg_no_messages_yet">Noch keine Nachrichten</string>
<string name="msg_build">Build %1$d - %2$s - %3$s</string> <!-- TODO Add translation --> <string name="msg_build">Build %1$d - %2$s - %3$s</string>
<string name="msg_update_app_version_in_order_to_continue">Server Version veraltet. Bitte kontaktieren Sie ihren Server Administrator.</string> <string name="msg_update_app_version_in_order_to_continue">Server Version veraltet. Bitte kontaktieren Sie ihren Server Administrator.</string>
<string name="msg_ver_not_recommended">Die Server Version scheint älter als die empfolene Version %1$s zu sein.\nSie können sich trotzdem einloggen, aber es kann zu einem unerwartetem Verhalten kommen.</string> <string name="msg_ver_not_recommended">Die Server Version scheint älter als die empfolene Version %1$s zu sein.\nSie können sich trotzdem einloggen, aber es kann zu einem unerwartetem Verhalten kommen.</string>
<string name="msg_ver_not_minimum">Die Server Version scheint älter als die minimale Version %1$s zu sein.\nBitte updaten Sie Ihren Server um sich einloggen zu können!</string> <string name="msg_ver_not_minimum">Die Server Version scheint älter als die minimale Version %1$s zu sein.\nBitte updaten Sie Ihren Server um sich einloggen zu können!</string>
...@@ -164,15 +168,16 @@ ...@@ -164,15 +168,16 @@
<string name="msg_two_factor_authentication">Zwei-Faktor-Authentifizierung</string> <string name="msg_two_factor_authentication">Zwei-Faktor-Authentifizierung</string>
<string name="msg__your_2fa_code">Wie lautet Ihr F2A Code?</string> <string name="msg__your_2fa_code">Wie lautet Ihr F2A Code?</string>
<string name="msg_muted_on_this_channel">Sie sind auf diesem Kanal stummgeschaltet</string> <string name="msg_muted_on_this_channel">Sie sind auf diesem Kanal stummgeschaltet</string>
<string name="msg_no_topic">Kein Thema hinzugefügt</string> <string name="msg_no_topic">Keine Überschrift</string>
<string name="msg_no_announcement">Keine Ankündigung hinzugefügt</string> <string name="msg_no_announcement">Keine Ankündigung</string>
<string name="msg_no_description">Keine Beschreibung hinzugefügt</string> <string name="msg_no_description">Keine Beschreibung</string>
<string name="msg_unable_to_update_password">Änderung des Passworts nicht möglich. Fehlermeldung: %1$s</string> <string name="msg_unable_to_update_password">Änderung des Passworts nicht möglich. Fehlermeldung: %1$s</string>
<string name="msg_password_updated_successfully">Passwort erfolgreich geändert</string> <string name="msg_password_updated_successfully">Passwort erfolgreich geändert</string>
<plurals name="msg_reacted_with_"> <plurals name="msg_reacted_with_">
<item quantity="one">%1$ s reagierte mit %2$s</item> <item quantity="one">%1$ s reagierte mit %2$s</item>
<item quantity="other">%1$s reagierte mit %2$s</item> <item quantity="other">%1$s reagierte mit %2$s</item>
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Login-Daten erfolgreich gespeichert</string>
<!-- Create channel messages --> <!-- Create channel messages -->
...@@ -216,7 +221,7 @@ ...@@ -216,7 +221,7 @@
<string name="message_unmuted">Benutzer %1$s nicht mehr stumm geschaltet von %2$s</string> <string name="message_unmuted">Benutzer %1$s nicht mehr stumm geschaltet von %2$s</string>
<string name="message_role_add">%1$s wurde gesetzt %2$s von %3$s</string> <string name="message_role_add">%1$s wurde gesetzt %2$s von %3$s</string>
<string name="message_role_removed">%1$s ist nicht länger %2$s von %3$s</string> <string name="message_role_removed">%1$s ist nicht länger %2$s von %3$s</string>
<string name="message_credentials_saved_successfully">Login-Daten erfolgreich gespeichert</string> <string name="message_video_call_started">Videoanruf von %1$s gestartet</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Antworten</string> <string name="action_msg_reply">Antworten</string>
...@@ -244,10 +249,6 @@ ...@@ -244,10 +249,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Suche Nachricht</string> <string name="title_search_message">Suche Nachricht</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Chat favorisieren</string>
<string name="title_unfavorite_chat">Chat nicht favorisieren</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Benutzer</string> <string name="title_members_list">Benutzer</string>
...@@ -324,7 +325,7 @@ ...@@ -324,7 +325,7 @@
<string name="chatroom_header">Kopf</string> <string name="chatroom_header">Kopf</string>
<!--ChatRooms Headers--> <!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation --> <string name="header_favorite">Favoriten</string>
<string name="header_channel">Räume</string> <string name="header_channel">Räume</string>
<string name="header_private_groups">Private Räume</string> <string name="header_private_groups">Private Räume</string>
<string name="header_direct_messages">Direkt Nachrichten</string> <string name="header_direct_messages">Direkt Nachrichten</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">Register</string> <!-- TODO Add translation --> <string name="action_register">Register</string> <!-- TODO Add translation -->
<string name="action_confirm">Confirm</string> <!-- TODO Add translation --> <string name="action_confirm">Confirm</string> <!-- TODO Add translation -->
<string name="action_delete_account">Delete account</string> <!-- TODO Add translation --> <string name="action_delete_account">Delete account</string> <!-- TODO Add translation -->
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">Ayer</string> <string name="msg_yesterday">Ayer</string>
<string name="msg_today">Hoy</string> <string name="msg_today">Hoy</string>
<string name="msg_message">Mensaje</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_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_2fa_code">Código 2FA no válido</string>
<string name="msg_invalid_file">Archivo inválido</string> <string name="msg_invalid_file">Archivo inválido</string>
...@@ -176,15 +180,16 @@ ...@@ -176,15 +180,16 @@
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation --> <string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<string name="msg_no_topic">No topic added</string> <!-- TODO Add translation --> <string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">No announcement added</string> <!-- TODO Add translation --> <string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">No description added</string> <!-- TODO Add translation --> <string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation --> <string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation -->
<string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation --> <string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_"> <plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation --> <item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation --> <item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Credenciales guardadas con éxito</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation --> <string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation -->
...@@ -205,7 +210,7 @@ ...@@ -205,7 +210,7 @@
<string name="message_unmuted">Usuario %1$s no silenciado por %2$s</string> <string name="message_unmuted">Usuario %1$s no silenciado por %2$s</string>
<string name="message_role_add">%1$s fue establecido %2$s por %3$s</string> <string name="message_role_add">%1$s fue establecido %2$s por %3$s</string>
<string name="message_role_removed">%1$s ya no es %2$s por %3$s</string> <string name="message_role_removed">%1$s ya no es %2$s por %3$s</string>
<string name="message_credentials_saved_successfully">Credenciales guardadas con éxito</string> <string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Respuesta</string> <string name="action_msg_reply">Respuesta</string>
...@@ -235,10 +240,6 @@ ...@@ -235,10 +240,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Búsqueda de mensajes</string> <string name="title_search_message">Búsqueda de mensajes</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Chat favorito</string>
<string name="title_unfavorite_chat">Deshacer chat favorito</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Miembros</string> <string name="title_members_list">Miembros</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">ثبت‌نام</string> <string name="action_register">ثبت‌نام</string>
<string name="action_confirm">تایید</string> <string name="action_confirm">تایید</string>
<string name="action_delete_account">حذف حساب کاربری</string> <string name="action_delete_account">حذف حساب کاربری</string>
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">دیروز</string> <string name="msg_yesterday">دیروز</string>
<string name="msg_today">امروز</string> <string name="msg_today">امروز</string>
<string name="msg_message">پیام</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_this_room_is_read_only">این اتاق فقط خواندنی است</string>
<string name="msg_invalid_2fa_code">Invalid 2FA Code</string> <!-- TODO Add translation --> <string name="msg_invalid_2fa_code">Invalid 2FA Code</string> <!-- TODO Add translation -->
<string name="msg_invalid_file">پرونده‌ی نامعتبر</string> <string name="msg_invalid_file">پرونده‌ی نامعتبر</string>
...@@ -161,9 +165,9 @@ ...@@ -161,9 +165,9 @@
<string name="msg_two_factor_authentication">تایید هویت دو فاکتوره</string> <string name="msg_two_factor_authentication">تایید هویت دو فاکتوره</string>
<string name="msg__your_2fa_code">کد 2FA تان چیست؟</string> <string name="msg__your_2fa_code">کد 2FA تان چیست؟</string>
<string name="msg_permalink_copied">پیوند کپی شد</string> <string name="msg_permalink_copied">پیوند کپی شد</string>
<string name="msg_no_topic">هیچ موضوعی اضافه نشد</string> <string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">هیچ اعلانی اضافه نشد</string> <string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">هیچ توضیحی اضافه نشد</string> <string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_send_email">ارسال ایمیل</string> <string name="msg_send_email">ارسال ایمیل</string>
<string name="msg_android_app_support">حمایت از اپ اندروید</string> <string name="msg_android_app_support">حمایت از اپ اندروید</string>
<string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO Add translation --> <string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO Add translation -->
...@@ -172,6 +176,7 @@ ...@@ -172,6 +176,7 @@
<item quantity="one">%1$s reacted with %2$s</item> <item quantity="one">%1$s reacted with %2$s</item>
<item quantity="other">%1$s reacted with %2$s</item> <item quantity="other">%1$s reacted with %2$s</item>
</plurals> <!-- TODO Add translation --> </plurals> <!-- TODO Add translation -->
<string name="msg_credentials_saved_successfully">اختیارها با موفقیت ذخیره شد</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">خصوصی</string> <string name="msg_private_channel">خصوصی</string>
...@@ -210,7 +215,7 @@ ...@@ -210,7 +215,7 @@
<string name="message_unmuted">User %1$s unmuted by %2$s</string> <!-- TODO Add translation --> <string name="message_unmuted">User %1$s unmuted by %2$s</string> <!-- TODO Add translation -->
<string name="message_role_add">%1$s was set %2$s by %3$s</string> <!-- TODO Add translation --> <string name="message_role_add">%1$s was set %2$s by %3$s</string> <!-- TODO Add translation -->
<string name="message_role_removed">%1$s is no longer %2$s by %3$s</string> <!-- TODO Add translation --> <string name="message_role_removed">%1$s is no longer %2$s by %3$s</string> <!-- TODO Add translation -->
<string name="message_credentials_saved_successfully">اختیارها با موفقیت ذخیره شد</string> <string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">جواب</string> <string name="action_msg_reply">جواب</string>
...@@ -238,10 +243,6 @@ ...@@ -238,10 +243,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">جست‌وجوی پیام</string> <string name="title_search_message">جست‌وجوی پیام</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">مورد علاقه کردن گفت‌وگو</string>
<string name="title_unfavorite_chat">از مورد علاقه دراوردن گفت‌وگو</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">اعضاء</string> <string name="title_members_list">اعضاء</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">registre</string> <string name="action_register">registre</string>
<string name="action_confirm">Confirmer</string> <string name="action_confirm">Confirmer</string>
<string name="action_delete_account">Effacer le compte</string> <string name="action_delete_account">Effacer le compte</string>
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">Hier</string> <string name="msg_yesterday">Hier</string>
<string name="msg_today">Aujourd\'hui</string> <string name="msg_today">Aujourd\'hui</string>
<string name="msg_message">Message</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_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_2fa_code">Code 2FA non valide</string>
<string name="msg_invalid_file">Fichier non valide</string> <string name="msg_invalid_file">Fichier non valide</string>
...@@ -176,6 +180,7 @@ ...@@ -176,6 +180,7 @@
<item quantity="one">%1$s a réagi avec %2$s</item> <item quantity="one">%1$s a réagi avec %2$s</item>
<item quantity="other">%1$s a réagi avec %2$s</item> <item quantity="other">%1$s a réagi avec %2$s</item>
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Certificats sauvegardés</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Privé</string> <string name="msg_private_channel">Privé</string>
...@@ -208,8 +213,7 @@ ...@@ -208,8 +213,7 @@
<string name="message_unmuted">Utilisateur %1$s a retrouvé la parole grâce à %2$s</string> <string name="message_unmuted">Utilisateur %1$s a retrouvé la parole grâce à %2$s</string>
<string name="message_role_add">%1$s a été défini %2$s par %3$s</string> <string name="message_role_add">%1$s a été défini %2$s par %3$s</string>
<string name="message_role_removed">%1$s n\'est plus %2$s par %3$s</string> <string name="message_role_removed">%1$s n\'est plus %2$s par %3$s</string>
<string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<string name="message_credentials_saved_successfully">Certificats sauvegardés</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Répondre</string> <string name="action_msg_reply">Répondre</string>
...@@ -237,10 +241,6 @@ ...@@ -237,10 +241,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Rechercher message</string> <string name="title_search_message">Rechercher message</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Mettre chat en favori</string>
<string name="title_unfavorite_chat">Retirer chat des favoris</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Membres</string> <string name="title_members_list">Membres</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">रजिस्टर</string> <string name="action_register">रजिस्टर</string>
<string name="action_confirm">पुष्टि करें</string> <string name="action_confirm">पुष्टि करें</string>
<string name="action_delete_account">खाता हटा दो</string> <string name="action_delete_account">खाता हटा दो</string>
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">कल</string> <string name="msg_yesterday">कल</string>
<string name="msg_today">आज</string> <string name="msg_today">आज</string>
<string name="msg_message">संदेश</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_this_room_is_read_only">यह रूम केवल पढ़ने के लिए है</string>
<string name="msg_invalid_2fa_code">अमान्य 2FA कोड</string> <string name="msg_invalid_2fa_code">अमान्य 2FA कोड</string>
<string name="msg_invalid_file">अवैध फाइल</string> <string name="msg_invalid_file">अवैध फाइल</string>
...@@ -181,9 +185,9 @@ ...@@ -181,9 +185,9 @@
<string name="msg_send_email">ईमेल भेजें</string> <string name="msg_send_email">ईमेल भेजें</string>
<string name="msg_android_app_support">एंड्रॉइड ऐप समर्थन</string> <string name="msg_android_app_support">एंड्रॉइड ऐप समर्थन</string>
<string name="msg_muted_on_this_channel">आप इस चैनल पर म्यूट कर रहे हैं</string> <string name="msg_muted_on_this_channel">आप इस चैनल पर म्यूट कर रहे हैं</string>
<string name="msg_no_topic">कोई विषय नहीं जोड़ा गया</string> <string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">कोई घोषणा नहीं जोड़ा गया</string> <string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">कोई विवरण नहीं जोड़ा गया</string> <string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_unable_to_update_password">पासवर्ड अपडेट करने में असमर्थ। त्रुटि संदेश: %1$s</string> <string name="msg_unable_to_update_password">पासवर्ड अपडेट करने में असमर्थ। त्रुटि संदेश: %1$s</string>
<string name="msg_password_updated_successfully">पासवर्ड सफलतापूर्वक अपडेट किया गया</string> <string name="msg_password_updated_successfully">पासवर्ड सफलतापूर्वक अपडेट किया गया</string>
<plurals name="msg_reacted_with_"> <plurals name="msg_reacted_with_">
...@@ -191,6 +195,7 @@ ...@@ -191,6 +195,7 @@
<item quantity="few">%1$s ने %2$s के साथ प्रतिक्रिया व्यक्त की</item> <item quantity="few">%1$s ने %2$s के साथ प्रतिक्रिया व्यक्त की</item>
<item quantity="many">%1$s ने %2$s के साथ प्रतिक्रिया व्यक्त की</item> <item quantity="many">%1$s ने %2$s के साथ प्रतिक्रिया व्यक्त की</item>
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">प्रमाण पत्र सफलतापूर्वक सहेजे गए</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">एनालिटिक्स ट्रैकिंग</string> <string name="msg_analytics_tracking">एनालिटिक्स ट्रैकिंग</string>
...@@ -211,7 +216,7 @@ ...@@ -211,7 +216,7 @@
<string name="message_unmuted">उपयोगकर्ता %1$s %2$s द्वारा अनम्यूट किया गया</string> <string name="message_unmuted">उपयोगकर्ता %1$s %2$s द्वारा अनम्यूट किया गया</string>
<string name="message_role_add">%1$s %3$s द्वारा %2$s सेट किया गया था</string> <string name="message_role_add">%1$s %3$s द्वारा %2$s सेट किया गया था</string>
<string name="message_role_removed">%1$s अब %3$s द्वारा %2$s नहीं है</string> <string name="message_role_removed">%1$s अब %3$s द्वारा %2$s नहीं है</string>
<string name="message_credentials_saved_successfully">प्रमाण पत्र सफलतापूर्वक सहेजे गए</string> <string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">जवाब दें</string> <string name="action_msg_reply">जवाब दें</string>
...@@ -239,10 +244,6 @@ ...@@ -239,10 +244,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">संदेश खोजें</string> <string name="title_search_message">संदेश खोजें</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">पसंदीदा चैट</string>
<string name="title_unfavorite_chat">नापसंद चैट</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">सदस्य</string> <string name="title_members_list">सदस्य</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">Registra</string> <string name="action_register">Registra</string>
<string name="action_confirm">Conferma</string> <string name="action_confirm">Conferma</string>
<string name="action_delete_account">Elimina utente</string> <string name="action_delete_account">Elimina utente</string>
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">Ieri</string> <string name="msg_yesterday">Ieri</string>
<string name="msg_today">Oggi</string> <string name="msg_today">Oggi</string>
<string name="msg_message">Messaggio</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_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_2fa_code">Invalido Codice 2FA non valido</string>
<string name="msg_invalid_file">Documento non valido</string> <string name="msg_invalid_file">Documento non valido</string>
...@@ -158,9 +162,9 @@ ...@@ -158,9 +162,9 @@
<string name="msg_two_factor_authentication">Autenticazione a 2 fattori (2FA)</string> <string name="msg_two_factor_authentication">Autenticazione a 2 fattori (2FA)</string>
<string name="msg__your_2fa_code">Qual è il tuo codice 2FA ?</string> <string name="msg__your_2fa_code">Qual è il tuo codice 2FA ?</string>
<string name="msg_permalink_copied">Permalink copiato</string> <string name="msg_permalink_copied">Permalink copiato</string>
<string name="msg_no_topic">Nessun argomento aggiunto</string> <string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">Nessun annuncio aggiunto</string> <string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">Nessuna descrizione aggiunta</string> <string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_send_email">Invia una email</string> <string name="msg_send_email">Invia una email</string>
<string name="msg_android_app_support">Supporto per le app Android</string> <string name="msg_android_app_support">Supporto per le app Android</string>
<string name="msg_unable_to_update_password">Impossibile aggiornare la password. Messaggio di errore: %1$s</string> <string name="msg_unable_to_update_password">Impossibile aggiornare la password. Messaggio di errore: %1$s</string>
...@@ -169,6 +173,7 @@ ...@@ -169,6 +173,7 @@
<item quantity="one">%1$s ha reagito con %2$s</item> <item quantity="one">%1$s ha reagito con %2$s</item>
<item quantity="other">%1$s ha reagito con %2$s</item> <item quantity="other">%1$s ha reagito con %2$s</item>
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Credenziali salvate con successo</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Privato</string> <string name="msg_private_channel">Privato</string>
...@@ -207,7 +212,7 @@ ...@@ -207,7 +212,7 @@
<string name="message_unmuted">Utente %1$s riattivato da %2$s</string> <string name="message_unmuted">Utente %1$s riattivato da %2$s</string>
<string name="message_role_add">%1$s ha il ruolo %2$s aggiunto da %3$s</string> <string name="message_role_add">%1$s ha il ruolo %2$s aggiunto da %3$s</string>
<string name="message_role_removed">%1$s ha il ruolo %2$s rimosso da %3$s</string> <string name="message_role_removed">%1$s ha il ruolo %2$s rimosso da %3$s</string>
<string name="message_credentials_saved_successfully">Credenziali salvate con successo</string> <string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Rispondi</string> <string name="action_msg_reply">Rispondi</string>
...@@ -235,10 +240,6 @@ ...@@ -235,10 +240,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Cerca messaggio</string> <string name="title_search_message">Cerca messaggio</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Stanza preferita</string>
<string name="title_unfavorite_chat">Stanza normale</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Partecipanti</string> <string name="title_members_list">Partecipanti</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">登録</string> <string name="action_register">登録</string>
<string name="action_confirm">確認</string> <string name="action_confirm">確認</string>
<string name="action_delete_account">アカウントを削除する</string> <string name="action_delete_account">アカウントを削除する</string>
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_today">今日</string> <string name="msg_today">今日</string>
<string name="msg_yesterday">昨日</string> <string name="msg_yesterday">昨日</string>
<string name="msg_message">メッセージ</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_this_room_is_read_only">この部屋は読み取り専用です</string>
<string name="msg_invalid_2fa_code">無効な 2FA コード</string> <string name="msg_invalid_2fa_code">無効な 2FA コード</string>
<string name="msg_invalid_file">無効なファイル</string> <string name="msg_invalid_file">無効なファイル</string>
...@@ -163,15 +167,16 @@ ...@@ -163,15 +167,16 @@
<string name="msg_view_more">更に表示</string> <string name="msg_view_more">更に表示</string>
<string name="msg_view_less">隠す</string> <string name="msg_view_less">隠す</string>
<string name="msg_muted_on_this_channel">あなたはこのチャンネルでミュートされています</string> <string name="msg_muted_on_this_channel">あなたはこのチャンネルでミュートされています</string>
<string name="msg_no_topic">No topic added</string> <!-- TODO Add translation --> <string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">No announcement added</string> <!-- TODO Add translation --> <string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">No description added</string> <!-- TODO Add translation --> <string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation --> <string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation -->
<string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation --> <string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_"> <plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation --> <item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation --> <item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">資格情報を正常に保存しました</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">プライベート</string> <string name="msg_private_channel">プライベート</string>
...@@ -210,7 +215,7 @@ ...@@ -210,7 +215,7 @@
<string name="message_unmuted">ユーザー %1$s は %2$s によってミュートされていません</string> <string name="message_unmuted">ユーザー %1$s は %2$s によってミュートされていません</string>
<string name="message_role_add">%1$s は %3$s によって %2$s に設定されました</string> <string name="message_role_add">%1$s は %3$s によって %2$s に設定されました</string>
<string name="message_role_removed">%1$s は %3$s で、もう %2$s ではありません</string> <string name="message_role_removed">%1$s は %3$s で、もう %2$s ではありません</string>
<string name="message_credentials_saved_successfully">資格情報を正常に保存しました</string> <string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">返信</string> <string name="action_msg_reply">返信</string>
...@@ -238,10 +243,6 @@ ...@@ -238,10 +243,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">メッセージを検索</string> <string name="title_search_message">メッセージを検索</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">お気に入り</string>
<string name="title_unfavorite_chat">お気に入り解除</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">メンバー</string> <string name="title_members_list">メンバー</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">Registrar</string> <string name="action_register">Registrar</string>
<string name="action_confirm">Confirmar</string> <string name="action_confirm">Confirmar</string>
<string name="action_delete_account">Deletar conta</string> <string name="action_delete_account">Deletar conta</string>
<string name="action_favorite">Favoritar</string>
<string name="action_remove_favorite">Remover favorito</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">Ontem</string> <string name="msg_yesterday">Ontem</string>
<string name="msg_today">Hoje</string> <string name="msg_today">Hoje</string>
<string name="msg_message">Mensagem</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_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_2fa_code">Código 2FA inválido</string>
<string name="msg_invalid_file">Arquivo inválido</string> <string name="msg_invalid_file">Arquivo inválido</string>
...@@ -168,9 +172,9 @@ ...@@ -168,9 +172,9 @@
<string name="msg_send_email">Enviar e-mail</string> <string name="msg_send_email">Enviar e-mail</string>
<string name="msg_android_app_support">Suporte ao aplicativo Android</string> <string name="msg_android_app_support">Suporte ao aplicativo Android</string>
<string name="msg_muted_on_this_channel">Você está silenciado neste canal</string> <string name="msg_muted_on_this_channel">Você está silenciado neste canal</string>
<string name="msg_no_topic">Nenhum tópico adicionado</string> <string name="msg_no_topic">Nenhum tópico</string>
<string name="msg_no_announcement">Nenhum anúncio adicionado</string> <string name="msg_no_announcement">Nenhum anúncio</string>
<string name="msg_no_description">Nenhuma descrição adicionada</string> <string name="msg_no_description">Nenhuma descrição</string>
<string name="msg_unable_to_update_password">Não foi possível atualizar a senha. Mensagem de erro: %1$s</string> <string name="msg_unable_to_update_password">Não foi possível atualizar a senha. Mensagem de erro: %1$s</string>
<string name="msg_password_updated_successfully">Senha alterada com sucesso</string> <string name="msg_password_updated_successfully">Senha alterada com sucesso</string>
<string name="msg_sort">Ordenar</string> <string name="msg_sort">Ordenar</string>
...@@ -178,6 +182,7 @@ ...@@ -178,6 +182,7 @@
<item quantity="one">%1$s reagiu com %2$s</item> <item quantity="one">%1$s reagiu com %2$s</item>
<item quantity="other">%1$s reagiram com %2$s</item> <item quantity="other">%1$s reagiram com %2$s</item>
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Credenciais salvas com sucesso</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Privado</string> <string name="msg_private_channel">Privado</string>
...@@ -210,7 +215,7 @@ ...@@ -210,7 +215,7 @@
<string name="message_unmuted">Usuário %1$s saiu do modo silenciado por %2$s</string> <string name="message_unmuted">Usuário %1$s saiu do modo silenciado por %2$s</string>
<string name="message_role_add">%1$s foi definido %2$s por %3$s</string> <string name="message_role_add">%1$s foi definido %2$s por %3$s</string>
<string name="message_role_removed">%1$s não é mais %2$s por %3$s</string> <string name="message_role_removed">%1$s não é mais %2$s por %3$s</string>
<string name="message_credentials_saved_successfully">Credenciais salvas com sucesso</string> <string name="message_video_call_started">Videochamada iniciada por %1$s</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Responder</string> <string name="action_msg_reply">Responder</string>
...@@ -238,10 +243,6 @@ ...@@ -238,10 +243,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Procurar mensagem</string> <string name="title_search_message">Procurar mensagem</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Marcar canal como favorito</string>
<string name="title_unfavorite_chat">Desmarcar canal como favorito</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Membros</string> <string name="title_members_list">Membros</string>
......
...@@ -211,7 +211,8 @@ ...@@ -211,7 +211,8 @@
<string name="message_unmuted">Utilizador %2$s removeu o silêncio de %1$s</string> <string name="message_unmuted">Utilizador %2$s removeu o silêncio de %1$s</string>
<string name="message_role_add">%3$s deu estatuto de %2$s a %1$s</string> <string name="message_role_add">%3$s deu estatuto de %2$s a %1$s</string>
<string name="message_role_removed">%3$s retirou o estatuto de %2$s a %1$s</string> <string name="message_role_removed">%3$s retirou o estatuto de %2$s a %1$s</string>
<string name="message_credentials_saved_successfully">Credenciais guardadas com sucesso</string> <string name="action_favorite">Marcar chat como favorito</string>
<string name="action_remove_favorite">Desmarcar chat como favorito</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Responder</string> <string name="action_msg_reply">Responder</string>
...@@ -239,10 +240,6 @@ ...@@ -239,10 +240,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Pesquisar mensagem</string> <string name="title_search_message">Pesquisar mensagem</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Marcar chat como favorito</string>
<string name="title_unfavorite_chat">Desmarcar chat como favorito</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Membros</string> <string name="title_members_list">Membros</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">Зарегистрировать</string> <string name="action_register">Зарегистрировать</string>
<string name="action_confirm">Подтвердить</string> <string name="action_confirm">Подтвердить</string>
<string name="action_delete_account">Удалить аккаунт</string> <string name="action_delete_account">Удалить аккаунт</string>
<string name="action_favorite">В избранное</string>
<string name="action_remove_favorite">Удалить из избранного</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">Вчера</string> <string name="msg_yesterday">Вчера</string>
<string name="msg_today">Сегодня</string> <string name="msg_today">Сегодня</string>
<string name="msg_message">Сообщение</string> <string name="msg_message">Сообщение</string>
<string name="msg_video_call">Видеозвонок</string>
<string name="msg_join_video_call">Присоединиться к видеозвонку</string>
<string name="msg_this_room_is_read_only">Канал только для чтения</string> <string name="msg_this_room_is_read_only">Канал только для чтения</string>
<string name="msg_invalid_2fa_code">Неверный код 2FA</string> <string name="msg_invalid_2fa_code">Неверный код 2FA</string>
<string name="msg_invalid_file">Неверный файл</string> <string name="msg_invalid_file">Неверный файл</string>
...@@ -175,6 +179,7 @@ ...@@ -175,6 +179,7 @@
<item quantity="few">%1$s реагируют с %2$s</item> <item quantity="few">%1$s реагируют с %2$s</item>
<item quantity="many">%1$s реагируют с %2$s</item> <item quantity="many">%1$s реагируют с %2$s</item>
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Учетные данные успешно сохранены</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Приватный</string> <string name="msg_private_channel">Приватный</string>
...@@ -207,7 +212,7 @@ ...@@ -207,7 +212,7 @@
<string name="message_unmuted">Пользователю %1$s вернули дар речи по решению %2$s</string> <string name="message_unmuted">Пользователю %1$s вернули дар речи по решению %2$s</string>
<string name="message_role_add">%1$s был назначен %2$s пользователем %3$s</string> <string name="message_role_add">%1$s был назначен %2$s пользователем %3$s</string>
<string name="message_role_removed">%1$s больше не %2$s по решению %3$s</string> <string name="message_role_removed">%1$s больше не %2$s по решению %3$s</string>
<string name="message_credentials_saved_successfully">Учетные данные успешно сохранены</string> <string name="message_video_call_started">Видеозвонок, начатый %1$s</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Ответить</string> <string name="action_msg_reply">Ответить</string>
...@@ -235,10 +240,6 @@ ...@@ -235,10 +240,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Поиск сообщения</string> <string name="title_search_message">Поиск сообщения</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Добавить в избранное</string>
<string name="title_unfavorite_chat">Удалить из избранного</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Пользователи</string> <string name="title_members_list">Пользователи</string>
...@@ -315,7 +316,7 @@ ...@@ -315,7 +316,7 @@
<string name="chatroom_header">Заголовок</string> <string name="chatroom_header">Заголовок</string>
<!--ChatRooms Headers--> <!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation --> <string name="header_favorite">Избранные</string>
<string name="header_channel">Каналы</string> <string name="header_channel">Каналы</string>
<string name="header_private_groups">Приватные каналы</string> <string name="header_private_groups">Приватные каналы</string>
<string name="header_direct_messages">Личная переписка</string> <string name="header_direct_messages">Личная переписка</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">Register</string> <!-- TODO Add translation --> <string name="action_register">Register</string> <!-- TODO Add translation -->
<string name="action_confirm">Confirm</string> <!-- TODO Add translation --> <string name="action_confirm">Confirm</string> <!-- TODO Add translation -->
<string name="action_delete_account">Delete account</string> <!-- TODO Add translation --> <string name="action_delete_account">Delete account</string> <!-- TODO Add translation -->
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">Dün</string> <string name="msg_yesterday">Dün</string>
<string name="msg_today">Bugün</string> <string name="msg_today">Bugün</string>
<string name="msg_message">Mesaj</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_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_2fa_code">Geçersiz 2FA Kodu</string>
<string name="msg_invalid_file">Geçersiz dosya</string> <string name="msg_invalid_file">Geçersiz dosya</string>
...@@ -181,15 +185,16 @@ ...@@ -181,15 +185,16 @@
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation --> <string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<string name="msg_no_topic">No topic added</string> <!-- TODO Add translation --> <string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">No announcement added</string> <!-- TODO Add translation --> <string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">No description added</string> <!-- TODO Add translation --> <string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation --> <string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation -->
<string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation --> <string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_"> <plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation --> <item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation --> <item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Kimlik bilgileri başarıyla kaydedildi</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">İstatistik takibi</string> <string name="msg_analytics_tracking">İstatistik takibi</string>
...@@ -210,7 +215,7 @@ ...@@ -210,7 +215,7 @@
<string name="message_unmuted">%1$s kullanıcısı %2$s tarafından sessizden çıkarıldı</string> <string name="message_unmuted">%1$s kullanıcısı %2$s tarafından sessizden çıkarıldı</string>
<string name="message_role_add">%1$s, %3$s tarafından %2$s olacak şekilde değiştirildi</string> <string name="message_role_add">%1$s, %3$s tarafından %2$s olacak şekilde değiştirildi</string>
<string name="message_role_removed">%1$s, artık %2$s olmayacak şekilde %3$s tarafından değiştirildi</string> <string name="message_role_removed">%1$s, artık %2$s olmayacak şekilde %3$s tarafından değiştirildi</string>
<string name="message_credentials_saved_successfully">Kimlik bilgileri başarıyla kaydedildi</string> <string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Kaydet</string> <string name="action_msg_reply">Kaydet</string>
...@@ -240,10 +245,6 @@ ...@@ -240,10 +245,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Mesajlarda arayın</string> <string name="title_search_message">Mesajlarda arayın</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Sohbeti favorilerime ekle</string>
<string name="title_unfavorite_chat">Sohbeti favorilerimden kaldır</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Üyeler</string> <string name="title_members_list">Üyeler</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">Register</string> <!-- TODO Add translation --> <string name="action_register">Register</string> <!-- TODO Add translation -->
<string name="action_confirm">Confirm</string> <!-- TODO Add translation --> <string name="action_confirm">Confirm</string> <!-- TODO Add translation -->
<string name="action_delete_account">Delete account</string> <!-- TODO Add translation --> <string name="action_delete_account">Delete account</string> <!-- TODO Add translation -->
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -93,7 +95,9 @@ ...@@ -93,7 +95,9 @@
<string name="msg_new_user_agreement">Продовжуючи, ви погоджуєтеся з %1$s і %2$s</string> <string name="msg_new_user_agreement">Продовжуючи, ви погоджуєтеся з %1$s і %2$s</string>
<string name="msg_yesterday">Вчора</string> <string name="msg_yesterday">Вчора</string>
<string name="msg_today">Сьогодні</string> <string name="msg_today">Сьогодні</string>
<string name="msg_message">"Повідомлення "</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_this_room_is_read_only">Канал тільки для читання</string>
<string name="msg_invalid_2fa_code">Неправильний код 2FA</string> <string name="msg_invalid_2fa_code">Неправильний код 2FA</string>
<string name="msg_invalid_file">Неправильний файл</string> <string name="msg_invalid_file">Неправильний файл</string>
...@@ -164,9 +168,9 @@ ...@@ -164,9 +168,9 @@
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation --> <string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation --> <string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation --> <string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<string name="msg_no_topic">No topic added</string> <!-- TODO Add translation --> <string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">No announcement added</string> <!-- TODO Add translation --> <string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">No description added</string> <!-- TODO Add translation --> <string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation --> <string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation -->
<string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation --> <string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_"> <plurals name="msg_reacted_with_">
...@@ -174,6 +178,7 @@ ...@@ -174,6 +178,7 @@
<item quantity="few">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation --> <item quantity="few">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="many">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation --> <item quantity="many">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Облікові дані було успішно збережено</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Приватний</string> <string name="msg_private_channel">Приватний</string>
...@@ -206,7 +211,7 @@ ...@@ -206,7 +211,7 @@
<string name="message_unmuted">Користувачу і%1$s повернули дар мови за рішенням %2$s</string> <string name="message_unmuted">Користувачу і%1$s повернули дар мови за рішенням %2$s</string>
<string name="message_role_add">%1$s був призначений як %2$s користувачем %3$s</string> <string name="message_role_add">%1$s був призначений як %2$s користувачем %3$s</string>
<string name="message_role_removed">%1$s більше не %2$s за рішенням %3$s</string> <string name="message_role_removed">%1$s більше не %2$s за рішенням %3$s</string>
<string name="message_credentials_saved_successfully">Облікові дані було успішно збережено</string> <string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Відповісти</string> <string name="action_msg_reply">Відповісти</string>
...@@ -236,10 +241,6 @@ ...@@ -236,10 +241,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Пошук повідомлення</string> <string name="title_search_message">Пошук повідомлення</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Додати до обраного</string>
<string name="title_unfavorite_chat">Видалити з обраного</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Користувачі</string> <string name="title_members_list">Користувачі</string>
......
...@@ -59,6 +59,8 @@ ...@@ -59,6 +59,8 @@
<string name="action_register">注册</string> <string name="action_register">注册</string>
<string name="action_confirm">确定</string> <string name="action_confirm">确定</string>
<string name="action_delete_account">删除账户</string> <string name="action_delete_account">删除账户</string>
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -94,6 +96,8 @@ ...@@ -94,6 +96,8 @@
<string name="msg_yesterday">昨天</string> <string name="msg_yesterday">昨天</string>
<string name="msg_today">今天</string> <string name="msg_today">今天</string>
<string name="msg_message">消息</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_this_room_is_read_only">这个频道只读</string>
<string name="msg_invalid_2fa_code">无效的2FA码</string> <string name="msg_invalid_2fa_code">无效的2FA码</string>
<string name="msg_invalid_file">无效文件</string> <string name="msg_invalid_file">无效文件</string>
...@@ -158,9 +162,9 @@ ...@@ -158,9 +162,9 @@
<string name="msg_two_factor_authentication">两步认证</string> <string name="msg_two_factor_authentication">两步认证</string>
<string name="msg__your_2fa_code">什么是2FA 码?</string> <string name="msg__your_2fa_code">什么是2FA 码?</string>
<string name="msg_permalink_copied">永久链接已拷贝</string> <string name="msg_permalink_copied">永久链接已拷贝</string>
<string name="msg_no_topic">没有添加主题</string> <string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">没有添加公告</string> <string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">没有添加描述</string> <string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_send_email">发送邮件</string> <string name="msg_send_email">发送邮件</string>
<string name="msg_android_app_support">安卓app支持</string> <string name="msg_android_app_support">安卓app支持</string>
<string name="msg_unable_to_update_password">无法更新密码,错误信息: %1$s</string> <string name="msg_unable_to_update_password">无法更新密码,错误信息: %1$s</string>
...@@ -169,6 +173,7 @@ ...@@ -169,6 +173,7 @@
<item quantity="one">%1$s 使用了 %2$s</item> <item quantity="one">%1$s 使用了 %2$s</item>
<item quantity="other">%1$s 使用了 %2$s</item> <item quantity="other">%1$s 使用了 %2$s</item>
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">凭证成功保存</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">隐私</string> <string name="msg_private_channel">隐私</string>
...@@ -207,7 +212,7 @@ ...@@ -207,7 +212,7 @@
<string name="message_unmuted">用户 %1$s被%2$s取消禁言</string> <string name="message_unmuted">用户 %1$s被%2$s取消禁言</string>
<string name="message_role_add">%1$s被设置为%2$s 由%3$s操作</string> <string name="message_role_add">%1$s被设置为%2$s 由%3$s操作</string>
<string name="message_role_removed">%1$s不在是%2$s 由%3$s操作</string> <string name="message_role_removed">%1$s不在是%2$s 由%3$s操作</string>
<string name="message_credentials_saved_successfully">凭证成功保存</string> <string name="message_video_call_started">Video call started by %1$s</string> <!-- TODO Add translation -->
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">回复</string> <string name="action_msg_reply">回复</string>
...@@ -235,10 +240,6 @@ ...@@ -235,10 +240,6 @@
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">搜索消息</string> <string name="title_search_message">搜索消息</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">收藏</string>
<string name="title_unfavorite_chat">取消收藏</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">成员</string> <string name="title_members_list">成员</string>
......
<resources>
<!-- Titles -->
<string name="title_sign_in_your_server">登入您的伺服器</string>
<string name="title_log_in">登入</string>
<string name="title_share_the_app">分享App</string>
<string name="title_register_username">註冊使用者</string>
<string name="title_reset_password">重置密碼</string>
<string name="title_sign_up">登入</string>
<string name="title_authentication">認證</string>
<string name="title_legal_terms">法律條款</string>
<string name="title_chats">聊天</string>
<string name="title_profile">資料</string>
<string name="title_members">成員</string>
<string name="title_counted_members">成員(%d)</string>
<string name="title_settings">設定</string>
<string name="title_preferences">選項</string>
<string name="title_change_password">修改密碼</string>
<string name="title_rate_us">給我們評價</string>
<string name="title_admin_panel">管理面板</string>
<string name="title_password">修改密碼</string>
<string name="title_update_profile">更新資料</string>
<string name="title_about">關於</string>
<string name="title_create_channel">建立新頻道</string>
<string name="title_licence">許可</string>
<string name="title_are_you_sure">確定嗎?</string>
<string name="title_channel_details">頻道訊息</string>
<string name="title_topic">主題</string>
<string name="title_announcement">公告</string>
<string name="title_description">描述</string>
<!-- Actions -->
<string name="action_connect">連接</string>
<string name="action_use_this_username">使用這個名稱</string>
<string name="action_terms_of_service">服務條款</string>
<string name="action_privacy_policy">隱私政策</string>
<string name="action_search">搜尋</string>
<string name="action_update">更新</string>
<string name="action_settings">設定</string>
<string name="action_create_channel">新建頻道</string>
<string name="action_create">建立</string>
<string name="action_logout">登出</string>
<string name="action_attach_a_files">添加檔案</string>
<string name="action_confirm_password">確認修改密碼</string>
<string name="action_join_chat">加入聊天</string>
<string name="action_add_account">新增帳戶</string>
<string name="action_online">線上</string>
<string name="action_away">離線</string>
<string name="action_busy">忙碌</string>
<string name="action_invisible">隱藏</string>
<string name="action_drawing">畫畫中...</string>
<string name="action_save_to_gallery">保存到相簿</string>
<string name="action_select_photo_from_gallery">從相簿中選取照片</string>
<string name="action_take_a_photo">拍照</string>
<string name="action_reset_avatar">重新設定頭貼</string>
<string name="action_connect_server">連接到伺服器</string>
<string name="action_join_community">加入社群</string>
<string name="action_create_server">建立一個新的伺服器</string>
<string name="action_register">註冊</string>
<string name="action_confirm">確定</string>
<string name="action_delete_account">刪除帳戶</string>
<string name="action_favorite">加入蒐藏</string>
<string name="action_remove_favorite">從蒐藏中移除</string>
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">首选项</item>
<item name="item_password">修改密碼</item>
<item name="change_language">更換語言</item>
<item name="item_share_app">分享這個App</item>
<item name="item_rate_us">給我們一個評價</item>
<item name="item_contact_us">聯絡我們</item>
<item name="item_licence">許可證</item>
<item name="item_about">關於</item>
</string-array>
<!-- Regular information messages -->
<string name="msg_generic_error">發生了錯誤,請稍後試試</string>
<string name="msg_no_data_to_display">沒有資料</string>
<string name="msg_check_this_out">請檢查這裡</string>
<string name="msg_share_using">分享使用</string>
<string name="msg_profile_update_successfully">資料更新成功</string>
<string name="msg_username">使用者名稱</string>
<string name="msg_username_or_email">使用者名稱或是電子信箱</string>
<string name="msg_password">密碼</string>
<string name="msg_name">名字</string>
<string name="msg_email">電子郵件</string>
<string name="msg_avatar_url">頭貼網址</string>
<string name="msg_or_continue_using_social_accounts">或繼續使用社交帳號</string>
<string name="msg_new_user">新的使用者? %1$s</string>
<string name="msg_forgot__your_password">忘記密碼?</string>
<string name="msg_reset">重置</string>
<string name="msg_check_your_email_to_reset_your_password">郵件已發送!請根據電子郵件提示重新設定密碼.</string>
<string name="msg_invalid_email">請輸入有效的電子信箱</string>
<string name="msg_new_user_agreement">如果繼續表示您同意我們的\n%1$s 和 %2$s</string>
<string name="msg_yesterday">昨天</string>
<string name="msg_today">今天</string>
<string name="msg_message">訊息</string>
<string name="msg_video_call">視訊</string>
<string name="msg_join_video_call">J加入視訊</string>
<string name="msg_this_room_is_read_only">這個頻道只能讀取</string>
<string name="msg_invalid_2fa_code">無效的2FA</string>
<string name="msg_invalid_file">無效的檔案</string>
<string name="msg_invalid_server_url">無效的伺服器地址</string>
<string name="msg_content_description_log_in_using_facebook">使用Facebook帳戶登入</string>
<string name="msg_content_description_log_in_using_github">使用Github帳戶登入</string>
<string name="msg_content_description_log_in_using_google">使用Google帳戶登入</string>
<string name="msg_content_description_log_in_using_linkedin">使用Linkedin帳戶登入</string>
<string name="msg_content_description_log_in_using_meteor">使用Meteor帳戶登入</string>
<string name="msg_content_description_log_in_using_twitter">使用Twitter帳戶登入</string>
<string name="msg_content_description_log_in_using_gitlab">使用Gitlab帳戶登入</string>
<string name="msg_content_description_log_in_using_wordpress">使用WordPress帳戶登入</string>
<string name="msg_content_description_send_message">發送訊息</string>
<string name="msg_content_description_show_more_login_options">顯示更多登入選像</string>
<string name="msg_content_description_show_attachment_options">顯示附件選像</string>
<string name="msg_you"></string>
<string name="msg_unknown">未知</string>
<string name="msg_email_address">郵件地址</string>
<string name="msg_utc_offset">時區</string>
<string name="msg_new_password">輸入新密碼</string>
<string name="msg_confirm_password">確認新密碼</string>
<string name="msg_channel_name">頻道名稱</string>
<string name="msg_search">搜尋</string>
<string name="msg_unread_messages">未讀</string>
<string name="msg_preview_video">影片</string>
<string name="msg_preview_audio">聲音</string>
<string name="msg_preview_photo">照片</string>
<string name="msg_preview_file">文件</string>
<string name="msg_no_messages_yet">沒有訊息</string>
<string name="msg_build">Build %1$d - %2$s - %3$s</string>
<string name="msg_update_app_version_in_order_to_continue">伺服器版本過舊。請聯繫伺服器管理員更新伺服器版本。</string>
<string name="msg_ver_not_recommended">您的伺服器版本低於建議版本%1$s.\n仍然可以登入,但可能不太穩定</string>
<string name="msg_ver_not_minimum">您的伺服器版本低於建議版本%1$s.\n必須更新伺服器才可以登入!</string>
<string name="msg_no_chat_title">没有聊天訊息</string>
<string name="msg_no_chat_description">開始新的對話\n顯示在這裡</string>
<string name="msg_http_insecure">您正在使用HTTP連接伺服器。我們建議使用HTTPS</string>
<string name="msg_error_checking_server_version">檢查伺服器版本時發生錯誤,請稍後試試</string>
<string name="msg_invalid_server_protocol">您選擇的協定被伺服器拒絕,請使用HTTPS</string>
<string name="msg_image_saved_successfully">圖片已保存到相簿</string>
<string name="msg_image_saved_failed">保存圖片失敗</string>
<string name="msg_edited">(已編輯)</string>
<string name="msg_and">\u0020和\u0020</string>
<string name="msg_is_typing">\u0020正在輸入…</string>
<string name="msg_are_typing">" 正在輸入…"</string>
<string name="msg_several_users_are_typing">多個成員正在輸入…</string>
<string name="msg_no_search_found">沒有搜尋到結果</string>
<string name="msg_log_out">登出中…</string>
<string name="msg_upload_file">上傳檔案</string>
<string name="msg_file_description">檔案描述</string>
<string name="msg_send">送出</string>
<string name="msg_sent_attachment">發送一個附件</string>
<string name="msg_welcome_to_rocket_chat">歡迎來到Rocket.Chat</string>
<string name="msg_team_communication">和團隊聯繫</string>
<string name="msg_login_with_email">使用<b>信箱</b>登入</string>
<string name="msg_create_account">新增帳戶</string>
<string name="msg_continue_with_facebook">使用<b>Facebook</b>帳號繼續</string>
<string name="msg_continue_with_github">使用<b>Github</b>帳號繼續</string>
<string name="msg_continue_with_google">使用<b>Google</b>帳號繼續</string>
<string name="msg_continue_with_linkedin">使用<b>Linkedin</b>帳號繼續</string>
<string name="msg_continue_with_gitlab">使用<b>GitLab</b>帳號繼續</string>
<string name="msg_continue_with_wordpress">使用<b>WordPress</b>帳號繼續</string>
<string name="msg_two_factor_authentication">兩步驟認證(2FA)</string>
<string name="msg__your_2fa_code">什麼是2FA?</string>
<string name="msg_permalink_copied">永久連結已經複製</string>
<string name="msg_no_topic">沒有主題</string>
<string name="msg_no_announcement">沒有公告</string>
<string name="msg_no_description">沒有描述</string>
<string name="msg_send_email">發送電子郵件</string>
<string name="msg_android_app_support">Android應用程式支援</string>
<string name="msg_unable_to_update_password">無法更新密碼,錯誤訊息: %1$s</string>
<string name="msg_password_updated_successfully">密碼更新成功</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s 使用了 %2$s</item>
<item quantity="other">%1$s 使用了 %2$s</item>
</plurals>
<string name="msg_credentials_saved_successfully">憑證保存成功</string>
<!-- Create channel messages -->
<string name="msg_private_channel">隱私</string>
<string name="msg_public_channel">公開</string>
<string name="msg_private_channel_description">只有你和被邀請的成員可以進入頻道</string>
<string name="msg_public_channel_description">所有人都可以進入頻道</string>
<string name="msg_ready_only_channel">只能讀取的頻道</string>
<string name="msg_ready_only_channel_description">只有管理員能發送訊息</string>
<string name="msg_invite_members">邀請成員</string>
<string name="msg_member_already_added">您已經選擇這個成員</string>
<string name="msg_member_not_found">找不到成員</string>
<string name="msg_channel_created_successfully">成功建立頻道</string>
<string name="msg_message_copied">訊息複製成功</string>
<string name="msg_delete_message">删除訊息</string>
<string name="msg_delete_description">您確定要刪除這個訊息嗎?</string>
<string name="msg_view_more">顯示更多</string>
<string name="msg_view_less">顯示更少</string>
<string name="msg_muted_on_this_channel">您被禁言了</string>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">追蹤分析</string>
<string name="msg_send_analytics_tracking">發送匿名訊息來改善App</string>
<string name="msg_do_not_send_analytics_tracking">不要發送匿名訊息,我不想幫助這個App</string>
<string name="msg_not_applicable_since_it_is_a_foss_version">不適用,因為這個軟體是Foss版本</string>
<!-- System messages -->
<string name="message_room_name_changed">頻道已經改名為: %1$s by %2$s</string>
<string name="message_user_added_by">%2$s增加了新的成員%1$s </string>
<string name="message_user_removed_by">%2$s移除了成員%1$s </string>
<string name="message_user_left">離開了頻道</string>
<string name="message_user_joined_channel">加入頻道</string>
<string name="message_welcome">歡迎 %s</string>
<string name="message_removed">訊息被刪除</string>
<string name="message_pinned">釘選的的訊息:</string>
<string name="message_muted">用户%1$s被%2$s禁言</string>
<string name="message_unmuted">用户 %1$s被%2$s取消禁言</string>
<string name="message_role_add">%1$s被設定為%2$s 由%3$s操作</string>
<string name="message_role_removed">%1$s不在是%2$s 由%3$s操作</string>
<string name="message_video_call_started">由%1$s發起視訊</string>
<!-- Message actions -->
<string name="action_msg_reply">回覆</string>
<string name="action_msg_info">訊息通知</string>
<string name="action_msg_edit">編輯</string>
<string name="action_msg_copy">複製</string>
<string name="action_msg_quote">引用</string>
<string name="action_msg_delete">刪除</string>
<string name="action_msg_pin">釘選訊息</string>
<string name="action_msg_unpin">取消釘選訊息</string>
<string name="action_msg_star">標註訊息</string>
<string name="action_msg_unstar">取消標註訊息</string>
<string name="action_msg_share">分享訊息</string>
<string name="action_title_editing">編輯訊息</string>
<string name="action_msg_add_reaction">增加操作</string>
<string name="action_msg_copy_permalink">複製永久連結</string>
<string name="action_msg_report">回報</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">没有編輯權限</string>
<string name="permission_deleting_not_allowed">没有刪除權限</string>
<string name="permission_pinning_not_allowed">没有釘選權限</string>
<string name="permission_starring_not_allowed">没有標註權限</string>
<!-- Search message -->
<string name="title_search_message">搜尋訊息</string>
<!-- Members List -->
<string name="title_members_list">成員</string>
<!-- Mentions -->
<string name="msg_mentions">提及的訊息</string>
<string name="msg_no_mention">沒有提及的訊息</string>
<string name="msg_all_the_mentions_appear_here">所有提及的訊息\n顯示在這裡</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">已釘選訊息</string>
<string name="no_pinned_messages">沒有釘選的訊息</string>
<string name="no_pinned_description">所有被釘選的訊息\n顯示在這裡</string>
<!-- Favorite Messages -->
<string name="title_favorite_messages">收藏的訊息</string>
<string name="no_favorite_messages">没有收藏的訊息</string>
<string name="no_favorite_description">所有顯藏的訊息\n顯示在這裡</string>
<!-- Files -->
<string name="title_files">檔案</string>
<string name="title_files_total">檔案 (%d)</string>
<string name="msg_no_files">沒有檔案</string>
<string name="msg_all_files_appear_here">顯示所有檔案</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">檔案大小 %1$d bytes 超過最大允許大小 %2$d bytes</string>
<!-- Socket status -->
<string name="status_connected">已連線</string>
<string name="status_disconnected">已斷線</string>
<string name="status_connecting">連線中</string>
<string name="status_authenticating">認證</string>
<string name="status_disconnecting">斷線中</string>
<string name="status_waiting">連線持續了 %d 秒</string>
<!--Suggestions-->
<string name="suggest_all_description">提醒頻道內所有成員</string>
<string name="suggest_here_description">提醒頻道內所有在線上的成員</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">在您的訊息前顯示 ༼ つ ◕_◕ ༽つ </string>
<string name="Slash_LennyFace_Description">在您的訊息前顯示 ( ͡° ͜ʖ ͡°) </string>
<string name="Slash_Shrug_Description">在您的訊息前顯示 ¯\_(ツ)_/¯ </string>
<string name="Slash_Tableflip_Description">顯示 (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">顯示 ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">建立新的頻道</string>
<string name="Show_the_keyboard_shortcut_list">顯示鍵盤快捷鍵列表</string>
<string name="Invite_user_to_join_channel_all_from">邀請 [#channel] 內所有的成員加入這個頻道</string>
<string name="Invite_user_to_join_channel_all_to">邀請這個頻道內所有的成員加入 [#channel]</string>
<string name="Archive">歸檔</string>
<string name="Remove_someone_from_room">從頻道內移除成員</string>
<string name="Leave_the_current_channel">離開頻道</string>
<string name="Displays_action_text">顯示操作</string>
<string name="Direct_message_someone">與成員對話</string>
<string name="Mute_someone_in_room">禁言成員</string>
<string name="Unmute_someone_in_room">取消禁言成員</string>
<string name="Invite_user_to_join_channel">邀請成員進入頻道</string>
<string name="Unarchive">取消歸檔</string>
<string name="Join_the_given_channel">加入頻道</string>
<string name="Guggy_Command_Description">根據輸入訊息產生GIF</string>
<string name="Slash_Topic_Description">設定主題</string>
<!-- Emoji message-->
<string name="msg_no_recent_emoji">最近沒有使用emojis</string>
<string name="alert_title_default_skin_tone">預設主題顏色</string>
<!-- Sorting and grouping-->
<string name="msg_sort">排序</string>
<string name="dialog_sort_title">根據開頭排序</string>
<string name="dialog_sort_by_alphabet">根據字母排序</string>
<string name="dialog_sort_by_activity">根據活躍度排序</string>
<string name="dialog_group_by_type">根據類型分組</string>
<string name="dialog_group_favourites">根據蒐藏分组</string>
<string name="chatroom_header">聊天室頭貼</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">頻道</string>
<string name="header_private_groups">私人群組</string>
<string name="header_direct_messages">直接對話</string>
<string name="header_live_chats">Live 對話</string>
<string name="header_unknown">未知</string>
<!--Notifications-->
<string name="share_label">編輯分享的訊息</string>
<string name="notif_action_reply_hint">回覆</string>
<string name="notif_error_sending">回覆失敗,請重試.</string>
<string name="notif_success_sending">發送訊息給 %1$s!</string>
<string name="read_by">已讀</string>
<string name="message_information_title">消息訊息</string>
<string name="message_room_changed_privacy">頻道類型更改為: %1$s by %2$s</string>
<string name="foss" translatable="false">(FOSS)</string>
<!-- User Details -->
<string name="timezone">時區</string>
<string name="status" translatable="false">狀態</string>
<!-- Report -->
<string name="submit">提交</string>
<string name="required">*必須輸入</string>
<string name="report_sent">您的報告已經提交!</string>
</resources>
...@@ -71,6 +71,8 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -71,6 +71,8 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="action_register">Register</string> <string name="action_register">Register</string>
<string name="action_confirm">Confirm</string> <string name="action_confirm">Confirm</string>
<string name="action_delete_account">Delete account</string> <string name="action_delete_account">Delete account</string>
<string name="action_favorite">Favorite</string>
<string name="action_remove_favorite">Remove favorite</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
...@@ -107,6 +109,8 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -107,6 +109,8 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_yesterday">Yesterday</string> <string name="msg_yesterday">Yesterday</string>
<string name="msg_today">Today</string> <string name="msg_today">Today</string>
<string name="msg_message">Message</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_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_2fa_code">Invalid 2FA Code</string>
<string name="msg_invalid_file">Invalid file</string> <string name="msg_invalid_file">Invalid file</string>
...@@ -174,9 +178,9 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -174,9 +178,9 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <string name="msg_two_factor_authentication">Two-factor Authentication</string>
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <string name="msg__your_2fa_code">What’s your 2FA code?</string>
<string name="msg_permalink_copied">Permalink copied</string> <string name="msg_permalink_copied">Permalink copied</string>
<string name="msg_no_topic">No topic added</string> <string name="msg_no_topic">No topic</string>
<string name="msg_no_announcement">No announcement added</string> <string name="msg_no_announcement">No announcement</string>
<string name="msg_no_description">No description added</string> <string name="msg_no_description">No description</string>
<string name="msg_send_email">Send email</string> <string name="msg_send_email">Send email</string>
<string name="msg_android_app_support">Android app support</string> <string name="msg_android_app_support">Android app support</string>
<string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string>
...@@ -185,6 +189,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -185,6 +189,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<item quantity="one">%1$s reacted with %2$s</item> <item quantity="one">%1$s reacted with %2$s</item>
<item quantity="other">%1$s reacted with %2$s</item> <item quantity="other">%1$s reacted with %2$s</item>
</plurals> </plurals>
<string name="msg_credentials_saved_successfully">Credentials saved successfully</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Private</string> <string name="msg_private_channel">Private</string>
...@@ -223,7 +228,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -223,7 +228,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="message_unmuted">User %1$s unmuted by %2$s</string> <string name="message_unmuted">User %1$s unmuted by %2$s</string>
<string name="message_role_add">%1$s was set %2$s by %3$s</string> <string name="message_role_add">%1$s was set %2$s by %3$s</string>
<string name="message_role_removed">%1$s is no longer %2$s by %3$s</string> <string name="message_role_removed">%1$s is no longer %2$s by %3$s</string>
<string name="message_credentials_saved_successfully">Credentials saved successfully</string> <string name="message_video_call_started">Video call started by %1$s</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Reply</string> <string name="action_msg_reply">Reply</string>
...@@ -251,10 +256,6 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin ...@@ -251,10 +256,6 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<!-- Search message --> <!-- Search message -->
<string name="title_search_message">Search message</string> <string name="title_search_message">Search message</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Favorite chat</string>
<string name="title_unfavorite_chat">Unfavorite chat</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Members</string> <string name="title_members_list">Members</string>
......
...@@ -155,6 +155,19 @@ ...@@ -155,6 +155,19 @@
</style> </style>
<!-- End chat list --> <!-- End chat list -->
<!-- Chat room -->
<style name="ChatRoom.ChatName.TextView" parent="TextAppearance.AppCompat">
<item name="android:textSize">20sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">normal</item>
<item name="android:textColor">@color/colorWhite</item>
<item name="android:drawableEnd">@drawable/ic_chatroom_toolbar_expand_more_20dp</item>
<item name="android:drawablePadding">4dp</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
</style>
<!-- End chat room -->
<!-- User details --> <!-- User details -->
<style name="UserDetails.TextView.Name" parent="TextAppearance.AppCompat"> <style name="UserDetails.TextView.Name" parent="TextAppearance.AppCompat">
<item name="android:textSize">18sp</item> <item name="android:textSize">18sp</item>
......
<lint>
<issue id="InvalidPackage">
<ignore path="**/dropbox*.jar"/> <!--For Jitsi -->
</issue>
</lint>
\ No newline at end of file
...@@ -74,4 +74,13 @@ class AnswersAnalytics : Analytics { ...@@ -74,4 +74,13 @@ class AnswersAnalytics : Analytics {
CustomEvent("reset_password") CustomEvent("reset_password")
.putCustomAttribute("resetPasswordSucceeded", resetPasswordSucceeded.toString()) .putCustomAttribute("resetPasswordSucceeded", resetPasswordSucceeded.toString())
) )
override fun logVideoConference(event: SubscriptionTypeEvent, serverUrl: String) =
Answers.getInstance()
.logCustom(
CustomEvent("video_conference")
.putCustomAttribute("subscription_type", event.subscriptionTypeName)
.putCustomAttribute("server", serverUrl)
)
} }
...@@ -13,14 +13,14 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) : ...@@ -13,14 +13,14 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) :
private val firebaseAnalytics = FirebaseAnalytics.getInstance(context) private val firebaseAnalytics = FirebaseAnalytics.getInstance(context)
override fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) { override fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) {
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle(1).apply { firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle(2).apply {
putString(FirebaseAnalytics.Param.METHOD, event.methodName) putString(FirebaseAnalytics.Param.METHOD, event.methodName)
putLong(FirebaseAnalytics.Param.SUCCESS, if (loginSucceeded) 1 else 0) putLong(FirebaseAnalytics.Param.SUCCESS, if (loginSucceeded) 1 else 0)
}) })
} }
override fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) { override fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) {
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SIGN_UP, Bundle(1).apply { firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SIGN_UP, Bundle(2).apply {
putString(FirebaseAnalytics.Param.METHOD, event.methodName) putString(FirebaseAnalytics.Param.METHOD, event.methodName)
putLong(FirebaseAnalytics.Param.SUCCESS, if (signUpSucceeded) 1 else 0) putLong(FirebaseAnalytics.Param.SUCCESS, if (signUpSucceeded) 1 else 0)
}) })
...@@ -33,27 +33,27 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) : ...@@ -33,27 +33,27 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) :
} }
override fun logMessageSent(event: SubscriptionTypeEvent, serverUrl: String) { override fun logMessageSent(event: SubscriptionTypeEvent, serverUrl: String) {
firebaseAnalytics.logEvent("message_sent", Bundle(1).apply { firebaseAnalytics.logEvent("message_sent", Bundle(2).apply {
putString("subscription_type", event.subscriptionTypeName) putString("subscription_type", event.subscriptionTypeName)
putString("server", serverUrl) putString("server", serverUrl)
}) })
} }
override fun logMediaUploaded(event: SubscriptionTypeEvent, mimeType: String) { override fun logMediaUploaded(event: SubscriptionTypeEvent, mimeType: String) {
firebaseAnalytics.logEvent("media_upload", Bundle(1).apply { firebaseAnalytics.logEvent("media_upload", Bundle(2).apply {
putString("subscription_type", event.subscriptionTypeName) putString("subscription_type", event.subscriptionTypeName)
putString("media_type", mimeType) putString("media_type", mimeType)
}) })
} }
override fun logReaction(event: SubscriptionTypeEvent) { override fun logReaction(event: SubscriptionTypeEvent) {
firebaseAnalytics.logEvent("reaction", Bundle(1).apply { firebaseAnalytics.logEvent("reaction", Bundle(2).apply {
putString("subscription_type", event.subscriptionTypeName) putString("subscription_type", event.subscriptionTypeName)
}) })
} }
override fun logServerSwitch(serverUrl: String, serverCount: Int) { override fun logServerSwitch(serverUrl: String, serverCount: Int) {
firebaseAnalytics.logEvent("server_switch", Bundle(1).apply { firebaseAnalytics.logEvent("server_switch", Bundle(2).apply {
putString("server_url", serverUrl) putString("server_url", serverUrl)
putInt("server_count", serverCount) putInt("server_count", serverCount)
}) })
...@@ -62,7 +62,14 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) : ...@@ -62,7 +62,14 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) :
override fun logOpenAdmin() = firebaseAnalytics.logEvent("open_admin", null) override fun logOpenAdmin() = firebaseAnalytics.logEvent("open_admin", null)
override fun logResetPassword(resetPasswordSucceeded: Boolean) = override fun logResetPassword(resetPasswordSucceeded: Boolean) =
firebaseAnalytics.logEvent("reset_password", Bundle(1).apply { firebaseAnalytics.logEvent("reset_password", Bundle(2).apply {
putBoolean("resetPasswordSucceeded", resetPasswordSucceeded) putBoolean("resetPasswordSucceeded", resetPasswordSucceeded)
}) })
override fun logVideoConference(event: SubscriptionTypeEvent, serverUrl: String) {
firebaseAnalytics.logEvent("video_conference", Bundle(2).apply {
putString("subscription_type", event.subscriptionTypeName)
putString("server", serverUrl)
})
}
} }
package chat.rocket.android.extensions package chat.rocket.android.extensions
import com.google.android.gms.tasks.Task 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") @JvmName("awaitVoid")
suspend fun Task<Void>.await() = suspendCoroutine<Unit> { continuation -> suspend fun Task<Void>.await() = suspendCoroutine<Unit> { continuation ->
......
...@@ -45,7 +45,7 @@ object SmartLockHelper { ...@@ -45,7 +45,7 @@ object SmartLockHelper {
.addOnCompleteListener { .addOnCompleteListener {
when { when {
it.isSuccessful -> { it.isSuccessful -> {
credential = it.result.credential credential = it.result?.credential
} }
it.exception is ResolvableApiException -> { it.exception is ResolvableApiException -> {
val resolvableApiException = (it.exception as ResolvableApiException) val resolvableApiException = (it.exception as ResolvableApiException)
......
...@@ -7,16 +7,22 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor ...@@ -7,16 +7,22 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.SITE_URL import chat.rocket.android.server.domain.SITE_URL
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.runBlocking
fun installCrashlyticsWrapper(context: Application, fun installCrashlyticsWrapper(
context: Application,
currentServerInteractor: GetCurrentServerInteractor, currentServerInteractor: GetCurrentServerInteractor,
settingsInteractor: GetSettingsInteractor, settingsInteractor: GetSettingsInteractor,
accountRepository: AccountsRepository, accountRepository: AccountsRepository,
localRepository: LocalRepository) { localRepository: LocalRepository
) {
if (isCrashlyticsEnabled()) { if (isCrashlyticsEnabled()) {
Thread.setDefaultUncaughtExceptionHandler(RocketChatUncaughtExceptionHandler(currentServerInteractor, Thread.setDefaultUncaughtExceptionHandler(
settingsInteractor, accountRepository, localRepository)) RocketChatUncaughtExceptionHandler(
currentServerInteractor,
settingsInteractor, accountRepository, localRepository
)
)
} }
} }
...@@ -28,10 +34,11 @@ private class RocketChatUncaughtExceptionHandler( ...@@ -28,10 +34,11 @@ private class RocketChatUncaughtExceptionHandler(
val currentServerInteractor: GetCurrentServerInteractor, val currentServerInteractor: GetCurrentServerInteractor,
val settingsInteractor: GetSettingsInteractor, val settingsInteractor: GetSettingsInteractor,
val accountRepository: AccountsRepository, val accountRepository: AccountsRepository,
val localRepository: LocalRepository) val localRepository: LocalRepository
: Thread.UncaughtExceptionHandler { ) : Thread.UncaughtExceptionHandler {
val crashlyticsHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler() val crashlyticsHandler: Thread.UncaughtExceptionHandler? =
Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(t: Thread, e: Throwable) { override fun uncaughtException(t: Thread, e: Throwable) {
val currentServer = currentServerInteractor.get() ?: "<unknown>" val currentServer = currentServerInteractor.get() ?: "<unknown>"
......
...@@ -9,7 +9,7 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory ...@@ -9,7 +9,7 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.registerPushToken import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.FirebaseInstanceId
import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.runBlocking
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
......
...@@ -10,12 +10,11 @@ buildscript { ...@@ -10,12 +10,11 @@ buildscript {
} }
dependencies { 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.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:4.1.0' classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.25.4' classpath 'io.fabric.tools:gradle:1.26.1'
classpath "com.github.ben-manes:gradle-versions-plugin:0.20.0" classpath "com.github.ben-manes:gradle-versions-plugin:0.20.0"
} }
} }
...@@ -26,6 +25,7 @@ allprojects { ...@@ -26,6 +25,7 @@ allprojects {
jcenter() jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases" }
} }
apply from: rootProject.file('dependencies.gradle') apply from: rootProject.file('dependencies.gradle')
......
...@@ -29,7 +29,7 @@ dependencies { ...@@ -29,7 +29,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutinesCore
implementation libraries.lifecycleExtensions implementation libraries.lifecycleExtensions
kapt libraries.lifecycleCompiler kapt libraries.lifecycleCompiler
......
...@@ -4,7 +4,7 @@ import androidx.lifecycle.Lifecycle ...@@ -4,7 +4,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
class CancelStrategy(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver { class CancelStrategy(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver {
......
...@@ -2,17 +2,17 @@ ext { ...@@ -2,17 +2,17 @@ ext {
versions = [ versions = [
// For project configuration // For project configuration
java : JavaVersion.VERSION_1_8, java : JavaVersion.VERSION_1_8,
minSdk : 21,
compileSdk : 28, compileSdk : 28,
targetSdk : 28, targetSdk : 28,
minSdk : 21,
buildTools : '28.0.3', buildTools : '28.0.3',
dokka : '0.9.16', dokka : '0.9.16',
// For app // For app
kotlin : '1.2.71', kotlin : '1.3.21',
coroutine : '0.25.0', coroutine : '1.1.1',
appCompat : '1.0.0', appCompat : '1.0.2',
recyclerview : '1.0.0', recyclerview : '1.0.0',
constraintLayout : '2.0.0-alpha2', constraintLayout : '2.0.0-alpha2',
cardview : '1.0.0', cardview : '1.0.0',
...@@ -21,9 +21,9 @@ ext { ...@@ -21,9 +21,9 @@ ext {
workmanager : '1.0.0-alpha09', workmanager : '1.0.0-alpha09',
dagger : '2.16', dagger : '2.16',
firebaseCloudMessage : '17.3.0', firebaseCloudMessage : '17.3.4',
firebaseAnalytics : '16.0.3', firebaseAnalytics : '16.0.6',
playServices : '16.0.0', playServicesAuth : '16.0.1',
exoPlayer : '2.8.2', exoPlayer : '2.8.2',
flexbox : '1.1.0', flexbox : '1.1.0',
material : '1.0.0', material : '1.0.0',
...@@ -36,8 +36,8 @@ ext { ...@@ -36,8 +36,8 @@ ext {
rxKotlin : '2.3.0', rxKotlin : '2.3.0',
rxAndroid : '2.1.0', rxAndroid : '2.1.0',
moshi : '1.6.0', moshi : '1.8.0',
okhttp : '3.11.0', okhttp : '3.12.1',
timber : '4.7.1', timber : '4.7.1',
threeTenABP : '1.1.0', threeTenABP : '1.1.0',
...@@ -45,18 +45,18 @@ ext { ...@@ -45,18 +45,18 @@ ext {
fresco : '1.10.0', fresco : '1.10.0',
kotshi : '1.0.4', kotshi : '1.0.6',
frescoImageViewer : '0.5.1', frescoImageViewer : '0.5.1',
markwon : '2.0.0', markwon : '2.0.0',
aVLoadingIndicatorView: '2.1.3', aVLoadingIndicatorView: '2.1.3',
glide : '4.8.0', glide : '4.8.0',
glideTransformations : '4.0.0', glideTransformations : '4.0.0',
// For wearable jitsi : '+', // TODO Avoid using + (https://github.com/jitsi/jitsi-meet/issues/3987)
wear : '2.3.0',
playServicesWearable : '15.0.1',
supportWearable : '27.1.1',
// For testing // For testing
junit : '4.12', junit : '4.12',
...@@ -66,7 +66,7 @@ ext { ...@@ -66,7 +66,7 @@ ext {
] ]
libraries = [ libraries = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}", 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}", coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
appCompat : "androidx.appcompat:appcompat:${versions.appCompat}", appCompat : "androidx.appcompat:appcompat:${versions.appCompat}",
...@@ -117,26 +117,20 @@ ext { ...@@ -117,26 +117,20 @@ ext {
kotshiCompiler : "se.ansman.kotshi:compiler:${versions.kotshi}", kotshiCompiler : "se.ansman.kotshi:compiler:${versions.kotshi}",
frescoImageViewer : "com.github.luciofm:FrescoImageViewer:${versions.frescoImageViewer}", 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}", markwon : "ru.noties:markwon:${versions.markwon}",
aVLoadingIndicatorView: "com.wang.avi:library:${versions.aVLoadingIndicatorView}", 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 // Proprietary libraries
fcm : "com.google.firebase:firebase-messaging:${versions.firebaseCloudMessage}", fcm : "com.google.firebase:firebase-messaging:${versions.firebaseCloudMessage}",
firebaseAnalytics : "com.google.firebase:firebase-core:${versions.firebaseAnalytics}", firebaseAnalytics : "com.google.firebase:firebase-core:${versions.firebaseAnalytics}",
playServicesAuth : "com.google.android.gms:play-services-auth:${versions.playServices}", playServicesAuth : "com.google.android.gms:play-services-auth:${versions.playServicesAuth}",
// 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}",
// For testing // For testing
junit : "junit:junit:${versions.junit}", junit : "junit:junit:${versions.junit}",
......
...@@ -51,7 +51,7 @@ dependencies { ...@@ -51,7 +51,7 @@ dependencies {
implementation project(':util') implementation project(':util')
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutinesCore
implementation libraries.appCompat implementation libraries.appCompat
implementation libraries.constraintlayout implementation libraries.constraintlayout
......
...@@ -6,7 +6,7 @@ import chat.rocket.android.draw.main.presenter.DrawView ...@@ -6,7 +6,7 @@ import chat.rocket.android.draw.main.presenter.DrawView
import chat.rocket.android.draw.main.ui.DrawingActivity import chat.rocket.android.draw.main.ui.DrawingActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
@Module @Module
class DrawModule { class DrawModule {
......
...@@ -5,14 +5,13 @@ apply plugin: 'kotlin-kapt' ...@@ -5,14 +5,13 @@ apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion versions.compileSdk compileSdkVersion versions.compileSdk
buildToolsVersion '28.0.3' buildToolsVersion versions.buildTools
defaultConfig { defaultConfig {
minSdkVersion versions.minSdk minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 1 versionCode 1
versionName "0.1" versionName "0.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions { javaCompileOptions {
...@@ -28,28 +27,24 @@ android { ...@@ -28,28 +27,24 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
} }
dependencies { dependencies {
implementation libraries.androidKtx
implementation libraries.appCompat
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutinesCore
implementation libraries.coroutinesAndroid implementation libraries.coroutinesAndroid
implementation libraries.constraintlayout
implementation libraries.appCompat
implementation libraries.recyclerview implementation libraries.recyclerview
implementation libraries.constraintlayout
implementation libraries.androidKtx
implementation libraries.material implementation libraries.material
implementation libraries.glide
kapt libraries.glideProcessor
implementation libraries.room implementation libraries.room
kapt libraries.roomProcessor kapt libraries.roomProcessor
}
kotlin { implementation libraries.glide
experimental {
coroutines "enable"
}
} }
androidExtensions { androidExtensions {
......
...@@ -22,8 +22,9 @@ import chat.rocket.android.emoji.internal.EmojiPagerAdapter ...@@ -22,8 +22,9 @@ import chat.rocket.android.emoji.internal.EmojiPagerAdapter
import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import kotlinx.android.synthetic.main.dialog_skin_tone_chooser.view.* import kotlinx.android.synthetic.main.dialog_skin_tone_chooser.view.*
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow(context, view) { class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow(context, view) {
private lateinit var viewPager: ViewPager private lateinit var viewPager: ViewPager
...@@ -49,7 +50,7 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow ...@@ -49,7 +50,7 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
} }
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
launch(UI) { GlobalScope.launch(Dispatchers.Main) {
setupViewPager() setupViewPager()
setupBottomBar() setupBottomBar()
} }
......
...@@ -8,12 +8,14 @@ import android.text.SpannableString ...@@ -8,12 +8,14 @@ import android.text.SpannableString
import android.text.Spanned import android.text.Spanned
import android.text.style.ImageSpan import android.text.style.ImageSpan
import android.util.Log 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.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.gif.GifDrawable import com.bumptech.glide.load.resource.gif.GifDrawable
import kotlinx.coroutines.experimental.CommonPool import com.bumptech.glide.request.RequestOptions
import kotlinx.coroutines.experimental.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.experimental.async import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
class EmojiParser { class EmojiParser {
...@@ -85,14 +87,14 @@ class EmojiParser { ...@@ -85,14 +87,14 @@ class EmojiParser {
emoji.url?.let { url -> emoji.url?.let { url ->
try { try {
val glideRequest = if (url.endsWith("gif", true)) { val glideRequest = if (url.endsWith("gif", true)) {
GlideApp.with(context).asGif() Glide.with(context).asGif()
} else { } else {
GlideApp.with(context).asBitmap() Glide.with(context).asBitmap()
} }
val futureTarget = glideRequest val futureTarget = glideRequest
.diskCacheStrategy(DiskCacheStrategy.ALL)
.load(url) .load(url)
.apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL))
.submit(px, px) .submit(px, px)
val range = match.range val range = match.range
...@@ -120,7 +122,7 @@ class EmojiParser { ...@@ -120,7 +122,7 @@ class EmojiParser {
text: CharSequence, text: CharSequence,
factory: Spannable.Factory? = null factory: Spannable.Factory? = null
): Deferred<CharSequence> { ): 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 ...@@ -14,8 +14,9 @@ import chat.rocket.android.emoji.internal.EmojiCategory
import chat.rocket.android.emoji.internal.EmojiPagerAdapter import chat.rocket.android.emoji.internal.EmojiPagerAdapter
import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE
import kotlinx.android.synthetic.main.emoji_picker.* import kotlinx.android.synthetic.main.emoji_picker.*
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class EmojiPickerPopup(context: Context) : Dialog(context) { class EmojiPickerPopup(context: Context) : Dialog(context) {
...@@ -29,7 +30,7 @@ class EmojiPickerPopup(context: Context) : Dialog(context) { ...@@ -29,7 +30,7 @@ class EmojiPickerPopup(context: Context) : Dialog(context) {
setContentView(R.layout.emoji_picker) setContentView(R.layout.emoji_picker)
tabs.setupWithViewPager(pager_categories) tabs.setupWithViewPager(pager_categories)
launch(UI) { GlobalScope.launch(Dispatchers.Main) {
setupViewPager() setupViewPager()
setSize() setSize()
} }
......
...@@ -10,22 +10,21 @@ import chat.rocket.android.emoji.internal.db.EmojiDatabase ...@@ -10,22 +10,21 @@ import chat.rocket.android.emoji.internal.db.EmojiDatabase
import chat.rocket.android.emoji.internal.isCustom import chat.rocket.android.emoji.internal.isCustom
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStream import java.io.InputStream
import java.io.InputStreamReader import java.io.InputStreamReader
import java.io.Reader
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.coroutines.experimental.buildSequence
object EmojiRepository { object EmojiRepository {
private val FITZPATRICK_REGEX = "(.*)_(tone[0-9]):".toRegex(RegexOption.IGNORE_CASE) private val FITZPATRICK_REGEX = "(.*)_(tone[0-9]):".toRegex(RegexOption.IGNORE_CASE)
private val shortNameToUnicode = HashMap<String, String>() private val shortNameToUnicode = HashMap<String, String>()
private val SHORTNAME_PATTERN = Pattern.compile(":([-+\\w]+):") private val SHORTNAME_PATTERN = Pattern.compile(":([-+\\w]+):")
...@@ -43,14 +42,19 @@ object EmojiRepository { ...@@ -43,14 +42,19 @@ object EmojiRepository {
return if (::currentServerUrl.isInitialized) currentServerUrl else null return if (::currentServerUrl.isInitialized) currentServerUrl else null
} }
fun load(context: Context, customEmojis: List<Emoji> = emptyList(), path: String = "emoji.json") { fun load(
launch(CommonPool) { context: Context,
customEmojis: List<Emoji> = emptyList(),
path: String = "emoji.json"
) {
GlobalScope.launch(Dispatchers.IO) {
this@EmojiRepository.customEmojis = customEmojis this@EmojiRepository.customEmojis = customEmojis
val allEmojis = mutableListOf<Emoji>() val allEmojis = mutableListOf<Emoji>()
db = EmojiDatabase.getInstance(context) db = EmojiDatabase.getInstance(context)
if (!::cachedTypeface.isInitialized) { 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) preferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE)
...@@ -117,9 +121,7 @@ object EmojiRepository { ...@@ -117,9 +121,7 @@ object EmojiRepository {
customEmojis.forEach { customEmojis.forEach {
try { try {
val future = Glide.with(context) val future = Glide.with(context).load(it.url).submit(px, px)
.load(it.url)
.submit(px, px)
future.get() future.get()
} catch (ex: Exception) { } catch (ex: Exception) {
Log.d("EmojiRepository", "Error fetching custom emoji ${it.shortname}", ex) Log.d("EmojiRepository", "Error fetching custom emoji ${it.shortname}", ex)
...@@ -132,7 +134,7 @@ object EmojiRepository { ...@@ -132,7 +134,7 @@ object EmojiRepository {
} }
private suspend fun saveEmojisToDatabase(emojis: List<Emoji>) { private suspend fun saveEmojisToDatabase(emojis: List<Emoji>) {
withContext(CommonPool) { withContext(Dispatchers.IO) {
db.emojiDao().insertAllEmojis(*emojis.toTypedArray()) db.emojiDao().insertAllEmojis(*emojis.toTypedArray())
} }
} }
...@@ -146,31 +148,30 @@ object EmojiRepository { ...@@ -146,31 +148,30 @@ object EmojiRepository {
* *
* @return All emojis for all categories. * @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() return@withContext db.emojiDao().loadAllEmojis()
} }
internal suspend fun getEmojiSequenceByCategory(category: EmojiCategory): Sequence<Emoji> { internal suspend fun getEmojiSequenceByCategory(category: EmojiCategory): Sequence<Emoji> {
val list = withContext(CommonPool) { val list = withContext(Dispatchers.IO) {
db.emojiDao().loadEmojisByCategory(category.name) db.emojiDao().loadEmojisByCategory(category.name)
} }
return buildSequence { return sequence {
list.forEach { list.forEach { yield(it) }
yield(it)
}
} }
} }
internal suspend fun getEmojiSequenceByCategoryAndUrl(category: EmojiCategory, url: String): Sequence<Emoji> { internal suspend fun getEmojiSequenceByCategoryAndUrl(
val list = withContext(CommonPool) { category: EmojiCategory,
url: String
): Sequence<Emoji> {
val list = withContext(Dispatchers.IO) {
db.emojiDao().loadEmojisByCategoryAndUrl(category.name, "$url%") db.emojiDao().loadEmojisByCategoryAndUrl(category.name, "$url%")
} }
return buildSequence { return sequence {
list.forEach { list.forEach { yield(it) }
yield(it)
}
} }
} }
...@@ -181,7 +182,8 @@ object EmojiRepository { ...@@ -181,7 +182,8 @@ object EmojiRepository {
* *
* @return Emoji given by shortname or null * @return Emoji given by shortname or null
*/ */
private suspend fun getEmojiByShortname(shortname: String): Emoji? = withContext(CommonPool) { private suspend fun getEmojiByShortname(shortname: String): Emoji? =
withContext(Dispatchers.IO) {
return@withContext db.emojiDao().loadAllCustomEmojis().firstOrNull() return@withContext db.emojiDao().loadAllCustomEmojis().firstOrNull()
} }
...@@ -203,9 +205,9 @@ object EmojiRepository { ...@@ -203,9 +205,9 @@ object EmojiRepository {
} }
internal suspend fun getCustomEmojisAsync(): List<Emoji> { internal suspend fun getCustomEmojisAsync(): List<Emoji> {
return withContext(CommonPool) { return withContext(Dispatchers.IO) {
db.emojiDao().loadAllCustomEmojis().also { db.emojiDao().loadAllCustomEmojis().also {
this.customEmojis = it customEmojis = it
} }
} }
} }
...@@ -217,7 +219,7 @@ object EmojiRepository { ...@@ -217,7 +219,7 @@ object EmojiRepository {
* *
* @return All recent emojis ordered by usage. * @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 list = mutableListOf<Emoji>()
val recentsJson = JSONObject(preferences.getString(PREF_EMOJI_RECENTS, "{}")) val recentsJson = JSONObject(preferences.getString(PREF_EMOJI_RECENTS, "{}"))
...@@ -281,11 +283,13 @@ object EmojiRepository { ...@@ -281,11 +283,13 @@ object EmojiRepository {
if (!json.has("shortname") || !json.has("unicode")) { if (!json.has("shortname") || !json.has("unicode")) {
return null return null
} }
return Emoji(shortname = json.getString("shortname"), return Emoji(
shortname = json.getString("shortname"),
unicode = json.getString("unicode"), unicode = json.getString("unicode"),
shortnameAlternates = buildStringListFromJsonArray(json.getJSONArray("shortnameAlternates")), shortnameAlternates = buildStringListFromJsonArray(json.getJSONArray("shortnameAlternates")),
category = json.getString("category"), category = json.getString("category"),
keywords = buildStringListFromJsonArray(json.getJSONArray("keywords"))) keywords = buildStringListFromJsonArray(json.getJSONArray("keywords"))
)
} }
private fun buildStringListFromJsonArray(array: JSONArray): List<String> { private fun buildStringListFromJsonArray(array: JSONArray): List<String> {
...@@ -297,7 +301,7 @@ object EmojiRepository { ...@@ -297,7 +301,7 @@ object EmojiRepository {
private fun inputStreamToString(stream: InputStream): String { private fun inputStreamToString(stream: InputStream): String {
val sb = StringBuilder() val sb = StringBuilder()
val isr = InputStreamReader(stream, Charsets.UTF_8) val isr = InputStreamReader(stream, Charsets.UTF_8)
val br = BufferedReader(isr) val br = BufferedReader(isr as Reader?)
var read: String? = br.readLine() var read: String? = br.readLine()
while (read != null) { while (read != null) {
sb.append(read) sb.append(read)
...@@ -315,7 +319,7 @@ object EmojiRepository { ...@@ -315,7 +319,7 @@ object EmojiRepository {
} }
fun init(context: Context) { fun init(context: Context) {
launch { GlobalScope.launch {
db = EmojiDatabase.getInstance(context) db = EmojiDatabase.getInstance(context)
preferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE) preferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE)
cachedTypeface = Typeface.createFromAsset(context.assets, "fonts/emojione-android.ttf") 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 ...@@ -14,15 +14,15 @@ import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiRepository import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.emoji.Fitzpatrick import chat.rocket.android.emoji.Fitzpatrick
import chat.rocket.android.emoji.R import chat.rocket.android.emoji.R
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy 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_category_layout.view.*
import kotlinx.android.synthetic.main.emoji_image_row_item.view.* import kotlinx.android.synthetic.main.emoji_image_row_item.view.*
import kotlinx.android.synthetic.main.emoji_row_item.view.* import kotlinx.android.synthetic.main.emoji_row_item.view.*
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.launch
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.withContext
import kotlinx.coroutines.experimental.withContext
internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) : PagerAdapter() { internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) : PagerAdapter() {
...@@ -44,7 +44,7 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) : ...@@ -44,7 +44,7 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
emoji_recycler_view.setRecycledViewPool(RecyclerView.RecycledViewPool()) emoji_recycler_view.setRecycledViewPool(RecyclerView.RecycledViewPool())
container.addView(view) container.addView(view)
launch(UI) { kotlinx.coroutines.GlobalScope.launch(Dispatchers.Main) {
val currentServerUrl = EmojiRepository.getCurrentServerUrl() val currentServerUrl = EmojiRepository.getCurrentServerUrl()
val emojis = if (category != EmojiCategory.RECENTS) { val emojis = if (category != EmojiCategory.RECENTS) {
if (category == EmojiCategory.CUSTOM) { if (category == EmojiCategory.CUSTOM) {
...@@ -111,9 +111,9 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) : ...@@ -111,9 +111,9 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
} }
suspend fun addEmojisFromSequence(emojiSequence: Sequence<Emoji>) { suspend fun addEmojisFromSequence(emojiSequence: Sequence<Emoji>) {
withContext(CommonPool) { withContext(Dispatchers.IO) {
emojiSequence.forEachIndexed { index, emoji -> emojiSequence.forEachIndexed { index, emoji ->
withContext(UI) { withContext(Dispatchers.Main) {
allEmojis.add(emoji) allEmojis.add(emoji)
if (emoji.isDefault) { if (emoji.isDefault) {
emojis.add(emoji) emojis.add(emoji)
...@@ -180,9 +180,9 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) : ...@@ -180,9 +180,9 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
} }
} else { } else {
// Handle custom emoji. // Handle custom emoji.
GlideApp.with(context) Glide.with(context)
.load(emoji.url) .load(emoji.url)
.diskCacheStrategy(DiskCacheStrategy.ALL) .apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL))
.into(emoji_image_view) .into(emoji_image_view)
} }
......
...@@ -13,6 +13,7 @@ android { ...@@ -13,6 +13,7 @@ android {
versionName "1.0.0" versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
......
include ':app', ':player', ':emoji', ':draw', ':util', ':core', ':suggestions' //, ':wear' include ':app', ':player', ':emoji', ':draw', ':util', ':core', ':suggestions'
\ No newline at end of file \ No newline at end of file
package chat.rocket.android.suggestions.strategy.trie.data package chat.rocket.android.suggestions.strategy.trie.data
import chat.rocket.android.suggestions.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import kotlin.coroutines.experimental.buildSequence
internal class TrieNode( internal class TrieNode(
internal var data: Char, internal var data: Char,
...@@ -32,17 +31,13 @@ internal class TrieNode( ...@@ -32,17 +31,13 @@ internal class TrieNode(
return list return list
} }
fun getItems(): Sequence<SuggestionModel> = buildSequence { fun getItems(): Sequence<SuggestionModel> = sequence {
if (isLeaf) { if (isLeaf) {
yield(item!!) yield(item!!)
} }
children.forEach { node -> children.forEach { node -> yieldAll(node.value.getItems()) }
node.value.let {
yieldAll(it.getItems())
}
}
} }
override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data" override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data"
......
...@@ -31,7 +31,7 @@ dependencies { ...@@ -31,7 +31,7 @@ dependencies {
implementation project(':core') implementation project(':core')
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutinesCore
implementation libraries.coroutinesAndroid implementation libraries.coroutinesAndroid
implementation libraries.appCompat implementation libraries.appCompat
......
package chat.rocket.android.util.extension package chat.rocket.android.util.extension
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import kotlinx.coroutines.experimental.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.MainScope
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.launch
/** /**
* Launches a coroutine on the UI context. * Launches a coroutine on the UI context.
* *
* @param strategy a CancelStrategy for canceling the coroutine job * @param strategy a CancelStrategy for canceling the coroutine job
*/ */
fun launchUI(strategy: CancelStrategy, block: suspend CoroutineScope.() -> Unit): Job { fun launchUI(strategy: CancelStrategy, block: suspend CoroutineScope.() -> Unit): Job =
return launch(context = UI, parent = strategy.jobs, block = block) MainScope().launch(context = strategy.jobs, block = block)
}
\ No newline at end of file
...@@ -6,8 +6,8 @@ import android.os.Environment ...@@ -6,8 +6,8 @@ import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.experimental.DefaultDispatcher import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
...@@ -25,11 +25,10 @@ import java.util.* ...@@ -25,11 +25,10 @@ import java.util.*
suspend fun Bitmap.compressImageAndGetInputStream(mimeType: String): InputStream? { suspend fun Bitmap.compressImageAndGetInputStream(mimeType: String): InputStream? {
var inputStream: InputStream? = null var inputStream: InputStream? = null
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
// TODO: Add an option the the app to the user be able to select the quality of the compressed image // TODO: Add an option the the app to the user be able to select the quality of the compressed image
val isCompressed = val isCompressed = compress(mimeType.getCompressFormat(), 70, byteArrayOutputStream)
this.compress(mimeType.getCompressFormat(), 70, byteArrayOutputStream)
if (isCompressed) { if (isCompressed) {
inputStream = ByteArrayInputStream(byteArrayOutputStream.toByteArray()) inputStream = ByteArrayInputStream(byteArrayOutputStream.toByteArray())
} }
...@@ -74,10 +73,9 @@ suspend fun Bitmap.getByteArray( ...@@ -74,10 +73,9 @@ suspend fun Bitmap.getByteArray(
suspend fun Bitmap.compressImageAndGetByteArray(mimeType: String, quality: Int = 100): ByteArray? { suspend fun Bitmap.compressImageAndGetByteArray(mimeType: String, quality: Int = 100): ByteArray? {
var byteArray: ByteArray? = null var byteArray: ByteArray? = null
withContext(DefaultDispatcher) { withContext(Dispatchers.Default) {
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
val isCompressed = val isCompressed = compress(mimeType.getCompressFormat(), quality, byteArrayOutputStream)
this.compress(mimeType.getCompressFormat(), quality, byteArrayOutputStream)
if (isCompressed) { if (isCompressed) {
byteArray = byteArrayOutputStream.toByteArray() byteArray = byteArrayOutputStream.toByteArray()
} }
......
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
buildToolsVersion versions.buildTools
defaultConfig {
applicationId "chat.rocket.android.wear"
minSdkVersion 23
targetSdkVersion versions.targetSdk
versionCode 1
versionName "1.0.0"
}
buildTypes {
release {
postprocessing {
removeUnusedCode false
removeUnusedResources false
obfuscate false
optimizeCode false
proguardFile 'proguard-rules.pro'
}
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation libraries.kotlin
implementation libraries.wearable
implementation libraries.playServicesWearable
implementation libraries.percentLayout
implementation libraries.supportWearable
implementation libraries.wearableRecyclerView
implementation libraries.wearSupport
compileOnly 'com.google.android.wearable:wearable:2.3.0'
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android.wear">
<uses-feature android:name="android.hardware.type.watch" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault">
<uses-library
android:name="com.google.android.wearable"
android:required="true" />
<!--
Set to true if your app is Standalone, that is, it does not require the handheld
app to run.
-->
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
package chat.rocket.android.wear
import android.os.Bundle
import android.support.wearable.activity.WearableActivity
class MainActivity : WearableActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Enables Always-on
setAmbientEnabled()
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.wear.widget.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/dark_grey"
android:padding="@dimen/box_inset_layout_padding"
tools:context="chat.rocket.android.wear.MainActivity"
tools:deviceIds="wear">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/inner_frame_layout_padding"
app:boxedEdges="all">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</FrameLayout>
</android.support.wear.widget.BoxInsetLayout>
<resources>
<string name="hello_world">Hello Round World!</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Because the window insets on round devices are larger than 15dp, this padding only applies
to square screens.
-->
<dimen name="box_inset_layout_padding">0dp</dimen>
<!--
This padding applies to both square and round screens. The total padding between the buttons
and the window insets is box_inset_layout_padding (above variable) on square screens and
inner_frame_layout_padding (below variable) on round screens.
-->
<dimen name="inner_frame_layout_padding">5dp</dimen>
</resources>
<resources>
<string name="app_name">Rocket&#46;Chat</string>
<!--
This string is used for square devices and overridden by hello_world in
values-round/strings.xml for round devices.
-->
<string name="hello_world">Hello Square World!</string>
</resources>
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