Unverified Commit 41e500a1 authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #1317 from RocketChat/feature/db-chatrooms

[Feature] Room DB on Chat rooms screen
parents 5db6d4d8 fc4c7f31
...@@ -93,7 +93,7 @@ jobs: ...@@ -93,7 +93,7 @@ jobs:
- run: - run:
name: Build APK name: Build APK
command: | command: |
./gradlew assembleRelease --quiet --console=plain --stacktrace ./gradlew assembleRelease --info --console=plain --stacktrace
- store_artifacts: - store_artifacts:
path: app/build/outputs/apk path: app/build/outputs/apk
destination: apks destination: apks
......
...@@ -3,19 +3,18 @@ apply plugin: 'io.fabric' ...@@ -3,19 +3,18 @@ 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'
apply plugin: 'realm-android'
android { android {
compileSdkVersion versions.compileSdk compileSdkVersion 'android-P'
buildToolsVersion versions.buildTools buildToolsVersion versions.buildTools
defaultConfig { defaultConfig {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 2026 versionCode 2030
versionName "2.3.2" versionName "2.4.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
} }
...@@ -68,11 +67,11 @@ dependencies { ...@@ -68,11 +67,11 @@ dependencies {
implementation libraries.appCompat implementation libraries.appCompat
implementation libraries.recyclerview implementation libraries.recyclerview
implementation libraries.design implementation libraries.material
implementation libraries.constraintLayout implementation libraries.constraintlayout
implementation libraries.cardView implementation libraries.cardview
implementation libraries.flexbox implementation libraries.flexbox
implementation libraries.customTabs implementation libraries.browser
implementation libraries.androidKtx implementation libraries.androidKtx
...@@ -86,7 +85,6 @@ dependencies { ...@@ -86,7 +85,6 @@ dependencies {
implementation libraries.room implementation libraries.room
kapt libraries.roomProcessor kapt libraries.roomProcessor
implementation libraries.roomRxjava
implementation libraries.lifecycleExtensions implementation libraries.lifecycleExtensions
kapt libraries.lifecycleCompiler kapt libraries.lifecycleCompiler
...@@ -118,7 +116,9 @@ dependencies { ...@@ -118,7 +116,9 @@ dependencies {
implementation libraries.aVLoadingIndicatorView implementation libraries.aVLoadingIndicatorView
implementation('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') { implementation "com.github.luciofm:livedata-ktx:b1e8bbc25a"
implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') {
transitive = true transitive = true
} }
......
package chat.rocket.android.chatroom.ui package chat.rocket.android.chatroom.ui
import android.content.Intent import android.content.Intent
import android.support.test.espresso.intent.rule.IntentsTestRule import androidx.test.espresso.intent.rule.IntentsTestRule
import android.support.test.filters.LargeTest import androidx.test.filters.LargeTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import android.app.Activity import android.app.Activity
import android.app.Instrumentation.ActivityResult import android.app.Instrumentation.ActivityResult
import android.support.test.InstrumentationRegistry import androidx.test.InstrumentationRegistry
import android.support.test.espresso.intent.Intents.intended import androidx.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.intending import androidx.test.espresso.intent.Intents.intending
import android.support.test.espresso.intent.matcher.IntentMatchers.* import androidx.test.espresso.intent.matcher.IntentMatchers.*
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.not import org.hamcrest.Matchers.not
import org.junit.Before import org.junit.Before
......
package chat.rocket.android.app package chat.rocket.android.app
import android.arch.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import android.arch.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import chat.rocket.android.server.domain.GetAccountInteractor 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.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
......
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
import android.support.v4.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import android.widget.TextView import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
......
...@@ -3,19 +3,12 @@ package chat.rocket.android.app ...@@ -3,19 +3,12 @@ package chat.rocket.android.app
import android.app.Activity import android.app.Activity
import android.app.Application import android.app.Application
import android.app.Service import android.app.Service
import android.arch.lifecycle.ProcessLifecycleOwner
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import androidx.lifecycle.ProcessLifecycleOwner
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.app.migration.RealmMigration
import chat.rocket.android.app.migration.RocketChatLibraryModule
import chat.rocket.android.app.migration.RocketChatServerModule
import chat.rocket.android.app.migration.model.RealmBasedServerInfo
import chat.rocket.android.app.migration.model.RealmPublicSetting
import chat.rocket.android.app.migration.model.RealmSession
import chat.rocket.android.app.migration.model.RealmUser
import chat.rocket.android.dagger.DaggerAppComponent import chat.rocket.android.dagger.DaggerAppComponent
import chat.rocket.android.dagger.qualifier.ForMessages import chat.rocket.android.dagger.qualifier.ForMessages
import chat.rocket.android.helper.CrashlyticsTree import chat.rocket.android.helper.CrashlyticsTree
...@@ -24,19 +17,9 @@ import chat.rocket.android.infrastructure.installCrashlyticsWrapper ...@@ -24,19 +17,9 @@ import chat.rocket.android.infrastructure.installCrashlyticsWrapper
import chat.rocket.android.server.domain.AccountsRepository import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SITE_URL import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.widget.emoji.EmojiRepository import chat.rocket.android.widget.emoji.EmojiRepository
import chat.rocket.common.model.Token
import chat.rocket.core.model.Value
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.crashlytics.android.core.CrashlyticsCore import com.crashlytics.android.core.CrashlyticsCore
import com.facebook.drawee.backends.pipeline.DraweeConfig import com.facebook.drawee.backends.pipeline.DraweeConfig
...@@ -49,10 +32,6 @@ import dagger.android.HasActivityInjector ...@@ -49,10 +32,6 @@ import dagger.android.HasActivityInjector
import dagger.android.HasBroadcastReceiverInjector import dagger.android.HasBroadcastReceiverInjector
import dagger.android.HasServiceInjector import dagger.android.HasServiceInjector
import io.fabric.sdk.android.Fabric import io.fabric.sdk.android.Fabric
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.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
...@@ -83,17 +62,11 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -83,17 +62,11 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
@Inject @Inject
lateinit var settingsInteractor: GetSettingsInteractor lateinit var settingsInteractor: GetSettingsInteractor
@Inject @Inject
lateinit var settingsRepository: SettingsRepository
@Inject
lateinit var tokenRepository: TokenRepository lateinit var tokenRepository: TokenRepository
@Inject @Inject
lateinit var accountRepository: AccountsRepository
@Inject
lateinit var saveCurrentServerRepository: SaveCurrentServerInteractor
@Inject
lateinit var prefs: SharedPreferences
@Inject
lateinit var localRepository: LocalRepository lateinit var localRepository: LocalRepository
@Inject
lateinit var accountRepository: AccountsRepository
@Inject @Inject
@field:ForMessages @field:ForMessages
...@@ -127,15 +100,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -127,15 +100,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
localRepository.setOldMessagesCleanedUp() localRepository.setOldMessagesCleanedUp()
} }
// TODO - remove this and all realm stuff when we got to 80% in 2.0 // TODO - remove REALM files.
try {
if (!localRepository.hasMigrated()) {
migrateFromLegacy()
}
} catch (ex: Exception) {
Timber.d(ex, "Error migrating old accounts")
}
// TODO - remove this // TODO - remove this
checkCurrentServer() checkCurrentServer()
} }
...@@ -160,110 +125,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -160,110 +125,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
} }
} }
private fun migrateFromLegacy() {
Realm.init(this)
val serveListConfiguration = RealmConfiguration.Builder()
.name("server.list.realm")
.schemaVersion(6)
.migration(RealmMigration())
.modules(RocketChatServerModule())
.build()
val serverRealm = Realm.getInstance(serveListConfiguration)
val serversInfoList = serverRealm.where(RealmBasedServerInfo::class.java).findAll().toList()
serversInfoList.forEach { server ->
val hostname = server.hostname
val url = if (server.insecure) "http://$hostname" else "https://$hostname"
val config = RealmConfiguration.Builder()
.name("${server.hostname}.realm")
.schemaVersion(6)
.migration(RealmMigration())
.modules(RocketChatLibraryModule())
.build()
val realm = Realm.getInstance(config)
val user = realm.where(RealmUser::class.java)
.isNotEmpty(RealmUser.EMAILS).findFirst()
val session = realm.where(RealmSession::class.java).findFirst()
migratePublicSettings(url, realm)
if (user != null && session != null) {
val authToken = session.token
settingsRepository.get(url)
migrateServerInfo(url, authToken!!, settingsRepository.get(url), user)
}
realm.close()
}
migrateCurrentServer(serversInfoList)
serverRealm.close()
localRepository.setMigrated(true)
}
private fun migrateServerInfo(url: String, authToken: String, settings: PublicSettings, user: RealmUser) {
val userId = user._id
val avatar = url.avatarUrl(user.username!!)
val icon = settings.favicon()?.let {
url.serverLogoUrl(it)
}
val logo = settings.wideTile()?.let {
url.serverLogoUrl(it)
}
val account = Account(url, icon, logo, user.username!!, avatar)
launch(CommonPool) {
tokenRepository.save(url, Token(userId!!, authToken))
accountRepository.save(account)
}
}
private fun migratePublicSettings(url: String, realm: Realm) {
val settings = realm.where(RealmPublicSetting::class.java).findAll()
val serverSettings = hashMapOf<String, Value<Any>>()
settings.toList().forEach { setting ->
val type = setting.type!!
val value = setting.value!!
val convertedSetting = when (type) {
"string" -> Value(value)
"language" -> Value(value)
"boolean" -> Value(value.toBoolean())
"int" -> try {
Value(value.toInt())
} catch (ex: NumberFormatException) {
Value(0)
}
else -> null // ignore
}
if (convertedSetting != null) {
val id = setting._id!!
serverSettings.put(id, convertedSetting)
}
}
settingsRepository.save(url, serverSettings)
}
private fun migrateCurrentServer(serversList: List<RealmBasedServerInfo>) {
if (getCurrentServerInteractor.get() == null) {
var currentServer = getSharedPreferences("cache", Context.MODE_PRIVATE)
.getString("KEY_SELECTED_SERVER_HOSTNAME", null)
currentServer = if (serversList.isNotEmpty()) {
val server = serversList.find { it.hostname == currentServer }
val hostname = server!!.hostname
if (server.insecure) {
"http://$hostname"
} else {
"https://$hostname"
}
} else {
"http://$currentServer"
}
saveCurrentServerRepository.save(currentServer)
}
}
private fun setupCrashlytics() { private fun setupCrashlytics() {
val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build() val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()
Fabric.with(this, Crashlytics.Builder().core(core).build()) Fabric.with(this, Crashlytics.Builder().core(core).build())
...@@ -305,11 +166,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -305,11 +166,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
} }
} }
private fun LocalRepository.setMigrated(migrated: Boolean) {
save(LocalRepository.MIGRATION_FINISHED_KEY, migrated)
}
private fun LocalRepository.hasMigrated() = getBoolean(LocalRepository.MIGRATION_FINISHED_KEY)
private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true) private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true)
private fun LocalRepository.setOldMessagesCleanedUp() = save(CLEANUP_OLD_MESSAGES_NEEDED, false) private fun LocalRepository.setOldMessagesCleanedUp() = save(CLEANUP_OLD_MESSAGES_NEEDED, false)
......
package chat.rocket.android.app
import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase
import chat.rocket.android.server.infraestructure.ServerDao
import chat.rocket.android.server.infraestructure.ServerEntity
@Database(entities = arrayOf(ServerEntity::class), version = 1, exportSchema = false)
abstract class RocketChatDatabase : RoomDatabase() {
abstract fun serverDao(): ServerDao
}
package chat.rocket.android.app.migration
import chat.rocket.android.BuildConfig
import chat.rocket.android.app.migration.model.RealmUser
import io.realm.DynamicRealm
import io.realm.RealmMigration
class RealmMigration : RealmMigration {
override fun migrate(dynamicRealm: DynamicRealm, oldVersion: Long, newVersion: Long) {
var oldVersion = oldVersion
val schema = dynamicRealm.schema
if (oldVersion == 0L) {
// NOOP
oldVersion++
}
if (oldVersion == 1L) {
oldVersion++
}
if (oldVersion == 2L) {
oldVersion++
}
if (oldVersion == 3L) {
oldVersion++
}
if (oldVersion == 4L) {
oldVersion++
}
if (oldVersion == 5L) {
val userSchema = schema.get("RealmUser")
try {
userSchema?.addField(RealmUser.NAME, String::class.java)
} catch (e: IllegalArgumentException) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
// ignore; it makes here if the schema for this model was already update before without migration
}
}
}
// hack around to avoid "new different configuration cannot access the same file" error
override fun hashCode(): Int {
return 37
}
override fun equals(o: Any?): Boolean {
return o is chat.rocket.android.app.migration.RealmMigration
}
// end hack
}
\ No newline at end of file
package chat.rocket.android.app.migration
import io.realm.annotations.RealmModule
@RealmModule(library = true, allClasses = true)
class RocketChatLibraryModule
\ No newline at end of file
package chat.rocket.android.app.migration
import chat.rocket.android.app.migration.model.RealmBasedServerInfo
import io.realm.annotations.RealmModule
@RealmModule(library = true, classes = arrayOf(RealmBasedServerInfo::class))
class RocketChatServerModule
\ No newline at end of file
package chat.rocket.android.app.migration.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class RealmBasedServerInfo : RealmObject() {
@PrimaryKey
@JvmField var hostname: String? = null
@JvmField var name: String? = null
@JvmField var session: String? = null
@JvmField var insecure: Boolean = false
}
\ No newline at end of file
package chat.rocket.android.app.migration.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class RealmEmail : RealmObject() {
@PrimaryKey
@JvmField
var address: String? = null
@JvmField
var verified: Boolean = false
}
\ No newline at end of file
package chat.rocket.android.app.migration.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class RealmPreferences : RealmObject() {
@PrimaryKey
@JvmField
var id: String? = null
@JvmField
var newRoomNotification: String? = null
@JvmField
var newMessageNotification: String? = null
@JvmField
var useEmojis: Boolean = false
@JvmField
var convertAsciiEmoji: Boolean = false
@JvmField
var saveMobileBandwidth: Boolean = false
@JvmField
var collapseMediaByDefault: Boolean = false
@JvmField
var unreadRoomsMode: Boolean = false
@JvmField
var autoImageLoad: Boolean = false
@JvmField
var emailNotificationMode: String? = null
@JvmField
var unreadAlert: Boolean = false
@JvmField
var desktopNotificationDuration: Int = 0
@JvmField
var viewMode: Int = 0
@JvmField
var hideUsernames: Boolean = false
@JvmField
var hideAvatars: Boolean = false
@JvmField
var hideFlexTab: Boolean = false
}
\ No newline at end of file
package chat.rocket.android.app.migration.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class RealmPublicSetting : RealmObject() {
@PrimaryKey
@JvmField
var _id: String? = null
@JvmField
var group: String? = null
@JvmField
var type: String? = null
@JvmField
var value: String? = null
@JvmField
var _updatedAt: Long = 0
@JvmField
var meta: String? = null
}
\ No newline at end of file
package chat.rocket.android.app.migration.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class RealmSession : RealmObject() {
@JvmField
@PrimaryKey
var sessionId: Int = 0 //only 0 is used!
@JvmField
var token: String? = null
@JvmField
var tokenVerified: Boolean = false
@JvmField
var error: String? = null
}
\ No newline at end of file
package chat.rocket.android.app.migration.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class RealmSettings : RealmObject() {
@PrimaryKey
@JvmField
var id: String? = null
@JvmField
var preferences: RealmPreferences? = null
}
\ No newline at end of file
package chat.rocket.android.app.migration.model
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class RealmUser : RealmObject() {
companion object {
const val ID = "_id"
const val NAME = "name"
const val USERNAME = "username"
const val STATUS = "status"
const val UTC_OFFSET = "utcOffset"
const val EMAILS = "emails"
const val SETTINGS = "settings"
const val STATUS_ONLINE = "online"
const val STATUS_BUSY = "busy"
const val STATUS_AWAY = "away"
const val STATUS_OFFLINE = "offline"
}
@PrimaryKey
@JvmField
var _id: String? = null
@JvmField
var name: String? = null
@JvmField
var username: String? = null
@JvmField
var status: String? = null
@JvmField
var utcOffset: Double = 0.toDouble()
@JvmField
var emails: RealmList<RealmEmail>? = null
@JvmField
var settings: RealmSettings? = null
}
\ No newline at end of file
package chat.rocket.android.authentication.login.di package chat.rocket.android.authentication.login.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.login.presentation.LoginView import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.authentication.login.ui.LoginFragment import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
...@@ -10,20 +10,26 @@ import dagger.Provides ...@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class LoginFragmentModule { class LoginFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun loginView(frag: LoginFragment): LoginView { fun loginView(frag: LoginFragment): LoginView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner { fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.authentication.login.di package chat.rocket.android.authentication.login.di
import chat.rocket.android.authentication.login.ui.LoginFragment import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
@Module abstract class LoginFragmentProvider { @Module abstract class LoginFragmentProvider {
@ContributesAndroidInjector(modules = [LoginFragmentModule::class]) @ContributesAndroidInjector(modules = [LoginFragmentModule::class])
@PerFragment
abstract fun provideLoginFragment(): LoginFragment abstract fun provideLoginFragment(): LoginFragment
} }
\ No newline at end of file
...@@ -6,7 +6,7 @@ import android.content.Intent ...@@ -6,7 +6,7 @@ import android.content.Intent
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
......
package chat.rocket.android.authentication.registerusername.di package chat.rocket.android.authentication.registerusername.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernameView import chat.rocket.android.authentication.registerusername.presentation.RegisterUsernameView
import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
...@@ -10,20 +10,26 @@ import dagger.Provides ...@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class RegisterUsernameFragmentModule { class RegisterUsernameFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun registerUsernameView(frag: RegisterUsernameFragment): RegisterUsernameView { fun registerUsernameView(frag: RegisterUsernameFragment): RegisterUsernameView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: RegisterUsernameFragment): LifecycleOwner { fun provideLifecycleOwner(frag: RegisterUsernameFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.authentication.registerusername.di package chat.rocket.android.authentication.registerusername.di
import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class RegisterUsernameFragmentProvider { abstract class RegisterUsernameFragmentProvider {
@ContributesAndroidInjector(modules = [RegisterUsernameFragmentModule::class]) @ContributesAndroidInjector(modules = [RegisterUsernameFragmentModule::class])
@PerFragment
abstract fun provideRegisterUsernameFragment(): RegisterUsernameFragment abstract fun provideRegisterUsernameFragment(): RegisterUsernameFragment
} }
\ No newline at end of file
...@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.registerusername.ui ...@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.registerusername.ui
import DrawableHelper import DrawableHelper
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
package chat.rocket.android.authentication.resetpassword.di package chat.rocket.android.authentication.resetpassword.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView import chat.rocket.android.authentication.resetpassword.presentation.ResetPasswordView
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
...@@ -10,20 +10,26 @@ import dagger.Provides ...@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class ResetPasswordFragmentModule { class ResetPasswordFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun resetPasswordView(frag: ResetPasswordFragment): ResetPasswordView { fun resetPasswordView(frag: ResetPasswordFragment): ResetPasswordView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: ResetPasswordFragment): LifecycleOwner { fun provideLifecycleOwner(frag: ResetPasswordFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.authentication.resetpassword.di package chat.rocket.android.authentication.resetpassword.di
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class ResetPasswordFragmentProvider { abstract class ResetPasswordFragmentProvider {
@ContributesAndroidInjector(modules = [ResetPasswordFragmentModule::class]) @ContributesAndroidInjector(modules = [ResetPasswordFragmentModule::class])
@PerFragment
abstract fun provideResetPasswordFragment(): ResetPasswordFragment abstract fun provideResetPasswordFragment(): ResetPasswordFragment
} }
\ No newline at end of file
...@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.resetpassword.ui ...@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.resetpassword.ui
import DrawableHelper import DrawableHelper
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
package chat.rocket.android.authentication.server.di package chat.rocket.android.authentication.server.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.server.presentation.ServerView import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.authentication.server.ui.ServerFragment import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
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.experimental.Job
...@@ -12,16 +13,23 @@ import kotlinx.coroutines.experimental.Job ...@@ -12,16 +13,23 @@ import kotlinx.coroutines.experimental.Job
class ServerFragmentModule { class ServerFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun serverView(frag: ServerFragment): ServerView { fun serverView(frag: ServerFragment): ServerView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: ServerFragment): LifecycleOwner { fun provideLifecycleOwner(frag: ServerFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.authentication.server.di package chat.rocket.android.authentication.server.di
import chat.rocket.android.authentication.server.ui.ServerFragment import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class ServerFragmentProvider { abstract class ServerFragmentProvider {
@ContributesAndroidInjector(modules = [ServerFragmentModule::class]) @ContributesAndroidInjector(modules = [ServerFragmentModule::class])
@PerFragment
abstract fun provideServerFragment(): ServerFragment abstract fun provideServerFragment(): ServerFragment
} }
\ No newline at end of file
...@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.server.ui ...@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.server.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
package chat.rocket.android.authentication.signup.di package chat.rocket.android.authentication.signup.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.signup.presentation.SignupView import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.authentication.signup.ui.SignupFragment import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
...@@ -10,20 +10,26 @@ import dagger.Provides ...@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class SignupFragmentModule { class SignupFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun signupView(frag: SignupFragment): SignupView { fun signupView(frag: SignupFragment): SignupView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner { fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.authentication.signup.di package chat.rocket.android.authentication.signup.di
import chat.rocket.android.authentication.signup.ui.SignupFragment import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class SignupFragmentProvider { abstract class SignupFragmentProvider {
@ContributesAndroidInjector(modules = [SignupFragmentModule::class]) @ContributesAndroidInjector(modules = [SignupFragmentModule::class])
@PerFragment
abstract fun provideSignupFragment(): SignupFragment abstract fun provideSignupFragment(): SignupFragment
} }
\ No newline at end of file
...@@ -5,7 +5,7 @@ import android.app.Activity ...@@ -5,7 +5,7 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
......
package chat.rocket.android.authentication.twofactor.di package chat.rocket.android.authentication.twofactor.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
...@@ -10,20 +10,26 @@ import dagger.Provides ...@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class TwoFAFragmentModule { class TwoFAFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun loginView(frag: TwoFAFragment): TwoFAView { fun loginView(frag: TwoFAFragment): TwoFAView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner { fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.authentication.twofactor.di package chat.rocket.android.authentication.twofactor.di
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
@Module abstract class TwoFAFragmentProvider { @Module abstract class TwoFAFragmentProvider {
@ContributesAndroidInjector(modules = [TwoFAFragmentModule::class]) @ContributesAndroidInjector(modules = [TwoFAFragmentModule::class])
@PerFragment
abstract fun provideTwoFAFragment(): TwoFAFragment abstract fun provideTwoFAFragment(): TwoFAFragment
} }
...@@ -4,7 +4,7 @@ import DrawableHelper ...@@ -4,7 +4,7 @@ import DrawableHelper
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
...@@ -3,8 +3,8 @@ package chat.rocket.android.authentication.ui ...@@ -3,8 +3,8 @@ package chat.rocket.android.authentication.ui
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 android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.domain.model.getLoginDeepLinkInfo import chat.rocket.android.authentication.domain.model.getLoginDeepLinkInfo
......
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.support.annotation.IntDef import androidx.annotation.IntDef
const val PEOPLE = 0 const val PEOPLE = 0
const val ROOMS = 1 const val ROOMS = 1
......
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.MenuItem import android.view.MenuItem
import android.view.ViewGroup import android.view.ViewGroup
import chat.rocket.android.R import chat.rocket.android.R
......
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.View import android.view.View
import chat.rocket.android.R import chat.rocket.android.R
......
package chat.rocket.android.chatroom.adapter package chat.rocket.android.chatroom.adapter
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
package chat.rocket.android.chatroom.di package chat.rocket.android.chatroom.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.presentation.ChatRoomView import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.ui.ChatRoomFragment import chat.rocket.android.chatroom.ui.ChatRoomFragment
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
...@@ -12,6 +12,10 @@ import kotlinx.coroutines.experimental.Job ...@@ -12,6 +12,10 @@ import kotlinx.coroutines.experimental.Job
@Module @Module
class ChatRoomFragmentModule { class ChatRoomFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides @Provides
@PerFragment @PerFragment
fun chatRoomView(frag: ChatRoomFragment): ChatRoomView { fun chatRoomView(frag: ChatRoomFragment): ChatRoomView {
......
...@@ -7,8 +7,8 @@ import dagger.Module ...@@ -7,8 +7,8 @@ import dagger.Module
import dagger.Provides import dagger.Provides
@Module @Module
@PerActivity
class ChatRoomModule { class ChatRoomModule {
@Provides @Provides
@PerActivity
fun provideChatRoomNavigator(activity: ChatRoomActivity) = ChatRoomNavigator(activity) fun provideChatRoomNavigator(activity: ChatRoomActivity) = ChatRoomNavigator(activity)
} }
\ No newline at end of file
...@@ -445,7 +445,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -445,7 +445,7 @@ class ChatRoomPresenter @Inject constructor(
is RoomType.DirectMessage -> "direct" is RoomType.DirectMessage -> "direct"
is RoomType.PrivateGroup -> "group" is RoomType.PrivateGroup -> "group"
is RoomType.Channel -> "channel" is RoomType.Channel -> "channel"
is RoomType.Livechat -> "livechat" is RoomType.LiveChat -> "livechat"
else -> "custom" else -> "custom"
} }
view.showReplyingAction( view.showReplyingAction(
...@@ -652,7 +652,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -652,7 +652,7 @@ class ChatRoomPresenter @Inject constructor(
try { try {
val chatRooms = chatRoomsInteractor.getAll(currentServer) val chatRooms = chatRoomsInteractor.getAll(currentServer)
.filterNot { .filterNot {
it.type is RoomType.DirectMessage || it.type is RoomType.Livechat it.type is RoomType.DirectMessage || it.type is RoomType.LiveChat
} }
.map { chatRoom -> .map { chatRoom ->
val name = chatRoom.name val name = chatRoom.name
......
package chat.rocket.android.chatroom.ui package chat.rocket.android.chatroom.ui
import android.support.design.widget.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
import android.support.v4.view.ViewCompat import androidx.core.view.ViewCompat
import android.text.Spannable import android.text.Spannable
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.view.LayoutInflater import android.view.LayoutInflater
......
...@@ -4,8 +4,8 @@ import DrawableHelper ...@@ -4,8 +4,8 @@ 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 android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import androidx.core.view.isVisible import androidx.core.view.isVisible
import chat.rocket.android.R import chat.rocket.android.R
......
...@@ -8,11 +8,11 @@ import android.content.Intent ...@@ -8,11 +8,11 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.support.annotation.DrawableRes import androidx.annotation.DrawableRes
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.view.* import android.view.*
import androidx.core.text.bold import androidx.core.text.bold
...@@ -519,7 +519,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -519,7 +519,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun onEmojiAdded(emoji: Emoji) { override fun onEmojiAdded(emoji: Emoji) {
val cursorPosition = text_message.selectionStart val cursorPosition = text_message.selectionStart
if (cursorPosition > -1) { if (cursorPosition > -1) {
text_message.text.insert(cursorPosition, EmojiParser.parse(emoji.shortname)) text_message.text?.insert(cursorPosition, EmojiParser.parse(emoji.shortname))
text_message.setSelection(cursorPosition + emoji.unicode.length) text_message.setSelection(cursorPosition + emoji.unicode.length)
} }
} }
...@@ -527,7 +527,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -527,7 +527,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun onNonEmojiKeyPressed(keyCode: Int) { override fun onNonEmojiKeyPressed(keyCode: Int) {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_BACK -> with(text_message) { KeyEvent.KEYCODE_BACK -> with(text_message) {
if (selectionStart > 0) text.delete(selectionStart - 1, selectionStart) if (selectionStart > 0) text?.delete(selectionStart - 1, selectionStart)
} }
else -> throw IllegalArgumentException("pressed key not expected") else -> throw IllegalArgumentException("pressed key not expected")
} }
...@@ -616,13 +616,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -616,13 +616,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupRecyclerView() { private fun setupRecyclerView() {
// Initialize the endlessRecyclerViewScrollListener so we don't NPE at onDestroyView // Initialize the endlessRecyclerViewScrollListener so we don't NPE at onDestroyView
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) val linearLayoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
linearLayoutManager.stackFromEnd = true linearLayoutManager.stackFromEnd = true
recycler_view.layoutManager = linearLayoutManager recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator() recycler_view.itemAnimator = DefaultItemAnimator()
endlessRecyclerViewScrollListener = object : endlessRecyclerViewScrollListener = object :
EndlessRecyclerViewScrollListener(recycler_view.layoutManager as LinearLayoutManager) { EndlessRecyclerViewScrollListener(recycler_view.layoutManager as LinearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) { override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView) {
presenter.loadMessages(chatRoomId, chatRoomType, page * 30L) presenter.loadMessages(chatRoomId, chatRoomType, page * 30L)
} }
} }
...@@ -641,7 +641,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -641,7 +641,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if (isChatRoomReadOnly && !canPost) { if (isChatRoomReadOnly && !canPost) {
text_room_is_read_only.setVisible(true) text_room_is_read_only.setVisible(true)
input_container.setVisible(false) input_container.setVisible(false)
} else if (!isSubscribed && roomTypeOf(chatRoomType) != RoomType.DIRECT_MESSAGE) { } else if (!isSubscribed && roomTypeOf(chatRoomType) !is RoomType.DirectMessage) {
input_container.setVisible(false) input_container.setVisible(false)
button_join_chat.setVisible(true) button_join_chat.setVisible(true)
button_join_chat.setOnClickListener { presenter.joinChat(chatRoomId) } button_join_chat.setOnClickListener { presenter.joinChat(chatRoomId) }
......
package chat.rocket.android.chatroom.ui.bottomsheet package chat.rocket.android.chatroom.ui.bottomsheet
import android.support.design.widget.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.MenuItem import android.view.MenuItem
import ru.whalemare.sheetmenu.SheetMenu import ru.whalemare.sheetmenu.SheetMenu
import ru.whalemare.sheetmenu.adapter.MenuAdapter import ru.whalemare.sheetmenu.adapter.MenuAdapter
...@@ -11,7 +11,7 @@ class BottomSheetMenu(adapter: MenuAdapter) : SheetMenu(adapter = adapter) { ...@@ -11,7 +11,7 @@ class BottomSheetMenu(adapter: MenuAdapter) : SheetMenu(adapter = adapter) {
override fun processRecycler(recycler: RecyclerView, dialog: BottomSheetDialog) { override fun processRecycler(recycler: RecyclerView, dialog: BottomSheetDialog) {
if (layoutManager == null) { if (layoutManager == null) {
layoutManager = LinearLayoutManager(recycler.context, LinearLayoutManager.VERTICAL, false) layoutManager = LinearLayoutManager(recycler.context)
} }
// Superclass SheetMenu adapter property is nullable MenuAdapter? but this class enforces // Superclass SheetMenu adapter property is nullable MenuAdapter? but this class enforces
......
package chat.rocket.android.chatroom.ui.bottomsheet.adapter package chat.rocket.android.chatroom.ui.bottomsheet.adapter
import android.graphics.Color import android.graphics.Color
import android.support.annotation.ColorInt import androidx.annotation.ColorInt
import android.support.annotation.IdRes import androidx.annotation.IdRes
import android.util.SparseIntArray import android.util.SparseIntArray
import android.view.MenuItem import android.view.MenuItem
import chat.rocket.android.R import chat.rocket.android.R
......
...@@ -4,7 +4,7 @@ import DateTimeHelper ...@@ -4,7 +4,7 @@ import DateTimeHelper
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
......
package chat.rocket.android.chatrooms.adapter
data class HeaderItemHolder(override val data: String) : ItemHolder<String>
\ No newline at end of file
package chat.rocket.android.chatrooms.adapter
import android.view.View
import kotlinx.android.synthetic.main.item_chatroom_header.view.*
class HeaderViewHolder(itemView: View) : ViewHolder<HeaderItemHolder>(itemView) {
override fun bindViews(data: HeaderItemHolder) {
with(itemView) {
text_chatroom_header.text = data.data
}
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.adapter
interface ItemHolder<T> {
val data: T
}
\ No newline at end of file
package chat.rocket.android.chatrooms.adapter
import chat.rocket.android.chatrooms.adapter.model.Room
data class RoomItemHolder(override val data: Room) : ItemHolder<Room>
\ No newline at end of file
package chat.rocket.android.chatrooms.adapter
import android.app.Application
import android.text.SpannableStringBuilder
import androidx.core.content.ContextCompat
import androidx.core.text.bold
import androidx.core.text.color
import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.model.Room
import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.checkIfMyself
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.showLastMessage
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.date
import chat.rocket.android.util.extensions.localDateTime
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.model.userStatusOf
class RoomMapper(private val context: Application,
private val settings: PublicSettings,
private val localRepository: LocalRepository,
private val serverUrl: String) {
private val nameUnreadColor = ContextCompat.getColor(context, R.color.colorPrimaryText)
private val nameColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
private val dateUnreadColor = ContextCompat.getColor(context, R.color.colorAccent)
private val dateColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
private val messageUnreadColor = ContextCompat.getColor(context, android.R.color.primary_text_light)
private val messageColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
fun map(rooms: List<ChatRoom>, grouped: Boolean): List<ItemHolder<*>> {
val list = ArrayList<ItemHolder<*>>(rooms.size + 4)
var lastType: String? = null
rooms.forEach { room ->
if (grouped && lastType != room.chatRoom.type) {
list.add(HeaderItemHolder(roomType(room.chatRoom.type)))
}
list.add(RoomItemHolder(map(room)))
lastType = room.chatRoom.type
}
return list
}
fun map(chatRoom: ChatRoom): Room {
return with(chatRoom.chatRoom) {
val isUnread = alert || unread > 0
val type = roomTypeOf(type)
val status = chatRoom.status?.let { userStatusOf(it) }
val roomName = mapName(name, chatRoom.userFullname, isUnread)
val timestamp = mapDate(lastMessageTimestamp ?: updatedAt, isUnread)
val avatar = if (type is RoomType.DirectMessage) {
serverUrl.avatarUrl(name)
} else {
serverUrl.avatarUrl(name, isGroupOrChannel = true)
}
val unread = mapUnread(unread)
val lastMessage = mapLastMessage(chatRoom.lastMessageUserName,
chatRoom.lastMessageUserFullName, lastMessageText, isUnread)
Room(
id = id,
name = roomName,
type = type,
avatar = avatar,
date = timestamp,
unread = unread,
alert = isUnread,
lastMessage = lastMessage,
status = status
)
}
}
private fun roomType(type: String): String {
val resources = context.resources
return when (type) {
RoomType.CHANNEL -> resources.getString(R.string.header_channel)
RoomType.PRIVATE_GROUP -> resources.getString(R.string.header_private_groups)
RoomType.DIRECT_MESSAGE -> resources.getString(R.string.header_direct_messages)
RoomType.LIVECHAT -> resources.getString(R.string.header_live_chats)
else -> resources.getString(R.string.header_unknown)
}
}
private fun mapLastMessage(name: String?, fullName: String?, text: String?, unread: Boolean): CharSequence? {
return if (!settings.showLastMessage()) {
null
} else if (name != null && text != null) {
val user = if (localRepository.checkIfMyself(name)) {
"${context.getString(R.string.msg_you)}: "
} else {
"${mapName(name, fullName, unread)}: "
}
val color = if (unread) messageUnreadColor else messageColor
SpannableStringBuilder()
.color(color) {
bold { append(user) }
append(text)
}
} else {
context.getText(R.string.msg_no_messages_yet)
}
}
private fun mapName(name: String, fullName: String?, unread: Boolean): CharSequence {
val roomName = if (settings.useRealName()) {
fullName ?: name
} else {
name
}
val color = if (unread) nameUnreadColor else nameColor
return SpannableStringBuilder()
.color(color) {
append(roomName)
}
}
private fun mapUnread(unread: Long): String? {
return when(unread) {
0L -> null
in 1..99 -> unread.toString()
else -> context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
}
}
private fun mapDate(date: Long?, unread: Boolean): CharSequence? {
return date?.localDateTime()?.date(context)?.let {
val color = if (unread) dateUnreadColor else dateColor
SpannableStringBuilder().color(color) {
append(it)
}
}
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.adapter
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.UserStatus
import kotlinx.android.synthetic.main.item_chat.view.*
import kotlinx.android.synthetic.main.unread_messages_badge.view.*
class RoomViewHolder(itemView: View, private val listener: (String) -> Unit) : ViewHolder<RoomItemHolder>(itemView) {
private val resources: Resources = itemView.resources
private val channelUnread: Drawable = resources.getDrawable(R.drawable.ic_hashtag_black_12dp)
private val channel: Drawable = resources.getDrawable(R.drawable.ic_hashtag_12dp)
private val groupUnread: Drawable = resources.getDrawable(R.drawable.ic_lock_black_12_dp)
private val group: Drawable = resources.getDrawable(R.drawable.ic_lock_12_dp)
private val online: Drawable = resources.getDrawable(R.drawable.ic_status_online_12dp)
private val away: Drawable = resources.getDrawable(R.drawable.ic_status_away_12dp)
private val busy: Drawable = resources.getDrawable(R.drawable.ic_status_busy_12dp)
private val offline: Drawable = resources.getDrawable(R.drawable.ic_status_invisible_12dp)
override fun bindViews(data: RoomItemHolder) {
val room = data.data
with(itemView) {
image_avatar.setImageURI(room.avatar)
text_chat_name.text = room.name
if (room.lastMessage != null) {
text_last_message.isVisible = true
text_last_message.text = room.lastMessage
} else {
text_last_message.isGone = true
}
if (room.date != null) {
text_last_message_date_time.isVisible = true
text_last_message_date_time.text = room.date
} else {
text_last_message_date_time.isGone = true
}
if (room.unread != null) {
text_total_unread_messages.isVisible = true
text_total_unread_messages.text = room.unread
} else {
text_total_unread_messages.isGone = true
}
if (room.status != null && room.type is RoomType.DirectMessage) {
image_chat_icon.setImageDrawable(getStatusDrawable(room.status))
} else {
image_chat_icon.setImageDrawable(getRoomDrawable(room.type, room.alert))
}
setOnClickListener {
listener(room.id)
}
}
}
private fun getRoomDrawable(type: RoomType, alert: Boolean): Drawable? {
return when(type) {
is RoomType.Channel -> if (alert) channelUnread else channel
is RoomType.PrivateGroup -> if (alert) groupUnread else group
else -> null
}
}
private fun getStatusDrawable(status: UserStatus): Drawable {
return when(status) {
is UserStatus.Online -> online
is UserStatus.Away -> away
is UserStatus.Busy -> busy
else -> offline
}
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.adapter
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.util.extensions.inflate
class RoomsAdapter(private val listener: (String) -> Unit) : RecyclerView.Adapter<ViewHolder<*>>() {
init {
setHasStableIds(true)
}
var values: List<ItemHolder<*>> = ArrayList(0)
set(items) {
field = items
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> {
if (viewType == 0) {
val view = parent.inflate(R.layout.item_chat)
return RoomViewHolder(view, listener)
} else if (viewType == 1) {
val view = parent.inflate(R.layout.item_chatroom_header)
return HeaderViewHolder(view)
}
throw IllegalStateException("View type must be either Room or Header")
}
override fun getItemCount() = values.size
override fun getItemId(position: Int): Long {
val item = values[position]
return when(item) {
is HeaderItemHolder -> item.data.hashCode().toLong()
is RoomItemHolder -> item.data.id.hashCode().toLong()
else -> throw IllegalStateException("View type must be either Room or Header")
}
}
override fun getItemViewType(position: Int): Int {
return when(values[position]) {
is RoomItemHolder -> 0
is HeaderItemHolder -> 1
else -> throw IllegalStateException("View type must be either Room or Header")
}
}
override fun onBindViewHolder(holder: ViewHolder<*>, position: Int) {
if (holder is RoomViewHolder) {
holder.bind(values[position] as RoomItemHolder)
} else if (holder is HeaderViewHolder) {
holder.bind(values[position] as HeaderItemHolder)
}
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.adapter
import android.view.View
import androidx.recyclerview.widget.RecyclerView
abstract class ViewHolder<T : ItemHolder<*>>(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
var data: T? = null
fun bind(data: T) {
this.data = data
bindViews(data)
}
abstract fun bindViews(data: T)
}
\ No newline at end of file
package chat.rocket.android.chatrooms.adapter.model
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.UserStatus
data class Room(
val id: String,
val type: RoomType,
val name: CharSequence,
val avatar: String,
val date: CharSequence?,
val unread: String?,
val alert: Boolean,
val lastMessage: CharSequence?,
val status: UserStatus?
)
\ No newline at end of file
package chat.rocket.android.chatrooms.di package chat.rocket.android.chatrooms.di
import android.arch.lifecycle.LifecycleOwner import android.app.Application
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatrooms.adapter.RoomMapper
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
import chat.rocket.android.chatrooms.presentation.ChatRoomsView import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.core.RocketChatClient
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import javax.inject.Named
@Module @Module
@PerFragment
class ChatRoomsFragmentModule { class ChatRoomsFragmentModule {
@Provides @Provides
@PerFragment
fun chatRoomsView(frag: ChatRoomsFragment): ChatRoomsView { fun chatRoomsView(frag: ChatRoomsFragment): ChatRoomsView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: ChatRoomsFragment): LifecycleOwner { fun provideLifecycleOwner(frag: ChatRoomsFragment): LifecycleOwner {
return frag return frag
} }
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides
@PerFragment
fun provideRocketChatClient(factory: RocketChatClientFactory,
@Named("currentServer") currentServer: String): RocketChatClient {
return factory.create(currentServer)
}
@Provides
@PerFragment
fun provideDatabaseManager(factory: DatabaseManagerFactory,
@Named("currentServer") currentServer: String): DatabaseManager {
return factory.create(currentServer)
}
@Provides
@PerFragment
fun provideChatRoomDao(manager: DatabaseManager): ChatRoomDao = manager.chatRoomDao()
@Provides
@PerFragment
fun provideConnectionManager(factory: ConnectionManagerFactory,
@Named("currentServer") currentServer: String): ConnectionManager {
return factory.create(currentServer)
}
@Provides
@PerFragment
fun provideFetchChatRoomsInteractor(client: RocketChatClient, dbManager: DatabaseManager): FetchChatRoomsInteractor {
return FetchChatRoomsInteractor(client, dbManager)
}
@Provides
@PerFragment
fun providePublicSettings(repository: SettingsRepository,
@Named("currentServer") currentServer: String): PublicSettings {
return repository.get(currentServer)
}
@Provides
@PerFragment
fun provideRoomMapper(context: Application,
repository: SettingsRepository,
localRepository: LocalRepository,
@Named("currentServer") serverUrl: String): RoomMapper {
return RoomMapper(context, repository.get(serverUrl), localRepository, serverUrl)
}
} }
\ No newline at end of file
package chat.rocket.android.chatrooms.domain
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.util.retryIO
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.userId
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
class FetchChatRoomsInteractor(
private val client: RocketChatClient,
private val dbManager: DatabaseManager
) {
suspend fun refreshChatRooms() {
launch(CommonPool) {
try {
val rooms = retryIO("fetch chatRooms", times = 10,
initialDelay = 200, maxDelay = 2000) {
client.chatRooms().update.map { room ->
mapChatRoom(room)
}
}
Timber.d("Refreshing rooms: $rooms")
dbManager.insert(rooms)
} catch (ex: Exception) {
Timber.d(ex, "Error getting chatrooms")
}
}
}
private suspend fun mapChatRoom(room: ChatRoom): ChatRoomEntity {
with(room) {
val userId = userId()
if (userId != null && dbManager.findUser(userId) == null) {
Timber.d("Missing user, inserting: $userId")
dbManager.insert(UserEntity(userId))
}
lastMessage?.sender?.let { user ->
user.id?.let { id ->
if (dbManager.findUser(id) == null) {
Timber.d("Missing last message user, inserting: $id")
dbManager.insert(UserEntity(id, user.username, user.name))
}
}
}
user?.id?.let { id ->
if (dbManager.findUser(id) == null) {
Timber.d("Missing owner user, inserting: $id")
dbManager.insert(UserEntity(id, user?.username, user?.name))
}
}
return ChatRoomEntity(
id = id,
subscriptionId = subscriptionId,
type = type.toString(),
name = name,
fullname = fullName,
userId = userId,
ownerId = user?.id,
readonly = readonly,
isDefault = default,
favorite = favorite,
open = open,
alert = alert,
unread = unread,
userMentions = userMentions,
groupMentions = groupMentions,
updatedAt = updatedAt,
timestamp = timestamp,
lastSeen = lastSeen,
lastMessageText = lastMessage?.message,
lastMessageUserId = lastMessage?.sender?.id,
lastMessageTimestamp = lastMessage?.timestamp
)
}
}
}
\ No newline at end of file
package chat.rocket.android.chatrooms.infrastructure
import androidx.lifecycle.LiveData
import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.model.ChatRoom
import javax.inject.Inject
class ChatRoomsRepository @Inject constructor(val dao: ChatRoomDao){
fun getChatRooms(order: Order): LiveData<List<ChatRoom>> {
return when(order) {
Order.ACTIVITY -> dao.getAll()
Order.GROUPED_ACTIVITY -> dao.getAllGrouped()
Order.NAME -> dao.getAllAlphabetically()
Order.GROUPED_NAME -> dao.getAllAlphabeticallyGrouped()
}
}
enum class Order {
ACTIVITY,
GROUPED_ACTIVITY,
NAME,
GROUPED_NAME,
}
}
fun ChatRoomsRepository.Order.isGrouped(): Boolean = this == ChatRoomsRepository.Order.GROUPED_ACTIVITY
|| this == ChatRoomsRepository.Order.GROUPED_NAME
\ No newline at end of file
package chat.rocket.android.chatrooms.presentation package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.ChatRoomsSortOrder
import chat.rocket.android.helper.Constants
import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.helper.UserHelper import chat.rocket.android.helper.UserHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.main.presentation.MainNavigator import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.server.domain.ChatRoomsInteractor
import chat.rocket.android.server.domain.GetActiveUsersInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.JobSchedulerInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveActiveUsersInteractor
import chat.rocket.android.server.domain.SaveChatRoomsInteractor
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.hasShowLastMessage
import chat.rocket.android.server.domain.showLastMessage
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManager import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.chatRooms
import chat.rocket.android.server.infraestructure.state
import chat.rocket.android.util.extensions.launchUI import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.common.model.SimpleUser
import chat.rocket.common.model.User import chat.rocket.common.model.User
import chat.rocket.common.util.ifNull import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.model.Subscription
import chat.rocket.core.internal.realtime.createDirectMessage import chat.rocket.core.internal.realtime.createDirectMessage
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.realtime.socket.model.StreamMessage
import chat.rocket.core.internal.realtime.socket.model.Type
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.permissions
import chat.rocket.core.internal.rest.spotlight
import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.KProperty1 import javax.inject.Named
class ChatRoomsPresenter @Inject constructor( class ChatRoomsPresenter @Inject constructor(
private val view: ChatRoomsView, private val view: ChatRoomsView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: MainNavigator, private val navigator: MainNavigator,
private val serverInteractor: GetCurrentServerInteractor, @Named("currentServer") private val currentServer: String,
private val chatRoomsInteractor: ChatRoomsInteractor, manager: ConnectionManager,
private val saveChatRoomsInteractor: SaveChatRoomsInteractor,
private val saveActiveUsersInteractor: SaveActiveUsersInteractor,
private val getActiveUsersInteractor: GetActiveUsersInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val viewModelMapper: ViewModelMapper,
private val jobSchedulerInteractor: JobSchedulerInteractor,
private val permissionsInteractor: PermissionsInteractor,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val userHelper: UserHelper, private val userHelper: UserHelper,
settingsRepository: SettingsRepository, settingsRepository: SettingsRepository
factory: ConnectionManagerFactory
) { ) {
private val manager: ConnectionManager = factory.create(serverInteractor.get()!!)
private val currentServer = serverInteractor.get()!!
private val client = manager.client private val client = manager.client
private var reloadJob: Deferred<List<ChatRoom>>? = null
private val settings = settingsRepository.get(currentServer) private val settings = settingsRepository.get(currentServer)
private val stateChannel = Channel<State>()
private val subscriptionsChannel = Channel<StreamMessage<BaseRoom>>()
private val activeUserChannel = Channel<User>()
private var lastState = manager.state
fun loadChatRooms() { fun loadChatRoom(chatRoom: chat.rocket.android.db.model.ChatRoom) {
refreshSettingsInteractor.refreshAsync(currentServer) with(chatRoom.chatRoom) {
launchUI(strategy) { val isDirectMessage = roomTypeOf(type) is RoomType.DirectMessage
view.showLoading() val roomName = if (isDirectMessage
subscribeStatusChange() && fullname != null
try { && settings.useRealName()) {
// If we still don't have 'Store_Last_Message' setting, refresh the settings fullname!!
if (!settings.hasShowLastMessage()) { } else {
refreshSettingsInteractor.refresh(currentServer) name
}
view.updateChatRooms(getUserChatRooms())
val permissions = retryIO { client.permissions() }
permissionsInteractor.saveAll(permissions)
} catch (ex: RocketChatException) {
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
Timber.e(ex)
} finally {
view.hideLoading()
} }
subscribeActiveUsers()
subscribeRoomUpdates()
}
}
fun loadChatRoom(chatRoom: ChatRoom) { launchUI(strategy) {
val isDirectMessage = chatRoom.type is RoomType.DirectMessage val myself = getCurrentUser()
val roomName = if (isDirectMessage if (myself?.username == null) {
&& chatRoom.fullName != null view.showMessage(R.string.msg_generic_error)
&& settings.useRealName()) {
chatRoom.fullName!!
} else {
chatRoom.name
}
launchUI(strategy) {
val myself = getCurrentUser()
if (myself?.username == null) {
view.showMessage(R.string.msg_generic_error)
} else {
val id = if (isDirectMessage && !chatRoom.open) {
retryIO("createDirectMessage(${chatRoom.name})") {
client.createDirectMessage(chatRoom.name)
}
val fromTo = mutableListOf(myself.id, chatRoom.id).apply {
sort()
}
fromTo.joinToString("")
} else { } else {
chatRoom.id val id = if (isDirectMessage && !open) {
retryIO("createDirectMessage($name)") {
client.createDirectMessage(name)
}
val fromTo = mutableListOf(myself.id, id).apply {
sort()
}
fromTo.joinToString("")
} else {
id
}
val isChatRoomOwner = ownerId == myself.id || isDirectMessage
navigator.toChatRoom(id, roomName, type, readonly ?: false,
lastSeen ?: -1, open, isChatRoomOwner)
} }
val isChatRoomOwner = chatRoom.user?.username == myself.username || isDirectMessage
navigator.toChatRoom(id, roomName,
chatRoom.type.toString(), chatRoom.readonly ?: false,
chatRoom.lastSeen ?: -1,
chatRoom.open, isChatRoomOwner)
} }
} }
} }
...@@ -166,496 +89,4 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -166,496 +89,4 @@ class ChatRoomsPresenter @Inject constructor(
} }
return null return null
} }
/**
* Gets a [ChatRoom] list from local repository.
* ChatRooms returned are filtered by name.
*/
fun chatRoomsByName(name: String) {
val currentServer = serverInteractor.get()!!
launchUI(strategy) {
try {
val roomList = chatRoomsInteractor.getAllByName(currentServer, name)
if (roomList.isEmpty()) {
val (users, rooms) = retryIO("spotlight($name)") {
client.spotlight(name)
}
val chatRoomsCombined = mutableListOf<ChatRoom>()
chatRoomsCombined.addAll(usersToChatRooms(users))
chatRoomsCombined.addAll(roomsToChatRooms(rooms))
val chatRoomsWithPreview = getChatRoomsWithPreviews(chatRoomsCombined)
val chatRoomsWithStatus = getChatRoomWithStatus(chatRoomsWithPreview)
view.updateChatRooms(chatRoomsWithStatus)
} else {
val chatRoomsWithPreview = getChatRoomsWithPreviews(roomList)
val chatRoomsWithStatus = getChatRoomWithStatus(chatRoomsWithPreview)
view.updateChatRooms(chatRoomsWithStatus)
}
} catch (ex: RocketChatException) {
Timber.e(ex)
}
}
}
// In the first time it will not come with the users status, but after called by the
// [reloadRooms] function may be with.
private suspend fun getUserChatRooms(): List<ChatRoom> {
val chatRooms = retryIO("chatRooms") { manager.chatRooms().update }
val chatRoomsWithPreview = getChatRoomsWithPreviews(chatRooms)
val chatRoomsWithUserStatus = getChatRoomWithStatus(chatRoomsWithPreview)
val sortedRooms = sortRooms(chatRoomsWithUserStatus)
Timber.d("Loaded rooms: ${sortedRooms.size}")
saveChatRoomsInteractor.save(currentServer, sortedRooms)
return sortedRooms
}
private fun usersToChatRooms(users: List<User>): List<ChatRoom> {
return users.map {
ChatRoom(
id = it.id,
type = RoomType.DIRECT_MESSAGE,
user = SimpleUser(username = it.username, name = it.name, id = null),
status = if (it.name != null) {
getActiveUsersInteractor.getActiveUserByUsername(currentServer, it.name!!)
?.status
} else {
null
},
name = it.username ?: it.name ?: "",
fullName = it.name,
readonly = false,
updatedAt = null,
timestamp = null,
lastSeen = null,
topic = null,
description = null,
announcement = null,
default = false,
open = false,
alert = false,
unread = 0L,
userMentions = null,
groupMentions = 0L,
lastMessage = null,
client = client,
broadcast = false
)
}
}
private fun roomsToChatRooms(rooms: List<Room>): List<ChatRoom> {
return rooms.map {
ChatRoom(
id = it.id,
type = it.type,
user = it.user,
status = if (it.name != null) {
getActiveUsersInteractor.getActiveUserByUsername(currentServer, it.name!!)
?.status
} else {
null
},
name = it.name ?: "",
fullName = it.fullName,
readonly = it.readonly,
updatedAt = it.updatedAt,
timestamp = null,
lastSeen = null,
topic = it.topic,
description = it.description,
announcement = it.announcement,
default = false,
open = false,
alert = false,
unread = 0L,
userMentions = null,
groupMentions = 0L,
lastMessage = it.lastMessage,
client = client,
broadcast = it.broadcast
)
}
}
fun updateSortedChatRooms() {
launchUI(strategy) {
val roomList = chatRoomsInteractor.getAll(currentServer)
view.updateChatRooms(sortRooms(roomList))
}
}
private fun sortRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
val sortType = SharedPreferenceHelper.getInt(Constants.CHATROOM_SORT_TYPE_KEY, ChatRoomsSortOrder.ACTIVITY)
val groupByType = SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)
val openChatRooms = getOpenChatRooms(chatRooms)
return when (sortType) {
ChatRoomsSortOrder.ALPHABETICAL -> {
when (groupByType) {
true -> openChatRooms.sortedWith(compareBy(ChatRoom::type).thenBy { it.name })
false -> openChatRooms.sortedWith(compareBy(ChatRoom::name))
}
}
ChatRoomsSortOrder.ACTIVITY -> {
when (groupByType) {
true -> openChatRooms.sortedWith(compareBy(ChatRoom::type).thenByDescending { chatroom ->
chatRoomTimestamp(chatroom)
})
false -> openChatRooms.sortedByDescending { chatRoom ->
chatRoomTimestamp(chatRoom)
}
}
}
else -> {
openChatRooms
}
}
}
private fun chatRoomTimestamp(chatRoom: ChatRoom): Long? {
return if (settings.hasShowLastMessage() && settings.showLastMessage()) {
chatRoom.lastMessage?.timestamp ?: chatRoom.updatedAt
} else {
chatRoom.updatedAt
}
}
private fun compareBy(selector: KProperty1<ChatRoom, RoomType>): Comparator<ChatRoom> {
return Comparator { a, b -> getTypeConstant(a.type) - getTypeConstant(b.type) }
}
private fun getTypeConstant(roomType: RoomType): Int {
return when (roomType) {
is RoomType.Channel -> Constants.CHATROOM_CHANNEL
is RoomType.PrivateGroup -> Constants.CHATROOM_PRIVATE_GROUP
is RoomType.DirectMessage -> Constants.CHATROOM_DM
is RoomType.Livechat -> Constants.CHATROOM_LIVE_CHAT
else -> 0
}
}
private fun getChatRoomWithStatus(chatRooms: List<ChatRoom>): List<ChatRoom> {
val chatRoomsList = mutableListOf<ChatRoom>()
chatRooms.forEach {
val newRoom = ChatRoom(
id = it.id,
type = it.type,
user = it.user,
status = getActiveUsersInteractor.getActiveUserByUsername(
currentServer,
it.name
)?.status,
name = it.name,
fullName = it.fullName,
readonly = it.readonly,
updatedAt = it.updatedAt,
timestamp = it.timestamp,
lastSeen = it.lastSeen,
topic = it.topic,
description = it.description,
announcement = it.announcement,
default = it.default,
favorite = it.favorite,
open = it.open,
alert = it.alert,
unread = it.unread,
userMentions = it.userMentions,
groupMentions = it.groupMentions,
lastMessage = it.lastMessage,
client = client,
broadcast = it.broadcast
)
chatRoomsList.add(newRoom)
}
return chatRoomsList
}
private suspend fun getChatRoomsWithPreviews(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.map {
val lastMessage = it.lastMessage
if (lastMessage != null) {
it.copy(lastMessage = viewModelMapper.map(lastMessage).last().preview)
} else {
it
}
}
}
private fun getOpenChatRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
return chatRooms.filter(ChatRoom::open)
}
private suspend fun subscribeStatusChange() {
lastState = manager.state
launch(CommonPool + strategy.jobs) {
for (state in stateChannel) {
Timber.d("Got new state: $state - last: $lastState")
if (state != lastState) {
launch(UI) {
view.showConnectionState(state)
}
if (state is State.Connected) {
jobSchedulerInteractor.scheduleSendingMessages()
reloadRooms()
updateChatRooms()
}
}
lastState = state
}
}
}
// TODO - Temporary stuff, remove when adding DB support
private suspend fun subscribeRoomUpdates() {
manager.addStatusChannel(stateChannel)
view.showConnectionState(client.state)
manager.addRoomsAndSubscriptionsChannel(subscriptionsChannel)
launch(CommonPool + strategy.jobs) {
for (message in subscriptionsChannel) {
Timber.d("Got message: $message")
when (message.data) {
is Room -> updateRoom(message as StreamMessage<Room>)
is Subscription -> updateSubscription(message as StreamMessage<Subscription>)
}
}
}
}
private suspend fun updateRoom(message: StreamMessage<Room>) {
Timber.d("Update Room: ${message.type} - ${message.data.id} - ${message.data.name}")
when (message.type) {
Type.Removed -> {
removeRoom(message.data.id)
}
Type.Updated -> {
updateRoom(message.data)
}
Type.Inserted -> {
// On insertion, just get all chatrooms again, since we can't create one just
// from a Room
reloadRooms()
}
}
updateChatRooms()
}
private suspend fun updateSubscription(message: StreamMessage<Subscription>) {
Timber.d("Update Subscription: ${message.type} - ${message.data.id} - ${message.data.name}")
when (message.type) {
Type.Removed -> {
removeRoom(message.data.roomId)
}
Type.Updated -> {
updateSubscription(message.data)
}
Type.Inserted -> {
// On insertion, just get all chatrooms again, since we can't create one just
// from a Subscription
reloadRooms()
}
}
updateChatRooms()
}
private suspend fun reloadRooms() {
Timber.d("realoadRooms()")
reloadJob?.cancel()
try {
reloadJob = async(CommonPool + strategy.jobs) {
delay(1000)
Timber.d("reloading rooms after wait")
getUserChatRooms()
}
reloadJob?.await()
} catch (ex: Exception) {
ex.printStackTrace()
}
}
// Update a ChatRoom with a Room information
private fun updateRoom(room: Room) {
Timber.d("Updating Room: ${room.id} - ${room.name}")
val chatRooms = chatRoomsInteractor.getAll(currentServer).toMutableList()
val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == room.id }
chatRoom?.apply {
val newRoom = ChatRoom(
id = room.id,
type = room.type,
user = room.user,
status = getActiveUsersInteractor.getActiveUserByUsername(
currentServer,
room.name ?: name
)?.status,
name = room.name ?: name,
fullName = room.fullName ?: fullName,
readonly = room.readonly,
updatedAt = room.updatedAt ?: updatedAt,
timestamp = timestamp,
lastSeen = lastSeen,
topic = room.topic,
description = room.description,
announcement = room.announcement,
default = default,
favorite = favorite,
open = open,
alert = alert,
unread = unread,
userMentions = userMentions,
groupMentions = groupMentions,
lastMessage = room.lastMessage,
client = client,
broadcast = broadcast
)
removeRoom(room.id, chatRooms)
chatRooms.add(newRoom)
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
}
}
// Update a ChatRoom with a Subscription information
private fun updateSubscription(subscription: Subscription) {
Timber.d("Updating subscription: ${subscription.id} - ${subscription.name}")
val chatRooms = chatRoomsInteractor.getAll(currentServer).toMutableList()
val chatRoom = chatRooms.find { chatRoom -> chatRoom.id == subscription.roomId }
chatRoom?.apply {
val newRoom = ChatRoom(
id = subscription.roomId,
type = subscription.type,
user = user,
status = getActiveUsersInteractor.getActiveUserByUsername(
currentServer,
subscription.name
)?.status,
name = subscription.name,
fullName = subscription.fullName ?: fullName,
readonly = subscription.readonly ?: readonly,
updatedAt = subscription.updatedAt ?: updatedAt,
timestamp = subscription.timestamp ?: timestamp,
lastSeen = subscription.lastSeen ?: lastSeen,
topic = topic,
description = description,
announcement = announcement,
default = subscription.isDefault,
favorite = subscription.isFavorite,
open = subscription.open,
alert = subscription.alert,
unread = subscription.unread,
userMentions = subscription.userMentions,
groupMentions = subscription.groupMentions,
lastMessage = lastMessage,
client = client,
broadcast = broadcast
)
removeRoom(subscription.roomId, chatRooms)
chatRooms.add(newRoom)
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
}
}
private fun removeRoom(
id: String,
chatRooms: MutableList<ChatRoom> = chatRoomsInteractor.getAll(currentServer).toMutableList()
) {
Timber.d("Removing ROOM: $id")
synchronized(this) {
chatRooms.removeAll { chatRoom -> chatRoom.id == id }
}
saveChatRoomsInteractor.save(currentServer, sortRooms(chatRooms))
}
private suspend fun subscribeActiveUsers() {
manager.addActiveUserChannel(activeUserChannel)
launch(CommonPool + strategy.jobs) {
for (user in activeUserChannel) {
processActiveUser(user)
}
}
}
private fun processActiveUser(user: User) {
// The first activeUsers stream contains all details of the users (username, UTC Offset,
// etc.), so we add each user to our [saveActiveUsersInteractor] class because the following
// streams don't contain those details.
if (!getActiveUsersInteractor.isActiveUserOnRepository(currentServer, user)) {
Timber.d("Got first active user stream for the user: $user")
saveActiveUsersInteractor.addActiveUser(currentServer, user)
} else {
// After the first stream the next is about the active users updates.
Timber.d("Got update of active user stream for the user: $user")
saveActiveUsersInteractor.updateActiveUser(currentServer, user)
}
getActiveUsersInteractor.getActiveUserById(currentServer, user.id)?.let {
updateChatRoomWithUserStatus(it)
}
}
private fun updateChatRoomWithUserStatus(user_: User) {
Timber.d("active User: $user_")
val username = user_.username
val status = user_.status
if (username != null && status != null) {
chatRoomsInteractor.getByName(currentServer, username)?.let {
val newRoom = ChatRoom(
id = it.id,
type = it.type,
user = it.user,
status = status,
name = it.name,
fullName = it.fullName,
readonly = it.readonly,
updatedAt = it.updatedAt,
timestamp = it.timestamp,
lastSeen = it.lastSeen,
topic = it.topic,
description = it.description,
announcement = it.announcement,
default = it.default,
favorite = it.favorite,
open = it.open,
alert = it.alert,
unread = it.unread,
userMentions = it.userMentions,
groupMentions = it.groupMentions,
lastMessage = it.lastMessage,
client = client,
broadcast = it.broadcast
)
chatRoomsInteractor.remove(currentServer, it)
chatRoomsInteractor.add(currentServer, newRoom)
launchUI(strategy) {
view.updateChatRooms(sortRooms(chatRoomsInteractor.getAll(currentServer)))
}
}
}
}
private fun updateChatRooms() {
Timber.i("Updating ChatRooms")
launch(strategy.jobs) {
val chatRoomsWithPreview = getChatRoomsWithPreviews(
chatRoomsInteractor.getAll(currentServer)
)
val chatRoomsWithStatus = getChatRoomWithStatus(chatRoomsWithPreview)
view.updateChatRooms(chatRoomsWithStatus)
}
}
fun disconnect() {
manager.removeStatusChannel(stateChannel)
manager.removeRoomsAndSubscriptionsChannel(subscriptionsChannel)
manager.removeActiveUserChannel(activeUserChannel)
}
fun goToChatRoomWithId(chatRoomId: String) {
launchUI(strategy) {
chatRoomsInteractor.getById(currentServer, chatRoomId)?.let { loadChatRoom(it) }
}
}
} }
\ No newline at end of file
...@@ -4,8 +4,8 @@ import DateTimeHelper ...@@ -4,8 +4,8 @@ import DateTimeHelper
import DrawableHelper import DrawableHelper
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.view.View import android.view.View
......
...@@ -3,13 +3,6 @@ package chat.rocket.android.chatrooms.ui ...@@ -3,13 +3,6 @@ package chat.rocket.android.chatrooms.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.renderscript.RSInvalidStateException
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.support.v7.util.DiffUtil
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
...@@ -18,16 +11,24 @@ import android.view.View ...@@ -18,16 +11,24 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.RadioGroup import android.widget.RadioGroup
import androidx.core.view.isVisible import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.RoomsAdapter
import chat.rocket.android.chatrooms.infrastructure.ChatRoomsRepository
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.chatrooms.viewmodel.ChatRoomsViewModel
import chat.rocket.android.chatrooms.viewmodel.ChatRoomsViewModelFactory
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.helper.ChatRoomsSortOrder import chat.rocket.android.helper.ChatRoomsSortOrder
import chat.rocket.android.helper.Constants import chat.rocket.android.helper.Constants
import chat.rocket.android.helper.SharedPreferenceHelper import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository
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.inflate import chat.rocket.android.util.extensions.inflate
...@@ -35,16 +36,13 @@ import chat.rocket.android.util.extensions.setVisible ...@@ -35,16 +36,13 @@ import chat.rocket.android.util.extensions.setVisible
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
import chat.rocket.android.widget.DividerItemDecoration import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.common.model.RoomType
import chat.rocket.core.internal.realtime.socket.model.State import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import com.crashlytics.android.Crashlytics
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_rooms.* import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.NonCancellable.isActive import kotlinx.coroutines.experimental.launch
import timber.log.Timber import timber.log.Timber
import java.security.InvalidParameterException
import javax.inject.Inject import javax.inject.Inject
private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID" private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID"
...@@ -53,16 +51,15 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -53,16 +51,15 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
@Inject @Inject
lateinit var presenter: ChatRoomsPresenter lateinit var presenter: ChatRoomsPresenter
@Inject @Inject
lateinit var serverInteractor: GetCurrentServerInteractor lateinit var factory: ChatRoomsViewModelFactory
@Inject @Inject
lateinit var settingsRepository: SettingsRepository lateinit var dbManager: DatabaseManager // TODO - remove when moving ChatRoom screen to DB
@Inject
lateinit var localRepository: LocalRepository lateinit var viewModel: ChatRoomsViewModel
private var searchView: SearchView? = null private var searchView: SearchView? = null
private val handler = Handler() private val handler = Handler()
private var listJob: Job? = null
private var sectionedAdapter: SimpleSectionedRecyclerViewAdapter? = null
private var chatRoomId: String? = null private var chatRoomId: String? = null
companion object { companion object {
...@@ -83,7 +80,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -83,7 +80,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
if (bundle != null) { if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID) chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomId?.let { chatRoomId?.let {
presenter.goToChatRoomWithId(it) // TODO - bring back support to load a room from id.
//presenter.goToChatRoomWithId(it)
chatRoomId = null chatRoomId = null
} }
} }
...@@ -91,7 +89,6 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -91,7 +89,6 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun onDestroy() { override fun onDestroy() {
handler.removeCallbacks(dismissStatus) handler.removeCallbacks(dismissStatus)
presenter.disconnect()
super.onDestroy() super.onDestroy()
} }
...@@ -104,14 +101,46 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -104,14 +101,46 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this, factory).get(ChatRoomsViewModel::class.java)
subscribeUi()
setupToolbar() setupToolbar()
setupRecyclerView()
presenter.loadChatRooms()
} }
override fun onDestroyView() { private fun subscribeUi() {
listJob?.cancel() ui {
super.onDestroyView()
val adapter = RoomsAdapter { roomId ->
launch(UI) {
dbManager.getRoom(roomId)?.let { room ->
ui {
presenter.loadChatRoom(room)
}
}
}
}
recycler_view.layoutManager = LinearLayoutManager(it)
recycler_view.addItemDecoration(DividerItemDecoration(it,
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_start),
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end)))
recycler_view.itemAnimator = DefaultItemAnimator()
recycler_view.adapter = adapter
viewModel.getChatRooms().observe(viewLifecycleOwner, Observer { rooms ->
rooms?.let {
Timber.d("Got items: $it")
adapter.values = it
}
})
viewModel.getStatus().observe(viewLifecycleOwner, Observer { status ->
status?.let { showConnectionState(status) }
})
updateSort()
}
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
...@@ -134,6 +163,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -134,6 +163,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
// TODO - simplify this
R.id.action_sort -> { R.id.action_sort -> {
val dialogLayout = layoutInflater.inflate(R.layout.chatroom_sort_dialog, null) val dialogLayout = layoutInflater.inflate(R.layout.chatroom_sort_dialog, null)
val sortType = SharedPreferenceHelper.getInt(Constants.CHATROOM_SORT_TYPE_KEY, ChatRoomsSortOrder.ACTIVITY) val sortType = SharedPreferenceHelper.getInt(Constants.CHATROOM_SORT_TYPE_KEY, ChatRoomsSortOrder.ACTIVITY)
...@@ -153,22 +183,22 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -153,22 +183,22 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
R.id.radio_sort_activity -> 1 R.id.radio_sort_activity -> 1
else -> 1 else -> 1
}) })
presenter.updateSortedChatRooms()
invalidateQueryOnSearch()
} }
}) })
groupByTypeCheckBox.isChecked = groupByType groupByTypeCheckBox.isChecked = groupByType
groupByTypeCheckBox.setOnCheckedChangeListener({ _, isChecked -> groupByTypeCheckBox.setOnCheckedChangeListener({ _, isChecked ->
SharedPreferenceHelper.putBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, isChecked) SharedPreferenceHelper.putBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, isChecked)
presenter.updateSortedChatRooms()
invalidateQueryOnSearch()
}) })
val dialogSort = AlertDialog.Builder(context) val dialogSort = AlertDialog.Builder(context)
.setTitle(R.string.dialog_sort_title) .setTitle(R.string.dialog_sort_title)
.setView(dialogLayout) .setView(dialogLayout)
.setPositiveButton("Done", { dialog, _ -> dialog.dismiss() }) .setPositiveButton("Done", { dialog, _ ->
invalidateQueryOnSearch()
updateSort()
dialog.dismiss()
})
dialogSort.show() dialogSort.show()
} }
...@@ -176,6 +206,31 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -176,6 +206,31 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
private fun updateSort() {
val sortType = SharedPreferenceHelper.getInt(Constants.CHATROOM_SORT_TYPE_KEY, ChatRoomsSortOrder.ACTIVITY)
val grouped = SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)
val order = when(sortType) {
ChatRoomsSortOrder.ALPHABETICAL -> {
if (grouped) {
ChatRoomsRepository.Order.GROUPED_NAME
} else {
ChatRoomsRepository.Order.NAME
}
}
ChatRoomsSortOrder.ACTIVITY -> {
if (grouped) {
ChatRoomsRepository.Order.GROUPED_ACTIVITY
} else {
ChatRoomsRepository.Order.ACTIVITY
}
}
else -> ChatRoomsRepository.Order.ACTIVITY
}
viewModel.setOrdering(order)
}
private fun invalidateQueryOnSearch() { private fun invalidateQueryOnSearch() {
searchView?.let { searchView?.let {
if (!searchView!!.isIconified) { if (!searchView!!.isIconified) {
...@@ -185,24 +240,6 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -185,24 +240,6 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
} }
override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) { override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) {
listJob?.cancel()
listJob = ui {
val adapter = recycler_view.adapter as SimpleSectionedRecyclerViewAdapter
// FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android/issues/5ac2916c36c7b235275ccccf
// TODO - fix this bug to re-enable DiffUtil
/*val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await()*/
text_no_search.isVisible = newDataSet.isEmpty()
if (isActive) {
adapter.baseAdapter.updateRooms(newDataSet)
// TODO - fix crash to re-enable diff.dispatchUpdatesTo(adapter)
adapter.notifyDataSetChanged()
//Set sections always after data set is updated
setSections()
}
}
} }
override fun showNoChatRoomsToDisplay() { override fun showNoChatRoomsToDisplay() {
...@@ -262,84 +299,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -262,84 +299,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
(activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_chats) (activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_chats)
} }
private fun setupRecyclerView() {
ui {
recycler_view.layoutManager = LinearLayoutManager(it, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(it,
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_start),
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end)))
recycler_view.itemAnimator = DefaultItemAnimator()
// TODO - use a ViewModel Mapper instead of using settings on the adapter
val baseAdapter = ChatRoomsAdapter(it,
settingsRepository.get(serverInteractor.get()!!), localRepository) { chatRoom ->
presenter.loadChatRoom(chatRoom)
}
sectionedAdapter = SimpleSectionedRecyclerViewAdapter(it,
R.layout.item_chatroom_header, R.id.text_chatroom_header, baseAdapter)
recycler_view.adapter = sectionedAdapter
}
}
private fun setSections() {
//Don't add section if not grouping by RoomType
if (!SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)) {
sectionedAdapter?.clearSections()
return
}
val sections = ArrayList<SimpleSectionedRecyclerViewAdapter.Section>()
sectionedAdapter?.baseAdapter?.dataSet?.let {
var previousChatRoomType = ""
for ((position, chatRoom) in it.withIndex()) {
val type = chatRoom.type.toString()
if (type != previousChatRoomType) {
val title = when (type) {
RoomType.CHANNEL.toString() -> resources.getString(R.string.header_channel)
RoomType.PRIVATE_GROUP.toString() -> resources.getString(R.string.header_private_groups)
RoomType.DIRECT_MESSAGE.toString() -> resources.getString(R.string.header_direct_messages)
RoomType.LIVECHAT.toString() -> resources.getString(R.string.header_live_chats)
else -> resources.getString(R.string.header_unknown)
}
sections.add(SimpleSectionedRecyclerViewAdapter.Section(position, title))
}
previousChatRoomType = chatRoom.type.toString()
}
}
val dummy = arrayOfNulls<SimpleSectionedRecyclerViewAdapter.Section>(sections.size)
sectionedAdapter?.setSections(sections.toArray(dummy))
}
private fun queryChatRoomsByName(name: String?): Boolean { private fun queryChatRoomsByName(name: String?): Boolean {
presenter.chatRoomsByName(name ?: "") //presenter.chatRoomsByName(name ?: "")
return true return true
} }
class RoomsDiffCallback(private val oldRooms: List<ChatRoom>,
private val newRooms: List<ChatRoom>) : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldRooms[oldItemPosition].id == newRooms[newItemPosition].id
}
override fun getOldListSize(): Int {
return oldRooms.size
}
override fun getNewListSize(): Int {
return newRooms.size
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldRooms[oldItemPosition].updatedAt == newRooms[newItemPosition].updatedAt
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
return newRooms[newItemPosition]
}
}
} }
\ No newline at end of file
package chat.rocket.android.chatrooms.ui package chat.rocket.android.chatrooms.ui
import android.content.Context import android.content.Context
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.util.SparseArray import android.util.SparseArray
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
......
package chat.rocket.android.chatrooms.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import chat.rocket.android.chatrooms.adapter.ItemHolder
import chat.rocket.android.chatrooms.adapter.RoomMapper
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
import chat.rocket.android.chatrooms.infrastructure.ChatRoomsRepository
import chat.rocket.android.chatrooms.infrastructure.isGrouped
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.util.livedata.TransformedLiveData
import chat.rocket.android.util.livedata.transform
import chat.rocket.core.internal.realtime.socket.model.State
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import me.henrytao.livedataktx.distinct
import me.henrytao.livedataktx.map
import me.henrytao.livedataktx.nonNull
import timber.log.Timber
class ChatRoomsViewModel(
private val connectionManager: ConnectionManager,
private val interactor: FetchChatRoomsInteractor,
private val repository: ChatRoomsRepository,
private val mapper: RoomMapper
) : ViewModel() {
private val ordering: MutableLiveData<ChatRoomsRepository.Order> = MutableLiveData()
private val runContext = newSingleThreadContext("chat-rooms-view-model")
init {
ordering.value = ChatRoomsRepository.Order.ACTIVITY
}
fun getChatRooms(): LiveData<List<ItemHolder<*>>> {
return Transformations.switchMap(ordering) { order ->
Timber.d("Querying rooms for order: $order")
repository.getChatRooms(order)
.nonNull()
.distinct()
.transform(runContext) { rooms ->
rooms?.let {
mapper.map(rooms, order.isGrouped())
}
}
}
}
fun getStatus(): MutableLiveData<State> {
return connectionManager.statusLiveData.nonNull().distinct().map { state ->
if (state is State.Connected) {
// TODO - add a loading status...
fetchRooms()
}
state
}
}
private fun fetchRooms() {
launch {
interactor.refreshChatRooms()
}
}
fun setOrdering(order: ChatRoomsRepository.Order) {
ordering.value = order
}
}
package chat.rocket.android.chatrooms.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import chat.rocket.android.chatrooms.adapter.RoomMapper
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
import chat.rocket.android.chatrooms.infrastructure.ChatRoomsRepository
import chat.rocket.android.server.infraestructure.ConnectionManager
import javax.inject.Inject
class ChatRoomsViewModelFactory @Inject constructor(
private val connectionManager: ConnectionManager,
private val interactor: FetchChatRoomsInteractor,
private val repository: ChatRoomsRepository,
private val mapper: RoomMapper
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>) =
ChatRoomsViewModel(connectionManager, interactor, repository, mapper) as T
}
\ No newline at end of file
package chat.rocket.android.core.behaviours package chat.rocket.android.core.behaviours
import android.support.annotation.StringRes import androidx.annotation.StringRes
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
interface MessageView { interface MessageView {
......
package chat.rocket.android.core.lifecycle package chat.rocket.android.core.lifecycle
import android.arch.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import android.arch.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import javax.inject.Inject import javax.inject.Inject
......
...@@ -4,13 +4,11 @@ import android.app.Application ...@@ -4,13 +4,11 @@ import android.app.Application
import android.app.NotificationManager import android.app.NotificationManager
import android.app.job.JobInfo import android.app.job.JobInfo
import android.app.job.JobScheduler import android.app.job.JobScheduler
import android.arch.persistence.room.Room
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository
import chat.rocket.android.chatroom.service.MessageService import chat.rocket.android.chatroom.service.MessageService
...@@ -21,8 +19,32 @@ import chat.rocket.android.infrastructure.LocalRepository ...@@ -21,8 +19,32 @@ import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository
import chat.rocket.android.push.GroupedPush import chat.rocket.android.push.GroupedPush
import chat.rocket.android.push.PushManager import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.infraestructure.* import chat.rocket.android.server.domain.ActiveUsersRepository
import chat.rocket.android.server.domain.ChatRoomsRepository
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetAccountInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.JobSchedulerInteractor
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.domain.PermissionsRepository
import chat.rocket.android.server.domain.RoomRepository
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl
import chat.rocket.android.server.infraestructure.MemoryActiveUsersRepository
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infraestructure.MemoryRoomRepository
import chat.rocket.android.server.infraestructure.MemoryUsersRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesMessagesRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesPermissionsRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository
import chat.rocket.android.server.infraestructure.SharedPrefsConnectingServerRepository
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.util.AppJsonAdapterFactory import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.HttpLoggingInterceptor import chat.rocket.android.util.HttpLoggingInterceptor
import chat.rocket.android.util.TimberLogger import chat.rocket.android.util.TimberLogger
...@@ -32,7 +54,6 @@ import chat.rocket.common.model.TimestampAdapter ...@@ -32,7 +54,6 @@ 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.Logger
import chat.rocket.common.util.PlatformLogger import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.AttachmentAdapterFactory import chat.rocket.core.internal.AttachmentAdapterFactory
import chat.rocket.core.internal.ReactionsAdapter import chat.rocket.core.internal.ReactionsAdapter
import com.facebook.drawee.backends.pipeline.DraweeConfig import com.facebook.drawee.backends.pipeline.DraweeConfig
...@@ -42,7 +63,6 @@ import com.facebook.imagepipeline.listener.RequestLoggingListener ...@@ -42,7 +63,6 @@ import com.facebook.imagepipeline.listener.RequestLoggingListener
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import ru.noties.markwon.SpannableConfiguration import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.spans.SpannableTheme import ru.noties.markwon.spans.SpannableTheme
...@@ -53,45 +73,12 @@ import javax.inject.Singleton ...@@ -53,45 +73,12 @@ import javax.inject.Singleton
@Module @Module
class AppModule { class AppModule {
@Provides
@Singleton
fun provideRocketChatClient(okHttpClient: OkHttpClient,
repository: TokenRepository,
logger: PlatformLogger): RocketChatClient {
return RocketChatClient.create {
httpClient = okHttpClient
tokenRepository = repository
platformLogger = logger
// TODO remove
restUrl = "https://open.rocket.chat"
}
}
@Provides
@Singleton
fun provideRocketChatDatabase(context: Application): RocketChatDatabase {
return Room.databaseBuilder(context.applicationContext, RocketChatDatabase::class.java,
"rocketchat-db").build()
}
@Provides
fun provideJob(): Job {
return Job()
}
@Provides @Provides
@Singleton @Singleton
fun provideContext(application: Application): Context { fun provideContext(application: Application): Context {
return application return application
} }
@Provides
@Singleton
fun provideServerDao(database: RocketChatDatabase): ServerDao {
return database.serverDao()
}
@Provides @Provides
@Singleton @Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
...@@ -129,7 +116,6 @@ class AppModule { ...@@ -129,7 +116,6 @@ class AppModule {
return OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient) return OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient)
.setRequestListeners(listeners) .setRequestListeners(listeners)
.setDownsampleEnabled(true) .setDownsampleEnabled(true)
//.experiment().setBitmapPrepareToDraw(true).experiment()
.experiment().setPartialImageCachingEnabled(true).build() .experiment().setPartialImageCachingEnabled(true).build()
} }
......
package chat.rocket.android.db
import androidx.room.Insert
import androidx.room.OnConflictStrategy
interface BaseDao<T> {
@Insert
fun insert(vararg obj: T)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(list: List<T>)
}
\ No newline at end of file
package chat.rocket.android.db
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.common.model.RoomType
@Dao
abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
@Transaction
@Query("""
$BASE_QUERY
WHERE chatrooms.id = :id
""")
abstract fun get(id: String): ChatRoom?
@Transaction
@Query("""
$BASE_QUERY
ORDER BY
CASE
WHEN lastMessageTimeStamp IS NOT NULL THEN lastMessageTimeStamp
ELSE updatedAt
END DESC
""")
abstract fun getAll(): LiveData<List<ChatRoom>>
@Transaction
@Query("""
$BASE_QUERY
ORDER BY
$TYPE_ORDER,
CASE
WHEN lastMessageTimeStamp IS NOT NULL THEN lastMessageTimeStamp
ELSE updatedAt
END DESC
""")
abstract fun getAllGrouped(): LiveData<List<ChatRoom>>
@Transaction
@Query("""
$BASE_QUERY
ORDER BY name
""")
abstract fun getAllAlphabetically(): LiveData<List<ChatRoom>>
@Transaction
@Query("""
$BASE_QUERY
ORDER BY
$TYPE_ORDER,
name
""")
abstract fun getAllAlphabeticallyGrouped(): LiveData<List<ChatRoom>>
@Query("DELETE FROM chatrooms WHERE ID = :id")
abstract fun delete(id: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertOrReplace(chatRooms: List<ChatRoomEntity>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertOrReplace(chatRoom: ChatRoomEntity)
@Update
abstract fun update(list: List<ChatRoomEntity>)
@Transaction
open fun update(toRemove: List<String>, toInsert: List<ChatRoomEntity>, toUpdate: List<ChatRoomEntity>) {
insertOrReplace(toInsert)
update(toUpdate)
toRemove.forEach { id ->
delete(id)
}
}
companion object {
const val BASE_QUERY = """
SELECT chatrooms.*,
users.username as username,
users.name as userFullname,
users.status,
lmUsers.username as lastMessageUserName,
lmUsers.name as lastMessageUserFullName
FROM chatrooms
LEFT JOIN users ON chatrooms.userId = users.id
LEFT JOIN users AS lmUsers ON chatrooms.lastMessageUserId = lmUsers.id
"""
const val TYPE_ORDER = """
CASE
WHEN type = '${RoomType.CHANNEL}' THEN 1
WHEN type = '${RoomType.PRIVATE_GROUP}' THEN 2
WHEN type = '${RoomType.DIRECT_MESSAGE}' THEN 3
WHEN type = '${RoomType.LIVECHAT}' THEN 4
ELSE 5
END
"""
}
}
\ No newline at end of file
package chat.rocket.android.db
import android.app.Application
import chat.rocket.android.db.model.BaseUserEntity
import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.db.model.UserStatus
import chat.rocket.android.util.extensions.removeTrailingSlash
import chat.rocket.android.util.extensions.userId
import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.User
import chat.rocket.core.internal.model.Subscription
import chat.rocket.core.internal.realtime.socket.model.StreamMessage
import chat.rocket.core.internal.realtime.socket.model.Type
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.withContext
import timber.log.Timber
import java.util.HashSet
class DatabaseManager(val context: Application,
val serverUrl: String) {
private val database: RCDatabase = androidx.room.Room.databaseBuilder(context,
RCDatabase::class.java, serverUrl.databaseName()).fallbackToDestructiveMigration()
.build()
private val dbContext = newSingleThreadContext("$serverUrl-db-context")
private val insertSubs = HashMap<String, Subscription>()
private val insertRooms = HashMap<String, Room>()
private val updateSubs = LinkedHashMap<String, Subscription>()
private val updateRooms = LinkedHashMap<String, Room>()
fun chatRoomDao(): ChatRoomDao = database.chatRoomDao()
fun userDao(): UserDao = database.userDao()
suspend fun getRoom(id: String) = withContext(dbContext) {
chatRoomDao().get(id)
}
fun processUsersBatch(users: List<User>) {
launch(dbContext) {
val dao = database.userDao()
val list = ArrayList<BaseUserEntity>(users.size)
users.forEach { user ->
user.toEntity()?.let { entity ->
list.add(entity)
}
}
dao.upsert(list)
}
}
fun processStreamBatch(batch: List<StreamMessage<BaseRoom>>) {
launch(dbContext) {
val toRemove = HashSet<String>()
val toInsert = ArrayList<ChatRoomEntity>(batch.size / 2)
val toUpdate = ArrayList<ChatRoomEntity>(batch.size)
batch.forEach {
when(it.type) {
is Type.Removed -> toRemove.add(removeChatRoom(it.data))
is Type.Inserted -> insertChatRoom(it.data)?.let { toInsert.add(it) }
is Type.Updated -> {
when(it.data) {
is Subscription -> updateSubs[(it.data as Subscription).roomId] = it.data as Subscription
is Room -> updateRooms[(it.data as Room).id] = it.data as Room
}
}
}
}
toUpdate.addAll(createMatchingUpdates())
toUpdate.addAll(createUpdates())
try {
val filteredUpdate = toUpdate.filterNot { toRemove.contains(it.id) }
val filteredInsert = toInsert.filterNot { toRemove.contains(it.id) }
Timber.d("Running ChatRooms transaction: remove: $toRemove - insert: $toInsert - update: $filteredUpdate")
chatRoomDao().update(toRemove.toList(), filteredInsert, filteredUpdate)
} catch (ex: Exception) {
Timber.d(ex, "Error updating chatrooms")
}
}
}
private suspend fun createUpdates(): List<ChatRoomEntity> {
val list = ArrayList<ChatRoomEntity>()
updateSubs.forEach { (_, subscription) ->
updateSubscription(subscription)?.let {
list.add(it)
}
}
updateRooms.forEach { (_, room) ->
updateRoom(room)?.let {
list.add(it)
}
}
updateSubs.clear()
updateRooms.clear()
return list
}
private suspend fun createMatchingUpdates(): List<ChatRoomEntity> {
val list = ArrayList<ChatRoomEntity>()
val matches = ArrayList<String>()
updateRooms.forEach { room ->
val (id, _) = room
if (updateSubs.containsKey(id)) {
matches.add(id)
}
}
matches.forEach { id ->
val room = updateRooms.remove(id)
val subscription = updateSubs.remove(id)
list.add(fullChatRoomEntity(subscription!!, room!!))
}
return list
}
private fun removeChatRoom(data: BaseRoom): String {
return when(data) {
is Subscription -> data.roomId
else -> data.id
}
}
private suspend fun updateChatRoom(data: BaseRoom): ChatRoomEntity? {
return when(data) {
is Room -> updateRoom(data)
is Subscription -> updateSubscription(data)
else -> null
}
}
private suspend fun updateRoom(data: Room): ChatRoomEntity? {
return chatRoomDao().get(data.id)?.let { current ->
with(data) {
val chatRoom = current.chatRoom
lastMessage?.sender?.let { user ->
if (findUser(user.id!!) == null) {
Timber.d("Missing last message user, inserting: ${user.id}")
insert(UserEntity(user.id!!, user.username, user.name))
}
}
user?.let { user ->
if (findUser(user.id!!) == null) {
Timber.d("Missing owner user, inserting: ${user.id}")
insert(UserEntity(user.id!!, user.username, user.name))
}
}
chatRoom.copy(
name = name ?: chatRoom.name,
fullname = fullName ?: chatRoom.fullname,
ownerId = user?.id ?: chatRoom.ownerId,
readonly = readonly,
updatedAt = updatedAt ?: chatRoom.updatedAt,
lastMessageText = lastMessage?.message,
lastMessageUserId = lastMessage?.sender?.id,
lastMessageTimestamp = lastMessage?.timestamp
)
}
}
}
private suspend fun updateSubscription(data: Subscription): ChatRoomEntity? {
return chatRoomDao().get(data.roomId)?.let { current ->
with(data) {
val userId = if (type is RoomType.DirectMessage) {
roomId.userId(user?.id)
} else {
null
}
if (userId != null && findUser(userId) == null) {
Timber.d("Missing user, inserting: $userId")
insert(UserEntity(userId))
}
val chatRoom = current.chatRoom
chatRoom.copy(
id = roomId,
subscriptionId = id,
type = type.toString(),
name = name,
fullname = fullName ?: chatRoom.fullname,
userId = userId ?: chatRoom.userId,
readonly = readonly ?: chatRoom.readonly,
isDefault = isDefault,
favorite = isFavorite,
open = open,
alert = alert,
unread = unread,
userMentions = userMentions ?: chatRoom.userMentions,
groupMentions = groupMentions ?: chatRoom.groupMentions,
updatedAt = updatedAt ?: chatRoom.updatedAt,
timestamp = timestamp ?: chatRoom.timestamp,
lastSeen = lastSeen ?: chatRoom.lastSeen
)
}
}
}
private suspend fun insertChatRoom(data: BaseRoom): ChatRoomEntity? {
return when(data) {
is Room -> insertRoom(data)
is Subscription -> insertSubscription(data)
else -> null
}
}
private suspend fun insertRoom(data: Room): ChatRoomEntity? {
val subscription = insertSubs.remove(data.id)
return if (subscription != null) {
fullChatRoomEntity(subscription, data)
} else {
insertRooms[data.id] = data
null
}
}
private suspend fun insertSubscription(data: Subscription): ChatRoomEntity? {
val room = insertRooms.remove(data.roomId)
return if (room != null) {
fullChatRoomEntity(data, room)
} else {
insertSubs[data.roomId] = data
null
}
}
private suspend fun fullChatRoomEntity(subscription: Subscription, room: Room): ChatRoomEntity {
val userId = if (room.type is RoomType.DirectMessage) {
subscription.roomId.userId(subscription.user?.id)
} else {
null
}
if (userId != null && findUser(userId) == null) {
Timber.d("Missing user, inserting: $userId")
insert(UserEntity(userId))
}
room.lastMessage?.sender?.let { user ->
if (findUser(user.id!!) == null) {
Timber.d("Missing last message user, inserting: ${user.id}")
insert(UserEntity(user.id!!, user.username, user.name))
}
}
room.user?.let { user ->
if (findUser(user.id!!) == null) {
Timber.d("Missing owner user, inserting: ${user.id}")
insert(UserEntity(user.id!!, user.username, user.name))
}
}
return ChatRoomEntity(
id = room.id,
subscriptionId = subscription.id,
type = room.type.toString(),
name = room.name ?: subscription.name,
fullname = subscription.fullName ?: room.fullName,
userId = userId,
ownerId = room.user?.id,
readonly = subscription.readonly,
isDefault = subscription.isDefault,
favorite = subscription.isFavorite,
open = subscription.open,
alert = subscription.alert,
unread = subscription.unread,
userMentions = subscription.userMentions,
groupMentions = subscription.groupMentions,
updatedAt = subscription.updatedAt,
timestamp = subscription.timestamp,
lastSeen = subscription.lastSeen,
lastMessageText = room.lastMessage?.message,
lastMessageUserId = room.lastMessage?.sender?.id,
lastMessageTimestamp = room.lastMessage?.timestamp
)
}
suspend fun insert(rooms: List<ChatRoomEntity>) {
withContext(dbContext) {
chatRoomDao().insert(rooms)
}
}
suspend fun insert(user: UserEntity) {
withContext(dbContext) {
userDao().insert(user)
}
}
fun findUser(userId: String): String? = userDao().findUser(userId)
}
fun User.toEntity(): BaseUserEntity? {
return if (name == null && username == null && utcOffset == null && status != null) {
UserStatus(id = id, status = status.toString())
} else if (username != null){
UserEntity(id, username, name, status?.toString() ?: "offline", utcOffset)
} else {
null
}
}
private fun String.databaseName(): String {
val tmp = this.removePrefix("https://")
.removePrefix("http://")
.removeTrailingSlash()
.replace(".", "_")
return "$tmp.db"
}
\ No newline at end of file
package chat.rocket.android.db
import android.app.Application
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class DatabaseManagerFactory @Inject constructor(private val context: Application) {
private val cache = HashMap<String, DatabaseManager>()
fun create(serverUrl: String): DatabaseManager {
cache[serverUrl]?.let {
Timber.d("Returning cached database for $serverUrl")
return it
}
Timber.d("Returning FRESH database for $serverUrl")
val db = DatabaseManager(context, serverUrl)
cache[serverUrl] = db
return db
}
}
\ No newline at end of file
package chat.rocket.android.db
import androidx.room.Database
import androidx.room.RoomDatabase
import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.UserEntity
@Database(
entities = [UserEntity::class, ChatRoomEntity::class],
version = 3,
exportSchema = true
)
abstract class RCDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun chatRoomDao(): ChatRoomDao
}
\ No newline at end of file
package chat.rocket.android.db
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import chat.rocket.android.db.model.BaseUserEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.db.model.UserStatus
import timber.log.Timber
@Dao
abstract class UserDao : BaseDao<UserEntity> {
@Update(onConflict = OnConflictStrategy.IGNORE)
abstract fun update(user: UserEntity): Int
@Query("UPDATE OR IGNORE users set STATUS = :status where ID = :id")
abstract fun update(id: String, status: String): Int
@Query("SELECT id FROM users WHERE ID = :id")
abstract fun findUser(id: String): String?
@Transaction
open fun upsert(user: BaseUserEntity) {
internalUpsert(user)
}
@Transaction
open fun upsert(users: List<BaseUserEntity>) {
users.forEach { internalUpsert(it) }
}
private inline fun internalUpsert(user: BaseUserEntity) {
val count = if (user is UserStatus) {
update(user.id, user.status)
} else {
update(user as UserEntity)
}
if (count == 0 && user is UserEntity) {
Timber.d("missing user, inserting: ${user.id}")
insert(user)
}
}
}
\ No newline at end of file
package chat.rocket.android.db.model
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "chatrooms",
indices = [
Index(value = ["userId"]),
Index(value = ["ownerId"]),
Index(value = ["subscriptionId"], unique = true),
Index(value = ["updatedAt"])
],
foreignKeys = [
ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["ownerId"]),
ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["userId"]),
ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["lastMessageUserId"])
]
)
data class ChatRoomEntity(
@PrimaryKey var id: String,
var subscriptionId: String,
var type: String,
var name: String,
var fullname: String?,
var userId: String?,
var ownerId: String?,
var readonly: Boolean? = false,
var isDefault: Boolean? = false,
var favorite: Boolean? = false,
var open: Boolean = true,
var alert: Boolean = false,
var unread: Long = 0,
var userMentions: Long? = 0,
var groupMentions: Long? = 0,
var updatedAt: Long? = -1,
var timestamp: Long? = -1,
var lastSeen: Long? = -1,
var lastMessageText: String?,
var lastMessageUserId: String?,
var lastMessageTimestamp: Long?
)
data class ChatRoom(
@Embedded var chatRoom: ChatRoomEntity,
var username: String?,
var userFullname: String?,
var status: String?,
var lastMessageUserName: String?,
var lastMessageUserFullName: String?
)
\ No newline at end of file
package chat.rocket.android.db.model
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "users",
indices = [Index(value = ["username"])])
data class UserEntity(
@PrimaryKey override val id: String,
var username: String? = null,
var name: String? = null,
override var status: String = "offline",
var utcOffset: Float? = null
) : BaseUserEntity
data class UserStatus(
override val id: String,
override val status: String
) : BaseUserEntity
interface BaseUserEntity {
val id: String
val status: String
}
\ No newline at end of file
package chat.rocket.android.chatroom.di package chat.rocket.android.chatroom.di
import android.arch.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.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.favoritemessages.presentation.FavoriteMessagesView import chat.rocket.android.favoritemessages.presentation.FavoriteMessagesView
...@@ -10,20 +10,26 @@ import dagger.Provides ...@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class FavoriteMessagesFragmentModule { class FavoriteMessagesFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun provideLifecycleOwner(frag: FavoriteMessagesFragment): LifecycleOwner { fun provideLifecycleOwner(frag: FavoriteMessagesFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
@Provides @Provides
@PerFragment
fun provideFavoriteMessagesView(frag: FavoriteMessagesFragment): FavoriteMessagesView { fun provideFavoriteMessagesView(frag: FavoriteMessagesFragment): FavoriteMessagesView {
return frag return frag
} }
......
package chat.rocket.android.favoritemessages.ui package chat.rocket.android.favoritemessages.ui
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.ChatRoomAdapter import chat.rocket.android.chatroom.adapter.ChatRoomAdapter
import chat.rocket.android.chatroom.ui.ChatRoomActivity import chat.rocket.android.chatroom.ui.ChatRoomActivity
...@@ -68,8 +68,7 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { ...@@ -68,8 +68,7 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
if (recycler_view.adapter == null) { if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false) adapter = ChatRoomAdapter(enableActions = false)
recycler_view.adapter = adapter recycler_view.adapter = adapter
val linearLayoutManager = val linearLayoutManager = LinearLayoutManager(context)
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recycler_view.layoutManager = linearLayoutManager recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator() recycler_view.itemAnimator = DefaultItemAnimator()
if (favoriteMessages.size >= 30) { if (favoriteMessages.size >= 30) {
...@@ -78,7 +77,7 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView { ...@@ -78,7 +77,7 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
override fun onLoadMore( override fun onLoadMore(
page: Int, page: Int,
totalItemsCount: Int, totalItemsCount: Int,
recyclerView: RecyclerView? recyclerView: RecyclerView
) { ) {
presenter.loadFavoriteMessages(chatRoomId) presenter.loadFavoriteMessages(chatRoomId)
} }
......
package chat.rocket.android.files.adapter package chat.rocket.android.files.adapter
import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.files.viewmodel.FileViewModel import chat.rocket.android.files.viewmodel.FileViewModel
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
......
package chat.rocket.android.files.di package chat.rocket.android.files.di
import android.arch.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.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.files.presentation.FilesView import chat.rocket.android.files.presentation.FilesView
...@@ -10,20 +10,26 @@ import dagger.Provides ...@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class FilesFragmentModule { class FilesFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun provideLifecycleOwner(frag: FilesFragment): LifecycleOwner { fun provideLifecycleOwner(frag: FilesFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
@Provides @Provides
@PerFragment
fun provideFilesView(frag: FilesFragment): FilesView { fun provideFilesView(frag: FilesFragment): FilesView {
return frag return frag
} }
......
package chat.rocket.android.files.di package chat.rocket.android.files.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.files.ui.FilesFragment import chat.rocket.android.files.ui.FilesFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class FilesFragmentProvider { abstract class FilesFragmentProvider {
@ContributesAndroidInjector(modules = [FilesFragmentModule::class]) @ContributesAndroidInjector(modules = [FilesFragmentModule::class])
@PerFragment
abstract fun provideFilesFragment(): FilesFragment abstract fun provideFilesFragment(): FilesFragment
} }
\ No newline at end of file
...@@ -3,13 +3,13 @@ package chat.rocket.android.files.ui ...@@ -3,13 +3,13 @@ package chat.rocket.android.files.ui
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.ui.ChatRoomActivity import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.files.adapter.FilesAdapter import chat.rocket.android.files.adapter.FilesAdapter
...@@ -42,8 +42,7 @@ class FilesFragment : Fragment(), FilesView { ...@@ -42,8 +42,7 @@ class FilesFragment : Fragment(), FilesView {
lateinit var presenter: FilesPresenter lateinit var presenter: FilesPresenter
private val adapter: FilesAdapter = private val adapter: FilesAdapter =
FilesAdapter { fileViewModel -> presenter.openFile(fileViewModel) } FilesAdapter { fileViewModel -> presenter.openFile(fileViewModel) }
private val linearLayoutManager = private val linearLayoutManager = LinearLayoutManager(context)
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
private lateinit var chatRoomId: String private lateinit var chatRoomId: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
...@@ -80,7 +79,7 @@ class FilesFragment : Fragment(), FilesView { ...@@ -80,7 +79,7 @@ class FilesFragment : Fragment(), FilesView {
override fun onLoadMore( override fun onLoadMore(
page: Int, page: Int,
totalItemsCount: Int, totalItemsCount: Int,
recyclerView: RecyclerView? recyclerView: RecyclerView
) { ) {
presenter.loadFiles(chatRoomId) presenter.loadFiles(chatRoomId)
} }
......
...@@ -3,8 +3,8 @@ package chat.rocket.android.helper ...@@ -3,8 +3,8 @@ package chat.rocket.android.helper
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.support.v4.app.ActivityCompat import androidx.core.app.ActivityCompat
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
object AndroidPermissionsHelper { object AndroidPermissionsHelper {
......
package chat.rocket.android.helper package chat.rocket.android.helper
import android.support.v7.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
/** /**
* Info: https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView * Info: https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
...@@ -45,7 +45,7 @@ abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener ...@@ -45,7 +45,7 @@ abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener
// This happens many times a second during a scroll, so be wary of the code you place here. // This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data, // We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish. // but first we check if we are waiting for the previous load to finish.
override fun onScrolled(view: RecyclerView?, dx: Int, dy: Int) { override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
var lastVisibleItemPosition = 0 var lastVisibleItemPosition = 0
val totalItemCount = layoutManager.itemCount val totalItemCount = layoutManager.itemCount
...@@ -106,5 +106,5 @@ abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener ...@@ -106,5 +106,5 @@ abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener
} }
// Defines the process for actually loading more data based on page // Defines the process for actually loading more data based on page
abstract fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) abstract fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView)
} }
\ No newline at end of file
...@@ -7,8 +7,6 @@ import android.graphics.Color ...@@ -7,8 +7,6 @@ import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.os.Environment import android.os.Environment
import android.support.design.widget.AppBarLayout
import android.support.v7.widget.Toolbar
import android.text.TextUtils import android.text.TextUtils
import android.util.TypedValue import android.util.TypedValue
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
...@@ -16,6 +14,7 @@ import android.view.ViewGroup ...@@ -16,6 +14,7 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.setPadding import androidx.core.view.setPadding
import chat.rocket.android.R import chat.rocket.android.R
...@@ -26,6 +25,7 @@ import com.facebook.imagepipeline.cache.DefaultCacheKeyFactory ...@@ -26,6 +25,7 @@ import com.facebook.imagepipeline.cache.DefaultCacheKeyFactory
import com.facebook.imagepipeline.core.ImagePipelineFactory import com.facebook.imagepipeline.core.ImagePipelineFactory
import com.facebook.imagepipeline.request.ImageRequest import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.request.ImageRequestBuilder import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.google.android.material.appbar.AppBarLayout
import com.stfalcon.frescoimageviewer.ImageViewer import com.stfalcon.frescoimageviewer.ImageViewer
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
......
...@@ -21,7 +21,7 @@ class MessageHelper @Inject constructor( ...@@ -21,7 +21,7 @@ class MessageHelper @Inject constructor(
is RoomType.PrivateGroup -> "group" is RoomType.PrivateGroup -> "group"
is RoomType.Channel -> "channel" is RoomType.Channel -> "channel"
is RoomType.DirectMessage -> "direct" is RoomType.DirectMessage -> "direct"
is RoomType.Livechat -> "livechat" is RoomType.LiveChat -> "livechat"
else -> "custom" else -> "custom"
} }
val name = if (settings.useRealName()) chatRoom.fullName ?: chatRoom.name else chatRoom.name val name = if (settings.useRealName()) chatRoom.fullName ?: chatRoom.name else chatRoom.name
......
...@@ -6,7 +6,7 @@ import android.graphics.Canvas ...@@ -6,7 +6,7 @@ import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.RectF import android.graphics.RectF
import android.net.Uri import android.net.Uri
import android.support.v4.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import android.text.Spanned import android.text.Spanned
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.text.style.ReplacementSpan import android.text.style.ReplacementSpan
......
...@@ -2,7 +2,7 @@ package chat.rocket.android.helper ...@@ -2,7 +2,7 @@ package chat.rocket.android.helper
import android.app.Activity import android.app.Activity
import android.content.IntentSender import android.content.IntentSender
import android.support.v4.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.google.android.gms.auth.api.credentials.* import com.google.android.gms.auth.api.credentials.*
import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.ResolvableApiException import com.google.android.gms.common.api.ResolvableApiException
......
...@@ -21,7 +21,6 @@ interface LocalRepository { ...@@ -21,7 +21,6 @@ interface LocalRepository {
companion object { companion object {
const val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN" const val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN"
const val MIGRATION_FINISHED_KEY = "MIGRATION_FINISHED_KEY"
const val TOKEN_KEY = "token_" const val TOKEN_KEY = "token_"
const val SETTINGS_KEY = "settings_" const val SETTINGS_KEY = "settings_"
const val PERMISSIONS_KEY = "permissions_" const val PERMISSIONS_KEY = "permissions_"
......
package chat.rocket.android.main.adapter package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import kotlinx.android.synthetic.main.item_account.view.* import kotlinx.android.synthetic.main.item_account.view.*
......
package chat.rocket.android.main.adapter package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
......
package chat.rocket.android.main.adapter package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
class AddAccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) class AddAccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
\ No newline at end of file
package chat.rocket.android.main.adapter package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
import kotlinx.android.synthetic.main.item_change_status.view.* import kotlinx.android.synthetic.main.item_change_status.view.*
......
package chat.rocket.android.main.di package chat.rocket.android.main.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import android.content.Context import android.content.Context
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.main.presentation.MainNavigator import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.main.presentation.MainView import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
...@@ -14,6 +15,10 @@ import kotlinx.coroutines.experimental.Job ...@@ -14,6 +15,10 @@ import kotlinx.coroutines.experimental.Job
@Module @Module
class MainModule { class MainModule {
@Provides
@PerActivity
fun provideJob() = Job()
@Provides @Provides
@PerActivity @PerActivity
fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity) fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity)
......
...@@ -4,9 +4,9 @@ import DrawableHelper ...@@ -4,9 +4,9 @@ import DrawableHelper
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Gravity import android.view.Gravity
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
......
package chat.rocket.android.members.adapter package chat.rocket.android.members.adapter
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import chat.rocket.android.R import chat.rocket.android.R
......
package chat.rocket.android.members.di package chat.rocket.android.members.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.ui.ChatRoomActivity import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
...@@ -12,23 +12,30 @@ import dagger.Provides ...@@ -12,23 +12,30 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class MembersFragmentModule { class MembersFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun provideChatRoomNavigator(activity: ChatRoomActivity) = MembersNavigator(activity) fun provideChatRoomNavigator(activity: ChatRoomActivity) = MembersNavigator(activity)
@Provides @Provides
@PerFragment
fun membersView(frag: MembersFragment): MembersView { fun membersView(frag: MembersFragment): MembersView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: MembersFragment): LifecycleOwner { fun provideLifecycleOwner(frag: MembersFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.members.di package chat.rocket.android.members.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.members.ui.MembersFragment import chat.rocket.android.members.ui.MembersFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class MembersFragmentProvider { abstract class MembersFragmentProvider {
@ContributesAndroidInjector(modules = [MembersFragmentModule::class]) @ContributesAndroidInjector(modules = [MembersFragmentModule::class])
@PerFragment
abstract fun provideMembersFragment(): MembersFragment abstract fun provideMembersFragment(): MembersFragment
} }
\ No newline at end of file
package chat.rocket.android.members.ui package chat.rocket.android.members.ui
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -11,7 +11,11 @@ import chat.rocket.android.util.extensions.setVisible ...@@ -11,7 +11,11 @@ import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import kotlinx.android.synthetic.main.fragment_member_bottom_sheet.* import kotlinx.android.synthetic.main.fragment_member_bottom_sheet.*
fun newInstance(avatarUri: String, realName: String, username: String, email: String, utcOffset: String): BottomSheetDialogFragment { fun newInstance(avatarUri: String,
realName: String,
username: String,
email: String,
utcOffset: String): BottomSheetDialogFragment {
return MemberBottomSheetFragment().apply { return MemberBottomSheetFragment().apply {
arguments = Bundle(1).apply { arguments = Bundle(1).apply {
putString(BUNDLE_AVATAR_URI, avatarUri) putString(BUNDLE_AVATAR_URI, avatarUri)
......
package chat.rocket.android.members.ui package chat.rocket.android.members.ui
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.widget.LinearLayoutManager import androidx.appcompat.app.AppCompatActivity
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -38,8 +39,7 @@ class MembersFragment : Fragment(), MembersView { ...@@ -38,8 +39,7 @@ class MembersFragment : Fragment(), MembersView {
lateinit var presenter: MembersPresenter lateinit var presenter: MembersPresenter
private val adapter: MembersAdapter = private val adapter: MembersAdapter =
MembersAdapter { memberViewModel -> presenter.toMemberDetails(memberViewModel) } MembersAdapter { memberViewModel -> presenter.toMemberDetails(memberViewModel) }
private val linearLayoutManager = private val linearLayoutManager = LinearLayoutManager(context)
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
private lateinit var chatRoomId: String private lateinit var chatRoomId: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
...@@ -77,7 +77,7 @@ class MembersFragment : Fragment(), MembersView { ...@@ -77,7 +77,7 @@ class MembersFragment : Fragment(), MembersView {
override fun onLoadMore( override fun onLoadMore(
page: Int, page: Int,
totalItemsCount: Int, totalItemsCount: Int,
recyclerView: RecyclerView? recyclerView: RecyclerView
) { ) {
presenter.loadChatRoomsMembers(chatRoomId) presenter.loadChatRoomsMembers(chatRoomId)
} }
......
package chat.rocket.android.chatroom.di package chat.rocket.android.chatroom.di
import android.arch.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.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.pinnedmessages.presentation.PinnedMessagesView import chat.rocket.android.pinnedmessages.presentation.PinnedMessagesView
...@@ -10,20 +10,26 @@ import dagger.Provides ...@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class PinnedMessagesFragmentModule { class PinnedMessagesFragmentModule {
@Provides @Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun provideLifecycleOwner(frag: PinnedMessagesFragment): LifecycleOwner { fun provideLifecycleOwner(frag: PinnedMessagesFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
@Provides @Provides
@PerFragment
fun providePinnedMessagesView(frag: PinnedMessagesFragment): PinnedMessagesView { fun providePinnedMessagesView(frag: PinnedMessagesFragment): PinnedMessagesView {
return frag return frag
} }
......
package chat.rocket.android.pinnedmessages.di package chat.rocket.android.pinnedmessages.di
import chat.rocket.android.chatroom.di.PinnedMessagesFragmentModule
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.chatroom.di.PinnedMessagesFragmentModule
import chat.rocket.android.pinnedmessages.ui.PinnedMessagesFragment import chat.rocket.android.pinnedmessages.ui.PinnedMessagesFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
......
package chat.rocket.android.pinnedmessages.ui package chat.rocket.android.pinnedmessages.ui
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -70,8 +70,8 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { ...@@ -70,8 +70,8 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
if (recycler_view_pinned.adapter == null) { if (recycler_view_pinned.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false) adapter = ChatRoomAdapter(enableActions = false)
recycler_view_pinned.adapter = adapter recycler_view_pinned.adapter = adapter
val linearLayoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) val linearLayoutManager = LinearLayoutManager(context)
recycler_view_pinned.layoutManager = linearLayoutManager recycler_view_pinned.layoutManager = linearLayoutManager
recycler_view_pinned.itemAnimator = DefaultItemAnimator() recycler_view_pinned.itemAnimator = DefaultItemAnimator()
if (pinnedMessages.size >= 30) { if (pinnedMessages.size >= 30) {
...@@ -80,7 +80,7 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView { ...@@ -80,7 +80,7 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
override fun onLoadMore( override fun onLoadMore(
page: Int, page: Int,
totalItemsCount: Int, totalItemsCount: Int,
recyclerView: RecyclerView? recyclerView: RecyclerView
) { ) {
presenter.loadPinnedMessages(chatRoomId) presenter.loadPinnedMessages(chatRoomId)
} }
......
package chat.rocket.android.profile.di package chat.rocket.android.profile.di
import android.arch.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.profile.presentation.ProfileView import chat.rocket.android.profile.presentation.ProfileView
import chat.rocket.android.profile.ui.ProfileFragment import chat.rocket.android.profile.ui.ProfileFragment
...@@ -8,15 +8,16 @@ import dagger.Module ...@@ -8,15 +8,16 @@ import dagger.Module
import dagger.Provides import dagger.Provides
@Module @Module
@PerFragment
class ProfileFragmentModule { class ProfileFragmentModule {
@Provides @Provides
@PerFragment
fun profileView(frag: ProfileFragment): ProfileView { fun profileView(frag: ProfileFragment): ProfileView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideLifecycleOwner(frag: ProfileFragment): LifecycleOwner { fun provideLifecycleOwner(frag: ProfileFragment): LifecycleOwner {
return frag return frag
} }
......
package chat.rocket.android.profile.di package chat.rocket.android.profile.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.profile.ui.ProfileFragment import chat.rocket.android.profile.ui.ProfileFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class ProfileFragmentProvider { abstract class ProfileFragmentProvider {
@ContributesAndroidInjector(modules = [ProfileFragmentModule::class]) @ContributesAndroidInjector(modules = [ProfileFragmentModule::class])
@PerFragment
abstract fun provideProfileFragment(): ProfileFragment abstract fun provideProfileFragment(): ProfileFragment
} }
\ No newline at end of file
...@@ -3,10 +3,10 @@ package chat.rocket.android.profile.ui ...@@ -3,10 +3,10 @@ package chat.rocket.android.profile.ui
import DrawableHelper import DrawableHelper
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.app.AppCompatActivity import androidx.appcompat.view.ActionMode
import android.support.v7.view.ActionMode
import android.view.* import android.view.*
import androidx.appcompat.app.AppCompatActivity
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.profile.presentation.ProfilePresenter import chat.rocket.android.profile.presentation.ProfilePresenter
......
...@@ -4,8 +4,8 @@ import android.app.NotificationManager ...@@ -4,8 +4,8 @@ import android.app.NotificationManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.support.v4.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import android.support.v4.app.RemoteInput import androidx.core.app.RemoteInput
import android.widget.Toast import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
......
...@@ -6,7 +6,7 @@ import javax.inject.Singleton ...@@ -6,7 +6,7 @@ import javax.inject.Singleton
typealias TupleGroupIdMessageCount = Pair<Int, AtomicInteger> typealias TupleGroupIdMessageCount = Pair<Int, AtomicInteger>
class GroupedPush { class GroupedPush {
// Notifications received from the same server are grouped in a single bundled notification. // Notifications received from the same server are isGrouped in a single bundled notification.
// This map associates a host to a group id. // This map associates a host to a group id.
val groupMap = HashMap<String, TupleGroupIdMessageCount>() val groupMap = HashMap<String, TupleGroupIdMessageCount>()
......
...@@ -12,10 +12,10 @@ import android.os.Build ...@@ -12,10 +12,10 @@ 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.support.annotation.RequiresApi import androidx.annotation.RequiresApi
import android.support.v4.app.NotificationCompat import androidx.core.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import android.support.v4.app.RemoteInput import androidx.core.app.RemoteInput
import android.text.Html import android.text.Html
import android.text.Spanned import android.text.Spanned
import chat.rocket.android.R import chat.rocket.android.R
...@@ -301,7 +301,7 @@ class PushManager @Inject constructor( ...@@ -301,7 +301,7 @@ class PushManager @Inject constructor(
private fun getContentIntent(context: Context, notificationId: Int, pushMessage: PushMessage, grouped: Boolean = false): PendingIntent { private fun getContentIntent(context: Context, notificationId: Int, pushMessage: PushMessage, grouped: Boolean = false): PendingIntent {
val notificationIntent = context.changeServerIntent(pushMessage.info.host, chatRoomId = pushMessage.info.roomId) val notificationIntent = context.changeServerIntent(pushMessage.info.host, chatRoomId = pushMessage.info.roomId)
// TODO - add support to go directly to the chatroom // TODO - add support to go directly to the chatroom
/*if (!grouped) { /*if (!isGrouped) {
notificationIntent.putExtra(EXTRA_ROOM_ID, pushMessage.info.roomId) notificationIntent.putExtra(EXTRA_ROOM_ID, pushMessage.info.roomId)
}*/ }*/
return PendingIntent.getActivity(context, random.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getActivity(context, random.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
......
package chat.rocket.android.server.di package chat.rocket.android.server.di
import android.arch.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
...@@ -12,6 +13,11 @@ import kotlinx.coroutines.experimental.Job ...@@ -12,6 +13,11 @@ import kotlinx.coroutines.experimental.Job
@Module @Module
class ChangeServerModule { class ChangeServerModule {
@Provides
@PerActivity
fun provideJob() = Job()
@Provides @Provides
@PerActivity @PerActivity
fun provideChangeServerNavigator(activity: ChangeServerActivity) = ChangeServerNavigator(activity) fun provideChangeServerNavigator(activity: ChangeServerActivity) = ChangeServerNavigator(activity)
......
package chat.rocket.android.server.infraestructure package chat.rocket.android.server.infraestructure
import androidx.lifecycle.MutableLiveData
import chat.rocket.android.db.DatabaseManager
import chat.rocket.common.model.BaseRoom import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.User import chat.rocket.common.model.User
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
...@@ -16,13 +18,24 @@ import chat.rocket.core.internal.realtime.unsubscribe ...@@ -16,13 +18,24 @@ import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.rest.chatRooms 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 kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.channels.SendChannel
import kotlinx.coroutines.experimental.channels.actor
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.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.math.absoluteValue
class ConnectionManager(internal val client: RocketChatClient) { class ConnectionManager(
internal val client: RocketChatClient,
private val dbManager: DatabaseManager
) {
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 var connectJob: Job? = null
...@@ -38,6 +51,9 @@ class ConnectionManager(internal val client: RocketChatClient) { ...@@ -38,6 +51,9 @@ class ConnectionManager(internal val client: RocketChatClient) {
private var userDataId: String? = null private var userDataId: String? = null
private var activeUserId: String? = null private var activeUserId: String? = null
private val activeUsersContext = newSingleThreadContext("activeUsersContext")
private val roomsContext = newSingleThreadContext("roomsContext")
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...")
...@@ -80,6 +96,8 @@ class ConnectionManager(internal val client: RocketChatClient) { ...@@ -80,6 +96,8 @@ class ConnectionManager(internal val client: RocketChatClient) {
} }
} }
statusLiveData.postValue(status)
for (channel in statusChannelList) { for (channel in statusChannelList) {
Timber.d("Sending status: $status to $channel") Timber.d("Sending status: $status to $channel")
channel.offer(status) channel.offer(status)
...@@ -87,24 +105,45 @@ class ConnectionManager(internal val client: RocketChatClient) { ...@@ -87,24 +105,45 @@ class ConnectionManager(internal val client: RocketChatClient) {
} }
} }
var totalBatchedUsers = 0
val userActor = createBatchActor<User>(activeUsersContext, parent = connectJob,
maxSize = 500, maxTime = 1000) { users ->
totalBatchedUsers += users.size
Timber.d("Processing Users batch: ${users.size} - $totalBatchedUsers")
// TODO - move this to an Interactor
dbManager.processUsersBatch(users)
}
val roomsActor = createBatchActor<StreamMessage<BaseRoom>>(roomsContext, parent = connectJob,
maxSize = 10) { batch ->
Timber.d("processing Stream batch: ${batch.size} - $batch")
dbManager.processStreamBatch(batch)
}
// stream-notify-user - ${userId}/rooms-changed
launch(parent = connectJob) { launch(parent = connectJob) {
for (room in client.roomsChannel) { for (room in client.roomsChannel) {
Timber.d("GOT Room streamed") Timber.d("GOT Room streamed")
roomsActor.send(room)
for (channel in roomAndSubscriptionChannels) { for (channel in roomAndSubscriptionChannels) {
channel.send(room) channel.send(room)
} }
} }
} }
// stream-notify-user - ${userId}/subscriptions-changed
launch(parent = connectJob) { launch(parent = connectJob) {
for (subscription in client.subscriptionsChannel) { for (subscription in client.subscriptionsChannel) {
Timber.d("GOT Subscription streamed") Timber.d("GOT Subscription streamed")
roomsActor.send(subscription)
for (channel in roomAndSubscriptionChannels) { for (channel in roomAndSubscriptionChannels) {
channel.send(subscription) channel.send(subscription)
} }
} }
} }
// stream-room-messages - $roomId
launch(parent = connectJob) { launch(parent = connectJob) {
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}")
...@@ -113,18 +152,24 @@ class ConnectionManager(internal val client: RocketChatClient) { ...@@ -113,18 +152,24 @@ class ConnectionManager(internal val client: RocketChatClient) {
} }
} }
// userData
launch(parent = connectJob) { launch(parent = connectJob) {
for (myself in client.userDataChannel) { for (myself in client.userDataChannel) {
Timber.d("Got userData") Timber.d("Got userData")
userActor.send(myself.asUser())
for (channel in userDataChannels) { for (channel in userDataChannels) {
channel.send(myself) channel.send(myself)
} }
} }
} }
var totalUsers = 0
// activeUsers
launch(parent = connectJob) { launch(parent = connectJob) {
for (user in client.activeUsersChannel) { for (user in client.activeUsersChannel) {
Timber.d("Got activeUsers") totalUsers++
//Timber.d("Got activeUsers: $totalUsers")
userActor.send(user)
for (channel in activeUsersChannels) { for (channel in activeUsersChannels) {
channel.send(user) channel.send(user)
} }
...@@ -196,6 +241,51 @@ class ConnectionManager(internal val client: RocketChatClient) { ...@@ -196,6 +241,51 @@ class ConnectionManager(internal val client: RocketChatClient) {
id?.let { client.unsubscribe(it) } id?.let { client.unsubscribe(it) }
} }
} }
private inline fun <T> createBatchActor(context: CoroutineContext = CommonPool,
parent: Job? = null,
maxSize: Int = 100,
maxTime: Int = 500,
crossinline block: (List<T>) -> Unit): SendChannel<T> {
return actor(context, parent = parent) {
val batch = ArrayList<T>(maxSize)
var deadline = 0L // deadline for sending this batch to callback block
while(true) {
// when deadline is reached or size is exceeded, pass the batch to the callback block
val remainingTime = deadline - System.currentTimeMillis()
if (batch.isNotEmpty() && remainingTime <= 0 || batch.size >= maxSize) {
Timber.d("Processing batch: ${batch.size}")
block(batch.toList())
batch.clear()
continue
}
// wait until items is received or timeout reached
select<Unit> {
// when received -> add to batch
channel.onReceive {
batch.add(it)
//Timber.d("Adding user to batch: ${batch.size}")
// init deadline on first item added to batch
if (batch.size == 1) deadline = System.currentTimeMillis() + maxTime
}
// when timeout is reached just finish select, note: no timeout when batch is empty
if (batch.isNotEmpty()) onTimeout(remainingTime.orZero()) {}
}
if (!isActive) break
}
}
}
}
private fun Myself.asUser(): User {
return User(id, name, username, status, utcOffset, null, roles)
}
private fun Long.orZero(): Long {
return if (this < 0) 0 else this
} }
suspend fun ConnectionManager.chatRooms(timestamp: Long = 0, filterCustom: Boolean = true) = suspend fun ConnectionManager.chatRooms(timestamp: Long = 0, filterCustom: Boolean = true) =
......
package chat.rocket.android.server.infraestructure package chat.rocket.android.server.infraestructure
import chat.rocket.android.db.DatabaseManagerFactory
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class ConnectionManagerFactory @Inject constructor(private val factory: RocketChatClientFactory) { class ConnectionManagerFactory @Inject constructor(
private val factory: RocketChatClientFactory,
private val dbFactory: DatabaseManagerFactory
) {
private val cache = HashMap<String, ConnectionManager>() private val cache = HashMap<String, ConnectionManager>()
fun create(url: String): ConnectionManager { fun create(url: String): ConnectionManager {
...@@ -15,7 +19,7 @@ class ConnectionManagerFactory @Inject constructor(private val factory: RocketCh ...@@ -15,7 +19,7 @@ class ConnectionManagerFactory @Inject constructor(private val factory: RocketCh
} }
Timber.d("Returning FRESH Manager for: $url") Timber.d("Returning FRESH Manager for: $url")
val manager = ConnectionManager(factory.create(url)) val manager = ConnectionManager(factory.create(url), dbFactory.create(url))
cache[url] = manager cache[url] = manager
return manager return manager
} }
......
package chat.rocket.android.server.infraestructure
import android.arch.persistence.room.*
import io.reactivex.Single
@Dao
interface ServerDao {
@Query("SELECT * FROM server")
fun getServers(): Single<List<ServerEntity>>
@Insert(onConflict = OnConflictStrategy.FAIL)
fun insertServer(serverEntity: ServerEntity)
@Update
fun updateServer(serverEntity: ServerEntity)
@Delete
fun deleteServer(serverEntity: ServerEntity)
@Query("SELECT * FROM server WHERE id = :serverId")
fun getServer(serverId: Long?): Single<ServerEntity>
}
package chat.rocket.android.server.infraestructure
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
@Entity(tableName = "server", indices = [(Index(value = ["host"], unique = true))])
data class ServerEntity(
@PrimaryKey(autoGenerate = true)
val id: Long,
val name: String,
val host: String,
val avatar: String
)
\ No newline at end of file
package chat.rocket.android.server.infraestructure
import chat.rocket.android.server.domain.model.Server
import chat.rocket.android.util.DataToDomain
class ServerEntityMapper : DataToDomain<ServerEntity, Server> {
override fun translate(data: ServerEntity): Server {
return Server(data.id, data.name, data.host, data.avatar)
}
}
...@@ -4,7 +4,7 @@ import android.app.ProgressDialog ...@@ -4,7 +4,7 @@ import android.app.ProgressDialog
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 android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import chat.rocket.android.server.presentation.ChangeServerPresenter import chat.rocket.android.server.presentation.ChangeServerPresenter
import chat.rocket.android.server.presentation.ChangeServerView import chat.rocket.android.server.presentation.ChangeServerView
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
......
package chat.rocket.android.settings.about.ui package chat.rocket.android.settings.about.ui
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
......
package chat.rocket.android.settings.di package chat.rocket.android.settings.di
import android.arch.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.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.settings.presentation.SettingsView import chat.rocket.android.settings.presentation.SettingsView
...@@ -10,19 +10,21 @@ import dagger.Provides ...@@ -10,19 +10,21 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class SettingsFragmentModule { class SettingsFragmentModule {
@Provides @Provides
@PerFragment
fun settingsView(frag: SettingsFragment): SettingsView { fun settingsView(frag: SettingsFragment): SettingsView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun settingsLifecycleOwner(frag: SettingsFragment): LifecycleOwner { fun settingsLifecycleOwner(frag: SettingsFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.settings.password.di package chat.rocket.android.settings.password.di
import android.arch.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.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.settings.password.presentation.PasswordView import chat.rocket.android.settings.password.presentation.PasswordView
...@@ -10,19 +10,26 @@ import dagger.Provides ...@@ -10,19 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
@Module @Module
@PerFragment
class PasswordFragmentModule { class PasswordFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides @Provides
@PerFragment
fun passwordView(frag: PasswordFragment): PasswordView { fun passwordView(frag: PasswordFragment): PasswordView {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun settingsLifecycleOwner(frag: PasswordFragment): LifecycleOwner { fun settingsLifecycleOwner(frag: PasswordFragment): LifecycleOwner {
return frag return frag
} }
@Provides @Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
......
package chat.rocket.android.settings.password.di package chat.rocket.android.settings.password.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.settings.password.ui.PasswordFragment import chat.rocket.android.settings.password.ui.PasswordFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -7,5 +8,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -7,5 +8,6 @@ import dagger.android.ContributesAndroidInjector
@Module @Module
abstract class PasswordFragmentProvider { abstract class PasswordFragmentProvider {
@ContributesAndroidInjector(modules = [PasswordFragmentModule::class]) @ContributesAndroidInjector(modules = [PasswordFragmentModule::class])
@PerFragment
abstract fun providePasswordFragment(): PasswordFragment abstract fun providePasswordFragment(): PasswordFragment
} }
\ No newline at end of file
package chat.rocket.android.settings.password.ui package chat.rocket.android.settings.password.ui
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.util.extensions.addFragment import chat.rocket.android.util.extensions.addFragment
......
package chat.rocket.android.settings.password.ui package chat.rocket.android.settings.password.ui
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.view.* import android.view.*
import android.widget.Toast import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
...@@ -10,7 +10,7 @@ import chat.rocket.android.settings.password.presentation.PasswordView ...@@ -10,7 +10,7 @@ import chat.rocket.android.settings.password.presentation.PasswordView
import chat.rocket.android.util.extensions.asObservable import chat.rocket.android.util.extensions.asObservable
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import android.support.v7.view.ActionMode import androidx.appcompat.view.ActionMode
import chat.rocket.android.util.extensions.ui import chat.rocket.android.util.extensions.ui
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
...@@ -19,7 +19,7 @@ import io.reactivex.rxkotlin.Observables ...@@ -19,7 +19,7 @@ import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.fragment_password.* import kotlinx.android.synthetic.main.fragment_password.*
import javax.inject.Inject import javax.inject.Inject
class PasswordFragment: Fragment(), PasswordView, android.support.v7.view.ActionMode.Callback { class PasswordFragment: Fragment(), PasswordView, ActionMode.Callback {
@Inject lateinit var presenter: PasswordPresenter @Inject lateinit var presenter: PasswordPresenter
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private val disposables = CompositeDisposable() private val disposables = CompositeDisposable()
......
...@@ -2,8 +2,8 @@ package chat.rocket.android.settings.ui ...@@ -2,8 +2,8 @@ package chat.rocket.android.settings.ui
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
...@@ -8,7 +8,7 @@ import android.content.Context ...@@ -8,7 +8,7 @@ import android.content.Context
import android.os.Build import android.os.Build
import android.os.VibrationEffect import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.view.View import android.view.View
import android.view.ViewAnimationUtils import android.view.ViewAnimationUtils
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
......
package chat.rocket.android.util.extensions
import android.content.Context
import org.threeten.bp.LocalDateTime
fun LocalDateTime?.date(context: Context): String? {
return this?.let {
DateTimeHelper.getDate(it, context)
}
}
\ No newline at end of file
package chat.rocket.android.util.extensions package chat.rocket.android.util.extensions
import android.os.Looper import android.os.Looper
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v4.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
......
package chat.rocket.android.util.extensions
import org.threeten.bp.LocalDateTime
fun Long?.localDateTime(): LocalDateTime? {
return this?.let {
DateTimeHelper.getLocalDateTime(it)
}
}
\ No newline at end of file
...@@ -60,4 +60,8 @@ fun String.parseColor(): Int { ...@@ -60,4 +60,8 @@ fun String.parseColor(): Int {
Timber.e(exception) Timber.e(exception)
Color.parseColor("white") Color.parseColor("white")
} }
}
fun String.userId(userId: String?): String? {
return userId?.let { this.replace(it, "") }
} }
\ No newline at end of file
...@@ -93,4 +93,10 @@ var TextView.content: CharSequence? ...@@ -93,4 +93,10 @@ var TextView.content: CharSequence?
} }
Markwon.scheduleDrawables(this) Markwon.scheduleDrawables(this)
Markwon.scheduleTableRows(this) Markwon.scheduleTableRows(this)
}
var TextView.spanned: CharSequence?
get() = text
set(value) {
text = spanned
} }
\ No newline at end of file
...@@ -2,12 +2,12 @@ package chat.rocket.android.util.extensions ...@@ -2,12 +2,12 @@ package chat.rocket.android.util.extensions
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.support.annotation.LayoutRes import androidx.annotation.LayoutRes
import android.support.annotation.StringRes import androidx.annotation.StringRes
import android.support.v4.app.Fragment import androidx.fragment.app.Fragment
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
package chat.rocket.android.util.extensions package chat.rocket.android.util.extensions
import android.net.Uri import android.net.Uri
import android.support.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import android.support.v4.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import android.view.View import android.view.View
import chat.rocket.android.R import chat.rocket.android.R
import timber.log.Timber import timber.log.Timber
......
package chat.rocket.android.util.livedata
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import kotlin.coroutines.experimental.CoroutineContext
class TransformedLiveData<Source, Output>(
private val runContext: CoroutineContext = CommonPool,
private val source: LiveData<Source>,
private val transformation: (Source?) -> Output?)
: LiveData<Output>() {
private var job: Job? = null
private val observer = Observer<Source> { source ->
job?.cancel()
job = launch(runContext) {
transformation(source)?.let { transformed ->
// Could have used postValue instead, but using the UI context I can guarantee that
// a canceled job will never emit values.
withContext(UI) {
value = transformed
}
}
}
}
override fun onActive() {
source.observeForever(observer)
}
override fun onInactive() {
job?.cancel()
source.removeObserver(observer)
}
}
fun <Source, Output> LiveData<Source>.transform(
runContext: CoroutineContext = CommonPool,
transformation: (Source?) -> Output?) = TransformedLiveData(runContext, this, transformation)
\ No newline at end of file
...@@ -5,7 +5,7 @@ import android.app.Activity ...@@ -5,7 +5,7 @@ import android.app.Activity
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 android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.webkit.CookieManager import android.webkit.CookieManager
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
......
...@@ -6,7 +6,7 @@ import android.content.Context ...@@ -6,7 +6,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.webkit.CookieManager import android.webkit.CookieManager
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
......
...@@ -4,7 +4,7 @@ import android.annotation.SuppressLint ...@@ -4,7 +4,7 @@ import android.annotation.SuppressLint
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 android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import chat.rocket.android.R import chat.rocket.android.R
......
...@@ -3,9 +3,9 @@ package chat.rocket.android.widget ...@@ -3,9 +3,9 @@ package chat.rocket.android.widget
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.annotation.DrawableRes import androidx.annotation.DrawableRes
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
/** /**
* Adds a default or custom divider to specific item views from the adapter's data set. * Adds a default or custom divider to specific item views from the adapter's data set.
......
package chat.rocket.android.widget.autocompletion.ui package chat.rocket.android.widget.autocompletion.ui
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.widget.autocompletion.model.SuggestionModel
......
package chat.rocket.android.widget.autocompletion.ui package chat.rocket.android.widget.autocompletion.ui
import android.content.Context import android.content.Context
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.util.AttributeSet import android.util.AttributeSet
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.WindowManager import android.view.WindowManager
...@@ -11,9 +11,9 @@ import chat.rocket.android.R ...@@ -11,9 +11,9 @@ import chat.rocket.android.R
internal class PopupRecyclerView : RecyclerView { internal class PopupRecyclerView : RecyclerView {
private var displayWidth: Int = 0 private var displayWidth: Int = 0
constructor(context: Context?) : this(context, null) constructor(context: Context) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) { constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
val wm = context!!.getSystemService(Context.WINDOW_SERVICE) as WindowManager val wm = context!!.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = wm.defaultDisplay val display = wm.defaultDisplay
val size = DisplayMetrics() val size = DisplayMetrics()
......
package chat.rocket.android.widget.autocompletion.ui package chat.rocket.android.widget.autocompletion.ui
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCompletionStrategy import chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCompletionStrategy
......
...@@ -4,13 +4,13 @@ import android.content.Context ...@@ -4,13 +4,13 @@ import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.support.annotation.DrawableRes import androidx.annotation.DrawableRes
import android.support.transition.Slide import androidx.transition.Slide
import android.support.transition.TransitionManager import androidx.transition.TransitionManager
import android.support.v4.content.ContextCompat import androidx.core.content.ContextCompat
import android.support.v7.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.text.Editable import android.text.Editable
import android.text.InputType import android.text.InputType
import android.text.TextWatcher import android.text.TextWatcher
...@@ -46,8 +46,7 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -46,8 +46,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr, 0) { constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr, 0) {
recyclerView = RecyclerView(context) recyclerView = RecyclerView(context)
val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, val layoutManager = LinearLayoutManager(context)
false)
recyclerView.itemAnimator = DefaultItemAnimator() recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.addItemDecoration(TopItemDecoration(context, R.drawable.suggestions_menu_decorator)) recyclerView.addItemDecoration(TopItemDecoration(context, R.drawable.suggestions_menu_decorator))
recyclerView.layoutManager = layoutManager recyclerView.layoutManager = layoutManager
......
package chat.rocket.android.widget.emoji package chat.rocket.android.widget.emoji
import android.support.v4.view.PagerAdapter import androidx.viewpager.widget.PagerAdapter
import android.support.v7.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import android.support.v7.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
......
package chat.rocket.android.widget.emoji package chat.rocket.android.widget.emoji
import android.content.Context import android.content.Context
import android.support.v7.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatEditText
import android.util.AttributeSet import android.util.AttributeSet
import android.view.KeyEvent import android.view.KeyEvent
...@@ -16,7 +16,7 @@ class ComposerEditText : AppCompatEditText { ...@@ -16,7 +16,7 @@ class ComposerEditText : AppCompatEditText {
isLongClickable = true isLongClickable = true
} }
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, android.support.v7.appcompat.R.attr.editTextStyle) constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, androidx.appcompat.R.attr.editTextStyle)
constructor(context: Context) : this(context, null) constructor(context: Context) : this(context, null)
......
package chat.rocket.android.widget.emoji package chat.rocket.android.widget.emoji
import android.support.annotation.DrawableRes import androidx.annotation.DrawableRes
import android.text.SpannableString import android.text.SpannableString
import android.text.Spanned import android.text.Spanned
import chat.rocket.android.R import chat.rocket.android.R
......
package chat.rocket.android.widget.emoji package chat.rocket.android.widget.emoji
import android.content.Context import android.content.Context
import android.support.design.widget.TabLayout import com.google.android.material.tabs.TabLayout
import android.support.v4.view.ViewPager import androidx.viewpager.widget.ViewPager
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.KeyEvent import android.view.KeyEvent
......
...@@ -3,8 +3,8 @@ package chat.rocket.android.widget.emoji ...@@ -3,8 +3,8 @@ package chat.rocket.android.widget.emoji
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.TabLayout import com.google.android.material.tabs.TabLayout
import android.support.v4.view.ViewPager import androidx.viewpager.widget.ViewPager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
......
...@@ -119,17 +119,12 @@ object EmojiRepository { ...@@ -119,17 +119,12 @@ object EmojiRepository {
*/ */
fun shortnameToUnicode(input: CharSequence, removeIfUnsupported: Boolean): String { fun shortnameToUnicode(input: CharSequence, removeIfUnsupported: Boolean): String {
val matcher = SHORTNAME_PATTERN.matcher(input) val matcher = SHORTNAME_PATTERN.matcher(input)
val supported = Build.VERSION.SDK_INT >= 16
var result: String = input.toString() var result: String = input.toString()
while (matcher.find()) { while (matcher.find()) {
val unicode = shortNameToUnicode.get(":${matcher.group(1)}:") ?: continue val unicode = shortNameToUnicode.get(":${matcher.group(1)}:") ?: continue
if (supported) { result = result.replace(":" + matcher.group(1) + ":", unicode)
result = result.replace(":" + matcher.group(1) + ":", unicode)
} else if (!supported && removeIfUnsupported) {
result = result.replace(":" + matcher.group(1) + ":", "")
}
} }
return result return result
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportHeight="12"
android:viewportWidth="12">
<path
android:fillColor="#787878"
android:fillType="evenOdd"
android:pathData="M2.4,0h1.2v12h-1.2z" />
<path
android:fillColor="#787878"
android:fillType="evenOdd"
android:pathData="M0,2.4h12v1.2h-12z" />
<path
android:fillColor="#787878"
android:fillType="evenOdd"
android:pathData="M0,8.4h12v1.2h-12z" />
<path
android:fillColor="#787878"
android:fillType="evenOdd"
android:pathData="M8.4,0h1.2v12h-1.2z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportHeight="12"
android:viewportWidth="12">
<path
android:fillColor="#DE000000"
android:fillType="evenOdd"
android:pathData="M2.4,0h1.2v12h-1.2z" />
<path
android:fillColor="#DE000000"
android:fillType="evenOdd"
android:pathData="M0,2.4h12v1.2h-12z" />
<path
android:fillColor="#DE000000"
android:fillType="evenOdd"
android:pathData="M0,8.4h12v1.2h-12z" />
<path
android:fillColor="#DE000000"
android:fillType="evenOdd"
android:pathData="M8.4,0h1.2v12h-1.2z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M1.5,5.5h9v6h-9z"
android:strokeWidth="1"
android:strokeColor="#787878"
android:fillType="evenOdd"/>
<path
android:pathData="M2.5,5.5L9.5,5.5L9.5,4C9.5,2.067 7.933,0.5 6,0.5C4.067,0.5 2.5,2.067 2.5,4L2.5,5.5Z"
android:strokeWidth="1"
android:strokeColor="#787878"
android:fillType="evenOdd"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:pathData="M1.5,5.5h9v6h-9z"
android:strokeWidth="1"
android:strokeColor="#DE000000"
android:fillType="evenOdd"/>
<path
android:pathData="M2.5,5.5L9.5,5.5L9.5,4C9.5,2.067 7.933,0.5 6,0.5C4.067,0.5 2.5,2.067 2.5,4L2.5,5.5Z"
android:strokeWidth="1"
android:strokeColor="#DE000000"
android:fillType="evenOdd"/>
</vector>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
...@@ -50,4 +50,4 @@ ...@@ -50,4 +50,4 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:textColor="@color/colorSecondaryText"/> android:textColor="@color/colorSecondaryText"/>
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
...@@ -29,14 +29,14 @@ ...@@ -29,14 +29,14 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start"> android:layout_gravity="start">
<android.support.design.widget.NavigationView <com.google.android.material.navigation.NavigationView
android:id="@+id/view_navigation" android:id="@+id/view_navigation"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
app:headerLayout="@layout/nav_header" app:headerLayout="@layout/nav_header"
app:menu="@menu/navigation" /> app:menu="@menu/navigation" />
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/accounts_list" android:id="@+id/accounts_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
...@@ -47,4 +47,4 @@ ...@@ -47,4 +47,4 @@
android:visibility="gone" /> android:visibility="gone" />
</FrameLayout> </FrameLayout>
</android.support.v4.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/colorPrimary" android:background="@color/colorPrimary"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"> android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
...@@ -14,4 +14,4 @@ ...@@ -14,4 +14,4 @@
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
</android.support.design.widget.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
android:background="@color/colorPrimary" android:background="@color/colorPrimary"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"> android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
...@@ -27,6 +27,6 @@ ...@@ -27,6 +27,6 @@
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="general" /> tools:text="general" />
</android.support.v7.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
</android.support.design.widget.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
android:background="@color/colorPrimary" android:background="@color/colorPrimary"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"> android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<android.support.v7.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
...@@ -30,6 +30,6 @@ ...@@ -30,6 +30,6 @@
android:textStyle="bold" android:textStyle="bold"
tools:text="@string/title_password" /> tools:text="@string/title_password" />
</RelativeLayout> </RelativeLayout>
</android.support.v7.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
</android.support.design.widget.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
...@@ -14,4 +14,4 @@ ...@@ -14,4 +14,4 @@
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:roundedCornerRadius="2dp" /> app:roundedCornerRadius="2dp" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/emojiRecyclerView" android:id="@+id/emojiRecyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
...@@ -25,4 +25,4 @@ ...@@ -25,4 +25,4 @@
android:textSize="16sp" android:textSize="16sp"
android:visibility="gone"/> android:visibility="gone"/>
</android.support.design.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/emoji_keyboard_container" android:id="@+id/emoji_keyboard_container"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -59,4 +59,4 @@ ...@@ -59,4 +59,4 @@
android:src="@drawable/ic_backspace_gray_24dp" /> android:src="@drawable/ic_backspace_gray_24dp" />
</RelativeLayout> </RelativeLayout>
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
android:background="@color/colorWhite" android:background="@color/colorWhite"
android:orientation="vertical"> android:orientation="vertical">
<android.support.design.widget.TabLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/tabs" android:id="@+id/tabs"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
app:tabMaxWidth="48dp" app:tabMaxWidth="48dp"
app:tabMode="scrollable" /> app:tabMode="scrollable" />
<android.support.v4.view.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/pager_categories" android:id="@+id/pager_categories"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
android:fillViewport="true" android:fillViewport="true"
tools:context=".authentication.login.ui.LoginFragment"> tools:context=".authentication.login.ui.LoginFragment">
<android.support.constraint.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
...@@ -220,7 +220,7 @@ ...@@ -220,7 +220,7 @@
tools:visibility="gone" /> tools:visibility="gone" />
</LinearLayout> </LinearLayout>
<android.support.design.widget.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/button_fab" android:id="@+id/button_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -243,6 +243,6 @@ ...@@ -243,6 +243,6 @@
android:text="@string/title_log_in" android:text="@string/title_log_in"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" /> app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </ScrollView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout" android:id="@+id/root_layout"
...@@ -144,4 +144,4 @@ ...@@ -144,4 +144,4 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" /> tools:visibility="visible" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".chatrooms.ui.ChatRoomsFragment"> tools:context=".chatrooms.ui.ChatRoomsFragment">
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".favoritemessages.ui.FavoriteMessagesFragment"> tools:context=".favoritemessages.ui.FavoriteMessagesFragment">
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_no_favorite_messages" /> app:layout_constraintTop_toBottomOf="@+id/text_no_favorite_messages" />
<android.support.constraint.Group <androidx.constraintlayout.widget.Group
android:id="@+id/no_messages_view" android:id="@+id/no_messages_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -74,4 +74,4 @@ ...@@ -74,4 +74,4 @@
app:constraint_referenced_ids="text_no_favorite_messages_description,image_star,text_no_favorite_messages" app:constraint_referenced_ids="text_no_favorite_messages_description,image_star,text_no_favorite_messages"
tools:visibility="visible" /> tools:visibility="visible" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout" android:id="@+id/root_layout"
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".files.ui.FilesFragment"> tools:context=".files.ui.FilesFragment">
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
...@@ -72,4 +72,4 @@ ...@@ -72,4 +72,4 @@
app:constraint_referenced_ids="image_file,text_no_file,text_all_files_appear_here" app:constraint_referenced_ids="image_file,text_no_file,text_all_files_appear_here"
tools:visibility="visible" /> tools:visibility="visible" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/member_bottom_sheet" android:id="@+id/member_bottom_sheet"
...@@ -85,4 +85,4 @@ ...@@ -85,4 +85,4 @@
app:layout_constraintTop_toBottomOf="@+id/text_utc" app:layout_constraintTop_toBottomOf="@+id/text_utc"
tools:text="+01:00" /> tools:text="+01:00" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".members.ui.MembersFragment"> tools:context=".members.ui.MembersFragment">
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
...@@ -21,4 +21,4 @@ ...@@ -21,4 +21,4 @@
app:indicatorColor="@color/colorBlack" app:indicatorColor="@color/colorBlack"
app:indicatorName="BallPulseIndicator" /> app:indicatorName="BallPulseIndicator" />
</android.support.design.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<android.support.design.widget.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_new_password" android:id="@+id/layout_new_password"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -22,9 +22,9 @@ ...@@ -22,9 +22,9 @@
android:hint="@string/msg_new_password" android:hint="@string/msg_new_password"
android:inputType="textPassword" android:inputType="textPassword"
/> />
</android.support.design.widget.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<android.support.design.widget.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_confirm_password" android:id="@+id/layout_confirm_password"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/msg_confirm_password" android:hint="@string/msg_confirm_password"
android:inputType="textPassword" /> android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.wang.avi.AVLoadingIndicatorView <com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading" android:id="@+id/view_loading"
...@@ -54,4 +54,4 @@ ...@@ -54,4 +54,4 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".pinnedmessages.ui.PinnedMessagesFragment"> tools:context=".pinnedmessages.ui.PinnedMessagesFragment">
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_pinned" android:id="@+id/recycler_view_pinned"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_pin_title" /> app:layout_constraintTop_toBottomOf="@id/tv_pin_title" />
<android.support.constraint.Group <androidx.constraintlayout.widget.Group
android:id="@+id/pin_view" android:id="@+id/pin_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -76,4 +76,4 @@ ...@@ -76,4 +76,4 @@
app:constraint_referenced_ids="tv_pin_description,iv_pin_icon,tv_pin_title" app:constraint_referenced_ids="tv_pin_description,iv_pin_icon,tv_pin_title"
tools:visibility="visible" /> tools:visibility="visible" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -43,4 +43,4 @@ ...@@ -43,4 +43,4 @@
app:layout_constraintStart_toEndOf="@id/server_logo" app:layout_constraintStart_toEndOf="@id/server_logo"
tools:text="Lucio Maciel" /> tools:text="Lucio Maciel" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -26,4 +26,4 @@ ...@@ -26,4 +26,4 @@
app:layout_constraintStart_toEndOf="@id/server_logo" app:layout_constraintStart_toEndOf="@id/server_logo"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/author_attachment_container" android:id="@+id/author_attachment_container"
...@@ -62,4 +62,4 @@ ...@@ -62,4 +62,4 @@
app:layout_constraintStart_toStartOf="@id/quote_bar" app:layout_constraintStart_toStartOf="@id/quote_bar"
app:layout_constraintTop_toBottomOf="@id/text_fields" /> app:layout_constraintTop_toBottomOf="@id/text_fields" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -27,8 +27,7 @@ ...@@ -27,8 +27,7 @@
app:layout_constraintBottom_toBottomOf="@+id/text_chat_name" app:layout_constraintBottom_toBottomOf="@+id/text_chat_name"
app:layout_constraintStart_toEndOf="@+id/image_avatar" app:layout_constraintStart_toEndOf="@+id/image_avatar"
app:layout_constraintTop_toTopOf="@+id/text_chat_name" app:layout_constraintTop_toTopOf="@+id/text_chat_name"
tools:src="@drawable/ic_hashtag_black_12dp" /> tools:src="@drawable/ic_hashtag_unread_12dp" />
<TextView <TextView
android:id="@+id/text_last_message" android:id="@+id/text_last_message"
...@@ -58,6 +57,7 @@ ...@@ -58,6 +57,7 @@
android:lines="1" android:lines="1"
android:maxLines="1" android:maxLines="1"
android:textDirection="locale" android:textDirection="locale"
android:textColor="@color/colorSecondaryText"
app:layout_constraintBottom_toTopOf="@+id/text_last_message" app:layout_constraintBottom_toTopOf="@+id/text_last_message"
app:layout_constraintEnd_toStartOf="@+id/text_last_message_date_time" app:layout_constraintEnd_toStartOf="@+id/text_last_message_date_time"
app:layout_constraintStart_toEndOf="@+id/image_chat_icon" app:layout_constraintStart_toEndOf="@+id/image_chat_icon"
...@@ -76,7 +76,6 @@ ...@@ -76,7 +76,6 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="2" android:maxLines="2"
android:textDirection="locale" android:textDirection="locale"
tools:visibility="visible"
app:layout_constraintEnd_toStartOf="@+id/layout_unread_messages_badge" app:layout_constraintEnd_toStartOf="@+id/layout_unread_messages_badge"
app:layout_constraintTop_toTopOf="@+id/text_chat_name" app:layout_constraintTop_toTopOf="@+id/text_chat_name"
tools:text="11:45 AM" /> tools:text="11:45 AM" />
...@@ -89,4 +88,4 @@ ...@@ -89,4 +88,4 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/text_chat_name" /> app:layout_constraintTop_toTopOf="@+id/text_chat_name" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<View <!--<View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="@color/darkGray" /> android:background="@color/darkGray" />-->
<TextView <TextView
android:id="@+id/text_chatroom_header" android:id="@+id/text_chatroom_header"
...@@ -22,6 +22,6 @@ ...@@ -22,6 +22,6 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="@color/darkGray" /> android:background="@color/quoteBar" />
</LinearLayout> </LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/color_attachment_container" android:id="@+id/color_attachment_container"
...@@ -41,4 +41,4 @@ ...@@ -41,4 +41,4 @@
app:layout_constraintStart_toStartOf="@id/quote_bar" app:layout_constraintStart_toStartOf="@id/quote_bar"
app:layout_constraintTop_toBottomOf="@id/attachment_text" /> app:layout_constraintTop_toBottomOf="@id/attachment_text" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/file_attachment_container" android:id="@+id/file_attachment_container"
...@@ -35,4 +35,4 @@ ...@@ -35,4 +35,4 @@
app:layout_constraintStart_toStartOf="@id/text_file_name" app:layout_constraintStart_toStartOf="@id/text_file_name"
app:layout_constraintTop_toBottomOf="@id/text_file_name" /> app:layout_constraintTop_toBottomOf="@id/text_file_name" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -29,4 +29,4 @@ ...@@ -29,4 +29,4 @@
app:layout_constraintTop_toTopOf="@+id/layout_avatar" app:layout_constraintTop_toTopOf="@+id/layout_avatar"
tools:text="Ronald Perkins" /> tools:text="Ronald Perkins" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/message_container" android:id="@+id/message_container"
...@@ -123,4 +123,4 @@ ...@@ -123,4 +123,4 @@
app:layout_constraintStart_toStartOf="@+id/text_content" app:layout_constraintStart_toStartOf="@+id/text_content"
app:layout_constraintTop_toBottomOf="@+id/text_content" /> app:layout_constraintTop_toBottomOf="@+id/text_content" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/attachment_container" android:id="@+id/attachment_container"
...@@ -64,4 +64,4 @@ ...@@ -64,4 +64,4 @@
app:layout_constraintStart_toStartOf="@+id/quote_bar" app:layout_constraintStart_toStartOf="@+id/quote_bar"
app:layout_constraintTop_toBottomOf="@+id/text_content" /> app:layout_constraintTop_toBottomOf="@+id/text_content" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view_reactions" android:id="@+id/recycler_view_reactions"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -61,4 +61,4 @@ ...@@ -61,4 +61,4 @@
app:layout_constraintStart_toEndOf="@+id/image_view_action_cancel_quote" app:layout_constraintStart_toEndOf="@+id/image_view_action_cancel_quote"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Edit message" /> tools:text="Edit message" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
android:orientation="vertical" android:orientation="vertical"
android:background="@color/default_background"> android:background="@color/default_background">
<android.support.constraint.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/composer" android:id="@+id/composer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
android:visibility="gone" /> android:visibility="gone" />
</LinearLayout> </LinearLayout>
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout <FrameLayout
android:id="@+id/emoji_fragment_placeholder" android:id="@+id/emoji_fragment_placeholder"
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scrollbars="vertical" /> android:scrollbars="vertical" />
<android.support.design.widget.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/button_fab" android:id="@+id/button_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -24,4 +24,4 @@ ...@@ -24,4 +24,4 @@
app:layout_anchor="@id/recycler_view" app:layout_anchor="@id/recycler_view"
app:layout_anchorGravity="bottom|end" /> app:layout_anchorGravity="bottom|end" />
</android.support.design.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/url_preview_layout" android:id="@+id/url_preview_layout"
...@@ -55,4 +55,4 @@ ...@@ -55,4 +55,4 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/image_preview" app:layout_constraintStart_toStartOf="@+id/image_preview"
app:layout_constraintTop_toBottomOf="@+id/text_description" /> app:layout_constraintTop_toBottomOf="@+id/text_description" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
app:roundedCornerRadius="3dp" app:roundedCornerRadius="3dp"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<android.support.constraint.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/account_container" android:id="@+id/account_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
...@@ -83,6 +83,6 @@ ...@@ -83,6 +83,6 @@
android:tint="@color/colorWhite" android:tint="@color/colorWhite"
app:layout_constraintBottom_toBottomOf="@+id/text_server_url" app:layout_constraintBottom_toBottomOf="@+id/text_server_url"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -53,4 +53,4 @@ ...@@ -53,4 +53,4 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" /> tools:text="@tools:sample/full_names" />
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
android:id="@+id/action_search" android:id="@+id/action_search"
android:icon="@drawable/ic_search_white_24px" android:icon="@drawable/ic_search_white_24px"
android:title="@string/action_search" android:title="@string/action_search"
app:actionViewClass="android.support.v7.widget.SearchView" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" /> app:showAsAction="ifRoom|collapseActionView" />
<item <item
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
android:title="@string/menu_chatroom_sort" android:title="@string/menu_chatroom_sort"
app:showAsAction="always" /> app:showAsAction="always" />
</menu> </menu>
\ No newline at end of file
...@@ -10,12 +10,11 @@ buildscript { ...@@ -10,12 +10,11 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.2' classpath 'com.android.tools.build:gradle:3.2.0-alpha17'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.2.0' classpath 'com.google.gms:google-services:3.2.0'
classpath 'io.fabric.tools:gradle:1.25.1' classpath 'io.fabric.tools:gradle:1.25.4'
classpath "io.realm:realm-gradle-plugin:5.0.0"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
......
ext { ext {
versions = [ versions = [
java : JavaVersion.VERSION_1_8, java : JavaVersion.VERSION_1_8,
compileSdk : 27, compileSdk : 28,
targetSdk : 27, targetSdk : 27,
buildTools : '27.0.3', buildTools : '28.0.0-rc2',
kotlin : '1.2.41', kotlin : '1.2.41',
coroutine : '0.22.5', coroutine : '0.22.5',
dokka : '0.9.16', dokka : '0.9.16',
// Main dependencies // Main dependencies
support : '27.1.1', appCompat : '1.0.0-alpha1',
recyclerview : '1.0.0-alpha1',
material : '1.0.0-alpha1',
cardview : '1.0.0-alpha1',
browser : '1.0.0-alpha1',
constraintLayout : '1.1.0', constraintLayout : '1.1.0',
androidKtx : '0.3', androidKtx : '1.0.0-alpha1',
dagger : '2.14.1', dagger : '2.16',
exoPlayer : '2.6.0', exoPlayer : '2.6.0',
playServices : '15.0.0', playServices : '15.0.0',
firebase : '15.0.0', firebase : '15.0.0',
room : '1.0.0', room : '2.0.0-alpha1',
lifecycle : '1.1.1', lifecycle : '2.0.0-alpha1',
rxKotlin : '2.2.0', rxKotlin : '2.2.0',
rxAndroid : '2.0.2', rxAndroid : '2.0.2',
moshi : '1.6.0-SNAPSHOT', moshi : '1.6.0',
okhttp : '3.10.0', okhttp : '3.10.0',
timber : '4.7.0', timber : '4.7.0',
threeTenABP : '1.0.5', threeTenABP : '1.0.5',
...@@ -29,14 +33,14 @@ ext { ...@@ -29,14 +33,14 @@ ext {
kotshi : '1.0.2', kotshi : '1.0.2',
frescoImageViewer : '0.5.1', frescoImageViewer : '0.5.1',
markwon : '1.0.3', markwon : '1.0.3',
sheetMenu : '1.3.3', sheetMenu : '5ff79ccf14',
aVLoadingIndicatorView: '2.1.3', aVLoadingIndicatorView: '2.1.3',
flexbox : '0.3.2', flexbox : '0.3.2',
// For testing // For testing
junit : '4.12', junit : '4.12',
truth : '0.36', truth : '0.36',
espresso : '3.0.2', espresso : '3.1.0-alpha2',
mockito : '2.10.0', mockito : '2.10.0',
//For wearable //For wearable
...@@ -49,14 +53,13 @@ ext { ...@@ -49,14 +53,13 @@ ext {
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}", coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutine}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}", coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
appCompat : "com.android.support:appcompat-v7:${versions.support}", appCompat : "androidx.appcompat:appcompat:${versions.appCompat}",
annotations : "com.android.support:support-annotations:${versions.support}", recyclerview : "androidx.recyclerview:recyclerview:${versions.recyclerview}",
recyclerview : "com.android.support:recyclerview-v7:${versions.support}", material : "com.google.android.material:material:${versions.material}",
design : "com.android.support:design:${versions.support}", constraintlayout : "androidx.constraintlayout:constraintlayout:${versions.constraintLayout}",
constraintLayout : "com.android.support.constraint:constraint-layout:${versions.constraintLayout}", cardview : "androidx.cardview:cardview:${versions.cardview}",
cardView : "com.android.support:cardview-v7:${versions.support}",
flexbox : "com.google.android:flexbox:${versions.flexbox}", flexbox : "com.google.android:flexbox:${versions.flexbox}",
customTabs : "com.android.support:customtabs:${versions.support}", browser : "androidx.browser:browser:${versions.browser}",
androidKtx : "androidx.core:core-ktx:${versions.androidKtx}", androidKtx : "androidx.core:core-ktx:${versions.androidKtx}",
...@@ -68,11 +71,10 @@ ext { ...@@ -68,11 +71,10 @@ ext {
playServicesAuth : "com.google.android.gms:play-services-auth:${versions.playServices}", playServicesAuth : "com.google.android.gms:play-services-auth:${versions.playServices}",
exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}", exoPlayer : "com.google.android.exoplayer:exoplayer:${versions.exoPlayer}",
room : "android.arch.persistence.room:runtime:${versions.room}", room : "androidx.room:room-runtime:${versions.room}",
roomProcessor : "android.arch.persistence.room:compiler:${versions.room}", roomProcessor : "androidx.room:room-compiler:${versions.room}",
roomRxjava : "android.arch.persistence.room:rxjava2:${versions.room}", lifecycleExtensions : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycle}",
lifecycleExtensions : "android.arch.lifecycle:extensions:${versions.lifecycle}", lifecycleCompiler : "androidx.lifecycle:lifecycle-compiler:${versions.lifecycle}",
lifecycleCompiler : "android.arch.lifecycle:compiler:${versions.lifecycle}",
rxKotlin : "io.reactivex.rxjava2:rxkotlin:${versions.rxKotlin}", rxKotlin : "io.reactivex.rxjava2:rxkotlin:${versions.rxKotlin}",
rxAndroid : "io.reactivex.rxjava2:rxandroid:${versions.rxAndroid}", rxAndroid : "io.reactivex.rxjava2:rxandroid:${versions.rxAndroid}",
...@@ -99,10 +101,12 @@ ext { ...@@ -99,10 +101,12 @@ ext {
markwon : "ru.noties:markwon:${versions.markwon}", markwon : "ru.noties:markwon:${versions.markwon}",
sheetMenu : "com.github.whalemare:sheetmenu:${versions.sheetMenu}", //sheetMenu : "com.github.whalemare:sheetmenu:${versions.sheetMenu}",
sheetMenu : "com.github.luciofm:sheetmenu:${versions.sheetMenu}",
aVLoadingIndicatorView: "com.wang.avi:library:${versions.aVLoadingIndicatorView}", aVLoadingIndicatorView: "com.wang.avi:library:${versions.aVLoadingIndicatorView}",
//For the wear app //For the wear app
wearable : "com.google.android.support:wearable:${versions.wear}", wearable : "com.google.android.support:wearable:${versions.wear}",
playServicesWearable : "com.google.android.gms:play-services-wearable:${versions.playServicesWearable}", playServicesWearable : "com.google.android.gms:play-services-wearable:${versions.playServicesWearable}",
...@@ -113,8 +117,8 @@ ext { ...@@ -113,8 +117,8 @@ ext {
// For testing // For testing
junit : "junit:junit:$versions.junit", junit : "junit:junit:$versions.junit",
espressoCore : "com.android.support.test.espresso:espresso-core:${versions.espresso}", espressoCore : "androidx.test.espresso:espresso-core:${versions.espresso}",
espressoIntents : "com.android.support.test.espresso:espresso-intents:${versions.espresso}", espressoIntents : "androidx.test.espresso:espresso-intents:${versions.espresso}",
roomTest : "android.arch.persistence.room:testing:${versions.room}", roomTest : "android.arch.persistence.room:testing:${versions.room}",
truth : "com.google.truth:truth:$versions.truth" truth : "com.google.truth:truth:$versions.truth"
] ]
......
...@@ -9,9 +9,13 @@ ...@@ -9,9 +9,13 @@
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
android.injected.testOnly=false
...@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android' ...@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
android { android {
compileSdkVersion versions.compileSdk compileSdkVersion 'android-P'
buildToolsVersion versions.buildTools buildToolsVersion versions.buildTools
defaultConfig { defaultConfig {
...@@ -11,7 +11,7 @@ android { ...@@ -11,7 +11,7 @@ android {
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 1 versionCode 1
versionName "1.0.0" versionName "1.0.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
......
...@@ -4,7 +4,7 @@ import android.content.Context ...@@ -4,7 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.view.View import android.view.View
import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory import com.google.android.exoplayer2.DefaultRenderersFactory
......
include ':app', ':player', ':wear' include ':app', ':player' //, ':wear'
\ No newline at end of file \ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment