Commit 60009b87 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Merge branch 'develop' into feature/create-channel

parents 2921363b 38fc3787
......@@ -93,7 +93,7 @@ jobs:
- run:
name: Build APK
command: |
./gradlew assembleRelease --quiet --console=plain --stacktrace
./gradlew assembleRelease --info --console=plain --stacktrace
- store_artifacts:
path: app/build/outputs/apk
destination: apks
......
......@@ -3,7 +3,6 @@ apply plugin: 'io.fabric'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
android {
compileSdkVersion versions.compileSdk
......@@ -13,9 +12,9 @@ android {
applicationId "chat.rocket.android"
minSdkVersion 21
targetSdkVersion versions.targetSdk
versionCode 2026
versionName "2.3.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode 2030
versionName "2.4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
......@@ -68,12 +67,11 @@ dependencies {
implementation libraries.appCompat
implementation libraries.recyclerview
implementation libraries.design
implementation libraries.constraintLayout
implementation libraries.cardView
implementation libraries.material
implementation libraries.constraintlayout
implementation libraries.cardview
implementation libraries.flexbox
implementation libraries.customTabs
implementation libraries.supportv4
implementation libraries.browser
implementation libraries.androidKtx
......@@ -87,7 +85,6 @@ dependencies {
implementation libraries.room
kapt libraries.roomProcessor
implementation libraries.roomRxjava
implementation libraries.lifecycleExtensions
kapt libraries.lifecycleCompiler
......@@ -119,7 +116,9 @@ dependencies {
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
}
......
package chat.rocket.android.chatroom.ui
import android.content.Intent
import android.support.test.espresso.intent.rule.IntentsTestRule
import android.support.test.filters.LargeTest
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.filters.LargeTest
import org.junit.Rule
import org.junit.Test
import android.app.Activity
import android.app.Instrumentation.ActivityResult
import android.support.test.InstrumentationRegistry
import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.intending
import android.support.test.espresso.intent.matcher.IntentMatchers.*
import androidx.test.InstrumentationRegistry
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.*
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.not
import org.junit.Before
......
package chat.rocket.android.app
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.OnLifecycleEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import chat.rocket.android.server.domain.GetAccountInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
......
import android.content.Context
import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat
import android.support.v4.graphics.drawable.DrawableCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.common.model.UserStatus
......
......@@ -3,19 +3,12 @@ package chat.rocket.android.app
import android.app.Activity
import android.app.Application
import android.app.Service
import android.arch.lifecycle.ProcessLifecycleOwner
import android.content.BroadcastReceiver
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.lifecycle.ProcessLifecycleOwner
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.qualifier.ForMessages
import chat.rocket.android.helper.CrashlyticsTree
......@@ -24,19 +17,9 @@ import chat.rocket.android.infrastructure.installCrashlyticsWrapper
import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
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.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository
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.common.model.Token
import chat.rocket.core.model.Value
import com.crashlytics.android.Crashlytics
import com.crashlytics.android.core.CrashlyticsCore
import com.facebook.drawee.backends.pipeline.DraweeConfig
......@@ -49,10 +32,6 @@ import dagger.android.HasActivityInjector
import dagger.android.HasBroadcastReceiverInjector
import dagger.android.HasServiceInjector
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 java.lang.ref.WeakReference
import javax.inject.Inject
......@@ -83,17 +62,11 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
@Inject
lateinit var settingsInteractor: GetSettingsInteractor
@Inject
lateinit var settingsRepository: SettingsRepository
@Inject
lateinit var tokenRepository: TokenRepository
@Inject
lateinit var accountRepository: AccountsRepository
@Inject
lateinit var saveCurrentServerRepository: SaveCurrentServerInteractor
@Inject
lateinit var prefs: SharedPreferences
@Inject
lateinit var localRepository: LocalRepository
@Inject
lateinit var accountRepository: AccountsRepository
@Inject
@field:ForMessages
......@@ -127,15 +100,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
localRepository.setOldMessagesCleanedUp()
}
// TODO - remove this and all realm stuff when we got to 80% in 2.0
try {
if (!localRepository.hasMigrated()) {
migrateFromLegacy()
}
} catch (ex: Exception) {
Timber.d(ex, "Error migrating old accounts")
}
// TODO - remove REALM files.
// TODO - remove this
checkCurrentServer()
}
......@@ -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() {
val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()
Fabric.with(this, Crashlytics.Builder().core(core).build())
......@@ -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.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
import android.arch.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
......@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class LoginFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun loginView(frag: LoginFragment): LoginView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
......
package chat.rocket.android.authentication.login.di
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class LoginFragmentProvider {
@ContributesAndroidInjector(modules = [LoginFragmentModule::class])
@PerFragment
abstract fun provideLoginFragment(): LoginFragment
}
\ No newline at end of file
......@@ -6,7 +6,7 @@ import android.content.Intent
import android.graphics.PorterDuff
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
import android.text.style.ClickableSpan
import android.view.LayoutInflater
import android.view.View
......
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.ui.RegisterUsernameFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
......@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class RegisterUsernameFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun registerUsernameView(frag: RegisterUsernameFragment): RegisterUsernameView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: RegisterUsernameFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
......
package chat.rocket.android.authentication.registerusername.di
import chat.rocket.android.authentication.registerusername.ui.RegisterUsernameFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class RegisterUsernameFragmentProvider {
@ContributesAndroidInjector(modules = [RegisterUsernameFragmentModule::class])
@PerFragment
abstract fun provideRegisterUsernameFragment(): RegisterUsernameFragment
}
\ No newline at end of file
......@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.registerusername.ui
import DrawableHelper
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
......
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.ui.ResetPasswordFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
......@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class ResetPasswordFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun resetPasswordView(frag: ResetPasswordFragment): ResetPasswordView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: ResetPasswordFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
......
package chat.rocket.android.authentication.resetpassword.di
import chat.rocket.android.authentication.resetpassword.ui.ResetPasswordFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class ResetPasswordFragmentProvider {
@ContributesAndroidInjector(modules = [ResetPasswordFragmentModule::class])
@PerFragment
abstract fun provideResetPasswordFragment(): ResetPasswordFragment
}
\ No newline at end of file
......@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.resetpassword.ui
import DrawableHelper
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
......
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.ui.ServerFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
......@@ -12,16 +13,23 @@ import kotlinx.coroutines.experimental.Job
class ServerFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun serverView(frag: ServerFragment): ServerView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: ServerFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
......
package chat.rocket.android.authentication.server.di
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class ServerFragmentProvider {
@ContributesAndroidInjector(modules = [ServerFragmentModule::class])
@PerFragment
abstract fun provideServerFragment(): ServerFragment
}
\ No newline at end of file
......@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.server.ui
import android.app.AlertDialog
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
......
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.ui.SignupFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
......@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class SignupFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun signupView(frag: SignupFragment): SignupView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
......
package chat.rocket.android.authentication.signup.di
import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class SignupFragmentProvider {
@ContributesAndroidInjector(modules = [SignupFragmentModule::class])
@PerFragment
abstract fun provideSignupFragment(): SignupFragment
}
\ No newline at end of file
......@@ -5,7 +5,7 @@ import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
import android.text.style.ClickableSpan
import android.view.LayoutInflater
import android.view.View
......
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.ui.TwoFAFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
......@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class TwoFAFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun loginView(frag: TwoFAFragment): TwoFAView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
......
package chat.rocket.android.authentication.twofactor.di
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class TwoFAFragmentProvider {
@ContributesAndroidInjector(modules = [TwoFAFragmentModule::class])
@PerFragment
abstract fun provideTwoFAFragment(): TwoFAFragment
}
......@@ -4,7 +4,7 @@ import DrawableHelper
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
......
......@@ -3,8 +3,8 @@ package chat.rocket.android.authentication.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.authentication.domain.model.LoginDeepLinkInfo
import chat.rocket.android.authentication.domain.model.getLoginDeepLinkInfo
......
package chat.rocket.android.chatroom.adapter
import android.support.annotation.IntDef
import androidx.annotation.IntDef
const val PEOPLE = 0
const val ROOMS = 1
......
package chat.rocket.android.chatroom.adapter
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
......
package chat.rocket.android.chatroom.adapter
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.MenuItem
import android.view.ViewGroup
import chat.rocket.android.R
......
package chat.rocket.android.chatroom.adapter
import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat
import androidx.core.content.ContextCompat
import android.text.method.LinkMovementMethod
import android.view.View
import chat.rocket.android.R
......
package chat.rocket.android.chatroom.adapter
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
......
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.ui.ChatRoomFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
......@@ -12,6 +12,10 @@ import kotlinx.coroutines.experimental.Job
@Module
class ChatRoomFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun chatRoomView(frag: ChatRoomFragment): ChatRoomView {
......
......@@ -7,8 +7,8 @@ import dagger.Module
import dagger.Provides
@Module
@PerActivity
class ChatRoomModule {
@Provides
@PerActivity
fun provideChatRoomNavigator(activity: ChatRoomActivity) = ChatRoomNavigator(activity)
}
\ No newline at end of file
......@@ -445,7 +445,7 @@ class ChatRoomPresenter @Inject constructor(
is RoomType.DirectMessage -> "direct"
is RoomType.PrivateGroup -> "group"
is RoomType.Channel -> "channel"
is RoomType.Livechat -> "livechat"
is RoomType.LiveChat -> "livechat"
else -> "custom"
}
view.showReplyingAction(
......@@ -658,7 +658,7 @@ class ChatRoomPresenter @Inject constructor(
try {
val chatRooms = chatRoomsInteractor.getAll(currentServer)
.filterNot {
it.type is RoomType.DirectMessage || it.type is RoomType.Livechat
it.type is RoomType.DirectMessage || it.type is RoomType.LiveChat
}
.map { chatRoom ->
val name = chatRoom.name
......
package chat.rocket.android.chatroom.ui
import android.support.design.widget.BaseTransientBottomBar
import android.support.v4.view.ViewCompat
import com.google.android.material.snackbar.BaseTransientBottomBar
import androidx.core.view.ViewCompat
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.view.LayoutInflater
......
......@@ -4,8 +4,8 @@ import DrawableHelper
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import android.text.SpannableStringBuilder
import androidx.core.view.isVisible
import chat.rocket.android.R
......
......@@ -8,11 +8,11 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.support.annotation.DrawableRes
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 androidx.annotation.DrawableRes
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.text.SpannableStringBuilder
import android.view.*
import androidx.core.text.bold
......@@ -526,8 +526,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun onNonEmojiKeyPressed(keyCode: Int) {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> {
if (text_message.selectionStart > 0) text_message.text!!.delete(text_message.selectionStart - 1, text_message.selectionStart)
KeyEvent.KEYCODE_BACK -> with(text_message) {
if (selectionStart > 0) text?.delete(selectionStart - 1, selectionStart)
}
else -> throw IllegalArgumentException("pressed key not expected")
}
......@@ -616,13 +616,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupRecyclerView() {
// 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
recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator()
endlessRecyclerViewScrollListener = object :
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)
}
}
......@@ -641,7 +641,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if (isChatRoomReadOnly && !canPost) {
text_room_is_read_only.setVisible(true)
input_container.setVisible(false)
} else if (!isSubscribed && roomTypeOf(chatRoomType) != RoomType.DIRECT_MESSAGE) {
} else if (!isSubscribed && roomTypeOf(chatRoomType) !is RoomType.DirectMessage) {
input_container.setVisible(false)
button_join_chat.setVisible(true)
button_join_chat.setOnClickListener { presenter.joinChat(chatRoomId) }
......
package chat.rocket.android.chatroom.ui.bottomsheet
import android.support.design.widget.BottomSheetDialog
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialog
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.MenuItem
import ru.whalemare.sheetmenu.SheetMenu
import ru.whalemare.sheetmenu.adapter.MenuAdapter
......@@ -11,7 +11,7 @@ class BottomSheetMenu(adapter: MenuAdapter) : SheetMenu(adapter = adapter) {
override fun processRecycler(recycler: RecyclerView, dialog: BottomSheetDialog) {
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
......
package chat.rocket.android.chatroom.ui.bottomsheet.adapter
import android.graphics.Color
import android.support.annotation.ColorInt
import android.support.annotation.IdRes
import androidx.annotation.ColorInt
import androidx.annotation.IdRes
import android.util.SparseIntArray
import android.view.MenuItem
import chat.rocket.android.R
......
......@@ -4,7 +4,7 @@ import DateTimeHelper
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.support.v4.content.ContextCompat
import androidx.core.content.ContextCompat
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
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
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.ui.ChatRoomsFragment
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.Provides
import javax.inject.Named
@Module
@PerFragment
class ChatRoomsFragmentModule {
@Provides
@PerFragment
fun chatRoomsView(frag: ChatRoomsFragment): ChatRoomsView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: ChatRoomsFragment): LifecycleOwner {
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
......@@ -4,8 +4,8 @@ import DateTimeHelper
import DrawableHelper
import android.content.Context
import android.graphics.Color
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.view.View
......
package chat.rocket.android.chatrooms.ui
import android.content.Context
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.util.SparseArray
import android.view.LayoutInflater
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
import android.support.annotation.StringRes
import androidx.annotation.StringRes
import chat.rocket.common.util.ifNull
interface MessageView {
......
package chat.rocket.android.core.lifecycle
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.OnLifecycleEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.experimental.Job
import javax.inject.Inject
......
package chat.rocket.android.createchannel.di
import android.arch.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.createchannel.presentation.CreateChannelView
import chat.rocket.android.createchannel.ui.CreateChannelFragment
import chat.rocket.android.dagger.scope.PerFragment
......@@ -8,15 +8,16 @@ import dagger.Module
import dagger.Provides
@Module
@PerFragment
class CreateChannelModule {
@Provides
@PerFragment
fun createChannelView(fragment: CreateChannelFragment): CreateChannelView {
return fragment
}
@Provides
@PerFragment
fun provideLifecycleOwner(fragment: CreateChannelFragment): LifecycleOwner {
return fragment
}
......
package chat.rocket.android.createchannel.di
import chat.rocket.android.createchannel.ui.CreateChannelFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class CreateChannelProvider {
@ContributesAndroidInjector(modules = [CreateChannelModule::class])
@PerFragment
abstract fun provideCreateChannelFragment(): CreateChannelFragment
}
\ No newline at end of file
package chat.rocket.android.createchannel.ui
import android.os.Bundle
import android.support.design.chip.Chip
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.support.v7.view.ActionMode
import android.support.v7.widget.LinearLayoutManager
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import chat.rocket.android.R
import chat.rocket.android.createchannel.presentation.CreateChannelPresenter
import chat.rocket.android.createchannel.presentation.CreateChannelView
......@@ -21,6 +20,8 @@ import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import com.google.android.material.chip.Chip
import dagger.android.support.AndroidSupportInjection
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
......@@ -38,7 +39,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
}
}
private val compositeDisposable = CompositeDisposable()
private var channelType: RoomType = RoomType.CHANNEL
private var channelType: String = RoomType.CHANNEL
private var isChannelReadOnly: Boolean = false
private var memberList = arrayListOf<String>()
......@@ -145,7 +146,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
return when (menuItem.itemId) {
R.id.action_create_channel -> {
createChannelPresenter.createChannel(
channelType,
roomTypeOf(channelType),
text_channel_name.text.toString(),
memberList,
isChannelReadOnly
......@@ -234,7 +235,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
private fun startActionMode() {
if (actionMode == null) {
actionMode = (activity as MainActivity).startSupportActionMode(this)
actionMode = (activity as AppCompatActivity).startSupportActionMode(this)
}
}
......
......@@ -4,13 +4,11 @@ import android.app.Application
import android.app.NotificationManager
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.arch.persistence.room.Room
import android.content.ComponentName
import android.content.Context
import android.content.SharedPreferences
import chat.rocket.android.BuildConfig
import chat.rocket.android.R
import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository
import chat.rocket.android.chatroom.service.MessageService
......@@ -21,8 +19,32 @@ import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository
import chat.rocket.android.push.GroupedPush
import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.*
import chat.rocket.android.server.domain.AccountsRepository
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.HttpLoggingInterceptor
import chat.rocket.android.util.TimberLogger
......@@ -32,7 +54,6 @@ import chat.rocket.common.model.TimestampAdapter
import chat.rocket.common.util.CalendarISO8601Converter
import chat.rocket.common.util.Logger
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.AttachmentAdapterFactory
import chat.rocket.core.internal.ReactionsAdapter
import com.facebook.drawee.backends.pipeline.DraweeConfig
......@@ -42,7 +63,6 @@ import com.facebook.imagepipeline.listener.RequestLoggingListener
import com.squareup.moshi.Moshi
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import okhttp3.OkHttpClient
import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.spans.SpannableTheme
......@@ -53,45 +73,12 @@ import javax.inject.Singleton
@Module
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
@Singleton
fun provideContext(application: Application): Context {
return application
}
@Provides
@Singleton
fun provideServerDao(database: RocketChatDatabase): ServerDao {
return database.serverDao()
}
@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
......@@ -129,7 +116,6 @@ class AppModule {
return OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient)
.setRequestListeners(listeners)
.setDownsampleEnabled(true)
//.experiment().setBitmapPrepareToDraw(true).experiment()
.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
This diff is collapsed.
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
import android.arch.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.favoritemessages.presentation.FavoriteMessagesView
......@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class FavoriteMessagesFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun provideLifecycleOwner(frag: FavoriteMessagesFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
@Provides
@PerFragment
fun provideFavoriteMessagesView(frag: FavoriteMessagesFragment): FavoriteMessagesView {
return frag
}
......
package chat.rocket.android.favoritemessages.ui
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.View
import android.view.ViewGroup
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.chatroom.adapter.ChatRoomAdapter
import chat.rocket.android.chatroom.ui.ChatRoomActivity
......@@ -68,8 +68,7 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false)
recycler_view.adapter = adapter
val linearLayoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
val linearLayoutManager = LinearLayoutManager(context)
recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator()
if (favoriteMessages.size >= 30) {
......@@ -78,7 +77,7 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
override fun onLoadMore(
page: Int,
totalItemsCount: Int,
recyclerView: RecyclerView?
recyclerView: RecyclerView
) {
presenter.loadFavoriteMessages(chatRoomId)
}
......
package chat.rocket.android.files.adapter
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.files.viewmodel.FileViewModel
import chat.rocket.android.util.extensions.inflate
......
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.dagger.scope.PerFragment
import chat.rocket.android.files.presentation.FilesView
......@@ -10,20 +10,26 @@ import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class FilesFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun provideLifecycleOwner(frag: FilesFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
@Provides
@PerFragment
fun provideFilesView(frag: FilesFragment): FilesView {
return frag
}
......
package chat.rocket.android.files.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.files.ui.FilesFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class FilesFragmentProvider {
@ContributesAndroidInjector(modules = [FilesFragmentModule::class])
@PerFragment
abstract fun provideFilesFragment(): FilesFragment
}
\ No newline at end of file
......@@ -3,13 +3,13 @@ package chat.rocket.android.files.ui
import android.content.Intent
import android.net.Uri
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.View
import android.view.ViewGroup
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.chatroom.ui.ChatRoomActivity
import chat.rocket.android.files.adapter.FilesAdapter
......@@ -42,8 +42,7 @@ class FilesFragment : Fragment(), FilesView {
lateinit var presenter: FilesPresenter
private val adapter: FilesAdapter =
FilesAdapter { fileViewModel -> presenter.openFile(fileViewModel) }
private val linearLayoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
private val linearLayoutManager = LinearLayoutManager(context)
private lateinit var chatRoomId: String
override fun onCreate(savedInstanceState: Bundle?) {
......@@ -80,7 +79,7 @@ class FilesFragment : Fragment(), FilesView {
override fun onLoadMore(
page: Int,
totalItemsCount: Int,
recyclerView: RecyclerView?
recyclerView: RecyclerView
) {
presenter.loadFiles(chatRoomId)
}
......
......@@ -3,8 +3,8 @@ package chat.rocket.android.helper
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
object AndroidPermissionsHelper {
......
package chat.rocket.android.helper
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
/**
* Info: https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
......@@ -106,5 +106,5 @@ abstract class EndlessRecyclerViewScrollListener : RecyclerView.OnScrollListener
}
// 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
import android.graphics.Typeface
import android.media.MediaScannerConnection
import android.os.Environment
import android.support.design.widget.AppBarLayout
import android.support.v7.widget.Toolbar
import android.text.TextUtils
import android.util.TypedValue
import android.view.ContextThemeWrapper
......@@ -16,6 +14,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.core.net.toUri
import androidx.core.view.setPadding
import chat.rocket.android.R
......@@ -26,6 +25,7 @@ import com.facebook.imagepipeline.cache.DefaultCacheKeyFactory
import com.facebook.imagepipeline.core.ImagePipelineFactory
import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.google.android.material.appbar.AppBarLayout
import com.stfalcon.frescoimageviewer.ImageViewer
import timber.log.Timber
import java.io.File
......
......@@ -21,7 +21,7 @@ class MessageHelper @Inject constructor(
is RoomType.PrivateGroup -> "group"
is RoomType.Channel -> "channel"
is RoomType.DirectMessage -> "direct"
is RoomType.Livechat -> "livechat"
is RoomType.LiveChat -> "livechat"
else -> "custom"
}
val name = if (settings.useRealName()) chatRoom.fullName ?: chatRoom.name else chatRoom.name
......
......@@ -6,7 +6,7 @@ import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.net.Uri
import android.support.v4.content.res.ResourcesCompat
import androidx.core.content.res.ResourcesCompat
import android.text.Spanned
import android.text.style.ClickableSpan
import android.text.style.ReplacementSpan
......
......@@ -2,7 +2,7 @@ package chat.rocket.android.helper
import android.app.Activity
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.common.api.CommonStatusCodes
import com.google.android.gms.common.api.ResolvableApiException
......
......@@ -21,7 +21,6 @@ interface LocalRepository {
companion object {
const val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN"
const val MIGRATION_FINISHED_KEY = "MIGRATION_FINISHED_KEY"
const val TOKEN_KEY = "token_"
const val SETTINGS_KEY = "settings_"
const val PERMISSIONS_KEY = "permissions_"
......
package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import chat.rocket.android.server.domain.model.Account
import kotlinx.android.synthetic.main.item_account.view.*
......
package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.server.domain.model.Account
......
package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.View
class AddAccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
\ No newline at end of file
package chat.rocket.android.main.adapter
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import chat.rocket.common.model.UserStatus
import kotlinx.android.synthetic.main.item_change_status.view.*
......
package chat.rocket.android.main.di
import android.arch.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleOwner
import android.content.Context
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.ui.MainActivity
......@@ -14,6 +15,10 @@ import kotlinx.coroutines.experimental.Job
@Module
class MainModule {
@Provides
@PerActivity
fun provideJob() = Job()
@Provides
@PerActivity
fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment