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

Merge pull request #2168 from RocketChat/beta

[RELEASE] Merge BETA into MASTER 
parents b342f3cd afe67756
...@@ -43,8 +43,8 @@ cd Rocket.Chat.Android/app ...@@ -43,8 +43,8 @@ cd Rocket.Chat.Android/app
## Bug report & Feature request ## Bug report & Feature request
Are you having a technical issue trying to compile the app, or setting up Push Notifications? Please use our Community Support channel for that: https://forums.rocket.chat/c/community-support. The issues are only suppose to be used for bugs, improvements and features in the native Android application. Are you having a technical issue trying to compile the app, or setting up Push Notifications? Please use our Community Support channel for that: https://forums.rocket.chat/c/community-support. The issues are only supposed to be used for bugs, improvements, and features in the native Android application.
## Coding Style ## Coding Style
Please follow the official [Kotlin coding convections](https://kotlinlang.org/docs/reference/coding-conventions.html) when contributing. Please follow the official [Kotlin coding conventions](https://kotlinlang.org/docs/reference/coding-conventions.html) when contributing.
...@@ -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 2059
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"
......
...@@ -34,12 +34,16 @@ class AboutFragment : Fragment() { ...@@ -34,12 +34,16 @@ class AboutFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar()
setupViews() setupViews()
analyticsManager.logScreenView(ScreenViewEvent.About) analyticsManager.logScreenView(ScreenViewEvent.About)
} }
override fun onResume() {
super.onResume()
setupToolbar()
}
private fun setupViews() { private fun setupViews() {
text_version_name.text = BuildConfig.VERSION_NAME text_version_name.text = BuildConfig.VERSION_NAME
text_build_number.text = getString( text_build_number.text = getString(
......
...@@ -71,4 +71,67 @@ interface Analytics { ...@@ -71,4 +71,67 @@ 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) {}
/**
* Logs the add reaction message action.
*/
fun logMessageActionAddReaction() {}
/**
* Logs the replay message action.
*/
fun logMessageActionReply() {}
/**
* Logs the quote message action.
*/
fun logMessageActionQuote() {}
/**
* Logs the permalink message action.
*/
fun logMessageActionPermalink() {}
/**
* Logs the copy message action.
*/
fun logMessageActionCopy() {}
/**
* Logs the edit message action.
*/
fun logMessageActionEdit() {}
/**
* Logs the info message action.
*/
fun logMessageActionInfo() {}
/**
* Logs the star message action.
*/
fun logMessageActionStar() {}
/**
* Logs the pin message action.
*/
fun logMessageActionPin() {}
/**
* Logs the report message action.
*/
fun logMessageActionReport() {}
/**
* Logs the delete message action.
*/
fun logMessageActionDelete() {}
} }
...@@ -76,4 +76,76 @@ class AnalyticsManager @Inject constructor( ...@@ -76,4 +76,76 @@ 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) }
}
}
fun logMessageActionAddReaction() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionAddReaction() }
}
}
fun logMessageActionReply() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionReply() }
}
}
fun logMessageActionQuote() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionQuote() }
}
}
fun logMessageActionPermalink() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionPermalink() }
}
}
fun logMessageActionCopy() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionCopy() }
}
}
fun logMessageActionEdit() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionEdit() }
}
}
fun logMessageActionInfo() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionInfo() }
}
}
fun logMessageActionStar() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionStar() }
}
}
fun logMessageActionPin() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionPin() }
}
}
fun logMessageActionReport() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionReport() }
}
}
fun logMessageActionDelete() {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logMessageActionDelete() }
}
}
} }
...@@ -3,15 +3,11 @@ package chat.rocket.android.app ...@@ -3,15 +3,11 @@ package chat.rocket.android.app
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import chat.rocket.android.server.domain.GetAccountInteractor
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.server.infraestructure.RocketChatClientFactory
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
import chat.rocket.core.internal.realtime.setTemporaryStatus import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class AppLifecycleObserver @Inject constructor( class AppLifecycleObserver @Inject constructor(
...@@ -33,7 +29,7 @@ class AppLifecycleObserver @Inject constructor( ...@@ -33,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)
} }
......
import android.content.Context import android.content.Context
import chat.rocket.android.R import chat.rocket.android.R
import org.threeten.bp.* import org.threeten.bp.Instant
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import org.threeten.bp.Period
import org.threeten.bp.ZoneId
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.FormatStyle import org.threeten.bp.format.FormatStyle
import org.threeten.bp.format.TextStyle import org.threeten.bp.format.TextStyle
...@@ -53,39 +58,39 @@ object DateTimeHelper { ...@@ -53,39 +58,39 @@ object DateTimeHelper {
} }
} }
/** /**
* Returns a time from a [LocalDateTime]. * Returns a time from a [LocalDateTime].
* *
* @param localDateTime The [LocalDateTime]. * @param localDateTime The [LocalDateTime].
* @return The time from a [LocalDateTime]. * @return The time from a [LocalDateTime].
*/ */
fun getTime(localDateTime: LocalDateTime): String { fun getTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localDateTime.toLocalTime().format(formatter).toString() return localDateTime.toLocalTime().format(formatter).toString()
} }
/** /**
* Returns a date time from a [LocalDateTime]. * Returns a date time from a [LocalDateTime].
* *
* @param localDateTime The [LocalDateTime]. * @param localDateTime The [LocalDateTime].
* @return The time from a [LocalDateTime]. * @return The time from a [LocalDateTime].
*/ */
fun getDateTime(localDateTime: LocalDateTime): String { fun getDateTime(localDateTime: LocalDateTime): String {
return formatLocalDateTime(localDateTime) return formatLocalDateTime(localDateTime)
} }
private fun formatLocalDateTime(localDateTime: LocalDateTime): String { private fun formatLocalDateTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
return localDateTime.format(formatter).toString() return localDateTime.format(formatter).toString()
} }
private fun formatLocalDate(localDate: LocalDate): String { private fun formatLocalDate(localDate: LocalDate): String {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
return localDate.format(formatter).toString() return localDate.format(formatter).toString()
} }
private fun formatLocalTime(localTime: LocalTime): String { private fun formatLocalTime(localTime: LocalTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localTime.format(formatter).toString() return localTime.format(formatter).toString()
} }
} }
\ No newline at end of file
...@@ -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 {
......
...@@ -8,7 +8,8 @@ import chat.rocket.android.server.domain.MultiServerTokenRepository ...@@ -8,7 +8,8 @@ import chat.rocket.android.server.domain.MultiServerTokenRepository
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
@PerActivity @PerActivity
class SharedPreferencesMultiServerTokenRepository(private val repository: LocalRepository, class SharedPreferencesMultiServerTokenRepository(
private val repository: LocalRepository,
private val moshi: Moshi private val moshi: Moshi
) : MultiServerTokenRepository { ) : MultiServerTokenRepository {
...@@ -16,11 +17,7 @@ class SharedPreferencesMultiServerTokenRepository(private val repository: LocalR ...@@ -16,11 +17,7 @@ class SharedPreferencesMultiServerTokenRepository(private val repository: LocalR
val token = repository.get("$TOKEN_KEY$server") val token = repository.get("$TOKEN_KEY$server")
val adapter = moshi.adapter<TokenModel>(TokenModel::class.java) val adapter = moshi.adapter<TokenModel>(TokenModel::class.java)
token?.let { return token?.let { adapter.fromJson(it) }
return adapter.fromJson(token)
}
return null
} }
override fun save(server: String, token: TokenModel) { override fun save(server: String, token: TokenModel) {
......
...@@ -85,20 +85,20 @@ class LoginPresenter @Inject constructor( ...@@ -85,20 +85,20 @@ class LoginPresenter @Inject constructor(
} }
} }
val myself = retryIO("me()") { client.me() } val myself = retryIO("me()") { client.me() }
if (myself.username != null) { myself.username?.let { username ->
val user = User( val user = User(
id = myself.id, id = myself.id,
roles = myself.roles, roles = myself.roles,
status = myself.status, status = myself.status,
name = myself.name, name = myself.name,
emails = myself.emails?.map { Email(it.address ?: "", it.verified) }, emails = myself.emails?.map { Email(it.address ?: "", it.verified) },
username = myself.username, username = username,
utcOffset = myself.utcOffset utcOffset = myself.utcOffset
) )
localRepository.saveCurrentUser(currentServer, user) localRepository.saveCurrentUser(currentServer, user)
saveCurrentServer.save(currentServer) saveCurrentServer.save(currentServer)
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, myself.username) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
saveAccount(myself.username!!) saveAccount(username)
saveToken(token) saveToken(token)
analyticsManager.logLogin( analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword, AuthenticationEvent.AuthenticationWithUserAndPassword,
...@@ -133,7 +133,7 @@ class LoginPresenter @Inject constructor( ...@@ -133,7 +133,7 @@ class LoginPresenter @Inject constructor(
fun forgotPassword() = navigator.toForgotPassword() fun forgotPassword() = navigator.toForgotPassword()
private suspend fun saveAccount(username: String) { private fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
} }
......
...@@ -39,12 +39,10 @@ internal const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1 ...@@ -39,12 +39,10 @@ internal const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1
internal const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2 internal const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2
internal const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3 internal const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3
fun newInstance(serverName: String): Fragment { fun newInstance(serverName: String): Fragment = LoginFragment().apply {
return LoginFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
putString(SERVER_NAME, serverName) putString(SERVER_NAME, serverName)
} }
}
} }
class LoginFragment : Fragment(), LoginView { class LoginFragment : Fragment(), LoginView {
...@@ -59,9 +57,8 @@ class LoginFragment : Fragment(), LoginView { ...@@ -59,9 +57,8 @@ class LoginFragment : Fragment(), LoginView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { serverName = getString(SERVER_NAME)
serverName = bundle.getString(SERVER_NAME)
} }
} }
...@@ -93,7 +90,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -93,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))
} }
} }
} }
...@@ -150,7 +147,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -150,7 +147,7 @@ class LoginFragment : Fragment(), LoginView {
override fun showGenericErrorMessage() = showMessage(R.string.msg_generic_error) override fun showGenericErrorMessage() = showMessage(R.string.msg_generic_error)
private fun setupOnClickListener() = private fun setupOnClickListener() =
ui { _ -> ui {
button_log_in.setOnClickListener { button_log_in.setOnClickListener {
presenter.authenticateWithUserAndPassword( presenter.authenticateWithUserAndPassword(
text_username_or_email.textContent, text_username_or_email.textContent,
...@@ -160,7 +157,7 @@ class LoginFragment : Fragment(), LoginView { ...@@ -160,7 +157,7 @@ class LoginFragment : Fragment(), LoginView {
} }
override fun showForgotPasswordView() { override fun showForgotPasswordView() {
ui { _ -> ui {
button_forgot_your_password.isVisible = true button_forgot_your_password.isVisible = true
button_forgot_your_password.setOnClickListener { presenter.forgotPassword() } button_forgot_your_password.setOnClickListener { presenter.forgotPassword() }
......
...@@ -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 -> {
...@@ -174,7 +173,7 @@ class LoginOptionsPresenter @Inject constructor( ...@@ -174,7 +173,7 @@ class LoginOptionsPresenter @Inject constructor(
settings = settingsInteractor.get(currentServer) settings = settingsInteractor.get(currentServer)
} }
private suspend fun saveAccount(username: String) { private fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
} }
......
...@@ -88,8 +88,7 @@ fun newInstance( ...@@ -88,8 +88,7 @@ fun newInstance(
isLoginFormEnabled: Boolean, isLoginFormEnabled: Boolean,
isNewAccountCreationEnabled: Boolean, isNewAccountCreationEnabled: Boolean,
deepLinkInfo: LoginDeepLinkInfo? = null deepLinkInfo: LoginDeepLinkInfo? = null
): Fragment { ): Fragment = LoginOptionsFragment().apply {
return LoginOptionsFragment().apply {
arguments = Bundle(23).apply { arguments = Bundle(23).apply {
putString(SERVER_NAME, serverName) putString(SERVER_NAME, serverName)
putString(STATE, state) putString(STATE, state)
...@@ -118,7 +117,6 @@ fun newInstance( ...@@ -118,7 +117,6 @@ fun newInstance(
putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled) putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled)
putParcelable(DEEP_LINK_INFO, deepLinkInfo) putParcelable(DEEP_LINK_INFO, deepLinkInfo)
} }
}
} }
class LoginOptionsFragment : Fragment(), LoginOptionsView { class LoginOptionsFragment : Fragment(), LoginOptionsView {
...@@ -157,34 +155,33 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { ...@@ -157,34 +155,33 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { serverName = getString(SERVER_NAME)
serverName = bundle.getString(SERVER_NAME) state = getString(STATE)
state = bundle.getString(STATE) facebookOauthUrl = getString(FACEBOOK_OAUTH_URL)
facebookOauthUrl = bundle.getString(FACEBOOK_OAUTH_URL) githubOauthUrl = getString(GITHUB_OAUTH_URL)
githubOauthUrl = bundle.getString(GITHUB_OAUTH_URL) googleOauthUrl = getString(GOOGLE_OAUTH_URL)
googleOauthUrl = bundle.getString(GOOGLE_OAUTH_URL) linkedinOauthUrl = getString(LINKEDIN_OAUTH_URL)
linkedinOauthUrl = bundle.getString(LINKEDIN_OAUTH_URL) gitlabOauthUrl = getString(GITLAB_OAUTH_URL)
gitlabOauthUrl = bundle.getString(GITLAB_OAUTH_URL) wordpressOauthUrl = getString(WORDPRESS_OAUTH_URL)
wordpressOauthUrl = bundle.getString(WORDPRESS_OAUTH_URL) casLoginUrl = getString(CAS_LOGIN_URL)
casLoginUrl = bundle.getString(CAS_LOGIN_URL) casToken = getString(CAS_TOKEN)
casToken = bundle.getString(CAS_TOKEN) casServiceName = getString(CAS_SERVICE_NAME)
casServiceName = bundle.getString(CAS_SERVICE_NAME) casServiceNameTextColor = getInt(CAS_SERVICE_NAME_TEXT_COLOR)
casServiceNameTextColor = bundle.getInt(CAS_SERVICE_NAME_TEXT_COLOR) casServiceButtonColor = getInt(CAS_SERVICE_BUTTON_COLOR)
casServiceButtonColor = bundle.getInt(CAS_SERVICE_BUTTON_COLOR) customOauthUrl = getString(CUSTOM_OAUTH_URL)
customOauthUrl = bundle.getString(CUSTOM_OAUTH_URL) customOauthServiceName = getString(CUSTOM_OAUTH_SERVICE_NAME)
customOauthServiceName = bundle.getString(CUSTOM_OAUTH_SERVICE_NAME) customOauthServiceTextColor = getInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR)
customOauthServiceTextColor = bundle.getInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR) customOauthServiceButtonColor = getInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR)
customOauthServiceButtonColor = bundle.getInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR) samlUrl = getString(SAML_URL)
samlUrl = bundle.getString(SAML_URL) samlToken = getString(SAML_TOKEN)
samlToken = bundle.getString(SAML_TOKEN) samlServiceName = getString(SAML_SERVICE_NAME)
samlServiceName = bundle.getString(SAML_SERVICE_NAME) samlServiceTextColor = getInt(SAML_SERVICE_NAME_TEXT_COLOR)
samlServiceTextColor = bundle.getInt(SAML_SERVICE_NAME_TEXT_COLOR) samlServiceButtonColor = getInt(SAML_SERVICE_BUTTON_COLOR)
samlServiceButtonColor = bundle.getInt(SAML_SERVICE_BUTTON_COLOR) totalSocialAccountsEnabled = getInt(TOTAL_SOCIAL_ACCOUNTS)
totalSocialAccountsEnabled = bundle.getInt(TOTAL_SOCIAL_ACCOUNTS) isLoginFormEnabled = getBoolean(IS_LOGIN_FORM_ENABLED)
isLoginFormEnabled = bundle.getBoolean(IS_LOGIN_FORM_ENABLED) isNewAccountCreationEnabled = getBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED)
isNewAccountCreationEnabled = bundle.getBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED) deepLinkInfo = getParcelable(DEEP_LINK_INFO)
deepLinkInfo = bundle.getParcelable(DEEP_LINK_INFO)
} }
} }
...@@ -388,7 +385,7 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { ...@@ -388,7 +385,7 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
} }
override fun setupExpandAccountsView() { override fun setupExpandAccountsView() {
ui { _ -> ui {
expand_more_accounts_container.isVisible = true expand_more_accounts_container.isVisible = true
var isAccountsCollapsed = true var isAccountsCollapsed = true
button_expand_collapse_accounts.setOnClickListener { button_expand_collapse_accounts.setOnClickListener {
...@@ -406,14 +403,14 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView { ...@@ -406,14 +403,14 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
} }
override fun showLoginWithEmailButton() { override fun showLoginWithEmailButton() {
ui { _ -> ui {
button_login_with_email.setOnClickListener { presenter.toLoginWithEmail() } button_login_with_email.setOnClickListener { presenter.toLoginWithEmail() }
button_login_with_email.isVisible = true button_login_with_email.isVisible = true
} }
} }
override fun showCreateNewAccountButton() { override fun showCreateNewAccountButton() {
ui { _ -> ui {
button_create_an_account.setOnClickListener { presenter.toCreateAccount() } button_create_an_account.setOnClickListener { presenter.toCreateAccount() }
button_create_an_account.isVisible = true button_create_an_account.isVisible = true
} }
......
...@@ -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
......
...@@ -31,13 +31,11 @@ import javax.inject.Inject ...@@ -31,13 +31,11 @@ import javax.inject.Inject
private const val BUNDLE_USER_ID = "user_id" private const val BUNDLE_USER_ID = "user_id"
private const val BUNDLE_AUTH_TOKEN = "auth_token" private const val BUNDLE_AUTH_TOKEN = "auth_token"
fun newInstance(userId: String, authToken: String): Fragment { fun newInstance(userId: String, authToken: String): Fragment = RegisterUsernameFragment().apply {
return RegisterUsernameFragment().apply {
arguments = Bundle(2).apply { arguments = Bundle(2).apply {
putString(BUNDLE_USER_ID, userId) putString(BUNDLE_USER_ID, userId)
putString(BUNDLE_AUTH_TOKEN, authToken) putString(BUNDLE_AUTH_TOKEN, authToken)
} }
}
} }
class RegisterUsernameFragment : Fragment(), RegisterUsernameView { class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
...@@ -53,13 +51,10 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { ...@@ -53,13 +51,10 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { userId = getString(BUNDLE_USER_ID, "")
userId = bundle.getString(BUNDLE_USER_ID) authToken = getString(BUNDLE_AUTH_TOKEN, "")
authToken = bundle.getString(BUNDLE_AUTH_TOKEN) } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
......
...@@ -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))
} }
} }
} }
...@@ -75,7 +75,7 @@ class SignupFragment : Fragment(), SignupView { ...@@ -75,7 +75,7 @@ class SignupFragment : Fragment(), SignupView {
} }
private fun setupOnClickListener() = private fun setupOnClickListener() =
ui { _ -> ui {
button_register.setOnClickListener { button_register.setOnClickListener {
presenter.signup( presenter.signup(
text_username.textContent, text_username.textContent,
......
...@@ -94,7 +94,7 @@ class TwoFAPresenter @Inject constructor( ...@@ -94,7 +94,7 @@ class TwoFAPresenter @Inject constructor(
} }
} }
private suspend fun saveAccount(me: Myself) { private fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
} }
......
...@@ -25,13 +25,11 @@ import io.reactivex.disposables.Disposable ...@@ -25,13 +25,11 @@ import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.* import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
import javax.inject.Inject import javax.inject.Inject
fun newInstance(username: String, password: String): Fragment { fun newInstance(username: String, password: String): Fragment = TwoFAFragment().apply {
return TwoFAFragment().apply {
arguments = Bundle(2).apply { arguments = Bundle(2).apply {
putString(BUNDLE_USERNAME, username) putString(BUNDLE_USERNAME, username)
putString(BUNDLE_PASSWORD, password) putString(BUNDLE_PASSWORD, password)
} }
}
} }
private const val BUNDLE_USERNAME = "username" private const val BUNDLE_USERNAME = "username"
...@@ -50,13 +48,10 @@ class TwoFAFragment : Fragment(), TwoFAView { ...@@ -50,13 +48,10 @@ class TwoFAFragment : Fragment(), TwoFAView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { username = getString(BUNDLE_USERNAME, "")
username = bundle.getString(BUNDLE_USERNAME) password = getString(BUNDLE_PASSWORD, "")
password = bundle.getString(BUNDLE_PASSWORD) } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
......
package chat.rocket.android.chatdetails.adapter package chat.rocket.android.chatdetails.adapter
import android.content.Context import DrawableHelper
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
......
...@@ -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
...@@ -11,9 +11,15 @@ import chat.rocket.android.util.extensions.inflate ...@@ -11,9 +11,15 @@ import chat.rocket.android.util.extensions.inflate
class ChatDetailsAdapter: RecyclerView.Adapter<OptionViewHolder>() { class ChatDetailsAdapter: RecyclerView.Adapter<OptionViewHolder>() {
private val options: MutableList<Option> = ArrayList() private val options: MutableList<Option> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionViewHolder = OptionViewHolder(parent.inflate(R.layout.item_detail_option)) override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): OptionViewHolder = OptionViewHolder(parent.inflate(R.layout.item_detail_option))
override fun onBindViewHolder(holder: OptionViewHolder, position: Int) = holder.bindViews(OptionItemHolder(options[position])) override fun onBindViewHolder(
holder: OptionViewHolder,
position: Int
) = holder.bindViews(OptionItemHolder(options[position]))
override fun getItemCount(): Int = options.size override fun getItemCount(): Int = options.size
......
...@@ -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,23 +35,28 @@ fun newInstance( ...@@ -31,23 +35,28 @@ fun newInstance(
chatRoomId: String, chatRoomId: String,
chatRoomType: String, chatRoomType: String,
isSubscribed: Boolean, isSubscribed: Boolean,
isFavorite: Boolean,
disableMenu: Boolean disableMenu: Boolean
): ChatDetailsFragment { ): ChatDetailsFragment {
return ChatDetailsFragment().apply { return ChatDetailsFragment().apply {
arguments = Bundle(4).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 {
...@@ -55,26 +64,32 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { ...@@ -55,26 +64,32 @@ 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?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) { arguments?.run {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) chatRoomId = getString(BUNDLE_CHAT_ROOM_ID)
chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE) chatRoomType = getString(BUNDLE_CHAT_ROOM_TYPE)
isSubscribed = bundle.getBoolean(BUNDLE_IS_SUBSCRIBED) isSubscribed = getBoolean(BUNDLE_IS_SUBSCRIBED)
disableMenu = bundle.getBoolean(BUNDLE_DISABLE_MENU) isFavorite = getBoolean(BUNDLE_IS_FAVORITE)
} else { disableMenu = getBoolean(BUNDLE_DISABLE_MENU)
requireNotNull(bundle) { "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(
...@@ -91,11 +106,27 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { ...@@ -91,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 =
...@@ -207,8 +238,8 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { ...@@ -207,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_white_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)
}
}
...@@ -5,9 +5,10 @@ import androidx.lifecycle.ViewModelProvider ...@@ -5,9 +5,10 @@ import androidx.lifecycle.ViewModelProvider
import chat.rocket.android.db.ChatRoomDao import chat.rocket.android.db.ChatRoomDao
import javax.inject.Inject import javax.inject.Inject
class ChatDetailsViewModelFactory @Inject constructor(private val chatRoomDao: ChatRoomDao) : ViewModelProvider.NewInstanceFactory() { class ChatDetailsViewModelFactory @Inject constructor(
private val chatRoomDao: ChatRoomDao
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>) = override fun <T : ViewModel?> create(modelClass: Class<T>) = ChatDetailsViewModel(chatRoomDao) as T
ChatDetailsViewModel(chatRoomDao) as T
} }
\ No newline at end of file
...@@ -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)
......
...@@ -12,7 +12,10 @@ import com.facebook.drawee.backends.pipeline.Fresco ...@@ -12,7 +12,10 @@ import com.facebook.drawee.backends.pipeline.Fresco
import kotlinx.android.synthetic.main.item_action_button.view.* import kotlinx.android.synthetic.main.item_action_button.view.*
import timber.log.Timber import timber.log.Timber
class ActionsListAdapter(actions: List<Action>, var actionAttachmentOnClickListener: ActionAttachmentOnClickListener) : RecyclerView.Adapter<ActionsListAdapter.ViewHolder>() { class ActionsListAdapter(
actions: List<Action>,
var actionAttachmentOnClickListener: ActionAttachmentOnClickListener
) : RecyclerView.Adapter<ActionsListAdapter.ViewHolder>() {
var actions: List<Action> = actions var actions: List<Action> = actions
...@@ -62,9 +65,7 @@ class ActionsListAdapter(actions: List<Action>, var actionAttachmentOnClickListe ...@@ -62,9 +65,7 @@ class ActionsListAdapter(actions: List<Action>, var actionAttachmentOnClickListe
return ViewHolder(view) return ViewHolder(view)
} }
override fun getItemCount(): Int { override fun getItemCount(): Int = actions.size
return actions.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val action = actions[position] val action = actions[position]
......
...@@ -92,14 +92,14 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>( ...@@ -92,14 +92,14 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
data?.let { vm -> data?.let { vm ->
vm.message.let { vm.message.let {
val menuItems = view.context.inflate(R.menu.message_actions).toList() val menuItems = view.context.inflate(R.menu.message_actions).toList()
menuItems.find { it.itemId == R.id.action_message_unpin }?.apply { menuItems.find { it.itemId == R.id.action_pin }?.apply {
setTitle(if (it.pinned) R.string.action_msg_unpin else R.string.action_msg_pin) setTitle(if (it.pinned) R.string.action_unpin else R.string.action_pin)
isChecked = it.pinned isChecked = it.pinned
} }
menuItems.find { it.itemId == R.id.action_message_star }?.apply { menuItems.find { it.itemId == R.id.action_star }?.apply {
val isStarred = it.starred?.isNotEmpty() ?: false val isStarred = it.starred?.isNotEmpty() ?: false
setTitle(if (isStarred) R.string.action_msg_unstar else R.string.action_msg_star) setTitle(if (isStarred) R.string.action_unstar else R.string.action_star)
isChecked = isStarred isChecked = isStarred
} }
view.context?.let { view.context?.let {
......
...@@ -5,6 +5,7 @@ import android.view.View ...@@ -5,6 +5,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.chatroom.uimodel.AttachmentUiModel import chat.rocket.android.chatroom.uimodel.AttachmentUiModel
import chat.rocket.android.chatroom.uimodel.BaseUiModel import chat.rocket.android.chatroom.uimodel.BaseUiModel
...@@ -29,7 +30,8 @@ class ChatRoomAdapter( ...@@ -29,7 +30,8 @@ class ChatRoomAdapter(
private val actionSelectListener: OnActionSelected? = null, private val actionSelectListener: OnActionSelected? = null,
private val enableActions: Boolean = true, private val enableActions: Boolean = true,
private val reactionListener: EmojiReactionListener? = null, private val reactionListener: EmojiReactionListener? = null,
private val navigator: ChatRoomNavigator? = null private val navigator: ChatRoomNavigator? = null,
private val analyticsManager: AnalyticsManager? = null
) : RecyclerView.Adapter<BaseViewHolder<*>>() { ) : RecyclerView.Adapter<BaseViewHolder<*>>() {
private val dataSet = ArrayList<BaseUiModel<*>>() private val dataSet = ArrayList<BaseUiModel<*>>()
...@@ -44,8 +46,14 @@ class ChatRoomAdapter( ...@@ -44,8 +46,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)
...@@ -76,13 +84,9 @@ class ChatRoomAdapter( ...@@ -76,13 +84,9 @@ class ChatRoomAdapter(
} }
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int = dataSet[position].viewType
return dataSet[position].viewType
}
override fun getItemCount(): Int { override fun getItemCount(): Int = dataSet.size
return dataSet.size
}
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
if (holder !is MessageViewHolder) { if (holder !is MessageViewHolder) {
...@@ -105,8 +109,9 @@ class ChatRoomAdapter( ...@@ -105,8 +109,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 ->
...@@ -174,12 +179,12 @@ class ChatRoomAdapter( ...@@ -174,12 +179,12 @@ class ChatRoomAdapter(
Timber.d("index: $index") Timber.d("index: $index")
if (index > -1) { if (index > -1) {
dataSet[index] = message dataSet[index] = message
dataSet.forEachIndexed { index, viewModel -> dataSet.forEachIndexed { ind, viewModel ->
if (viewModel.messageId == message.messageId) { if (viewModel.messageId == message.messageId) {
if (viewModel.nextDownStreamMessage == null) { if (viewModel.nextDownStreamMessage == null) {
viewModel.reactions = message.reactions viewModel.reactions = message.reactions
} }
notifyItemChanged(index) notifyItemChanged(ind)
} }
} }
// Delete message only if current is a system message update, i.e.: Message Removed // Delete message only if current is a system message update, i.e.: Message Removed
...@@ -236,47 +241,63 @@ class ChatRoomAdapter( ...@@ -236,47 +241,63 @@ class ChatRoomAdapter(
override fun isActionsEnabled(): Boolean = enableActions override fun isActionsEnabled(): Boolean = enableActions
override fun onActionSelected(item: MenuItem, message: Message) { override fun onActionSelected(item: MenuItem, message: Message) {
message.apply { if (analyticsManager != null && roomName != null && roomType != null && actionSelectListener != null) {
with(message) {
when (item.itemId) { when (item.itemId) {
R.id.action_message_info -> { R.id.action_info -> {
actionSelectListener?.showMessageInfo(id) actionSelectListener.showMessageInfo(id)
} analyticsManager.logMessageActionInfo()
R.id.action_message_reply -> {
if (roomName != null && roomType != null) {
actionSelectListener?.citeMessage(roomName, roomType, id, true)
} }
R.id.action_reply -> {
actionSelectListener.citeMessage(roomName, roomType, id, true)
analyticsManager.logMessageActionReply()
} }
R.id.action_message_quote -> {
if (roomName != null && roomType != null) { R.id.action_quote -> {
actionSelectListener?.citeMessage(roomName, roomType, id, false) actionSelectListener.citeMessage(roomName, roomType, id, false)
} analyticsManager.logMessageActionQuote()
} }
R.id.action_message_copy -> {
actionSelectListener?.copyMessage(id) R.id.action_copy -> {
actionSelectListener.copyMessage(id)
analyticsManager.logMessageActionCopy()
} }
R.id.action_message_edit -> {
actionSelectListener?.editMessage(roomId, id, message.message) R.id.action_edit -> {
actionSelectListener.editMessage(roomId, id, this.message)
analyticsManager.logMessageActionEdit()
} }
R.id.action_message_star -> {
actionSelectListener?.toogleStar(id, !item.isChecked) R.id.action_star -> {
actionSelectListener.toggleStar(id, !item.isChecked)
analyticsManager.logMessageActionStar()
} }
R.id.action_message_unpin -> {
actionSelectListener?.tooglePin(id, !item.isChecked) R.id.action_pin -> {
actionSelectListener.togglePin(id, !item.isChecked)
analyticsManager.logMessageActionPin()
} }
R.id.action_message_delete -> {
actionSelectListener?.deleteMessage(roomId, id) R.id.action_delete -> {
actionSelectListener.deleteMessage(roomId, id)
analyticsManager.logMessageActionDelete()
} }
R.id.action_menu_msg_react -> {
actionSelectListener?.showReactions(id) R.id.action_add_reaction -> {
actionSelectListener.showReactions(id)
analyticsManager.logMessageActionAddReaction()
} }
R.id.action_message_permalink -> {
actionSelectListener?.copyPermalink(id) R.id.action_permalink -> {
actionSelectListener.copyPermalink(id)
analyticsManager.logMessageActionPermalink()
} }
R.id.action_message_report -> {
actionSelectListener?.reportMessage(id) R.id.action_report -> {
actionSelectListener.reportMessage(id)
analyticsManager.logMessageActionReport()
} }
else -> {
TODO("Not implemented")
} }
} }
} }
...@@ -298,9 +319,9 @@ class ChatRoomAdapter( ...@@ -298,9 +319,9 @@ class ChatRoomAdapter(
fun editMessage(roomId: String, messageId: String, text: String) fun editMessage(roomId: String, messageId: String, text: String)
fun toogleStar(id: String, star: Boolean) fun toggleStar(id: String, star: Boolean)
fun tooglePin(id: String, pin: Boolean) fun togglePin(id: String, pin: Boolean)
fun deleteMessage(roomId: String, id: String) fun deleteMessage(roomId: String, id: String)
......
...@@ -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)
......
...@@ -24,8 +24,8 @@ class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#" ...@@ -24,8 +24,8 @@ class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#"
override fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) { override fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) {
item as ChatRoomSuggestionUiModel item as ChatRoomSuggestionUiModel
with(itemView) { with(itemView) {
val fullname = itemView.findViewById<TextView>(R.id.text_fullname) val fullname = findViewById<TextView>(R.id.text_fullname)
val name = itemView.findViewById<TextView>(R.id.text_name) val name = findViewById<TextView>(R.id.text_name)
name.text = item.name name.text = item.name
fullname.text = item.fullName fullname.text = item.fullName
setOnClickListener { setOnClickListener {
......
...@@ -8,15 +8,14 @@ import chat.rocket.android.util.extensions.content ...@@ -8,15 +8,14 @@ import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.openTabbedUrl import chat.rocket.android.util.extensions.openTabbedUrl
import kotlinx.android.synthetic.main.message_url_preview.view.* import kotlinx.android.synthetic.main.message_url_preview.view.*
class UrlPreviewViewHolder(itemView: View, class UrlPreviewViewHolder(
itemView: View,
listener: ActionsListener, listener: ActionsListener,
reactionListener: EmojiReactionListener? = null) reactionListener: EmojiReactionListener? = null
: BaseViewHolder<UrlPreviewUiModel>(itemView, listener, reactionListener) { ) : BaseViewHolder<UrlPreviewUiModel>(itemView, listener, reactionListener) {
init { init {
with(itemView) { setupActionMenu(itemView.url_preview_layout)
setupActionMenu(url_preview_layout)
}
} }
override fun bindViews(data: UrlPreviewUiModel) { override fun bindViews(data: UrlPreviewUiModel) {
......
...@@ -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
) )
} }
......
...@@ -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
...@@ -31,8 +30,7 @@ fun Context.chatRoomIntent( ...@@ -31,8 +30,7 @@ fun Context.chatRoomIntent(
isCreator: Boolean = false, isCreator: Boolean = false,
isFavorite: Boolean = false, isFavorite: Boolean = false,
chatRoomMessage: String? = null chatRoomMessage: String? = null
): Intent { ): Intent = Intent(this, ChatRoomActivity::class.java).apply {
return Intent(this, ChatRoomActivity::class.java).apply {
putExtra(INTENT_CHAT_ROOM_ID, chatRoomId) putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
putExtra(INTENT_CHAT_ROOM_NAME, chatRoomName) putExtra(INTENT_CHAT_ROOM_NAME, chatRoomName)
putExtra(INTENT_CHAT_ROOM_TYPE, chatRoomType) putExtra(INTENT_CHAT_ROOM_TYPE, chatRoomType)
...@@ -42,7 +40,6 @@ fun Context.chatRoomIntent( ...@@ -42,7 +40,6 @@ fun Context.chatRoomIntent(
putExtra(INTENT_CHAT_ROOM_IS_CREATOR, isCreator) putExtra(INTENT_CHAT_ROOM_IS_CREATOR, isCreator)
putExtra(INTENT_CHAT_ROOM_IS_FAVORITE, isFavorite) putExtra(INTENT_CHAT_ROOM_IS_FAVORITE, isFavorite)
putExtra(INTENT_CHAT_ROOM_MESSAGE, chatRoomMessage) putExtra(INTENT_CHAT_ROOM_MESSAGE, chatRoomMessage)
}
} }
private const val INTENT_CHAT_ROOM_ID = "chat_room_id" private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
...@@ -58,7 +55,6 @@ private const val INTENT_CHAT_ROOM_MESSAGE = "chat_room_message" ...@@ -58,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
...@@ -81,26 +77,27 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -81,26 +77,27 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
return return
} }
val chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) with(intent) {
val chatRoomId = getStringExtra(INTENT_CHAT_ROOM_ID)
requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" } requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" }
val chatRoomName = intent.getStringExtra(INTENT_CHAT_ROOM_NAME) val chatRoomName = getStringExtra(INTENT_CHAT_ROOM_NAME)
requireNotNull(chatRoomName) { "no chat_room_name provided in Intent extras" } requireNotNull(chatRoomName) { "no chat_room_name provided in Intent extras" }
val chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE) val chatRoomType = getStringExtra(INTENT_CHAT_ROOM_TYPE)
requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" } requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" }
val isReadOnly = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, true) val isReadOnly = getBooleanExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, true)
val isCreator = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_CREATOR, false) val isCreator = getBooleanExtra(INTENT_CHAT_ROOM_IS_CREATOR, false)
val isFavorite = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_FAVORITE, false) val isFavorite = getBooleanExtra(INTENT_CHAT_ROOM_IS_FAVORITE, false)
val chatRoomLastSeen = intent.getLongExtra(INTENT_CHAT_ROOM_LAST_SEEN, -1) val chatRoomLastSeen = getLongExtra(INTENT_CHAT_ROOM_LAST_SEEN, -1)
val isSubscribed = intent.getBooleanExtra(INTENT_CHAT_IS_SUBSCRIBED, true) val isSubscribed = getBooleanExtra(INTENT_CHAT_IS_SUBSCRIBED, true)
val chatRoomMessage = intent.getStringExtra(INTENT_CHAT_ROOM_MESSAGE) val chatRoomMessage = getStringExtra(INTENT_CHAT_ROOM_MESSAGE)
setupToolbar() setupToolbar()
...@@ -120,6 +117,7 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -120,6 +117,7 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
} }
} }
} }
}
override fun onBackPressed() { override fun onBackPressed() {
finishActivity() finishActivity()
...@@ -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() {
......
...@@ -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,
...@@ -75,10 +76,10 @@ fun ChatRoomFragment.showFileAttachmentDialog(uri: Uri) { ...@@ -75,10 +76,10 @@ fun ChatRoomFragment.showFileAttachmentDialog(uri: Uri) {
(citation ?: "") + description.text.toString() (citation ?: "") + description.text.toString()
) )
} }
alertDialog.dismiss() alertDialog?.dismiss()
} }
cancelButton.setOnClickListener { alertDialog.dismiss() } cancelButton.setOnClickListener { alertDialog?.dismiss() }
alertDialog.show() alertDialog?.show()
} }
fun ChatRoomFragment.showDrawAttachmentDialog(byteArray: ByteArray) { fun ChatRoomFragment.showDrawAttachmentDialog(byteArray: ByteArray) {
...@@ -92,9 +93,9 @@ fun ChatRoomFragment.showDrawAttachmentDialog(byteArray: ByteArray) { ...@@ -92,9 +93,9 @@ fun ChatRoomFragment.showDrawAttachmentDialog(byteArray: ByteArray) {
byteArray, byteArray,
(citation ?: "") + description.text.toString() (citation ?: "") + description.text.toString()
) )
alertDialog.dismiss() alertDialog?.dismiss()
} }
cancelButton.setOnClickListener { alertDialog.dismiss() } cancelButton.setOnClickListener { alertDialog?.dismiss() }
alertDialog.show() alertDialog?.show()
} }
...@@ -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,7 +20,7 @@ private fun ChatRoomFragment.setupSearchMessageMenuItem(menu: Menu, context: Con ...@@ -37,7 +20,7 @@ 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
) )
...@@ -53,8 +36,9 @@ private fun ChatRoomFragment.setupSearchMessageMenuItem(menu: Menu, context: Con ...@@ -53,8 +36,9 @@ private fun ChatRoomFragment.setupSearchMessageMenuItem(menu: Menu, context: Con
} }
}) })
(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
stylizeSearchView(it, context) stylizeSearchView(it, context)
setupSearchViewTextListener(it) setupSearchViewTextListener(it)
if (it.isIconified) { if (it.isIconified) {
...@@ -82,33 +66,3 @@ private fun ChatRoomFragment.setupSearchViewTextListener(searchView: SearchView) ...@@ -82,33 +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)
}
\ 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
...@@ -36,15 +36,30 @@ class RoomUiModelMapper( ...@@ -36,15 +36,30 @@ class RoomUiModelMapper(
grouped: Boolean = false, grouped: Boolean = false,
showLastMessage: Boolean = true showLastMessage: Boolean = true
): List<ItemHolder<*>> { ): List<ItemHolder<*>> {
val list = ArrayList<ItemHolder<*>>(rooms.size + 4) val list = ArrayList<ItemHolder<*>>(rooms.size + 5)
var lastType: String? = null var lastType: String? = null
rooms.forEach { room -> if (grouped) {
if (grouped && lastType != room.chatRoom.type) { val favRooms = rooms.filter { it.chatRoom.favorite == true }
val unfavRooms = rooms.filterNot { it.chatRoom.favorite == true }
if (favRooms.isNotEmpty()) {
list.add(HeaderItemHolder(context.resources.getString(R.string.header_favorite)))
}
favRooms.forEach { room ->
list.add(RoomItemHolder(map(room, showLastMessage)))
}
unfavRooms.forEach { room ->
if (lastType != room.chatRoom.type) {
list.add(HeaderItemHolder(roomType(room.chatRoom.type))) list.add(HeaderItemHolder(roomType(room.chatRoom.type)))
} }
list.add(RoomItemHolder(map(room, showLastMessage))) list.add(RoomItemHolder(map(room, showLastMessage)))
lastType = room.chatRoom.type lastType = room.chatRoom.type
} }
} else {
rooms.forEach { room ->
list.add(RoomItemHolder(map(room, showLastMessage)))
}
}
return list return list
} }
...@@ -62,8 +77,7 @@ class RoomUiModelMapper( ...@@ -62,8 +77,7 @@ class RoomUiModelMapper(
return list return list
} }
private fun mapUser(user: User): RoomUiModel { private fun mapUser(user: User): RoomUiModel = with(user) {
return with(user) {
val name = mapName(user.username!!, user.name) val name = mapName(user.username!!, user.name)
val status = user.status val status = user.status
val avatar = serverUrl.avatarUrl(user.username!!) val avatar = serverUrl.avatarUrl(user.username!!)
...@@ -78,10 +92,8 @@ class RoomUiModelMapper( ...@@ -78,10 +92,8 @@ class RoomUiModelMapper(
username = username username = username
) )
} }
}
private fun mapRoom(room: Room, showLastMessage: Boolean = true): RoomUiModel { private fun mapRoom(room: Room, showLastMessage: Boolean = true): RoomUiModel = with(room) {
return with(room) {
RoomUiModel( RoomUiModel(
id = id, id = id,
name = name!!, name = name!!,
...@@ -100,14 +112,13 @@ class RoomUiModelMapper( ...@@ -100,14 +112,13 @@ class RoomUiModelMapper(
writable = isChannelWritable(muted) writable = isChannelWritable(muted)
) )
} }
}
fun map(chatRoom: ChatRoom, showLastMessage: Boolean = true): RoomUiModel { fun map(chatRoom: ChatRoom, showLastMessage: Boolean = true): RoomUiModel = with(chatRoom.chatRoom) {
return with(chatRoom.chatRoom) {
val isUnread = alert || unread > 0 val isUnread = alert || unread > 0
val type = roomTypeOf(type) val type = roomTypeOf(type)
val status = chatRoom.status?.let { userStatusOf(it) } val status = chatRoom.status?.let { userStatusOf(it) }
val roomName = mapName(name, fullname) val roomName = mapName(name, fullname)
val favorite = favorite
val timestamp = mapDate(lastMessageTimestamp ?: updatedAt) val timestamp = mapDate(lastMessageTimestamp ?: updatedAt)
val avatar = if (type is RoomType.DirectMessage) { val avatar = if (type is RoomType.DirectMessage) {
serverUrl.avatarUrl(name) serverUrl.avatarUrl(name)
...@@ -128,8 +139,7 @@ class RoomUiModelMapper( ...@@ -128,8 +139,7 @@ class RoomUiModelMapper(
} }
val hasMentions = mapMentions(userMentions, groupMentions) val hasMentions = mapMentions(userMentions, groupMentions)
val open = open val open = open
val lastMessageMarkdown = val lastMessageMarkdown = lastMessage?.let { Markwon.markdown(context, it.toString()).toString() }
lastMessage?.let { Markwon.markdown(context, it.toString()).toString() }
RoomUiModel( RoomUiModel(
id = id, id = id,
...@@ -140,6 +150,7 @@ class RoomUiModelMapper( ...@@ -140,6 +150,7 @@ class RoomUiModelMapper(
date = timestamp, date = timestamp,
unread = unread, unread = unread,
mentions = hasMentions, mentions = hasMentions,
favorite = favorite,
alert = isUnread, alert = isUnread,
lastMessage = lastMessageMarkdown, lastMessage = lastMessageMarkdown,
status = status, status = status,
...@@ -148,21 +159,19 @@ class RoomUiModelMapper( ...@@ -148,21 +159,19 @@ class RoomUiModelMapper(
writable = isChannelWritable(muted) writable = isChannelWritable(muted)
) )
} }
}
private fun isChannelWritable(muted: List<String>?): Boolean { private fun isChannelWritable(muted: List<String>?): Boolean {
val canWriteToReadOnlyChannels = permissions.canPostToReadOnlyChannels() val canWriteToReadOnlyChannels = permissions.canPostToReadOnlyChannels()
return canWriteToReadOnlyChannels || !muted.orEmpty().contains(currentUser?.username) return canWriteToReadOnlyChannels || !muted.orEmpty().contains(currentUser?.username)
} }
private fun roomType(type: String): String { private fun roomType(type: String): String = with(context.resources) {
val resources = context.resources when (type) {
return when (type) { RoomType.CHANNEL -> getString(R.string.header_channel)
RoomType.CHANNEL -> resources.getString(R.string.header_channel) RoomType.PRIVATE_GROUP -> getString(R.string.header_private_groups)
RoomType.PRIVATE_GROUP -> resources.getString(R.string.header_private_groups) RoomType.DIRECT_MESSAGE -> getString(R.string.header_direct_messages)
RoomType.DIRECT_MESSAGE -> resources.getString(R.string.header_direct_messages) RoomType.LIVECHAT -> getString(R.string.header_live_chats)
RoomType.LIVECHAT -> resources.getString(R.string.header_live_chats) else -> getString(R.string.header_unknown)
else -> resources.getString(R.string.header_unknown)
} }
} }
...@@ -195,14 +204,12 @@ class RoomUiModelMapper( ...@@ -195,14 +204,12 @@ class RoomUiModelMapper(
} }
} }
private fun mapUnread(unread: Long): String? { private fun mapUnread(unread: Long): String? = when (unread) {
return when (unread) {
0L -> null 0L -> null
in 1..99 -> unread.toString() in 1..99 -> unread.toString()
else -> context.getString(R.string.msg_more_than_ninety_nine_unread_messages) else -> context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
} }
}
private fun mapMentions(userMentions: Long?, groupMentions: Long?): Boolean { private fun mapMentions(userMentions: Long?, groupMentions: Long?): Boolean {
if (userMentions != null && groupMentions != null) { if (userMentions != null && groupMentions != null) {
......
...@@ -8,6 +8,7 @@ import androidx.core.view.isInvisible ...@@ -8,6 +8,7 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.util.extension.setTextViewAppearance
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
import kotlinx.android.synthetic.main.item_chat.view.* import kotlinx.android.synthetic.main.item_chat.view.*
...@@ -16,12 +17,12 @@ import kotlinx.android.synthetic.main.unread_messages_badge.view.* ...@@ -16,12 +17,12 @@ import kotlinx.android.synthetic.main.unread_messages_badge.view.*
class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit) : class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit) :
ViewHolder<RoomItemHolder>(itemView) { ViewHolder<RoomItemHolder>(itemView) {
private val resources: Resources = itemView.resources private val resources: Resources = itemView.resources
private val channelIcon: Drawable = resources.getDrawable(R.drawable.ic_hashtag_12dp) private val channelIcon: Drawable = resources.getDrawable(R.drawable.ic_hashtag_12dp, null)
private val groupIcon: Drawable = resources.getDrawable(R.drawable.ic_lock_12_dp) private val groupIcon: Drawable = resources.getDrawable(R.drawable.ic_lock_12_dp, null)
private val onlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_online_12dp) private val onlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_online_12dp, null)
private val awayIcon: Drawable = resources.getDrawable(R.drawable.ic_status_away_12dp) private val awayIcon: Drawable = resources.getDrawable(R.drawable.ic_status_away_12dp, null)
private val busyIcon: Drawable = resources.getDrawable(R.drawable.ic_status_busy_12dp) private val busyIcon: Drawable = resources.getDrawable(R.drawable.ic_status_busy_12dp, null)
private val offlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_invisible_12dp) private val offlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_invisible_12dp, null)
override fun bindViews(data: RoomItemHolder) { override fun bindViews(data: RoomItemHolder) {
val room = data.data val room = data.data
...@@ -53,14 +54,14 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit ...@@ -53,14 +54,14 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit
if (room.unread == null) text_total_unread_messages.text = "!" if (room.unread == null) text_total_unread_messages.text = "!"
if (room.unread != null) text_total_unread_messages.text = room.unread if (room.unread != null) text_total_unread_messages.text = room.unread
if (room.mentions) text_total_unread_messages.text = "@${room.unread}" if (room.mentions) text_total_unread_messages.text = "@${room.unread}"
text_chat_name.setTextAppearance(context, R.style.ChatList_ChatName_Unread_TextView) text_chat_name.setTextViewAppearance(context, R.style.ChatList_ChatName_Unread_TextView)
text_timestamp.setTextAppearance(context, R.style.ChatList_Timestamp_Unread_TextView) text_timestamp.setTextViewAppearance(context, R.style.ChatList_Timestamp_Unread_TextView)
text_last_message.setTextAppearance(context, R.style.ChatList_LastMessage_Unread_TextView) text_last_message.setTextViewAppearance(context, R.style.ChatList_LastMessage_Unread_TextView)
text_total_unread_messages.isVisible = true text_total_unread_messages.isVisible = true
} else { } else {
text_chat_name.setTextAppearance(context, R.style.ChatList_ChatName_TextView) text_chat_name.setTextViewAppearance(context, R.style.ChatList_ChatName_TextView)
text_timestamp.setTextAppearance(context, R.style.ChatList_Timestamp_TextView) text_timestamp.setTextViewAppearance(context, R.style.ChatList_Timestamp_TextView)
text_last_message.setTextAppearance(context, R.style.ChatList_LastMessage_TextView) text_last_message.setTextViewAppearance(context, R.style.ChatList_LastMessage_TextView)
text_total_unread_messages.isInvisible = true text_total_unread_messages.isInvisible = true
} }
...@@ -68,20 +69,16 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit ...@@ -68,20 +69,16 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit
} }
} }
private fun getRoomDrawable(type: RoomType): Drawable? { private fun getRoomDrawable(type: RoomType): Drawable? = when (type) {
return when (type) {
is RoomType.Channel -> channelIcon is RoomType.Channel -> channelIcon
is RoomType.PrivateGroup -> groupIcon is RoomType.PrivateGroup -> groupIcon
else -> null else -> null
} }
}
private fun getStatusDrawable(status: UserStatus): Drawable { private fun getStatusDrawable(status: UserStatus): Drawable = when (status) {
return when (status) {
is UserStatus.Online -> onlineIcon is UserStatus.Online -> onlineIcon
is UserStatus.Away -> awayIcon is UserStatus.Away -> awayIcon
is UserStatus.Busy -> busyIcon is UserStatus.Busy -> busyIcon
else -> offlineIcon else -> offlineIcon
} }
}
} }
\ No newline at end of file
...@@ -19,8 +19,7 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) : ...@@ -19,8 +19,7 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) :
notifyDataSetChanged() notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> = when (viewType) {
return when (viewType) {
VIEW_TYPE_ROOM -> { VIEW_TYPE_ROOM -> {
val view = parent.inflate(R.layout.item_chat) val view = parent.inflate(R.layout.item_chat)
RoomViewHolder(view, listener) RoomViewHolder(view, listener)
...@@ -35,7 +34,6 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) : ...@@ -35,7 +34,6 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) :
} }
else -> throw IllegalStateException("View type must be either Room, Header or Loading") else -> throw IllegalStateException("View type must be either Room, Header or Loading")
} }
}
override fun getItemCount() = values.size override fun getItemCount() = values.size
...@@ -49,14 +47,12 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) : ...@@ -49,14 +47,12 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) :
} }
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int = when (values[position]) {
return when (values[position]) {
is RoomItemHolder -> VIEW_TYPE_ROOM is RoomItemHolder -> VIEW_TYPE_ROOM
is HeaderItemHolder -> VIEW_TYPE_HEADER is HeaderItemHolder -> VIEW_TYPE_HEADER
is LoadingItemHolder -> VIEW_TYPE_LOADING is LoadingItemHolder -> VIEW_TYPE_LOADING
else -> throw IllegalStateException("View type must be either Room, Header or Loading") else -> throw IllegalStateException("View type must be either Room, Header or Loading")
} }
}
override fun onBindViewHolder(holder: ViewHolder<*>, position: Int) { override fun onBindViewHolder(holder: ViewHolder<*>, position: Int) {
if (holder is RoomViewHolder) { if (holder is RoomViewHolder) {
......
...@@ -12,6 +12,7 @@ data class RoomUiModel( ...@@ -12,6 +12,7 @@ data class RoomUiModel(
val date: CharSequence? = null, val date: CharSequence? = null,
val unread: String? = null, val unread: String? = null,
val alert: Boolean = false, val alert: Boolean = false,
val favorite: Boolean? = false,
val mentions: Boolean = false, val mentions: Boolean = false,
val lastMessage: CharSequence? = null, val lastMessage: CharSequence? = null,
val status: UserStatus? = null, val status: UserStatus? = null,
......
...@@ -2,6 +2,7 @@ package chat.rocket.android.chatrooms.presentation ...@@ -2,6 +2,7 @@ package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
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.db.model.ChatRoomEntity import chat.rocket.android.db.model.ChatRoomEntity
...@@ -22,11 +23,12 @@ import chat.rocket.common.model.roomTypeOf ...@@ -22,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,
...@@ -116,6 +118,7 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -116,6 +118,7 @@ class ChatRoomsPresenter @Inject constructor(
retryIO("createDirectMessage($name)") { retryIO("createDirectMessage($name)") {
withTimeout(10000) { withTimeout(10000) {
createDirectMessage(name) createDirectMessage(name)
FetchChatRoomsInteractor(client, dbManager).refreshChatRooms()
} }
} }
val fromTo = mutableListOf(myself.id, id).apply { val fromTo = mutableListOf(myself.id, id).apply {
......
package chat.rocket.android.chatrooms.ui package chat.rocket.android.chatrooms.ui
import android.app.AlertDialog import androidx.appcompat.app.AlertDialog
import android.app.ProgressDialog import android.app.ProgressDialog
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
...@@ -36,7 +36,6 @@ import chat.rocket.android.helper.SharedPreferenceHelper ...@@ -36,7 +36,6 @@ import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.util.extension.onQueryTextListener import chat.rocket.android.util.extension.onQueryTextListener
import chat.rocket.android.util.extensions.fadeIn import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.ifNotNullNorEmpty
import chat.rocket.android.util.extensions.ifNotNullNotEmpty import chat.rocket.android.util.extensions.ifNotNullNotEmpty
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
...@@ -69,22 +68,19 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -69,22 +68,19 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
private var progressDialog: ProgressDialog? = null private var progressDialog: ProgressDialog? = null
companion object { companion object {
fun newInstance(chatRoomId: String? = null): ChatRoomsFragment { fun newInstance(chatRoomId: String? = null): ChatRoomsFragment = ChatRoomsFragment().apply {
return ChatRoomsFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
} }
} }
} }
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
setHasOptionsMenu(true) setHasOptionsMenu(true)
val bundle = arguments arguments?.run {
if (bundle != null) { chatRoomId = getString(BUNDLE_CHAT_ROOM_ID)
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomId.ifNotNullNotEmpty { roomId -> chatRoomId.ifNotNullNotEmpty { roomId ->
presenter.loadChatRoom(roomId) presenter.loadChatRoom(roomId)
chatRoomId = null chatRoomId = null
...@@ -129,12 +125,14 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -129,12 +125,14 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
) )
) )
recycler_view.itemAnimator = DefaultItemAnimator() recycler_view.itemAnimator = DefaultItemAnimator()
recycler_view.adapter = adapter
viewModel.getChatRooms().observe(viewLifecycleOwner, Observer { rooms -> viewModel.getChatRooms().observe(viewLifecycleOwner, Observer { rooms ->
rooms?.let { rooms?.let {
Timber.d("Got items: $it") Timber.d("Got items: $it")
adapter.values = it adapter.values = it
if (recycler_view.adapter != adapter) {
recycler_view.adapter = adapter
}
if (rooms.isNotEmpty()) { if (rooms.isNotEmpty()) {
text_no_data_to_display.isVisible = false text_no_data_to_display.isVisible = false
} }
...@@ -236,7 +234,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -236,7 +234,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
) )
} }
AlertDialog.Builder(context) context?.let {
AlertDialog.Builder(it)
.setTitle(R.string.dialog_sort_title) .setTitle(R.string.dialog_sort_title)
.setView(dialogLayout) .setView(dialogLayout)
.setPositiveButton(R.string.msg_sort) { dialog, _ -> .setPositiveButton(R.string.msg_sort) { dialog, _ ->
...@@ -246,6 +245,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -246,6 +245,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}.show() }.show()
} }
} }
}
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
...@@ -318,21 +318,20 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -318,21 +318,20 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
ui { ui {
text_connection_status.fadeIn() text_connection_status.fadeIn()
handler.removeCallbacks(dismissStatus) handler.removeCallbacks(dismissStatus)
when (state) { text_connection_status.text = when (state) {
is State.Connected -> { is State.Connected -> {
text_connection_status.text = getString(R.string.status_connected)
handler.postDelayed(dismissStatus, 2000) handler.postDelayed(dismissStatus, 2000)
getString(R.string.status_connected)
}
is State.Disconnected -> getString(R.string.status_disconnected)
is State.Connecting -> getString(R.string.status_connecting)
is State.Authenticating -> getString(R.string.status_authenticating)
is State.Disconnecting -> getString(R.string.status_disconnecting)
is State.Waiting -> getString(R.string.status_waiting, state.seconds)
else -> {
handler.postDelayed(dismissStatus, 500)
""
} }
is State.Disconnected -> text_connection_status.text =
getString(R.string.status_disconnected)
is State.Connecting -> text_connection_status.text =
getString(R.string.status_connecting)
is State.Authenticating -> text_connection_status.text =
getString(R.string.status_authenticating)
is State.Disconnecting -> text_connection_status.text =
getString(R.string.status_disconnecting)
is State.Waiting -> text_connection_status.text =
getString(R.string.status_waiting, state.seconds)
} }
} }
} }
......
...@@ -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
} }
} }
......
...@@ -45,9 +45,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback ...@@ -45,9 +45,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private val adapter: MembersAdapter = MembersAdapter { private val adapter: MembersAdapter = MembersAdapter {
if (it.username != null) { it.username?.run { processSelectedMember(this) }
processSelectedMember(it.username)
}
} }
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
private var channelType: String = RoomType.CHANNEL private var channelType: String = RoomType.CHANNEL
...@@ -294,8 +292,8 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback ...@@ -294,8 +292,8 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
private fun addChip(chipText: String) { private fun addChip(chipText: String) {
val chip = Chip(context) val chip = Chip(context)
chip.chipText = chipText chip.text = chipText
chip.isCloseIconEnabled = true chip.isCloseIconVisible = true
chip.setChipBackgroundColorResource(R.color.icon_grey) chip.setChipBackgroundColorResource(R.color.icon_grey)
setupChipOnCloseIconClickListener(chip) setupChipOnCloseIconClickListener(chip)
chip_group_member.addView(chip) chip_group_member.addView(chip)
...@@ -304,7 +302,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback ...@@ -304,7 +302,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
private fun setupChipOnCloseIconClickListener(chip: Chip) { private fun setupChipOnCloseIconClickListener(chip: Chip) {
chip.setOnCloseIconClickListener { chip.setOnCloseIconClickListener {
removeChip(it) removeChip(it)
removeMember((it as Chip).chipText.toString()) removeMember((it as Chip).text.toString())
// whenever we remove a chip we should process the chip group visibility. // whenever we remove a chip we should process the chip group visibility.
processChipGroupVisibility() processChipGroupVisibility()
} }
......
...@@ -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
......
...@@ -115,7 +115,7 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity ...@@ -115,7 +115,7 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity
val text = mapAttachmentText(text, attachments?.firstOrNull(), context) val text = mapAttachmentText(text, attachments?.firstOrNull(), context)
val entity = AttachmentEntity( list.add(AttachmentEntity(
_id = attachmentId, _id = attachmentId,
messageId = msgId, messageId = msgId,
title = title, title = title,
...@@ -144,16 +144,14 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity ...@@ -144,16 +144,14 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity
buttonAlignment = buttonAlignment, buttonAlignment = buttonAlignment,
hasActions = actions?.isNotEmpty() == true, hasActions = actions?.isNotEmpty() == true,
hasFields = fields?.isNotEmpty() == true hasFields = fields?.isNotEmpty() == true
) ))
list.add(entity)
fields?.forEach { field -> fields?.forEach { field ->
val entity = AttachmentFieldEntity( list.add(AttachmentFieldEntity(
attachmentId = attachmentId, attachmentId = attachmentId,
title = field.title, title = field.title,
value = field.value value = field.value
) ))
list.add(entity)
} }
actions?.forEach { action -> actions?.forEach { action ->
...@@ -175,18 +173,14 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity ...@@ -175,18 +173,14 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity
return list return list
} }
fun mapAttachmentText(text: String?, attachment: Attachment?, context: Context): String? { fun mapAttachmentText(text: String?, attachment: Attachment?, context: Context): String? = attachment?.run {
return if (attachment != null) {
when { when {
attachment.imageUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_photo) imageUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_photo)
attachment.videoUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_video) videoUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_video)
attachment.audioUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_audio) audioUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_audio)
attachment.titleLink.isNotNullNorEmpty() && titleLink.isNotNullNorEmpty() &&
attachment.type?.contentEquals("file") == true -> type?.contentEquals("file") == true ->
context.getString(R.string.msg_preview_file) context.getString(R.string.msg_preview_file)
else -> text else -> text
} }
} else { } ?: text
text \ No newline at end of file
}
}
...@@ -36,8 +36,7 @@ class FavoriteMessagesPresenter @Inject constructor( ...@@ -36,8 +36,7 @@ class FavoriteMessagesPresenter @Inject constructor(
try { try {
view.showLoading() view.showLoading()
dbManager.getRoom(roomId)?.let { dbManager.getRoom(roomId)?.let {
val favoriteMessages = val favoriteMessages = client.getFavoriteMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
client.getFavoriteMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
val messageList = mapper.map(favoriteMessages.result, asNotReversed = true) val messageList = mapper.map(favoriteMessages.result, asNotReversed = true)
view.showFavoriteMessages(messageList) view.showFavoriteMessages(messageList)
offset += 1 * 30 offset += 1 * 30
......
...@@ -25,12 +25,10 @@ import dagger.android.support.AndroidSupportInjection ...@@ -25,12 +25,10 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_favorite_messages.* import kotlinx.android.synthetic.main.fragment_favorite_messages.*
import javax.inject.Inject import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment { fun newInstance(chatRoomId: String): Fragment = FavoriteMessagesFragment().apply {
return FavoriteMessagesFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
putString(INTENT_CHAT_ROOM_ID, chatRoomId) putString(INTENT_CHAT_ROOM_ID, chatRoomId)
} }
}
} }
internal const val TAG_FAVORITE_MESSAGES_FRAGMENT = "FavoriteMessagesFragment" internal const val TAG_FAVORITE_MESSAGES_FRAGMENT = "FavoriteMessagesFragment"
...@@ -48,12 +46,9 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { ...@@ -48,12 +46,9 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { chatRoomId = getString(INTENT_CHAT_ROOM_ID, "")
chatRoomId = bundle.getString(INTENT_CHAT_ROOM_ID) } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
...@@ -115,6 +110,6 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { ...@@ -115,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
...@@ -30,12 +30,10 @@ import dagger.android.support.AndroidSupportInjection ...@@ -30,12 +30,10 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_files.* import kotlinx.android.synthetic.main.fragment_files.*
import javax.inject.Inject import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment { fun newInstance(chatRoomId: String): Fragment = FilesFragment().apply {
return FilesFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
} }
}
} }
internal const val TAG_FILES_FRAGMENT = "FilesFragment" internal const val TAG_FILES_FRAGMENT = "FilesFragment"
...@@ -55,12 +53,9 @@ class FilesFragment : Fragment(), FilesView { ...@@ -55,12 +53,9 @@ class FilesFragment : Fragment(), FilesView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
...@@ -152,7 +147,7 @@ class FilesFragment : Fragment(), FilesView { ...@@ -152,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,8 +11,10 @@ object AndroidPermissionsHelper { ...@@ -11,8 +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) == return ContextCompat.checkSelfPermission(
PackageManager.PERMISSION_GRANTED context,
permission
) == PackageManager.PERMISSION_GRANTED
} }
fun requestPermission(context: Activity, permission: String, requestCode: Int) { fun requestPermission(context: Activity, permission: String, requestCode: Int) {
......
...@@ -53,8 +53,8 @@ object ImageHelper { ...@@ -53,8 +53,8 @@ object ImageHelper {
) )
val toolbar = Toolbar(context).also { val toolbar = Toolbar(context).also {
it.inflateMenu(R.menu.image_actions) it.inflateMenu(R.menu.image_actions)
it.setOnMenuItemClickListener { it.setOnMenuItemClickListener { view ->
return@setOnMenuItemClickListener when (it.itemId) { return@setOnMenuItemClickListener when (view.itemId) {
R.id.action_save_image -> saveImage(context) R.id.action_save_image -> saveImage(context)
else -> true else -> true
} }
...@@ -62,20 +62,24 @@ object ImageHelper { ...@@ -62,20 +62,24 @@ object ImageHelper {
val titleSize = context.resources val titleSize = context.resources
.getDimensionPixelSize(R.dimen.viewer_toolbar_title) .getDimensionPixelSize(R.dimen.viewer_toolbar_title)
val titleTextView = TextView(context).also { val titleTextView = TextView(context).also { tv ->
it.text = imageName with(tv) {
it.setTextColor(Color.WHITE) text = imageName
it.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize.toFloat()) setTextColor(Color.WHITE)
it.ellipsize = TextUtils.TruncateAt.END setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize.toFloat())
it.setSingleLine() ellipsize = TextUtils.TruncateAt.END
it.typeface = Typeface.DEFAULT_BOLD setSingleLine()
it.setPadding(pad) typeface = Typeface.DEFAULT_BOLD
setPadding(pad)
}
} }
val backArrowView = ImageView(context).also { val backArrowView = ImageView(context).also { imgView ->
it.setImageResource(R.drawable.ic_arrow_back_white_24dp) with(imgView) {
it.setOnClickListener { imageViewer?.onDismiss() } setImageResource(R.drawable.ic_arrow_back_white_24dp)
it.setPadding(0, pad, pad, pad) setOnClickListener { imageViewer?.onDismiss() }
setPadding(0, pad, pad, pad)
}
} }
val layoutParams = AppBarLayout.LayoutParams( val layoutParams = AppBarLayout.LayoutParams(
...@@ -88,15 +92,17 @@ object ImageHelper { ...@@ -88,15 +92,17 @@ object ImageHelper {
} }
val appBarLayout = AppBarLayout(context).also { val appBarLayout = AppBarLayout(context).also {
it.layoutParams = lparams with(it) {
it.setBackgroundColor(Color.BLACK) layoutParams = lparams
it.addView( setBackgroundColor(Color.BLACK)
addView(
toolbar, AppBarLayout.LayoutParams( toolbar, AppBarLayout.LayoutParams(
AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT ViewGroup.LayoutParams.WRAP_CONTENT
) )
) )
} }
}
val builder = ImageViewer.createPipelineDraweeControllerBuilder() val builder = ImageViewer.createPipelineDraweeControllerBuilder()
.setImageRequest(request) .setImageRequest(request)
......
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
...@@ -210,7 +210,7 @@ class MainPresenter @Inject constructor( ...@@ -210,7 +210,7 @@ class MainPresenter @Inject constructor(
} }
} }
private suspend fun saveAccount(uiModel: NavHeaderUiModel) { private fun saveAccount(uiModel: NavHeaderUiModel) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
} }
......
...@@ -2,7 +2,7 @@ package chat.rocket.android.main.ui ...@@ -2,7 +2,7 @@ package chat.rocket.android.main.ui
import DrawableHelper import DrawableHelper
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import androidx.appcompat.app.AlertDialog
import android.app.ProgressDialog import android.app.ProgressDialog
import android.os.Bundle import android.os.Bundle
import androidx.annotation.IdRes import androidx.annotation.IdRes
...@@ -13,6 +13,7 @@ import androidx.fragment.app.Fragment ...@@ -13,6 +13,7 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.main.adapter.AccountsAdapter import chat.rocket.android.main.adapter.AccountsAdapter
import chat.rocket.android.main.adapter.Selector import chat.rocket.android.main.adapter.Selector
import chat.rocket.android.main.presentation.MainPresenter import chat.rocket.android.main.presentation.MainPresenter
...@@ -37,6 +38,9 @@ import kotlinx.android.synthetic.main.activity_main.* ...@@ -37,6 +38,9 @@ import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.nav_header.view.* import kotlinx.android.synthetic.main.nav_header.view.*
import javax.inject.Inject import javax.inject.Inject
import android.app.NotificationManager
import android.content.Context
private const val CURRENT_STATE = "current_state" private const val CURRENT_STATE = "current_state"
...@@ -89,6 +93,9 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, ...@@ -89,6 +93,9 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
presenter.toChatList(chatRoomId) presenter.toChatList(chatRoomId)
isFragmentAdded = true isFragmentAdded = true
} }
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
notificationManager.cancelAll()
} }
override fun onDestroy() { override fun onDestroy() {
...@@ -98,6 +105,21 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, ...@@ -98,6 +105,21 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
} }
} }
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
closeDrawer()
} else {
supportFragmentManager.findFragmentById(R.id.fragment_container)?.let {
if (it !is ChatRoomsFragment && supportFragmentManager.backStackEntryCount == 0) {
presenter.toChatList(chatRoomId)
setCheckedNavDrawerItem(R.id.menu_action_chats)
} else {
super.onBackPressed()
}
}
}
}
override fun activityInjector(): AndroidInjector<Activity> = activityDispatchingAndroidInjector override fun activityInjector(): AndroidInjector<Activity> = activityDispatchingAndroidInjector
override fun supportFragmentInjector(): AndroidInjector<Fragment> = override fun supportFragmentInjector(): AndroidInjector<Fragment> =
...@@ -180,7 +202,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, ...@@ -180,7 +202,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
BuildConfig.RECOMMENDED_SERVER_VERSION BuildConfig.RECOMMENDED_SERVER_VERSION
) )
) )
.setPositiveButton(R.string.msg_ok, null) .setPositiveButton(android.R.string.ok, null)
.create() .create()
.show() .show()
} }
...@@ -194,7 +216,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, ...@@ -194,7 +216,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
) )
) )
.setOnDismissListener { presenter.logout() } .setOnDismissListener { presenter.logout() }
.setPositiveButton(R.string.msg_ok, null) .setPositiveButton(android.R.string.ok, null)
.create() .create()
.show() .show()
} }
......
...@@ -10,8 +10,9 @@ import chat.rocket.android.util.extensions.inflate ...@@ -10,8 +10,9 @@ import chat.rocket.android.util.extensions.inflate
import kotlinx.android.synthetic.main.avatar.view.* import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_member.view.* import kotlinx.android.synthetic.main.item_member.view.*
class MembersAdapter(private val listener: (MemberUiModel) -> Unit) : class MembersAdapter(
RecyclerView.Adapter<MembersAdapter.ViewHolder>() { private val listener: (MemberUiModel) -> Unit
) : RecyclerView.Adapter<MembersAdapter.ViewHolder>() {
private var dataSet: List<MemberUiModel> = ArrayList() private var dataSet: List<MemberUiModel> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MembersAdapter.ViewHolder = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MembersAdapter.ViewHolder =
...@@ -43,7 +44,8 @@ class MembersAdapter(private val listener: (MemberUiModel) -> Unit) : ...@@ -43,7 +44,8 @@ class MembersAdapter(private val listener: (MemberUiModel) -> Unit) :
fun bind(memberUiModel: MemberUiModel, listener: (MemberUiModel) -> Unit) = with(itemView) { fun bind(memberUiModel: MemberUiModel, listener: (MemberUiModel) -> Unit) = with(itemView) {
image_avatar.setImageURI(memberUiModel.avatarUri) image_avatar.setImageURI(memberUiModel.avatarUri)
text_member.content = memberUiModel.displayName text_member.content = memberUiModel.displayName
text_member.setCompoundDrawablesRelativeWithIntrinsicBounds(DrawableHelper.getUserStatusDrawable(memberUiModel.status, context), null, null, null) text_member.setCompoundDrawablesRelativeWithIntrinsicBounds(
DrawableHelper.getUserStatusDrawable(memberUiModel.status, context), null, null, null)
setOnClickListener { listener(memberUiModel) } setOnClickListener { listener(memberUiModel) }
} }
} }
......
...@@ -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)
}
}
} }
} }
...@@ -27,12 +27,10 @@ import kotlinx.android.synthetic.main.app_bar_chat_room.* ...@@ -27,12 +27,10 @@ import kotlinx.android.synthetic.main.app_bar_chat_room.*
import kotlinx.android.synthetic.main.fragment_members.* import kotlinx.android.synthetic.main.fragment_members.*
import javax.inject.Inject import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment { fun newInstance(chatRoomId: String): Fragment = MembersFragment().apply {
return MembersFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
} }
}
} }
internal const val TAG_MEMBERS_FRAGMENT = "MembersFragment" internal const val TAG_MEMBERS_FRAGMENT = "MembersFragment"
...@@ -52,12 +50,9 @@ class MembersFragment : Fragment(), MembersView { ...@@ -52,12 +50,9 @@ class MembersFragment : Fragment(), MembersView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
...@@ -80,7 +75,7 @@ class MembersFragment : Fragment(), MembersView { ...@@ -80,7 +75,7 @@ class MembersFragment : Fragment(), MembersView {
setupToolbar(total) setupToolbar(total)
if (adapter.itemCount == 0) { if (adapter.itemCount == 0) {
adapter.prependData(dataSet) adapter.prependData(dataSet)
if (dataSet.size >= 59) { // TODO Check why the API retorns the specified count -1 if (dataSet.size >= 59) { // TODO Check why the API returns the specified count -1
recycler_view.addOnScrollListener(object : recycler_view.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) { EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore( override fun onLoadMore(
...@@ -136,14 +131,9 @@ class MembersFragment : Fragment(), MembersView { ...@@ -136,14 +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( setupToolbarTitle((getString(R.string.title_counted_members, totalMembers)))
(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
......
...@@ -25,12 +25,10 @@ import dagger.android.support.AndroidSupportInjection ...@@ -25,12 +25,10 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_mentions.* import kotlinx.android.synthetic.main.fragment_mentions.*
import javax.inject.Inject import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment { fun newInstance(chatRoomId: String): Fragment = MentionsFragment().apply {
return MentionsFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
} }
}
} }
internal const val TAG_MENTIONS_FRAGMENT = "MentionsFragment" internal const val TAG_MENTIONS_FRAGMENT = "MentionsFragment"
...@@ -48,12 +46,9 @@ class MentionsFragment : Fragment(), MentionsView { ...@@ -48,12 +46,9 @@ class MentionsFragment : Fragment(), MentionsView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
...@@ -121,6 +116,6 @@ class MentionsFragment : Fragment(), MentionsView { ...@@ -121,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
...@@ -25,12 +25,10 @@ import dagger.android.support.AndroidSupportInjection ...@@ -25,12 +25,10 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_pinned_messages.* import kotlinx.android.synthetic.main.fragment_pinned_messages.*
import javax.inject.Inject import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment { fun newInstance(chatRoomId: String): Fragment = PinnedMessagesFragment().apply {
return PinnedMessagesFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId) putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
} }
}
} }
internal const val TAG_PINNED_MESSAGES_FRAGMENT = "PinnedMessagesFragment" internal const val TAG_PINNED_MESSAGES_FRAGMENT = "PinnedMessagesFragment"
...@@ -48,12 +46,9 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { ...@@ -48,12 +46,9 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
val bundle = arguments arguments?.run {
if (bundle != null) { chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) } ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
} }
override fun onCreateView( override fun onCreateView(
...@@ -121,6 +116,6 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { ...@@ -121,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
...@@ -38,13 +38,17 @@ class PreferencesFragment : Fragment(), PreferencesView { ...@@ -38,13 +38,17 @@ class PreferencesFragment : Fragment(), PreferencesView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar()
setupListeners() setupListeners()
presenter.loadAnalyticsTrackingInformation() presenter.loadAnalyticsTrackingInformation()
analyticsManager.logScreenView(ScreenViewEvent.Preferences) analyticsManager.logScreenView(ScreenViewEvent.Preferences)
} }
override fun onResume() {
setupToolbar()
super.onResume()
}
override fun setupAnalyticsTrackingView(isAnalyticsTrackingEnabled: Boolean) { override fun setupAnalyticsTrackingView(isAnalyticsTrackingEnabled: Boolean) {
if (BuildConfig.FLAVOR == "foss") { if (BuildConfig.FLAVOR == "foss") {
switch_analytics_tracking.isChecked = false switch_analytics_tracking.isChecked = false
......
...@@ -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
...@@ -82,7 +82,7 @@ class ProfilePresenter @Inject constructor( ...@@ -82,7 +82,7 @@ class ProfilePresenter @Inject constructor(
view.showLoading() view.showLoading()
try { try {
user?.id?.let { id -> user?.id?.let { id ->
retryIO { client.updateProfile(id, email, name, username) } retryIO { client.updateProfile(userId = id, email = email, name = name, username = username) }
view.showProfileUpdateSuccessfullyMessage() view.showProfileUpdateSuccessfullyMessage()
view.showProfile( view.showProfile(
serverUrl.avatarUrl(user.username ?: ""), serverUrl.avatarUrl(user.username ?: ""),
...@@ -115,7 +115,7 @@ class ProfilePresenter @Inject constructor( ...@@ -115,7 +115,7 @@ class ProfilePresenter @Inject constructor(
uriInteractor.getInputStream(uri) uriInteractor.getInputStream(uri)
} }
} }
user?.username?.let { view.reloadUserAvatar(it) } user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) }
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
...@@ -143,7 +143,7 @@ class ProfilePresenter @Inject constructor( ...@@ -143,7 +143,7 @@ class ProfilePresenter @Inject constructor(
} }
} }
user?.username?.let { view.reloadUserAvatar(it) } user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) }
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
...@@ -163,7 +163,7 @@ class ProfilePresenter @Inject constructor( ...@@ -163,7 +163,7 @@ class ProfilePresenter @Inject constructor(
user?.id?.let { id -> user?.id?.let { id ->
retryIO { client.resetAvatar(id) } retryIO { client.resetAvatar(id) }
} }
user?.username?.let { view.reloadUserAvatar(it) } user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) }
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
...@@ -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()) }
......
...@@ -2,7 +2,7 @@ package chat.rocket.android.profile.ui ...@@ -2,7 +2,7 @@ package chat.rocket.android.profile.ui
import DrawableHelper import DrawableHelper
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import androidx.appcompat.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Build import android.os.Build
...@@ -94,11 +94,13 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -94,11 +94,13 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (resultData != null && resultCode == Activity.RESULT_OK) { resultData?.run {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF) { if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF) {
presenter.updateAvatar(resultData.data) data?.let { presenter.updateAvatar(it) }
} else if (requestCode == REQUEST_CODE_FOR_PERFORM_CAMERA) { } else if (requestCode == REQUEST_CODE_FOR_PERFORM_CAMERA) {
presenter.preparePhotoAndUpdateAvatar(resultData.extras["data"] as Bitmap) extras?.get("data")?.let { presenter.preparePhotoAndUpdateAvatar(it as Bitmap) }
}
} }
} }
} }
...@@ -203,8 +205,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -203,8 +205,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} }
private fun setupToolbar() { private fun setupToolbar() {
(activity as AppCompatActivity?)?.supportActionBar?.title = (activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_profile)
getString(R.string.title_profile)
} }
private fun setupListeners() { private fun setupListeners() {
...@@ -293,17 +294,14 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -293,17 +294,14 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} }
fun showDeleteAccountDialog() { fun showDeleteAccountDialog() {
val passwordEditText = EditText(context) context?.let {
passwordEditText.hint = getString(R.string.msg_password) val passwordEText = EditText(context);
val mDialogView = LayoutInflater.from(it).inflate(R.layout.item_account_delete, null)
val builder = AlertDialog.Builder(context) val mBuilder = AlertDialog.Builder(it)
builder.setTitle(R.string.title_are_you_sure)
.setView(passwordEditText) mBuilder.setView(mDialogView).setPositiveButton(R.string.action_delete_account) { _, _ ->
.setPositiveButton(R.string.action_delete_account) { _, _ -> presenter.deleteAccount(passwordEText.text.toString())
presenter.deleteAccount(passwordEditText.text.toString()) }.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }.create().show()
} }
.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }
.create()
.show()
} }
} }
...@@ -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 {
......
...@@ -12,13 +12,14 @@ import android.os.Build ...@@ -12,13 +12,14 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.text.Html
import android.text.Spanned
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput import androidx.core.app.RemoteInput
import android.text.Html
import android.text.Spanned
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.server.domain.GetAccountInteractor import chat.rocket.android.server.domain.GetAccountInteractor
...@@ -29,7 +30,7 @@ import chat.rocket.common.model.RoomType ...@@ -29,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
...@@ -303,7 +304,11 @@ class PushManager @Inject constructor( ...@@ -303,7 +304,11 @@ class PushManager @Inject constructor(
// CharSequence extensions // CharSequence extensions
private fun CharSequence.fromHtml(): Spanned { private fun CharSequence.fromHtml(): Spanned {
return Html.fromHtml(this as String) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(this as String, FROM_HTML_MODE_LEGACY, null, null)
} else {
Html.fromHtml(this as String)
}
} }
// NotificationCompat.Builder extensions // NotificationCompat.Builder extensions
...@@ -383,12 +388,12 @@ data class PushMessage( ...@@ -383,12 +388,12 @@ data class PushMessage(
) : Parcelable { ) : Parcelable {
constructor(parcel: Parcel) : this( constructor(parcel: Parcel) : this(
parcel.readString().orEmpty(),
parcel.readString().orEmpty(),
parcel.readParcelable(PushMessage::class.java.classLoader) ?: PushInfo.EMPTY,
parcel.readString(), parcel.readString(),
parcel.readString(), parcel.readString(),
parcel.readParcelable(PushMessage::class.java.classLoader), parcel.readString().orEmpty(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(), parcel.readString(),
parcel.readString()) parcel.readString())
...@@ -433,9 +438,9 @@ data class PushInfo @KotshiConstructor constructor( ...@@ -433,9 +438,9 @@ data class PushInfo @KotshiConstructor constructor(
} }
constructor(parcel: Parcel) : this( constructor(parcel: Parcel) : this(
parcel.readString(), parcel.readString().orEmpty(),
parcel.readString(), parcel.readString().orEmpty(),
roomTypeOf(parcel.readString()), roomTypeOf(parcel.readString().orEmpty()),
parcel.readString(), parcel.readString(),
parcel.readParcelable(PushInfo::class.java.classLoader)) parcel.readParcelable(PushInfo::class.java.classLoader))
...@@ -481,7 +486,7 @@ data class PushSender @KotshiConstructor constructor( ...@@ -481,7 +486,7 @@ data class PushSender @KotshiConstructor constructor(
val name: String? val name: String?
) : Parcelable { ) : Parcelable {
constructor(parcel: Parcel) : this( constructor(parcel: Parcel) : this(
parcel.readString(), parcel.readString().orEmpty(),
parcel.readString(), parcel.readString(),
parcel.readString()) parcel.readString())
......
...@@ -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 {
......
...@@ -3,6 +3,7 @@ package chat.rocket.android.server.domain ...@@ -3,6 +3,7 @@ package chat.rocket.android.server.domain
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
interface AccountsRepository { interface AccountsRepository {
fun save(account: Account) fun save(account: Account)
fun load(): List<Account> fun load(): List<Account>
fun remove(serverUrl: String) fun remove(serverUrl: String)
......
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,7 +3,8 @@ package chat.rocket.android.server.domain ...@@ -3,7 +3,8 @@ package chat.rocket.android.server.domain
import javax.inject.Inject import javax.inject.Inject
class GetAccountInteractor @Inject constructor(val repository: AccountsRepository) { class GetAccountInteractor @Inject constructor(val repository: AccountsRepository) {
suspend fun get(url: String) = repository.load().firstOrNull { account ->
fun get(url: String) = repository.load().firstOrNull { account ->
url == account.serverUrl url == account.serverUrl
} }
} }
\ No newline at end of file
...@@ -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) {
......
...@@ -3,7 +3,8 @@ package chat.rocket.android.server.domain ...@@ -3,7 +3,8 @@ package chat.rocket.android.server.domain
import javax.inject.Inject import javax.inject.Inject
class RemoveAccountInteractor @Inject constructor(val repository: AccountsRepository) { class RemoveAccountInteractor @Inject constructor(val repository: AccountsRepository) {
suspend fun remove(serverUrl: String) {
fun remove(serverUrl: String) {
repository.remove(serverUrl) repository.remove(serverUrl)
} }
} }
\ No newline at end of file
...@@ -4,5 +4,5 @@ import chat.rocket.android.server.domain.model.Account ...@@ -4,5 +4,5 @@ import chat.rocket.android.server.domain.model.Account
import javax.inject.Inject import javax.inject.Inject
class SaveAccountInteractor @Inject constructor(val repository: AccountsRepository) { class SaveAccountInteractor @Inject constructor(val repository: AccountsRepository) {
suspend fun save(account: Account) = repository.save(account) fun save(account: Account) = repository.save(account)
} }
\ No newline at end of file
...@@ -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 { status ->
client.setTemporaryStatus(status)
}
}
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)
...@@ -135,16 +142,15 @@ class ConnectionManager( ...@@ -135,16 +142,15 @@ class ConnectionManager(
if (it.type == Type.Updated) { if (it.type == Type.Updated) {
if (it.data is Room) { if (it.data is Room) {
val room = it.data as Room val room = it.data as Room
roomsChannels[it.data.id]?.let { channel -> roomsChannels[it.data.id]?.offer(room)
channel.offer(room)
}
} }
} }
} }
} }
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 })
...@@ -157,7 +163,7 @@ class ConnectionManager( ...@@ -157,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)
...@@ -170,7 +176,7 @@ class ConnectionManager( ...@@ -170,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)
...@@ -178,7 +184,7 @@ class ConnectionManager( ...@@ -178,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)
...@@ -186,7 +192,7 @@ class ConnectionManager( ...@@ -186,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)
...@@ -197,7 +203,7 @@ class ConnectionManager( ...@@ -197,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)
} }
...@@ -231,7 +237,7 @@ class ConnectionManager( ...@@ -231,7 +237,7 @@ class ConnectionManager(
} }
private fun resubscribeRooms() { private fun resubscribeRooms() {
roomMessagesChannels.toList().map { (roomId, channel) -> roomMessagesChannels.toList().map { (roomId, _) ->
client.subscribeRoomMessages(roomId) { _, id -> client.subscribeRoomMessages(roomId) { _, id ->
Timber.d("Subscribed to $roomId: $id") Timber.d("Subscribed to $roomId: $id")
subscriptionIdMap[roomId] = id subscriptionIdMap[roomId] = id
...@@ -286,16 +292,18 @@ class ConnectionManager( ...@@ -286,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
...@@ -14,7 +9,6 @@ import chat.rocket.core.model.Message ...@@ -14,7 +9,6 @@ import chat.rocket.core.model.Message
import chat.rocket.core.model.Reactions import chat.rocket.core.model.Reactions
import chat.rocket.core.model.attachment.Attachment import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.Color import chat.rocket.core.model.attachment.Color
import chat.rocket.core.model.attachment.DEFAULT_COLOR_STR
import chat.rocket.core.model.attachment.Field import chat.rocket.core.model.attachment.Field
import chat.rocket.core.model.attachment.actions.Action import chat.rocket.core.model.attachment.actions.Action
import chat.rocket.core.model.attachment.actions.ButtonAction import chat.rocket.core.model.attachment.actions.ButtonAction
...@@ -22,8 +16,8 @@ import chat.rocket.core.model.messageTypeOf ...@@ -22,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()
...@@ -58,7 +52,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -58,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,
...@@ -82,7 +77,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -82,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
)) )
)
} }
} }
...@@ -106,12 +102,18 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -106,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))
...@@ -135,7 +137,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -135,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)
} }
...@@ -144,7 +146,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -144,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)
} }
...@@ -152,8 +154,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -152,8 +154,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
} else { } else {
null null
} }
list.add(
val attachment = Attachment( Attachment(
title = title, title = title,
type = type, type = type,
description = description, description = description,
...@@ -179,10 +181,11 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -179,10 +181,11 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
authorLink = authorLink, authorLink = authorLink,
fields = fields, fields = fields,
fallback = fallback, fallback = fallback,
buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment ?: "vertical" else null, buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment
?: "vertical" else null,
actions = actions actions = actions
) )
list.add(attachment) )
} }
} }
return list return list
...@@ -190,9 +193,11 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -190,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 }
...@@ -46,25 +47,25 @@ class DatabaseMessagesRepository( ...@@ -46,25 +47,25 @@ class DatabaseMessagesRepository(
dbManager.processMessagesBatch(listOf(message)).join() dbManager.processMessagesBatch(listOf(message)).join()
} }
override suspend fun saveAll(messages: List<Message>) { override suspend fun saveAll(newMessages: List<Message>) {
dbManager.processMessagesBatch(messages).join() dbManager.processMessagesBatch(newMessages).join()
} }
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,9 +77,9 @@ class DatabaseMessagesRepository( ...@@ -76,9 +77,9 @@ 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)?.let { it.timestamp } dbManager.messageDao().getLastSync(roomId)?.timestamp
} }
} }
} }
\ No newline at end of file
...@@ -14,13 +14,13 @@ class SharedPreferencesAccountsRepository( ...@@ -14,13 +14,13 @@ class SharedPreferencesAccountsRepository(
private val moshi: Moshi private val moshi: Moshi
) : AccountsRepository { ) : AccountsRepository {
override fun save(newAccount: Account) { override fun save(account: Account) {
val accounts = load() load().filter { it.serverUrl != account.serverUrl }
val newList = accounts.filter { account -> newAccount.serverUrl != account.serverUrl }
.toMutableList() .toMutableList()
newList.add(0, newAccount) .apply {
save(newList) add(0, account)
save(this)
}
} }
override fun load(): List<Account> { override fun load(): List<Account> {
...@@ -28,22 +28,16 @@ class SharedPreferencesAccountsRepository( ...@@ -28,22 +28,16 @@ class SharedPreferencesAccountsRepository(
val type = Types.newParameterizedType(List::class.java, Account::class.java) val type = Types.newParameterizedType(List::class.java, Account::class.java)
val adapter = moshi.adapter<List<Account>>(type) val adapter = moshi.adapter<List<Account>>(type)
return adapter.fromJson(json) ?: emptyList() return json?.let { adapter.fromJson(it) } ?: emptyList()
} }
override fun remove(serverUrl: String) { override fun remove(serverUrl: String) {
val accounts = load() save(load().filter { account -> serverUrl != account.serverUrl }.toMutableList())
val newList = accounts.filter { account -> serverUrl != account.serverUrl }
.toMutableList()
save(newList)
} }
private fun save(accounts: List<Account>) { private fun save(accounts: List<Account>) {
val type = Types.newParameterizedType(List::class.java, Account::class.java) val type = Types.newParameterizedType(List::class.java, Account::class.java)
val adapter = moshi.adapter<List<Account>>(type) val adapter = moshi.adapter<List<Account>>(type)
preferences.edit { preferences.edit { putString(ACCOUNTS_KEY, adapter.toJson(accounts)) }
putString(ACCOUNTS_KEY, adapter.toJson(accounts))
}
} }
} }
\ No newline at end of file
...@@ -15,8 +15,7 @@ class SharedPrefsBasicAuthRepository( ...@@ -15,8 +15,7 @@ class SharedPrefsBasicAuthRepository(
) : BasicAuthRepository { ) : BasicAuthRepository {
override fun save(basicAuth: BasicAuth) { override fun save(basicAuth: BasicAuth) {
val newList = load().filter { basicAuth -> basicAuth.host != basicAuth.host } val newList = load().filter { auth -> auth.host != auth.host }.toMutableList()
.toMutableList()
newList.add(0, basicAuth) newList.add(0, basicAuth)
save(newList) save(newList)
} }
...@@ -26,7 +25,7 @@ class SharedPrefsBasicAuthRepository( ...@@ -26,7 +25,7 @@ class SharedPrefsBasicAuthRepository(
val type = Types.newParameterizedType(List::class.java, BasicAuth::class.java) val type = Types.newParameterizedType(List::class.java, BasicAuth::class.java)
val adapter = moshi.adapter<List<BasicAuth>>(type) val adapter = moshi.adapter<List<BasicAuth>>(type)
return adapter.fromJson(json) ?: emptyList() return json?.let { adapter.fromJson(it) ?: emptyList() } ?: emptyList()
} }
private fun save(basicAuths: List<BasicAuth>) { private fun save(basicAuths: List<BasicAuth>) {
......
...@@ -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 {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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