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

Merge branch 'develop' into new/add-onboarding-icon

parents e0c09a7c e4127ee6
...@@ -12,8 +12,8 @@ android { ...@@ -12,8 +12,8 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion versions.minSdk minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 2030 versionCode 2032
versionName "2.4.0" versionName "2.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
......
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "e389d26bfb975f00c75dc6fc5d06d012",
"entities": [
{
"tableName": "users",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "utcOffset",
"columnName": "utcOffset",
"affinity": "REAL",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_users_username",
"unique": false,
"columnNames": [
"username"
],
"createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)"
}
],
"foreignKeys": []
},
{
"tableName": "chatrooms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscriptionId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "fullname",
"columnName": "fullname",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "userId",
"columnName": "userId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "readonly",
"columnName": "readonly",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isDefault",
"columnName": "isDefault",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "favorite",
"columnName": "favorite",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "open",
"columnName": "open",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "alert",
"columnName": "alert",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userMentions",
"columnName": "userMentions",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "groupMentions",
"columnName": "groupMentions",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastSeen",
"columnName": "lastSeen",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastMessageText",
"columnName": "lastMessageText",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastMessageUserId",
"columnName": "lastMessageUserId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastMessageTimestamp",
"columnName": "lastMessageTimestamp",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "broadcast",
"columnName": "broadcast",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_chatrooms_userId",
"unique": false,
"columnNames": [
"userId"
],
"createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)"
},
{
"name": "index_chatrooms_ownerId",
"unique": false,
"columnNames": [
"ownerId"
],
"createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)"
},
{
"name": "index_chatrooms_subscriptionId",
"unique": true,
"columnNames": [
"subscriptionId"
],
"createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)"
},
{
"name": "index_chatrooms_updatedAt",
"unique": false,
"columnNames": [
"updatedAt"
],
"createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"userId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"lastMessageUserId"
],
"referencedColumns": [
"id"
]
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"e389d26bfb975f00c75dc6fc5d06d012\")"
]
}
}
\ No newline at end of file
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "47a0c30e2696ae09bc86df16cc37279d",
"entities": [
{
"tableName": "users",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `username` TEXT, `name` TEXT, `status` TEXT NOT NULL, `utcOffset` REAL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "utcOffset",
"columnName": "utcOffset",
"affinity": "REAL",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_users_username",
"unique": false,
"columnNames": [
"username"
],
"createSql": "CREATE INDEX `index_users_username` ON `${TABLE_NAME}` (`username`)"
}
],
"foreignKeys": []
},
{
"tableName": "chatrooms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscriptionId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "fullname",
"columnName": "fullname",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "userId",
"columnName": "userId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "readonly",
"columnName": "readonly",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isDefault",
"columnName": "isDefault",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "favorite",
"columnName": "favorite",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "open",
"columnName": "open",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "alert",
"columnName": "alert",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userMentions",
"columnName": "userMentions",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "groupMentions",
"columnName": "groupMentions",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastSeen",
"columnName": "lastSeen",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastMessageText",
"columnName": "lastMessageText",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastMessageUserId",
"columnName": "lastMessageUserId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastMessageTimestamp",
"columnName": "lastMessageTimestamp",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "broadcast",
"columnName": "broadcast",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_chatrooms_userId",
"unique": false,
"columnNames": [
"userId"
],
"createSql": "CREATE INDEX `index_chatrooms_userId` ON `${TABLE_NAME}` (`userId`)"
},
{
"name": "index_chatrooms_ownerId",
"unique": false,
"columnNames": [
"ownerId"
],
"createSql": "CREATE INDEX `index_chatrooms_ownerId` ON `${TABLE_NAME}` (`ownerId`)"
},
{
"name": "index_chatrooms_subscriptionId",
"unique": true,
"columnNames": [
"subscriptionId"
],
"createSql": "CREATE UNIQUE INDEX `index_chatrooms_subscriptionId` ON `${TABLE_NAME}` (`subscriptionId`)"
},
{
"name": "index_chatrooms_updatedAt",
"unique": false,
"columnNames": [
"updatedAt"
],
"createSql": "CREATE INDEX `index_chatrooms_updatedAt` ON `${TABLE_NAME}` (`updatedAt`)"
},
{
"name": "index_chatrooms_lastMessageUserId",
"unique": false,
"columnNames": [
"lastMessageUserId"
],
"createSql": "CREATE INDEX `index_chatrooms_lastMessageUserId` ON `${TABLE_NAME}` (`lastMessageUserId`)"
}
],
"foreignKeys": [
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"ownerId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"userId"
],
"referencedColumns": [
"id"
]
},
{
"table": "users",
"onDelete": "NO ACTION",
"onUpdate": "NO ACTION",
"columns": [
"lastMessageUserId"
],
"referencedColumns": [
"id"
]
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"47a0c30e2696ae09bc86df16cc37279d\")"
]
}
}
\ No newline at end of file
...@@ -76,7 +76,12 @@ ...@@ -76,7 +76,12 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" /> android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<!-- TODO: Change to fragment --> <activity
android:name=".chatinformation.ui.MessageInfoActivity"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
<!-- TODO: Change to fragment-->
<activity <activity
android:name=".settings.password.ui.PasswordActivity" android:name=".settings.password.ui.PasswordActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
......
...@@ -5,19 +5,58 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator ...@@ -5,19 +5,58 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.OauthHelper import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SaveAccountInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.casLoginUrl
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.gitlabUrl
import chat.rocket.android.server.domain.isCasAuthenticationEnabled
import chat.rocket.android.server.domain.isFacebookAuthenticationEnabled
import chat.rocket.android.server.domain.isGithubAuthenticationEnabled
import chat.rocket.android.server.domain.isGitlabAuthenticationEnabled
import chat.rocket.android.server.domain.isGoogleAuthenticationEnabled
import chat.rocket.android.server.domain.isLdapAuthenticationEnabled
import chat.rocket.android.server.domain.isLinkedinAuthenticationEnabled
import chat.rocket.android.server.domain.isLoginFormEnabled
import chat.rocket.android.server.domain.isMeteorAuthenticationEnabled
import chat.rocket.android.server.domain.isPasswordResetEnabled
import chat.rocket.android.server.domain.isRegistrationEnabledForNewUsers
import chat.rocket.android.server.domain.isTwitterAuthenticationEnabled
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.casUrl
import chat.rocket.android.util.extensions.encodeToBase64
import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.isEmail
import chat.rocket.android.util.extensions.parseColor
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.samlUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.model.Email
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
import chat.rocket.common.model.User
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.* import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.loginWithCas
import chat.rocket.core.internal.rest.loginWithEmail
import chat.rocket.core.internal.rest.loginWithLdap
import chat.rocket.core.internal.rest.loginWithOauth
import chat.rocket.core.internal.rest.loginWithSaml
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.settingsOauth
import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.delay
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
...@@ -351,11 +390,20 @@ class LoginPresenter @Inject constructor( ...@@ -351,11 +390,20 @@ class LoginPresenter @Inject constructor(
} }
} }
} }
val username = retryIO("me()") { client.me().username } val myself = retryIO("me()") { client.me() }
if (username != null) { if (myself.username != null) {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username) val user = User(
id = myself.id,
roles = myself.roles,
status = myself.status,
name = myself.name,
emails = myself.emails?.map { Email(it.address ?: "", it.verified) },
username = myself.username,
utcOffset = myself.utcOffset
)
localRepository.saveCurrentUser(url = currentServer, user = user)
saveCurrentServer.save(currentServer) saveCurrentServer.save(currentServer)
saveAccount(username) saveAccount(myself.username!!)
saveToken(token) saveToken(token)
registerPushToken() registerPushToken()
if (loginType == TYPE_LOGIN_USER_EMAIL) { if (loginType == TYPE_LOGIN_USER_EMAIL) {
...@@ -391,7 +439,7 @@ class LoginPresenter @Inject constructor( ...@@ -391,7 +439,7 @@ class LoginPresenter @Inject constructor(
}.toString() }.toString()
} }
private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> { private fun getSamlServices(listMap: List<Map<String, Any>>): List<Map<String, Any>> {
return listMap.filter { map -> map["service"] == "saml" } return listMap.filter { map -> map["service"] == "saml" }
} }
......
package chat.rocket.android.chatinformation.adapter
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.chatinformation.adapter.ReadReceiptAdapter.ReadReceiptViewHolder
import chat.rocket.android.chatinformation.viewmodel.ReadReceiptViewModel
import chat.rocket.android.util.extensions.inflate
import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_read_receipt.view.*
class ReadReceiptAdapter : RecyclerView.Adapter<ReadReceiptViewHolder>() {
private val data = ArrayList<ReadReceiptViewModel>()
init {
setHasStableIds(true)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReadReceiptViewHolder {
return ReadReceiptViewHolder(parent.inflate(R.layout.item_read_receipt, false))
}
override fun getItemCount(): Int {
return data.size
}
override fun onBindViewHolder(holder: ReadReceiptViewHolder, position: Int) {
holder.bind(data[position])
}
fun addAll(items: List<ReadReceiptViewModel>) {
data.clear()
data.addAll(items)
notifyItemRangeInserted(0, items.size)
}
class ReadReceiptViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(readReceipt: ReadReceiptViewModel) {
with(itemView) {
image_avatar.setImageURI(readReceipt.avatar)
receipt_name.text = readReceipt.name
receipt_time.text = readReceipt.time
}
}
}
}
package chat.rocket.android.chatinformation.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatinformation.presentation.MessageInfoView
import chat.rocket.android.chatinformation.ui.MessageInfoFragment
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
@Module
class MessageInfoFragmentModule {
@Provides
@PerFragment
fun provideJob() = Job()
@Provides
@PerFragment
fun messageInfoView(frag: MessageInfoFragment): MessageInfoView {
return frag
}
@Provides
@PerFragment
fun provideLifecycleOwner(frag: MessageInfoFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
package chat.rocket.android.chatinformation.di
import chat.rocket.android.chatinformation.ui.MessageInfoFragment
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class MessageInfoFragmentProvider {
@ContributesAndroidInjector(modules = [MessageInfoFragmentModule::class])
@PerFragment
abstract fun provideMessageInfoFragment(): MessageInfoFragment
}
package chat.rocket.android.chatinformation.presentation
import chat.rocket.android.chatroom.uimodel.UiModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.getMessageReadReceipts
import timber.log.Timber
import javax.inject.Inject
class MessageInfoPresenter @Inject constructor(
private val view: MessageInfoView,
private val strategy: CancelStrategy,
private val mapper: UiModelMapper,
serverInteractor: GetCurrentServerInteractor,
factory: ConnectionManagerFactory
) {
private val currentServer = serverInteractor.get()!!
private val manager = factory.create(currentServer)
private val client = manager.client
fun loadReadReceipts(messageId: String) {
launchUI(strategy) {
try {
view.showLoading()
val readReceipts = retryIO(description = "getMessageReadReceipts") {
client.getMessageReadReceipts(messageId = messageId).result
}
view.showReadReceipts(mapper.map(readReceipts))
} catch (ex: RocketChatException) {
Timber.e(ex)
view.showGenericErrorMessage()
} finally {
view.hideLoading()
}
}
}
}
package chat.rocket.android.chatinformation.presentation
import chat.rocket.android.chatinformation.viewmodel.ReadReceiptViewModel
import chat.rocket.android.core.behaviours.LoadingView
interface MessageInfoView : LoadingView {
fun showGenericErrorMessage()
fun showReadReceipts(messageReceipts: List<ReadReceiptViewModel>)
}
package chat.rocket.android.chatinformation.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.chatinformation.ui.MessageInfoFragment.Companion.TAG_MESSAGE_INFO_FRAGMENT
import chat.rocket.android.util.extensions.addFragment
import chat.rocket.android.util.extensions.textContent
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.app_bar_chat_room.*
import javax.inject.Inject
fun Context.messageInformationIntent(messageId: String): Intent {
return Intent(this, MessageInfoActivity::class.java).apply {
putExtra(INTENT_MESSAGE_ID, messageId)
}
}
private const val INTENT_MESSAGE_ID = "message_id"
class MessageInfoActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_room)
setupToolbar()
val messageId = intent.getStringExtra(INTENT_MESSAGE_ID)
requireNotNull(messageId) { "no message_id provided in Intent extras" }
if (supportFragmentManager.findFragmentByTag(TAG_MESSAGE_INFO_FRAGMENT) == null) {
addFragment(TAG_MESSAGE_INFO_FRAGMENT, R.id.fragment_container) {
newInstance(messageId = messageId)
}
}
}
private fun setupToolbar() {
text_room_name.textContent = getString(R.string.message_information_title)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
toolbar.setNavigationOnClickListener { finishActivity() }
}
private fun finishActivity() {
super.onBackPressed()
overridePendingTransition(R.anim.close_enter, R.anim.close_exit)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector
}
}
\ No newline at end of file
package chat.rocket.android.chatinformation.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.chatinformation.adapter.ReadReceiptAdapter
import chat.rocket.android.chatinformation.presentation.MessageInfoPresenter
import chat.rocket.android.chatinformation.presentation.MessageInfoView
import chat.rocket.android.chatinformation.viewmodel.ReadReceiptViewModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.showToast
import chat.rocket.core.model.ReadReceipt
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_message_info.*
import javax.inject.Inject
fun newInstance(messageId: String): Fragment {
return MessageInfoFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_MESSAGE_ID, messageId)
}
}
}
private const val BUNDLE_MESSAGE_ID = "message_id"
class MessageInfoFragment : Fragment(), MessageInfoView {
@Inject
lateinit var presenter: MessageInfoPresenter
private lateinit var adapter: ReadReceiptAdapter
private lateinit var messageId: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
val bundle = arguments
if (bundle != null) {
messageId = bundle.getString(BUNDLE_MESSAGE_ID)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_message_info, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
presenter.loadReadReceipts(messageId = messageId)
}
private fun setupRecyclerView() {
// Initialize the endlessRecyclerViewScrollListener so we don't NPE at onDestroyView
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
adapter = ReadReceiptAdapter()
linearLayoutManager.stackFromEnd = true
receipt_list.layoutManager = linearLayoutManager
receipt_list.itemAnimator = DefaultItemAnimator()
receipt_list.adapter = adapter
}
override fun showGenericErrorMessage() {
showToast(R.string.msg_generic_error)
}
override fun showLoading() {
view_loading.setVisible(true)
view_loading.show()
}
override fun hideLoading() {
view_loading.hide()
view_loading.setVisible(false)
}
override fun showReadReceipts(messageReceipts: List<ReadReceiptViewModel>) {
adapter.addAll(messageReceipts)
}
companion object {
const val TAG_MESSAGE_INFO_FRAGMENT = "MessageInfoFragment"
}
}
package chat.rocket.android.chatinformation.viewmodel
data class ReadReceiptViewModel(
val avatar: String,
val name: String,
val time: String
)
\ No newline at end of file
...@@ -78,24 +78,26 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>( ...@@ -78,24 +78,26 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
private val onClickListener = { view: View -> private val onClickListener = { view: View ->
if (data?.message?.isSystemMessage() == false) { if (data?.message?.isSystemMessage() == false) {
data?.message?.let { data?.let { vm ->
val menuItems = view.context.inflate(R.menu.message_actions).toList() vm.message.let {
menuItems.find { it.itemId == R.id.action_message_unpin }?.apply { val menuItems = view.context.inflate(R.menu.message_actions).toList()
setTitle(if (it.pinned) R.string.action_msg_unpin else R.string.action_msg_pin) menuItems.find { it.itemId == R.id.action_message_unpin }?.apply {
isChecked = it.pinned setTitle(if (it.pinned) R.string.action_msg_unpin else R.string.action_msg_pin)
} isChecked = it.pinned
}
menuItems.find { it.itemId == R.id.action_message_star }?.apply { menuItems.find { it.itemId == R.id.action_message_star }?.apply {
val isStarred = it.starred?.isNotEmpty() ?: false val isStarred = it.starred?.isNotEmpty() ?: false
setTitle(if (isStarred) R.string.action_msg_unstar else R.string.action_msg_star) setTitle(if (isStarred) R.string.action_msg_unstar else R.string.action_msg_star)
isChecked = isStarred isChecked = isStarred
} }
view.context?.let { view.context?.let {
if (it is ContextThemeWrapper && it.baseContext is AppCompatActivity) { if (it is ContextThemeWrapper && it.baseContext is AppCompatActivity) {
with(it.baseContext as AppCompatActivity) { with(it.baseContext as AppCompatActivity) {
val actionsBottomSheet = MessageActionsBottomSheet() val actionsBottomSheet = MessageActionsBottomSheet()
actionsBottomSheet.addItems(menuItems, this@BaseViewHolder) actionsBottomSheet.addItems(menuItems, this@BaseViewHolder)
actionsBottomSheet.show(supportFragmentManager, null) actionsBottomSheet.show(supportFragmentManager, null)
}
} }
} }
} }
......
...@@ -211,6 +211,9 @@ class ChatRoomAdapter( ...@@ -211,6 +211,9 @@ class ChatRoomAdapter(
override fun onActionSelected(item: MenuItem, message: Message) { override fun onActionSelected(item: MenuItem, message: Message) {
message.apply { message.apply {
when (item.itemId) { when (item.itemId) {
R.id.action_message_info -> {
presenter?.messageInfo(id)
}
R.id.action_message_reply -> { R.id.action_message_reply -> {
if (roomName != null && roomType != null) { if (roomName != null && roomType != null) {
presenter?.citeMessage(roomName, roomType, id, true) presenter?.citeMessage(roomName, roomType, id, true)
......
...@@ -4,6 +4,7 @@ import android.graphics.Color ...@@ -4,6 +4,7 @@ import android.graphics.Color
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.MessageUiModel import chat.rocket.android.chatroom.uimodel.MessageUiModel
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.core.model.isSystemMessage import chat.rocket.core.model.isSystemMessage
...@@ -39,6 +40,18 @@ class MessageViewHolder( ...@@ -39,6 +40,18 @@ class MessageViewHolder(
text_edit_indicator.isVisible = !it.isSystemMessage() && it.editedBy != null text_edit_indicator.isVisible = !it.isSystemMessage() && it.editedBy != null
image_star_indicator.isVisible = it.starred?.isNotEmpty() ?: false image_star_indicator.isVisible = it.starred?.isNotEmpty() ?: false
} }
if (data.unread == null) {
read_receipt_view.isVisible = false
} else {
read_receipt_view.setImageResource(
if (data.unread == true) {
R.drawable.ic_check_unread_24dp
} else {
R.drawable.ic_check_read_24dp
}
)
read_receipt_view.isVisible = true
}
} }
} }
} }
\ No newline at end of file
...@@ -33,4 +33,4 @@ class ChatRoomFragmentModule { ...@@ -33,4 +33,4 @@ class ChatRoomFragmentModule {
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy { fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs) return CancelStrategy(owner, jobs)
} }
} }
\ No newline at end of file
package chat.rocket.android.chatroom.domain package chat.rocket.android.chatroom.domain
data class MessageReply( data class MessageReply(val roomName: String, val permalink: String)
val roomName: String, \ No newline at end of file
val permalink: String
)
\ No newline at end of file
package chat.rocket.android.chatroom.presentation package chat.rocket.android.chatroom.presentation
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatinformation.ui.messageInformationIntent
import chat.rocket.android.chatroom.ui.ChatRoomActivity import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatroom.ui.chatRoomIntent import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.server.ui.changeServerIntent import chat.rocket.android.server.ui.changeServerIntent
...@@ -69,4 +70,9 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) { ...@@ -69,4 +70,9 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
) )
activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit) activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
} }
fun toMessageInformation(messageId: String) {
activity.startActivity(activity.messageInformationIntent(messageId = messageId))
activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
}
} }
\ No newline at end of file
...@@ -153,4 +153,4 @@ interface ChatRoomView : LoadingView, MessageView { ...@@ -153,4 +153,4 @@ interface ChatRoomView : LoadingView, MessageView {
* to reply. * to reply.
*/ */
fun openDirectMessage(chatRoom: ChatRoom, permalink: String) fun openDirectMessage(chatRoom: ChatRoom, permalink: String)
} }
\ No newline at end of file
...@@ -12,6 +12,7 @@ import android.text.SpannableStringBuilder ...@@ -12,6 +12,7 @@ import android.text.SpannableStringBuilder
import android.view.KeyEvent import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
...@@ -147,6 +148,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -147,6 +148,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private lateinit var actionSnackbar: ActionSnackbar private lateinit var actionSnackbar: ActionSnackbar
internal var citation: String? = null internal var citation: String? = null
private var editingMessageId: String? = null private var editingMessageId: String? = null
internal var disableMenu: Boolean = false
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
private var playComposeMessageButtonsAnimation = true private var playComposeMessageButtonsAnimation = true
...@@ -249,7 +251,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -249,7 +251,16 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
setReactionButtonIcon(R.drawable.ic_reaction_24dp) setReactionButtonIcon(R.drawable.ic_reaction_24dp)
emojiKeyboardPopup.dismiss() dismissEmojiKeyboard()
activity?.invalidateOptionsMenu()
}
private fun dismissEmojiKeyboard() {
// Check if the keyboard was ever initialized.
// It may be the case when you are looking a not joined room
if (::emojiKeyboardPopup.isInitialized) {
emojiKeyboardPopup.dismiss()
}
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
...@@ -320,6 +331,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -320,6 +331,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} }
recycler_view.addOnLayoutChangeListener(layoutChangeListener) recycler_view.addOnLayoutChangeListener(layoutChangeListener)
recycler_view.addOnScrollListener(onScrollListener) recycler_view.addOnScrollListener(onScrollListener)
// Load just once, on the first page...
presenter.loadActiveMembers(chatRoomId, chatRoomType, filterSelfOut = true)
} }
val oldMessagesCount = adapter.itemCount val oldMessagesCount = adapter.itemCount
...@@ -350,7 +364,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -350,7 +364,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
ui { ui {
setupMessageComposer(userCanPost) setupMessageComposer(userCanPost)
isBroadcastChannel = channelIsBroadcast isBroadcastChannel = channelIsBroadcast
if (isBroadcastChannel && !userCanMod) activity?.invalidateOptionsMenu() if (isBroadcastChannel && !userCanMod) {
disableMenu = true
activity?.invalidateOptionsMenu()
}
} }
} }
...@@ -934,4 +951,4 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -934,4 +951,4 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupToolbar(toolbarTitle: String) { private fun setupToolbar(toolbarTitle: String) {
(activity as ChatRoomActivity).showToolbarTitle(toolbarTitle) (activity as ChatRoomActivity).showToolbarTitle(toolbarTitle)
} }
} }
\ No newline at end of file
...@@ -14,7 +14,21 @@ internal fun ChatRoomFragment.setupMenu(menu: Menu) { ...@@ -14,7 +14,21 @@ internal fun ChatRoomFragment.setupMenu(menu: Menu) {
setupSearchMessageMenuItem(menu, requireContext()) setupSearchMessageMenuItem(menu, requireContext())
setupFavoriteMenuItem(menu) setupFavoriteMenuItem(menu)
if (chatRoomType != RoomType.DIRECT_MESSAGE) { menu.add(
Menu.NONE,
MENU_ACTION_PINNED_MESSAGES,
Menu.NONE,
R.string.title_pinned_messages
)
menu.add(
Menu.NONE,
MENU_ACTION_FAVORITE_MESSAGES,
Menu.NONE,
R.string.title_favorite_messages
)
if (chatRoomType != RoomType.DIRECT_MESSAGE && !disableMenu) {
menu.add( menu.add(
Menu.NONE, Menu.NONE,
MENU_ACTION_MEMBER, MENU_ACTION_MEMBER,
...@@ -30,26 +44,14 @@ internal fun ChatRoomFragment.setupMenu(menu: Menu) { ...@@ -30,26 +44,14 @@ internal fun ChatRoomFragment.setupMenu(menu: Menu) {
) )
} }
menu.add( if (!disableMenu) {
Menu.NONE, menu.add(
MENU_ACTION_PINNED_MESSAGES, Menu.NONE,
Menu.NONE, MENU_ACTION_FILES,
R.string.title_pinned_messages Menu.NONE,
) R.string.title_files
)
menu.add( }
Menu.NONE,
MENU_ACTION_FAVORITE_MESSAGES,
Menu.NONE,
R.string.title_favorite_messages
)
menu.add(
Menu.NONE,
MENU_ACTION_FILES,
Menu.NONE,
R.string.title_files
)
} }
internal fun ChatRoomFragment.setOnMenuItemClickListener(item: MenuItem) { internal fun ChatRoomFragment.setOnMenuItemClickListener(item: MenuItem) {
...@@ -101,7 +103,7 @@ private fun ChatRoomFragment.setupSearchViewTextListener(searchView: SearchView) ...@@ -101,7 +103,7 @@ private fun ChatRoomFragment.setupSearchViewTextListener(searchView: SearchView)
// TODO: We use isSearchTermQueried to avoid querying when the search view is expanded but the user doesn't start typing. Check for a native solution. // TODO: We use isSearchTermQueried to avoid querying when the search view is expanded but the user doesn't start typing. Check for a native solution.
if (it.isEmpty() && isSearchTermQueried) { if (it.isEmpty() && isSearchTermQueried) {
presenter.loadMessages(chatRoomId, chatRoomType, clearDataSet = true) presenter.loadMessages(chatRoomId, chatRoomType, clearDataSet = true)
} else if (it.isNotEmpty()){ } else if (it.isNotEmpty()) {
presenter.searchMessages(chatRoomId, it) presenter.searchMessages(chatRoomId, it)
isSearchTermQueried = true isSearchTermQueried = true
} }
......
...@@ -14,7 +14,9 @@ data class AudioAttachmentUiModel( ...@@ -14,7 +14,9 @@ data class AudioAttachmentUiModel(
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
override var isTemporary: Boolean = false override var isTemporary: Boolean = false,
override var unread: Boolean? = null,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseFileAttachmentUiModel<AudioAttachment> { ) : BaseFileAttachmentUiModel<AudioAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.AUDIO_ATTACHMENT.viewType get() = BaseUiModel.ViewType.AUDIO_ATTACHMENT.viewType
......
...@@ -16,7 +16,9 @@ data class AuthorAttachmentUiModel( ...@@ -16,7 +16,9 @@ data class AuthorAttachmentUiModel(
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
override var isTemporary: Boolean = false override var isTemporary: Boolean = false,
override var unread: Boolean? = null,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseAttachmentUiModel<AuthorAttachment> { ) : BaseAttachmentUiModel<AuthorAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.AUTHOR_ATTACHMENT.viewType get() = BaseUiModel.ViewType.AUTHOR_ATTACHMENT.viewType
......
...@@ -13,6 +13,8 @@ interface BaseUiModel<out T> { ...@@ -13,6 +13,8 @@ interface BaseUiModel<out T> {
var nextDownStreamMessage: BaseUiModel<*>? var nextDownStreamMessage: BaseUiModel<*>?
var preview: Message? var preview: Message?
var isTemporary: Boolean var isTemporary: Boolean
var unread: Boolean?
var menuItemsToHide: MutableList<Int>
enum class ViewType(val viewType: Int) { enum class ViewType(val viewType: Int) {
MESSAGE(0), MESSAGE(0),
......
...@@ -15,7 +15,9 @@ data class ColorAttachmentUiModel( ...@@ -15,7 +15,9 @@ data class ColorAttachmentUiModel(
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
override var isTemporary: Boolean = false override var isTemporary: Boolean = false,
override var unread: Boolean?,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseAttachmentUiModel<ColorAttachment> { ) : BaseAttachmentUiModel<ColorAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.COLOR_ATTACHMENT.viewType get() = BaseUiModel.ViewType.COLOR_ATTACHMENT.viewType
......
...@@ -14,7 +14,9 @@ data class GenericFileAttachmentUiModel( ...@@ -14,7 +14,9 @@ data class GenericFileAttachmentUiModel(
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
override var isTemporary: Boolean = false override var isTemporary: Boolean = false,
override var unread: Boolean? = null,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseFileAttachmentUiModel<GenericFileAttachment> { ) : BaseFileAttachmentUiModel<GenericFileAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.GENERIC_FILE_ATTACHMENT.viewType get() = BaseUiModel.ViewType.GENERIC_FILE_ATTACHMENT.viewType
......
...@@ -16,7 +16,9 @@ data class ImageAttachmentUiModel( ...@@ -16,7 +16,9 @@ data class ImageAttachmentUiModel(
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
override var isTemporary: Boolean = false override var isTemporary: Boolean = false,
override var unread: Boolean? = null,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseFileAttachmentUiModel<ImageAttachment> { ) : BaseFileAttachmentUiModel<ImageAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.IMAGE_ATTACHMENT.viewType get() = BaseUiModel.ViewType.IMAGE_ATTACHMENT.viewType
......
...@@ -15,7 +15,9 @@ data class MessageAttachmentUiModel( ...@@ -15,7 +15,9 @@ data class MessageAttachmentUiModel(
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
var messageLink: String? = null, var messageLink: String? = null,
override var preview: Message? = null, override var preview: Message? = null,
override var isTemporary: Boolean = false override var isTemporary: Boolean = false,
override var unread: Boolean? = null,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseUiModel<Message> { ) : BaseUiModel<Message> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.MESSAGE_ATTACHMENT.viewType get() = BaseUiModel.ViewType.MESSAGE_ATTACHMENT.viewType
......
...@@ -5,13 +5,15 @@ import chat.rocket.android.chatroom.domain.MessageReply ...@@ -5,13 +5,15 @@ import chat.rocket.android.chatroom.domain.MessageReply
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
data class MessageReplyUiModel( data class MessageReplyUiModel(
override val rawData: MessageReply, override val rawData: MessageReply,
override val messageId: String, override val messageId: String,
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>?, override var nextDownStreamMessage: BaseUiModel<*>?,
override var preview: Message?, override var preview: Message?,
override var isTemporary: Boolean = false, override var isTemporary: Boolean = false,
override val message: Message override val message: Message,
override var unread: Boolean? = null,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseUiModel<MessageReply> { ) : BaseUiModel<MessageReply> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.MESSAGE_REPLY.viewType get() = BaseUiModel.ViewType.MESSAGE_REPLY.viewType
......
...@@ -4,19 +4,21 @@ import chat.rocket.android.R ...@@ -4,19 +4,21 @@ import chat.rocket.android.R
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
data class MessageUiModel( data class MessageUiModel(
override val message: Message, override val message: Message,
override val rawData: Message, override val rawData: Message,
override val messageId: String, override val messageId: String,
override val avatar: String, override val avatar: String,
override val time: CharSequence, override val time: CharSequence,
override val senderName: CharSequence, override val senderName: CharSequence,
override val content: CharSequence, override val content: CharSequence,
override val isPinned: Boolean, override val isPinned: Boolean,
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
var isFirstUnread: Boolean, override var unread: Boolean? = null,
override var isTemporary: Boolean = false var isFirstUnread: Boolean,
override var isTemporary: Boolean = false,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseMessageUiModel<Message> { ) : BaseMessageUiModel<Message> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.MESSAGE.viewType get() = BaseUiModel.ViewType.MESSAGE.viewType
......
...@@ -15,7 +15,9 @@ data class UrlPreviewUiModel( ...@@ -15,7 +15,9 @@ data class UrlPreviewUiModel(
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
override var isTemporary: Boolean = false override var isTemporary: Boolean = false,
override var unread: Boolean? = null,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseUiModel<Url> { ) : BaseUiModel<Url> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.URL_PREVIEW.viewType get() = BaseUiModel.ViewType.URL_PREVIEW.viewType
......
...@@ -14,7 +14,9 @@ data class VideoAttachmentUiModel( ...@@ -14,7 +14,9 @@ data class VideoAttachmentUiModel(
override var reactions: List<ReactionUiModel>, override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null, override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null, override var preview: Message? = null,
override var isTemporary: Boolean = false override var isTemporary: Boolean = false,
override var unread: Boolean? = null,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseFileAttachmentUiModel<VideoAttachment> { ) : BaseFileAttachmentUiModel<VideoAttachment> {
override val viewType: Int override val viewType: Int
get() = BaseUiModel.ViewType.VIDEO_ATTACHMENT.viewType get() = BaseUiModel.ViewType.VIDEO_ATTACHMENT.viewType
......
...@@ -10,6 +10,7 @@ import chat.rocket.android.chatrooms.adapter.model.RoomUiModel ...@@ -10,6 +10,7 @@ import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.db.model.ChatRoom import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.checkIfMyself import chat.rocket.android.infrastructure.checkIfMyself
import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.showLastMessage import chat.rocket.android.server.domain.showLastMessage
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
...@@ -27,7 +28,7 @@ import chat.rocket.core.model.SpotlightResult ...@@ -27,7 +28,7 @@ import chat.rocket.core.model.SpotlightResult
class RoomUiModelMapper( class RoomUiModelMapper(
private val context: Application, private val context: Application,
private val settings: PublicSettings, private val settings: PublicSettings,
private val localRepository: LocalRepository, private val userInteractor: GetCurrentUserInteractor,
private val serverUrl: String private val serverUrl: String
) { ) {
private val nameUnreadColor = ContextCompat.getColor(context, R.color.colorPrimaryText) private val nameUnreadColor = ContextCompat.getColor(context, R.color.colorPrimaryText)
...@@ -37,6 +38,10 @@ class RoomUiModelMapper( ...@@ -37,6 +38,10 @@ class RoomUiModelMapper(
private val messageUnreadColor = ContextCompat.getColor(context, android.R.color.primary_text_light) private val messageUnreadColor = ContextCompat.getColor(context, android.R.color.primary_text_light)
private val messageColor = ContextCompat.getColor(context, R.color.colorSecondaryText) private val messageColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
private val currentUser by lazy {
userInteractor.get()
}
fun map(rooms: List<ChatRoom>, grouped: Boolean = false): List<ItemHolder<*>> { fun map(rooms: List<ChatRoom>, grouped: Boolean = false): List<ItemHolder<*>> {
val list = ArrayList<ItemHolder<*>>(rooms.size + 4) val list = ArrayList<ItemHolder<*>>(rooms.size + 4)
var lastType: String? = null var lastType: String? = null
...@@ -68,13 +73,15 @@ class RoomUiModelMapper( ...@@ -68,13 +73,15 @@ class RoomUiModelMapper(
val name = mapName(user.username!!, user.name, false) val name = mapName(user.username!!, user.name, false)
val status = user.status val status = user.status
val avatar = serverUrl.avatarUrl(user.username!!) val avatar = serverUrl.avatarUrl(user.username!!)
val username = user.username!!
RoomUiModel( RoomUiModel(
id = user.id, id = user.id,
name = name, name = name,
type = roomTypeOf(RoomType.DIRECT_MESSAGE), type = roomTypeOf(RoomType.DIRECT_MESSAGE),
avatar = avatar, avatar = avatar,
status = status status = status,
username = username
) )
} }
} }
...@@ -86,8 +93,9 @@ class RoomUiModelMapper( ...@@ -86,8 +93,9 @@ class RoomUiModelMapper(
name = name!!, name = name!!,
type = type, type = type,
avatar = serverUrl.avatarUrl(name!!, isGroupOrChannel = true), avatar = serverUrl.avatarUrl(name!!, isGroupOrChannel = true),
lastMessage = mapLastMessage(lastMessage?.sender?.username, lastMessage = mapLastMessage(lastMessage?.sender?.id, lastMessage?.sender?.username,
lastMessage?.sender?.name, lastMessage?.message) lastMessage?.sender?.name, lastMessage?.message,
isDirectMessage = type is RoomType.DirectMessage)
) )
} }
} }
...@@ -97,7 +105,7 @@ class RoomUiModelMapper( ...@@ -97,7 +105,7 @@ class RoomUiModelMapper(
val isUnread = alert || unread > 0 val isUnread = alert || unread > 0
val type = roomTypeOf(type) val type = roomTypeOf(type)
val status = chatRoom.status?.let { userStatusOf(it) } val status = chatRoom.status?.let { userStatusOf(it) }
val roomName = mapName(name, chatRoom.userFullname, isUnread) val roomName = mapName(name, fullname, isUnread)
val timestamp = mapDate(lastMessageTimestamp ?: updatedAt, isUnread) val timestamp = mapDate(lastMessageTimestamp ?: updatedAt, isUnread)
val avatar = if (type is RoomType.DirectMessage) { val avatar = if (type is RoomType.DirectMessage) {
serverUrl.avatarUrl(name) serverUrl.avatarUrl(name)
...@@ -105,19 +113,23 @@ class RoomUiModelMapper( ...@@ -105,19 +113,23 @@ class RoomUiModelMapper(
serverUrl.avatarUrl(name, isGroupOrChannel = true) serverUrl.avatarUrl(name, isGroupOrChannel = true)
} }
val unread = mapUnread(unread) val unread = mapUnread(unread)
val lastMessage = mapLastMessage(chatRoom.lastMessageUserName, val lastMessage = mapLastMessage(lastMessageUserId, chatRoom.lastMessageUserName,
chatRoom.lastMessageUserFullName, lastMessageText, isUnread) chatRoom.lastMessageUserFullName, lastMessageText, isUnread,
type is RoomType.DirectMessage)
val open = open
RoomUiModel( RoomUiModel(
id = id, id = id,
name = roomName, name = roomName,
type = type, type = type,
avatar = avatar, avatar = avatar,
open = open,
date = timestamp, date = timestamp,
unread = unread, unread = unread,
alert = isUnread, alert = isUnread,
lastMessage = lastMessage, lastMessage = lastMessage,
status = status status = status,
username = if (type is RoomType.DirectMessage) name else null
) )
} }
} }
...@@ -133,14 +145,16 @@ class RoomUiModelMapper( ...@@ -133,14 +145,16 @@ class RoomUiModelMapper(
} }
} }
private fun mapLastMessage(name: String?, fullName: String?, text: String?, unread: Boolean = false): CharSequence? { private fun mapLastMessage(userId: String?, name: String?, fullName: String?, text: String?,
unread: Boolean = false,
isDirectMessage: Boolean = false): CharSequence? {
return if (!settings.showLastMessage()) { return if (!settings.showLastMessage()) {
null null
} else if (name != null && text != null) { } else if (name != null && text != null) {
val user = if (localRepository.checkIfMyself(name)) { val user = if (currentUser != null && currentUser!!.id == userId) {
"${context.getString(R.string.msg_you)}: " "${context.getString(R.string.msg_you)}: "
} else { } else {
"${mapName(name, fullName, unread)}: " if (isDirectMessage) "" else "${mapName(name, fullName, unread)}: "
} }
val color = if (unread) messageUnreadColor else messageColor val color = if (unread) messageUnreadColor else messageColor
......
...@@ -8,9 +8,11 @@ data class RoomUiModel( ...@@ -8,9 +8,11 @@ data class RoomUiModel(
val type: RoomType, val type: RoomType,
val name: CharSequence, val name: CharSequence,
val avatar: String, val avatar: String,
val open: Boolean = false,
val date: CharSequence? = null, val date: CharSequence? = null,
val unread: String? = null, val unread: String? = null,
val alert: Boolean = false, val alert: Boolean = false,
val lastMessage: CharSequence? = null, val lastMessage: CharSequence? = null,
val status: UserStatus? = null val status: UserStatus? = null,
val username: String? = null
) )
\ No newline at end of file
...@@ -9,11 +9,12 @@ import chat.rocket.android.chatrooms.ui.ChatRoomsFragment ...@@ -9,11 +9,12 @@ 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.ChatRoomDao
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory import chat.rocket.android.db.UserDao
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
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.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
...@@ -37,13 +38,6 @@ class ChatRoomsFragmentModule { ...@@ -37,13 +38,6 @@ class ChatRoomsFragmentModule {
return frag return frag
} }
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides @Provides
@PerFragment @PerFragment
fun provideRocketChatClient( fun provideRocketChatClient(
...@@ -55,16 +49,11 @@ class ChatRoomsFragmentModule { ...@@ -55,16 +49,11 @@ class ChatRoomsFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun provideDatabaseManager( fun provideChatRoomDao(manager: DatabaseManager): ChatRoomDao = manager.chatRoomDao()
factory: DatabaseManagerFactory,
@Named("currentServer") currentServer: String
): DatabaseManager {
return factory.create(currentServer)
}
@Provides @Provides
@PerFragment @PerFragment
fun provideChatRoomDao(manager: DatabaseManager): ChatRoomDao = manager.chatRoomDao() fun provideUserDao(manager: DatabaseManager): UserDao = manager.userDao()
@Provides @Provides
@PerFragment @PerFragment
...@@ -98,9 +87,19 @@ class ChatRoomsFragmentModule { ...@@ -98,9 +87,19 @@ class ChatRoomsFragmentModule {
fun provideRoomMapper( fun provideRoomMapper(
context: Application, context: Application,
repository: SettingsRepository, repository: SettingsRepository,
localRepository: LocalRepository, userInteractor: GetCurrentUserInteractor,
@Named("currentServer") serverUrl: String @Named("currentServer") serverUrl: String
): RoomUiModelMapper { ): RoomUiModelMapper {
return RoomUiModelMapper(context, repository.get(serverUrl), localRepository, serverUrl) return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl)
}
@Provides
@PerFragment
fun provideGetCurrentUserInteractor(
tokenRepository: TokenRepository,
@Named("currentServer") serverUrl: String,
userDao: UserDao
): GetCurrentUserInteractor {
return GetCurrentUserInteractor(tokenRepository, serverUrl, userDao)
} }
} }
\ No newline at end of file
...@@ -3,7 +3,6 @@ package chat.rocket.android.chatrooms.domain ...@@ -3,7 +3,6 @@ package chat.rocket.android.chatrooms.domain
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.ChatRoomEntity import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.UserEntity import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.chatRooms import chat.rocket.core.internal.rest.chatRooms
...@@ -18,60 +17,11 @@ class FetchChatRoomsInteractor( ...@@ -18,60 +17,11 @@ class FetchChatRoomsInteractor(
suspend fun refreshChatRooms() { suspend fun refreshChatRooms() {
val rooms = retryIO("fetch chatRooms", times = 10, val rooms = retryIO("fetch chatRooms", times = 10,
initialDelay = 200, maxDelay = 2000) { initialDelay = 200, maxDelay = 2000) {
client.chatRooms().update.map { room -> client.chatRooms().update
mapChatRoom(room)
}
} }
Timber.d("Refreshing rooms: $rooms") Timber.d("Refreshing rooms: $rooms")
dbManager.insert(rooms) dbManager.processRooms(rooms)
}
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
...@@ -9,8 +9,8 @@ import chat.rocket.android.helper.UserHelper ...@@ -9,8 +9,8 @@ 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.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.useSpecialCharsOnRoom
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.domain.useSpecialCharsOnRoom
import chat.rocket.android.server.infraestructure.ConnectionManager import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
...@@ -20,9 +20,12 @@ import chat.rocket.common.model.User ...@@ -20,9 +20,12 @@ import chat.rocket.common.model.User
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.createDirectMessage import chat.rocket.core.internal.realtime.createDirectMessage
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.show
import kotlinx.coroutines.experimental.withTimeout
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
import kotlin.coroutines.experimental.suspendCoroutine
class ChatRoomsPresenter @Inject constructor( class ChatRoomsPresenter @Inject constructor(
private val view: ChatRoomsView, private val view: ChatRoomsView,
...@@ -44,52 +47,65 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -44,52 +47,65 @@ class ChatRoomsPresenter @Inject constructor(
try { try {
val room = dbManager.getRoom(chatRoom.id) val room = dbManager.getRoom(chatRoom.id)
if (room != null) { if (room != null) {
loadChatRoom(room.chatRoom) loadChatRoom(room.chatRoom, true)
} else { } else {
with(chatRoom) { with(chatRoom) {
val entity = ChatRoomEntity( val entity = ChatRoomEntity(
id = id, id = id,
subscriptionId = "", subscriptionId = "",
type = type.toString(), type = type.toString(),
name = name.toString(), name = username ?: name.toString(),
open = false fullname = name.toString(),
open = open
) )
loadChatRoom(entity) loadChatRoom(entity, false)
} }
} }
} catch (ex: Exception) {
Timber.d(ex, "Error loading channel")
view.showGenericErrorMessage()
} finally { } finally {
view.hideLoadingRoom() view.hideLoadingRoom()
} }
} }
} }
fun loadChatRoom(chatRoom: ChatRoomEntity) { suspend fun loadChatRoom(chatRoom: ChatRoomEntity, local: Boolean = false) {
with(chatRoom) { with(chatRoom) {
val isDirectMessage = roomTypeOf(type) is RoomType.DirectMessage val isDirectMessage = roomTypeOf(type) is RoomType.DirectMessage
val roomName = if (settings.useSpecialCharsOnRoom() || (isDirectMessage && settings.useRealName())) { val roomName = if (settings.useSpecialCharsOnRoom() || (isDirectMessage && settings.useRealName())) {
fullname ?: name fullname ?: name
} else { } else {
name name
} }
launchUI(strategy) { val myself = getCurrentUser()
val myself = getCurrentUser() if (myself?.username == null) {
if (myself?.username == null) { view.showMessage(R.string.msg_generic_error)
view.showMessage(R.string.msg_generic_error) } else {
} else { val id = if (isDirectMessage && !open) {
val id = if (isDirectMessage && !open) { // If from local database, we already have the roomId, no need to concatenate
if (local) {
retryIO {
client.show(id, roomTypeOf(RoomType.DIRECT_MESSAGE))
}
id
} else {
retryIO("createDirectMessage($name)") { retryIO("createDirectMessage($name)") {
client.createDirectMessage(name) withTimeout(10000) {
createDirectMessage(name)
}
} }
val fromTo = mutableListOf(myself.id, id).apply { val fromTo = mutableListOf(myself.id, id).apply {
sort() sort()
} }
fromTo.joinToString("") fromTo.joinToString("")
} else {
id
} }
} else {
id
}
navigator.toChatRoom( navigator.toChatRoom(
chatRoomId = id, chatRoomId = id,
chatRoomName = roomName, chatRoomName = roomName,
chatRoomType = type, chatRoomType = type,
...@@ -98,8 +114,7 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -98,8 +114,7 @@ class ChatRoomsPresenter @Inject constructor(
isSubscribed = open, isSubscribed = open,
isCreator = ownerId == myself.id || isDirectMessage, isCreator = ownerId == myself.id || isDirectMessage,
isFavorite = favorite ?: false isFavorite = favorite ?: false
) )
}
} }
} }
} }
...@@ -125,4 +140,10 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -125,4 +140,10 @@ class ChatRoomsPresenter @Inject constructor(
} }
return null return null
} }
private suspend fun createDirectMessage(name: String): Boolean = suspendCoroutine { cont ->
client.createDirectMessage(name) { success, _ ->
cont.resume(success)
}
}
} }
\ No newline at end of file
...@@ -8,6 +8,8 @@ import chat.rocket.android.authentication.server.di.ServerFragmentProvider ...@@ -8,6 +8,8 @@ import chat.rocket.android.authentication.server.di.ServerFragmentProvider
import chat.rocket.android.authentication.signup.di.SignupFragmentProvider import chat.rocket.android.authentication.signup.di.SignupFragmentProvider
import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider
import chat.rocket.android.authentication.ui.AuthenticationActivity import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatinformation.di.MessageInfoFragmentProvider
import chat.rocket.android.chatinformation.ui.MessageInfoActivity
import chat.rocket.android.chatroom.di.ChatRoomFragmentProvider import chat.rocket.android.chatroom.di.ChatRoomFragmentProvider
import chat.rocket.android.chatroom.di.ChatRoomModule import chat.rocket.android.chatroom.di.ChatRoomModule
import chat.rocket.android.chatroom.ui.ChatRoomActivity import chat.rocket.android.chatroom.ui.ChatRoomActivity
...@@ -82,6 +84,8 @@ abstract class ActivityBuilder { ...@@ -82,6 +84,8 @@ abstract class ActivityBuilder {
abstract fun bindChangeServerActivity(): ChangeServerActivity abstract fun bindChangeServerActivity(): ChangeServerActivity
@PerActivity @PerActivity
@ContributesAndroidInjector(modules = [MessageInfoFragmentProvider::class])
abstract fun bindMessageInfoActiviy(): MessageInfoActivity
@ContributesAndroidInjector(modules = [DrawModule::class]) @ContributesAndroidInjector(modules = [DrawModule::class])
abstract fun bindDrawingActivity(): DrawingActivity abstract fun bindDrawingActivity(): DrawingActivity
} }
\ No newline at end of file
...@@ -14,6 +14,8 @@ import chat.rocket.android.authentication.infraestructure.SharedPreferencesToken ...@@ -14,6 +14,8 @@ import chat.rocket.android.authentication.infraestructure.SharedPreferencesToken
import chat.rocket.android.chatroom.service.MessageService import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.qualifier.ForAuthentication import chat.rocket.android.dagger.qualifier.ForAuthentication
import chat.rocket.android.dagger.qualifier.ForMessages import chat.rocket.android.dagger.qualifier.ForMessages
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository
...@@ -68,6 +70,7 @@ import ru.noties.markwon.SpannableConfiguration ...@@ -68,6 +70,7 @@ import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.spans.SpannableTheme import ru.noties.markwon.spans.SpannableTheme
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
...@@ -82,7 +85,7 @@ class AppModule { ...@@ -82,7 +85,7 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor { fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) { override fun log(message: String) {
Timber.d(message) Timber.d(message)
} }
...@@ -254,7 +257,12 @@ class AppModule { ...@@ -254,7 +257,12 @@ class AppModule {
} }
@Provides @Provides
fun provideMessageParser(context: Application, configuration: SpannableConfiguration, serverInteractor: GetCurrentServerInteractor, settingsInteractor: GetSettingsInteractor): MessageParser { fun provideMessageParser(
context: Application,
configuration: SpannableConfiguration,
serverInteractor: GetCurrentServerInteractor,
settingsInteractor: GetSettingsInteractor
): MessageParser {
val url = serverInteractor.get()!! val url = serverInteractor.get()!!
return MessageParser(context, configuration, settingsInteractor.get(url)) return MessageParser(context, configuration, settingsInteractor.get(url))
} }
...@@ -301,4 +309,18 @@ class AppModule { ...@@ -301,4 +309,18 @@ class AppModule {
fun provideJobSchedulerInteractor(jobScheduler: JobScheduler, jobInfo: JobInfo): JobSchedulerInteractor { fun provideJobSchedulerInteractor(jobScheduler: JobScheduler, jobInfo: JobInfo): JobSchedulerInteractor {
return JobSchedulerInteractorImpl(jobScheduler, jobInfo) return JobSchedulerInteractorImpl(jobScheduler, jobInfo)
} }
}
\ No newline at end of file @Provides
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides
fun provideDatabaseManager(
factory: DatabaseManagerFactory,
@Named("currentServer") currentServer: String
): DatabaseManager {
return factory.create(currentServer)
}
}
...@@ -21,19 +21,23 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> { ...@@ -21,19 +21,23 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
abstract fun get(id: String): ChatRoom? abstract fun get(id: String): ChatRoom?
@Transaction @Transaction
@Query("$BASE_QUERY") @Query("$BASE_QUERY $FILTER_NOT_OPENED")
abstract fun getAllSync(): List<ChatRoom> abstract fun getAllSync(): List<ChatRoom>
@Transaction @Transaction
@Query("""$BASE_QUERY WHERE chatrooms.name LIKE '%' || :query || '%' OR users.name LIKE '%' || :query || '%'""") @Query("""$BASE_QUERY
WHERE chatrooms.name LIKE '%' || :query || '%'
OR users.name LIKE '%' || :query || '%'
""")
abstract fun searchSync(query: String): List<ChatRoom> abstract fun searchSync(query: String): List<ChatRoom>
@Query("SELECT COUNT(id) FROM chatrooms") @Query("SELECT COUNT(id) FROM chatrooms WHERE open = 1")
abstract fun count(): Long abstract fun count(): Long
@Transaction @Transaction
@Query(""" @Query("""
$BASE_QUERY $BASE_QUERY
$FILTER_NOT_OPENED
ORDER BY ORDER BY
CASE CASE
WHEN lastMessageTimeStamp IS NOT NULL THEN lastMessageTimeStamp WHEN lastMessageTimeStamp IS NOT NULL THEN lastMessageTimeStamp
...@@ -45,6 +49,7 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> { ...@@ -45,6 +49,7 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
@Transaction @Transaction
@Query(""" @Query("""
$BASE_QUERY $BASE_QUERY
$FILTER_NOT_OPENED
ORDER BY ORDER BY
$TYPE_ORDER, $TYPE_ORDER,
CASE CASE
...@@ -57,6 +62,7 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> { ...@@ -57,6 +62,7 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
@Transaction @Transaction
@Query(""" @Query("""
$BASE_QUERY $BASE_QUERY
$FILTER_NOT_OPENED
ORDER BY name ORDER BY name
""") """)
abstract fun getAllAlphabetically(): LiveData<List<ChatRoom>> abstract fun getAllAlphabetically(): LiveData<List<ChatRoom>>
...@@ -64,6 +70,7 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> { ...@@ -64,6 +70,7 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
@Transaction @Transaction
@Query(""" @Query("""
$BASE_QUERY $BASE_QUERY
$FILTER_NOT_OPENED
ORDER BY ORDER BY
$TYPE_ORDER, $TYPE_ORDER,
name name
...@@ -113,12 +120,16 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> { ...@@ -113,12 +120,16 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
LEFT JOIN users AS lmUsers ON chatrooms.lastMessageUserId = lmUsers.id LEFT JOIN users AS lmUsers ON chatrooms.lastMessageUserId = lmUsers.id
""" """
const val FILTER_NOT_OPENED = """
WHERE chatrooms.open = 1
"""
const val TYPE_ORDER = """ const val TYPE_ORDER = """
CASE CASE
WHEN type = '${RoomType.CHANNEL}' THEN 1 WHEN type = 'c' THEN 1
WHEN type = '${RoomType.PRIVATE_GROUP}' THEN 2 WHEN type = 'p' THEN 2
WHEN type = '${RoomType.DIRECT_MESSAGE}' THEN 3 WHEN type = 'd' THEN 3
WHEN type = '${RoomType.LIVECHAT}' THEN 4 WHEN type = 'l' THEN 4
ELSE 5 ELSE 5
END END
""" """
......
...@@ -2,16 +2,29 @@ package chat.rocket.android.db ...@@ -2,16 +2,29 @@ package chat.rocket.android.db
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import chat.rocket.android.db.model.ChatRoomEntity import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.UserEntity import chat.rocket.android.db.model.UserEntity
@Database( @Database(
entities = [UserEntity::class, ChatRoomEntity::class], entities = [UserEntity::class, ChatRoomEntity::class],
version = 3, version = 5,
exportSchema = true exportSchema = true
) )
abstract class RCDatabase : RoomDatabase() { abstract class RCDatabase : RoomDatabase() {
abstract fun userDao(): UserDao abstract fun userDao(): UserDao
abstract fun chatRoomDao(): ChatRoomDao abstract fun chatRoomDao(): ChatRoomDao
companion object {
@JvmField
val MIGRATION_4_5 = Migration4to5()
}
}
class Migration4to5 : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE INDEX `index_chatrooms_lastMessageUserId` ON `chatrooms` (`lastMessageUserId`)")
}
} }
\ No newline at end of file
...@@ -13,6 +13,12 @@ import timber.log.Timber ...@@ -13,6 +13,12 @@ import timber.log.Timber
@Dao @Dao
abstract class UserDao : BaseDao<UserEntity> { abstract class UserDao : BaseDao<UserEntity> {
@Query("""
UPDATE users set STATUS = "offline"
""")
abstract fun clearStatus()
@Update(onConflict = OnConflictStrategy.IGNORE) @Update(onConflict = OnConflictStrategy.IGNORE)
abstract fun update(user: UserEntity): Int abstract fun update(user: UserEntity): Int
...@@ -22,6 +28,9 @@ abstract class UserDao : BaseDao<UserEntity> { ...@@ -22,6 +28,9 @@ abstract class UserDao : BaseDao<UserEntity> {
@Query("SELECT id FROM users WHERE ID = :id") @Query("SELECT id FROM users WHERE ID = :id")
abstract fun findUser(id: String): String? abstract fun findUser(id: String): String?
@Query("SELECT * FROM users WHERE ID = :id")
abstract fun getUser(id:String): UserEntity?
@Transaction @Transaction
open fun upsert(user: BaseUserEntity) { open fun upsert(user: BaseUserEntity) {
internalUpsert(user) internalUpsert(user)
......
...@@ -11,7 +11,8 @@ import androidx.room.PrimaryKey ...@@ -11,7 +11,8 @@ import androidx.room.PrimaryKey
Index(value = ["userId"]), Index(value = ["userId"]),
Index(value = ["ownerId"]), Index(value = ["ownerId"]),
Index(value = ["subscriptionId"], unique = true), Index(value = ["subscriptionId"], unique = true),
Index(value = ["updatedAt"]) Index(value = ["updatedAt"]),
Index(value = ["lastMessageUserId"])
], ],
foreignKeys = [ foreignKeys = [
ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["ownerId"]), ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["ownerId"]),
...@@ -40,7 +41,8 @@ data class ChatRoomEntity( ...@@ -40,7 +41,8 @@ data class ChatRoomEntity(
var lastSeen: Long? = -1, var lastSeen: Long? = -1,
var lastMessageText: String? = null, var lastMessageText: String? = null,
var lastMessageUserId: String? = null, var lastMessageUserId: String? = null,
var lastMessageTimestamp: Long? = null var lastMessageTimestamp: Long? = null,
var broadcast: Boolean? = false
) )
data class ChatRoom( data class ChatRoom(
......
...@@ -22,22 +22,6 @@ class FavoriteMessagesFragmentModule { ...@@ -22,22 +22,6 @@ class FavoriteMessagesFragmentModule {
return frag return frag
} }
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides
@PerFragment
fun provideDatabaseManager(
factory: DatabaseManagerFactory,
@Named("currentServer") currentServer: String
): DatabaseManager {
return factory.create(currentServer)
}
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun provideJob() = Job()
......
...@@ -3,15 +3,11 @@ package chat.rocket.android.files.di ...@@ -3,15 +3,11 @@ package chat.rocket.android.files.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.files.presentation.FilesView import chat.rocket.android.files.presentation.FilesView
import chat.rocket.android.files.ui.FilesFragment import chat.rocket.android.files.ui.FilesFragment
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module @Module
class FilesFragmentModule { class FilesFragmentModule {
...@@ -22,22 +18,6 @@ class FilesFragmentModule { ...@@ -22,22 +18,6 @@ class FilesFragmentModule {
return frag return frag
} }
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides
@PerFragment
fun provideDatabaseManager(
factory: DatabaseManagerFactory,
@Named("currentServer") currentServer: String
): DatabaseManager {
return factory.create(currentServer)
}
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun provideJob() = Job()
......
...@@ -5,6 +5,7 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor ...@@ -5,6 +5,7 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
import chat.rocket.common.model.SimpleUser
import chat.rocket.common.model.User import chat.rocket.common.model.User
import javax.inject.Inject import javax.inject.Inject
...@@ -26,6 +27,10 @@ class UserHelper @Inject constructor( ...@@ -26,6 +27,10 @@ class UserHelper @Inject constructor(
return if (settings.useRealName()) user.name ?: user.username else user.username return if (settings.useRealName()) user.name ?: user.username else user.username
} }
fun displayName(user: SimpleUser): String {
return if (settings.useRealName()) user.name ?: user.username ?: "" else user.username ?: ""
}
/** /**
* Return current logged user's display name. * Return current logged user's display name.
* *
......
...@@ -32,5 +32,6 @@ interface LocalRepository { ...@@ -32,5 +32,6 @@ interface LocalRepository {
} }
} }
// FIXME - we are saving the user full name here when the server is UI_Use_Real_Name true
fun LocalRepository.checkIfMyself(username: String) = username() == username fun LocalRepository.checkIfMyself(username: String) = username() == username
fun LocalRepository.username() = get(LocalRepository.CURRENT_USERNAME_KEY) fun LocalRepository.username() = get(LocalRepository.CURRENT_USERNAME_KEY)
\ No newline at end of file
...@@ -52,9 +52,10 @@ class SharedPreferencesLocalRepository( ...@@ -52,9 +52,10 @@ class SharedPreferencesLocalRepository(
override fun clear(key: String) = preferences.edit { remove(key) } override fun clear(key: String) = preferences.edit { remove(key) }
override fun clearAllFromServer(server: String) { override fun clearAllFromServer(server: String) {
clear(LocalRepository.KEY_PUSH_TOKEN) preferences.all.keys.forEach { key ->
clear(LocalRepository.TOKEN_KEY + server) if (key.startsWith(server, ignoreCase = true)) {
clear(LocalRepository.SETTINGS_KEY + server) clear(key)
clear(LocalRepository.CURRENT_USERNAME_KEY) }
}
} }
} }
\ No newline at end of file
...@@ -9,6 +9,7 @@ import chat.rocket.android.server.domain.GetAccountsInteractor ...@@ -9,6 +9,7 @@ import chat.rocket.android.server.domain.GetAccountsInteractor
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.PublicSettings
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.RemoveAccountInteractor import chat.rocket.android.server.domain.RemoveAccountInteractor
import chat.rocket.android.server.domain.SaveAccountInteractor import chat.rocket.android.server.domain.SaveAccountInteractor
import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.TokenRepository
...@@ -33,6 +34,7 @@ import chat.rocket.core.internal.rest.unregisterPushToken ...@@ -33,6 +34,7 @@ import chat.rocket.core.internal.rest.unregisterPushToken
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
...@@ -43,6 +45,7 @@ class MainPresenter @Inject constructor( ...@@ -43,6 +45,7 @@ class MainPresenter @Inject constructor(
private val navigator: MainNavigator, private val navigator: MainNavigator,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val serverInteractor: GetCurrentServerInteractor, private val serverInteractor: GetCurrentServerInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val navHeaderMapper: NavHeaderUiModelMapper, private val navHeaderMapper: NavHeaderUiModelMapper,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
...@@ -149,6 +152,7 @@ class MainPresenter @Inject constructor( ...@@ -149,6 +152,7 @@ class MainPresenter @Inject constructor(
} }
fun connect() { fun connect() {
launch { refreshSettingsInteractor.refresh(currentServer) }
manager.connect() manager.connect()
} }
......
...@@ -4,16 +4,12 @@ import androidx.lifecycle.LifecycleOwner ...@@ -4,16 +4,12 @@ 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
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.members.presentation.MembersNavigator import chat.rocket.android.members.presentation.MembersNavigator
import chat.rocket.android.members.presentation.MembersView import chat.rocket.android.members.presentation.MembersView
import chat.rocket.android.members.ui.MembersFragment import chat.rocket.android.members.ui.MembersFragment
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module @Module
class MembersFragmentModule { class MembersFragmentModule {
...@@ -28,22 +24,6 @@ class MembersFragmentModule { ...@@ -28,22 +24,6 @@ class MembersFragmentModule {
@PerFragment @PerFragment
fun provideChatRoomNavigator(activity: ChatRoomActivity) = MembersNavigator(activity) fun provideChatRoomNavigator(activity: ChatRoomActivity) = MembersNavigator(activity)
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides
@PerFragment
fun provideDatabaseManager(
factory: DatabaseManagerFactory,
@Named("currentServer") currentServer: String
): DatabaseManager {
return factory.create(currentServer)
}
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun provideJob() = Job()
......
...@@ -5,11 +5,9 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -5,11 +5,9 @@ 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.mentions.presentention.MentionsView import chat.rocket.android.mentions.presentention.MentionsView
import chat.rocket.android.mentions.ui.MentionsFragment import chat.rocket.android.mentions.ui.MentionsFragment
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module @Module
class MentionsFragmentModule { class MentionsFragmentModule {
...@@ -20,13 +18,6 @@ class MentionsFragmentModule { ...@@ -20,13 +18,6 @@ class MentionsFragmentModule {
return frag return frag
} }
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun provideJob() = Job()
......
...@@ -3,15 +3,11 @@ package chat.rocket.android.pinnedmessages.di ...@@ -3,15 +3,11 @@ package chat.rocket.android.pinnedmessages.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.pinnedmessages.presentation.PinnedMessagesView import chat.rocket.android.pinnedmessages.presentation.PinnedMessagesView
import chat.rocket.android.pinnedmessages.ui.PinnedMessagesFragment import chat.rocket.android.pinnedmessages.ui.PinnedMessagesFragment
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module @Module
class PinnedMessagesFragmentModule { class PinnedMessagesFragmentModule {
...@@ -22,22 +18,6 @@ class PinnedMessagesFragmentModule { ...@@ -22,22 +18,6 @@ class PinnedMessagesFragmentModule {
return frag return frag
} }
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides
@PerFragment
fun provideDatabaseManager(
factory: DatabaseManagerFactory,
@Named("currentServer") currentServer: String
): DatabaseManager {
return factory.create(currentServer)
}
@Provides @Provides
@PerFragment @PerFragment
fun provideJob() = Job() fun provideJob() = Job()
......
...@@ -14,6 +14,7 @@ import chat.rocket.android.profile.presentation.ProfileView ...@@ -14,6 +14,7 @@ import chat.rocket.android.profile.presentation.ProfileView
import chat.rocket.android.util.extension.asObservable import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.avatar_profile.* import kotlinx.android.synthetic.main.avatar_profile.*
...@@ -28,7 +29,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -28,7 +29,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
private lateinit var currentEmail: String private lateinit var currentEmail: String
private lateinit var currentAvatar: String private lateinit var currentAvatar: String
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private lateinit var editTextsDisposable: Disposable private val editTextsDisposable = CompositeDisposable()
companion object { companion object {
fun newInstance() = ProfileFragment() fun newInstance() = ProfileFragment()
...@@ -170,7 +171,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -170,7 +171,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} }
private fun subscribeEditTexts() { private fun subscribeEditTexts() {
editTextsDisposable = Observables.combineLatest( editTextsDisposable.add(Observables.combineLatest(
text_name.asObservable(), text_name.asObservable(),
text_username.asObservable(), text_username.asObservable(),
text_email.asObservable(), text_email.asObservable(),
...@@ -186,11 +187,11 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -186,11 +187,11 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} else { } else {
finishActionMode() finishActionMode()
} }
} })
} }
private fun unsubscribeEditTexts() { private fun unsubscribeEditTexts() {
editTextsDisposable.dispose() editTextsDisposable.clear()
} }
private fun startActionMode() { private fun startActionMode() {
......
package chat.rocket.android.server.domain
import chat.rocket.android.db.UserDao
import chat.rocket.android.db.model.UserEntity
class GetCurrentUserInteractor(
private val tokenRepository: TokenRepository,
private val currentServer: String,
private val userDao: UserDao
) {
fun get(): UserEntity? {
return tokenRepository.get(currentServer)?.let {
userDao.getUser(it.userId)
}
}
}
...@@ -25,7 +25,7 @@ class RefreshSettingsInteractor @Inject constructor( ...@@ -25,7 +25,7 @@ class RefreshSettingsInteractor @Inject constructor(
HIDE_USER_JOIN, HIDE_USER_LEAVE, HIDE_USER_JOIN, HIDE_USER_LEAVE,
HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING, HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING,
ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, ALLOW_MESSAGE_STARRING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS, ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, ALLOW_MESSAGE_STARRING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS,
WIDE_TILE_310, STORE_LAST_MESSAGE) WIDE_TILE_310, STORE_LAST_MESSAGE, MESSAGE_READ_RECEIPT_ENABLED, MESSAGE_READ_RECEIPT_STORE_USERS)
suspend fun refresh(server: String) { suspend fun refresh(server: String) {
withContext(CommonPool) { withContext(CommonPool) {
......
...@@ -46,6 +46,8 @@ const val SHOW_EDITED_STATUS = "Message_ShowEditedStatus" ...@@ -46,6 +46,8 @@ const val SHOW_EDITED_STATUS = "Message_ShowEditedStatus"
const val ALLOW_MESSAGE_PINNING = "Message_AllowPinning" const val ALLOW_MESSAGE_PINNING = "Message_AllowPinning"
const val ALLOW_MESSAGE_STARRING = "Message_AllowStarring" const val ALLOW_MESSAGE_STARRING = "Message_AllowStarring"
const val STORE_LAST_MESSAGE = "Store_Last_Message" const val STORE_LAST_MESSAGE = "Store_Last_Message"
const val MESSAGE_READ_RECEIPT_ENABLED = "Message_Read_Receipt_Enabled"
const val MESSAGE_READ_RECEIPT_STORE_USERS = "Message_Read_Receipt_Store_Users"
/* /*
* Extension functions for Public Settings. * Extension functions for Public Settings.
...@@ -87,6 +89,9 @@ fun PublicSettings.allowedMessageDeleting(): Boolean = this[ALLOW_MESSAGE_DELETI ...@@ -87,6 +89,9 @@ fun PublicSettings.allowedMessageDeleting(): Boolean = this[ALLOW_MESSAGE_DELETI
fun PublicSettings.hasShowLastMessage(): Boolean = this[STORE_LAST_MESSAGE] != null fun PublicSettings.hasShowLastMessage(): Boolean = this[STORE_LAST_MESSAGE] != null
fun PublicSettings.showLastMessage(): Boolean = this[STORE_LAST_MESSAGE]?.value == true fun PublicSettings.showLastMessage(): Boolean = this[STORE_LAST_MESSAGE]?.value == true
fun PublicSettings.messageReadReceiptEnabled(): Boolean = this[MESSAGE_READ_RECEIPT_ENABLED]?.value == true
fun PublicSettings.messageReadReceiptStoreUsers(): Boolean = this[MESSAGE_READ_RECEIPT_STORE_USERS]?.value == true
fun PublicSettings.uploadMimeTypeFilter(): Array<String>? { fun PublicSettings.uploadMimeTypeFilter(): Array<String>? {
val values = this[UPLOAD_WHITELIST_MIMETYPES]?.value as String? val values = this[UPLOAD_WHITELIST_MIMETYPES]?.value as String?
if (!values.isNullOrBlank()) { if (!values.isNullOrBlank()) {
......
...@@ -71,6 +71,7 @@ class ConnectionManager( ...@@ -71,6 +71,7 @@ class ConnectionManager(
Timber.d("Changing status to: $status") Timber.d("Changing status to: $status")
when (status) { when (status) {
is State.Connected -> { is State.Connected -> {
dbManager.clearUsersStatus()
client.subscribeSubscriptions { _, id -> client.subscribeSubscriptions { _, id ->
Timber.d("Subscribed to subscriptions: $id") Timber.d("Subscribed to subscriptions: $id")
subscriptionId = id subscriptionId = id
...@@ -149,7 +150,7 @@ class ConnectionManager( ...@@ -149,7 +150,7 @@ class ConnectionManager(
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()) dbManager.updateSelfUser(myself)
for (channel in userDataChannels) { for (channel in userDataChannels) {
channel.send(myself) channel.send(myself)
} }
...@@ -260,10 +261,6 @@ class ConnectionManager( ...@@ -260,10 +261,6 @@ class ConnectionManager(
} }
} }
private fun Myself.asUser(): User {
return User(id, name, username, status, utcOffset, null, roles)
}
private fun Long.orZero(): Long { private fun Long.orZero(): Long {
return if (this < 0) 0 else this return if (this < 0) 0 else this
} }
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/actionMenuColor"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/actionMenuColor"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#1D74F5"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF1D74F5"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#999999"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF999999"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>
<?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">
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<android.support.constraint.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_container" android:id="@+id/layout_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar_layout"> app:layout_constraintTop_toBottomOf="@id/toolbar_layout">
<android.support.design.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/members_chips" android:id="@+id/members_chips"
style="@style/Widget.MaterialComponents.Chip.Entry" style="@style/Widget.MaterialComponents.Chip.Entry"
android:layout_width="match_parent" android:layout_width="match_parent"
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
</android.support.design.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
<com.wang.avi.AVLoadingIndicatorView <com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading" android:id="@+id/view_loading"
...@@ -63,12 +63,12 @@ ...@@ -63,12 +63,12 @@
android:background="@color/colorDividerMessageComposer" android:background="@color/colorDividerMessageComposer"
app:layout_constraintTop_toBottomOf="@id/text_search_member" /> app:layout_constraintTop_toBottomOf="@id/text_search_member" />
<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="0dp" android:layout_height="0dp"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/separator_1" /> app:layout_constraintTop_toBottomOf="@id/separator_1" />
</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"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".chatinformation.ui.MessageInfoActivity">
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:indicatorColor="@color/colorBlack"
app:indicatorName="BallPulseIndicator"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/text_read_by"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textSize="18sp"
android:text="@string/read_by"
app:layout_constraintBottom_toTopOf="@+id/receipt_list"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/receipt_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_read_by" />
</androidx.constraintlayout.widget.ConstraintLayout>
...@@ -98,12 +98,24 @@ ...@@ -98,12 +98,24 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:src="@drawable/ic_action_message_star_24dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/text_sender" app:layout_constraintBottom_toBottomOf="@+id/text_sender"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/text_edit_indicator" app:layout_constraintStart_toEndOf="@+id/text_edit_indicator"
app:layout_constraintTop_toTopOf="@+id/text_sender" app:layout_constraintTop_toTopOf="@+id/text_sender"
app:srcCompat="@drawable/ic_action_message_star_24dp"
tools:visibility="visible" />
<ImageView
android:id="@+id/read_receipt_view"
android:layout_width="24dp"
android:layout_height="24dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/text_sender"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/image_star_indicator"
app:layout_constraintTop_toTopOf="@+id/text_sender"
app:srcCompat="@drawable/ic_check_unread_24dp"
tools:visibility="visible" /> tools:visibility="visible" />
<TextView <TextView
...@@ -112,9 +124,9 @@ ...@@ -112,9 +124,9 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="2dp" android:layout_marginBottom="2dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="4dp"
app:layout_constraintStart_toStartOf="@+id/text_sender" app:layout_constraintStart_toStartOf="@+id/text_sender"
app:layout_constraintTop_toBottomOf="@+id/text_sender" app:layout_constraintTop_toBottomOf="@+id/text_sender"
tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" /> tools:text="This is a multiline chat message from Bertie that will take more than just one line of text. I have sure that everything is amazing!" />
......
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/receipt_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/receipt_time"
app:layout_constraintStart_toEndOf="@+id/avatar_layout"
app:layout_constraintTop_toTopOf="parent"
tools:text="John Doe" />
<include
android:id="@+id/avatar_layout"
layout="@layout/avatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/receipt_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:gravity="center"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="04/06/2018 14:18:36" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?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/toolbar_container" android:id="@+id/toolbar_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<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="@dimen/toolbar_height" android:layout_height="@dimen/toolbar_height"
...@@ -29,6 +29,6 @@ ...@@ -29,6 +29,6 @@
android:gravity="end" android:gravity="end"
android:textSize="14sp" /> android:textSize="14sp" />
</android.support.v7.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file \ No newline at end of file
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu 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">
<item
android:id="@+id/action_message_info"
android:icon="@drawable/ic_action_message_info_outline_24dp"
android:title="@string/action_msg_info" />
<item <item
android:id="@+id/action_message_reply" android:id="@+id/action_message_reply"
android:icon="@drawable/ic_action_message_reply_24dp" android:icon="@drawable/ic_action_message_reply_24dp"
......
...@@ -150,6 +150,7 @@ ...@@ -150,6 +150,7 @@
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Respuesta</string> <string name="action_msg_reply">Respuesta</string>
<string name="action_msg_info">Información del mensaje</string>
<string name="action_msg_edit">Editar</string> <string name="action_msg_edit">Editar</string>
<string name="action_msg_copy">Copiar</string> <string name="action_msg_copy">Copiar</string>
<string name="action_msg_quote">Citar</string> <string name="action_msg_quote">Citar</string>
...@@ -157,8 +158,7 @@ ...@@ -157,8 +158,7 @@
<string name="action_msg_pin">Fijar mensaje</string> <string name="action_msg_pin">Fijar mensaje</string>
<string name="action_msg_unpin">Soltar mensaje</string> <string name="action_msg_unpin">Soltar mensaje</string>
<string name="action_msg_star">Star mensaje</string> <string name="action_msg_star">Star mensaje</string>
// TODO: Add proper translation. <string name="action_msg_unstar">Unstar mensaje</string>
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">Compartir</string> <string name="action_msg_share">Compartir</string>
<string name="action_title_editing">Edición de mensaje</string> <string name="action_title_editing">Edición de mensaje</string>
<string name="action_msg_add_reaction">Añadir una reacción</string> <string name="action_msg_add_reaction">Añadir una reacción</string>
...@@ -259,4 +259,8 @@ ...@@ -259,4 +259,8 @@
<string name="notif_action_reply_hint">RESPUESTA</string> <string name="notif_action_reply_hint">RESPUESTA</string>
<string name="notif_error_sending">La respuesta ha fallado. Inténtalo de nuevo.</string> <string name="notif_error_sending">La respuesta ha fallado. Inténtalo de nuevo.</string>
<string name="notif_success_sending">Mensaje enviado a %1$s!</string> <string name="notif_success_sending">Mensaje enviado a %1$s!</string>
<string name="read_by">Leído por</string>
<string name="message_information_title">Información del mensaje</string>
<string name="msg_log_out">Saliendo de tu cuenta…</string>
<string name="msg_sent_attachment">Envió un archivo</string>
</resources> </resources>
...@@ -172,16 +172,15 @@ ...@@ -172,16 +172,15 @@
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Répondre</string> <string name="action_msg_reply">Répondre</string>
<string name="action_msg_info">Informations sur le message</string>
<string name="action_msg_edit">Modifier</string> <string name="action_msg_edit">Modifier</string>
<string name="action_msg_copy">Copier</string> <string name="action_msg_copy">Copier</string>
<string name="action_msg_quote">Citation</string> <string name="action_msg_quote">Citation</string>
<string name="action_msg_delete">Effacer</string> <string name="action_msg_delete">Effacer</string>
<string name="action_msg_pin">Épingle message</string> <string name="action_msg_pin">Épingle message</string>
<string name="action_msg_unpin">Enlever message</string> <string name="action_msg_unpin">Enlever message</string>
// TODO: Add proper translation. <string name="action_msg_star">Ajouter aux Favoris</string>
<string name="action_msg_star">Star message</string> <string name="action_msg_unstar">Retirer des favoris</string>
// TODO: Add proper translation.
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">Partager</string> <string name="action_msg_share">Partager</string>
<string name="action_title_editing">Modification du message</string> <string name="action_title_editing">Modification du message</string>
<string name="action_msg_add_reaction">Ajouter une réaction</string> <string name="action_msg_add_reaction">Ajouter une réaction</string>
...@@ -291,6 +290,8 @@ ...@@ -291,6 +290,8 @@
<string name="notif_action_reply_hint">RÉPONDRE</string> <string name="notif_action_reply_hint">RÉPONDRE</string>
<string name="notif_error_sending">La réponse a échoué. Veuillez réessayer.</string> <string name="notif_error_sending">La réponse a échoué. Veuillez réessayer.</string>
<string name="notif_success_sending">Message envoyé à %1$s!</string> <string name="notif_success_sending">Message envoyé à %1$s!</string>
<!-- TODO Add proper translation--> <string name="read_by">Lire par</string>
<string name="msg_log_out">Logging out…</string> <string name="message_information_title">Informations sur le message</string>
<string name="msg_log_out">Déconnecter…</string>
<string name="msg_sent_attachment">Envoyé un fichier</string>
</resources> </resources>
...@@ -156,6 +156,7 @@ ...@@ -156,6 +156,7 @@
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">जवाब दें</string> <string name="action_msg_reply">जवाब दें</string>
<string name="action_msg_info">संदेश जानकारी</string>
<string name="action_msg_edit">संपादन करें</string> <string name="action_msg_edit">संपादन करें</string>
<string name="action_msg_copy">कॉपी</string> <string name="action_msg_copy">कॉपी</string>
<string name="action_msg_quote">उद्धरण</string> <string name="action_msg_quote">उद्धरण</string>
...@@ -268,6 +269,7 @@ ...@@ -268,6 +269,7 @@
<string name="notif_action_reply_hint">जवाब</string> <string name="notif_action_reply_hint">जवाब</string>
<string name="notif_error_sending">उत्तर विफल हुआ है। कृपया फिर से प्रयास करें।</string> <string name="notif_error_sending">उत्तर विफल हुआ है। कृपया फिर से प्रयास करें।</string>
<string name="notif_success_sending">संदेश भेजा गया %1$s!</string> <string name="notif_success_sending">संदेश भेजा गया %1$s!</string>
<!-- TODO Add proper translation--> <string name="read_by">द्वारा पढ़ें</string>
<string name="msg_log_out">Logging out…</string> <string name="message_information_title">संदेश की जानकारी</string>
</resources> <string name="msg_log_out">लॉग आउट कर रहा हूं…</string>
\ No newline at end of file </resources>
...@@ -157,6 +157,7 @@ ...@@ -157,6 +157,7 @@
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Responder</string> <string name="action_msg_reply">Responder</string>
<string name="action_msg_info">Informações da mensagem</string>
<string name="action_msg_edit">Editar</string> <string name="action_msg_edit">Editar</string>
<string name="action_msg_copy">Copiar</string> <string name="action_msg_copy">Copiar</string>
<string name="action_msg_quote">Citar</string> <string name="action_msg_quote">Citar</string>
...@@ -268,5 +269,8 @@ ...@@ -268,5 +269,8 @@
<string name="notif_action_reply_hint">RESPONDER</string> <string name="notif_action_reply_hint">RESPONDER</string>
<string name="notif_error_sending">Falha ao enviar a mensagem.</string> <string name="notif_error_sending">Falha ao enviar a mensagem.</string>
<string name="notif_success_sending">Mensagem enviada para %1$s!</string> <string name="notif_success_sending">Mensagem enviada para %1$s!</string>
<string name="read_by">Lida por</string>
<string name="message_information_title">Informações da mensagem</string>
<string name="msg_log_out">Deslogando…</string> <string name="msg_log_out">Deslogando…</string>
<string name="msg_sent_attachment">Enviou um arquivo</string>
</resources> </resources>
...@@ -153,6 +153,7 @@ ...@@ -153,6 +153,7 @@
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Ответить</string> <string name="action_msg_reply">Ответить</string>
<string name="action_msg_info">Інформація про повідомлення</string>
<string name="action_msg_edit">Редактировать</string> <string name="action_msg_edit">Редактировать</string>
<string name="action_msg_copy">Копировать</string> <string name="action_msg_copy">Копировать</string>
<string name="action_msg_quote">Цитата</string> <string name="action_msg_quote">Цитата</string>
......
...@@ -124,6 +124,7 @@ ...@@ -124,6 +124,7 @@
<string name="msg_upload_file">Upload file</string> <string name="msg_upload_file">Upload file</string>
<string name="msg_file_description">File description</string> <string name="msg_file_description">File description</string>
<string name="msg_send">Send</string> <string name="msg_send">Send</string>
<string name="msg_sent_attachment">Sent an attachment</string>
<!-- Create channel messages --> <!-- Create channel messages -->
<string name="msg_private_channel">Private</string> <string name="msg_private_channel">Private</string>
...@@ -157,6 +158,7 @@ ...@@ -157,6 +158,7 @@
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Reply</string> <string name="action_msg_reply">Reply</string>
<string name="action_msg_info">Message info</string>
<string name="action_msg_edit">Edit</string> <string name="action_msg_edit">Edit</string>
<string name="action_msg_copy">Copy</string> <string name="action_msg_copy">Copy</string>
<string name="action_msg_quote">Quote</string> <string name="action_msg_quote">Quote</string>
...@@ -268,4 +270,6 @@ ...@@ -268,4 +270,6 @@
<string name="notif_action_reply_hint">REPLY</string> <string name="notif_action_reply_hint">REPLY</string>
<string name="notif_error_sending">Reply has failed. Please try again.</string> <string name="notif_error_sending">Reply has failed. Please try again.</string>
<string name="notif_success_sending">Message sent to %1$s!</string> <string name="notif_success_sending">Message sent to %1$s!</string>
<string name="read_by">Read by</string>
<string name="message_information_title">Message information</string>
</resources> </resources>
\ No newline at end of file
...@@ -5,12 +5,12 @@ buildscript { ...@@ -5,12 +5,12 @@ buildscript {
repositories { repositories {
google() google()
jcenter() jcenter()
mavenCentral()
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.0-beta02' classpath 'com.android.tools.build:gradle:3.3.0-alpha03'
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'
......
...@@ -24,9 +24,9 @@ ext { ...@@ -24,9 +24,9 @@ ext {
playServices : '15.0.0', playServices : '15.0.0',
exoPlayer : '2.6.0', exoPlayer : '2.6.0',
flexbox : '0.3.2', flexbox : '0.3.2',
material : '1.0.0-alpha1', material : '1.0.0-beta01',
room : '2.0.0-alpha1', room : '2.0.0-beta01',
lifecycle : '2.0.0-beta01', lifecycle : '2.0.0-beta01',
rxKotlin : '2.2.0', rxKotlin : '2.2.0',
...@@ -59,7 +59,7 @@ ext { ...@@ -59,7 +59,7 @@ ext {
mockito : '2.10.0' mockito : '2.10.0'
] ]
libraries = [ libraries = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}", kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}",
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}",
......
...@@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME ...@@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-rc-2-all.zip
distributionSha256Sum=9af7345c199f1731c187c96d3fe3d31f5405192a42046bafa71d846c3d9adacb distributionSha256Sum=9bc16f929e5920a9a27fef22c65a8ab8140d705e418de0159ca6eb7f08a74200
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