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

Merge branch 'develop' of github.com:RocketChat/Rocket.Chat.Android into fix/typing-status

parents 492c29a6 638759d7
......@@ -12,8 +12,8 @@ android {
applicationId "chat.rocket.android"
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 2030
versionName "2.4.0"
versionCode 2031
versionName "2.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
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
......@@ -76,7 +76,12 @@
android:theme="@style/AppTheme"
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
android:name=".settings.password.ui.PasswordActivity"
android:theme="@style/AppTheme" />
......
......@@ -5,19 +5,58 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.OauthHelper
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.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
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.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.model.Email
import chat.rocket.common.model.Token
import chat.rocket.common.model.User
import chat.rocket.common.util.ifNull
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 timber.log.Timber
import java.util.concurrent.TimeUnit
......@@ -351,11 +390,20 @@ class LoginPresenter @Inject constructor(
}
}
}
val username = retryIO("me()") { client.me().username }
if (username != null) {
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
val myself = retryIO("me()") { client.me() }
if (myself.username != null) {
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)
saveAccount(username)
saveAccount(myself.username!!)
saveToken(token)
registerPushToken()
if (loginType == TYPE_LOGIN_USER_EMAIL) {
......
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,7 +78,8 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
private val onClickListener = { view: View ->
if (data?.message?.isSystemMessage() == false) {
data?.message?.let {
data?.let { vm ->
vm.message.let {
val menuItems = view.context.inflate(R.menu.message_actions).toList()
menuItems.find { it.itemId == R.id.action_message_unpin }?.apply {
setTitle(if (it.pinned) R.string.action_msg_unpin else R.string.action_msg_pin)
......@@ -102,6 +103,7 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
}
}
}
}
internal fun setupActionMenu(view: View) {
if (listener.isActionsEnabled()) {
......
......@@ -211,6 +211,9 @@ class ChatRoomAdapter(
override fun onActionSelected(item: MenuItem, message: Message) {
message.apply {
when (item.itemId) {
R.id.action_message_info -> {
presenter?.messageInfo(id)
}
R.id.action_message_reply -> {
if (roomName != null && roomType != null) {
presenter?.citeMessage(roomName, roomType, id, true)
......
......@@ -4,6 +4,7 @@ import android.graphics.Color
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.MessageUiModel
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.core.model.isSystemMessage
......@@ -39,6 +40,18 @@ class MessageViewHolder(
text_edit_indicator.isVisible = !it.isSystemMessage() && it.editedBy != null
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
}
}
}
}
package chat.rocket.android.chatroom.domain
data class MessageReply(
val roomName: String,
val permalink: String
)
\ No newline at end of file
data class MessageReply(val roomName: String, val permalink: String)
\ No newline at end of file
package chat.rocket.android.chatroom.presentation
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.chatRoomIntent
import chat.rocket.android.server.ui.changeServerIntent
......@@ -69,4 +70,9 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
)
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
......@@ -12,6 +12,7 @@ import android.text.SpannableStringBuilder
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
......@@ -147,6 +148,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private lateinit var actionSnackbar: ActionSnackbar
internal var citation: String? = null
private var editingMessageId: String? = null
internal var disableMenu: Boolean = false
private val compositeDisposable = CompositeDisposable()
private var playComposeMessageButtonsAnimation = true
......@@ -249,8 +251,17 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun onPause() {
super.onPause()
setReactionButtonIcon(R.drawable.ic_reaction_24dp)
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?) {
if (resultData != null && resultCode == Activity.RESULT_OK) {
......@@ -320,6 +331,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
recycler_view.addOnLayoutChangeListener(layoutChangeListener)
recycler_view.addOnScrollListener(onScrollListener)
// Load just once, on the first page...
presenter.loadActiveMembers(chatRoomId, chatRoomType, filterSelfOut = true)
}
val oldMessagesCount = adapter.itemCount
......@@ -350,7 +364,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
ui {
setupMessageComposer(userCanPost)
isBroadcastChannel = channelIsBroadcast
if (isBroadcastChannel && !userCanMod) activity?.invalidateOptionsMenu()
if (isBroadcastChannel && !userCanMod) {
disableMenu = true
activity?.invalidateOptionsMenu()
}
}
}
......
......@@ -14,42 +14,44 @@ internal fun ChatRoomFragment.setupMenu(menu: Menu) {
setupSearchMessageMenuItem(menu, requireContext())
setupFavoriteMenuItem(menu)
if (chatRoomType != RoomType.DIRECT_MESSAGE) {
menu.add(
Menu.NONE,
MENU_ACTION_MEMBER,
MENU_ACTION_PINNED_MESSAGES,
Menu.NONE,
R.string.title_members_list
R.string.title_pinned_messages
)
menu.add(
Menu.NONE,
MENU_ACTION_MENTIONS,
MENU_ACTION_FAVORITE_MESSAGES,
Menu.NONE,
R.string.msg_mentions
R.string.title_favorite_messages
)
}
if (chatRoomType != RoomType.DIRECT_MESSAGE && !disableMenu) {
menu.add(
Menu.NONE,
MENU_ACTION_PINNED_MESSAGES,
MENU_ACTION_MEMBER,
Menu.NONE,
R.string.title_pinned_messages
R.string.title_members_list
)
menu.add(
Menu.NONE,
MENU_ACTION_FAVORITE_MESSAGES,
MENU_ACTION_MENTIONS,
Menu.NONE,
R.string.title_favorite_messages
R.string.msg_mentions
)
}
if (!disableMenu) {
menu.add(
Menu.NONE,
MENU_ACTION_FILES,
Menu.NONE,
R.string.title_files
)
}
}
internal fun ChatRoomFragment.setOnMenuItemClickListener(item: MenuItem) {
......@@ -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.
if (it.isEmpty() && isSearchTermQueried) {
presenter.loadMessages(chatRoomId, chatRoomType, clearDataSet = true)
} else if (it.isNotEmpty()){
} else if (it.isNotEmpty()) {
presenter.searchMessages(chatRoomId, it)
isSearchTermQueried = true
}
......
......@@ -14,7 +14,9 @@ data class AudioAttachmentUiModel(
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = 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> {
override val viewType: Int
get() = BaseUiModel.ViewType.AUDIO_ATTACHMENT.viewType
......
......@@ -16,7 +16,9 @@ data class AuthorAttachmentUiModel(
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = 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> {
override val viewType: Int
get() = BaseUiModel.ViewType.AUTHOR_ATTACHMENT.viewType
......
......@@ -13,6 +13,8 @@ interface BaseUiModel<out T> {
var nextDownStreamMessage: BaseUiModel<*>?
var preview: Message?
var isTemporary: Boolean
var unread: Boolean?
var menuItemsToHide: MutableList<Int>
enum class ViewType(val viewType: Int) {
MESSAGE(0),
......
......@@ -15,7 +15,9 @@ data class ColorAttachmentUiModel(
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = 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> {
override val viewType: Int
get() = BaseUiModel.ViewType.COLOR_ATTACHMENT.viewType
......
......@@ -14,7 +14,9 @@ data class GenericFileAttachmentUiModel(
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = 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> {
override val viewType: Int
get() = BaseUiModel.ViewType.GENERIC_FILE_ATTACHMENT.viewType
......
......@@ -16,7 +16,9 @@ data class ImageAttachmentUiModel(
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = 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> {
override val viewType: Int
get() = BaseUiModel.ViewType.IMAGE_ATTACHMENT.viewType
......
......@@ -15,7 +15,9 @@ data class MessageAttachmentUiModel(
override var nextDownStreamMessage: BaseUiModel<*>? = null,
var messageLink: String? = 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> {
override val viewType: Int
get() = BaseUiModel.ViewType.MESSAGE_ATTACHMENT.viewType
......
......@@ -11,7 +11,9 @@ data class MessageReplyUiModel(
override var nextDownStreamMessage: BaseUiModel<*>?,
override var preview: Message?,
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> {
override val viewType: Int
get() = BaseUiModel.ViewType.MESSAGE_REPLY.viewType
......
......@@ -15,8 +15,10 @@ data class MessageUiModel(
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = null,
override var preview: Message? = null,
override var unread: Boolean? = null,
var isFirstUnread: Boolean,
override var isTemporary: Boolean = false
override var isTemporary: Boolean = false,
override var menuItemsToHide: MutableList<Int> = mutableListOf()
) : BaseMessageUiModel<Message> {
override val viewType: Int
get() = BaseUiModel.ViewType.MESSAGE.viewType
......
......@@ -15,7 +15,9 @@ data class UrlPreviewUiModel(
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = 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> {
override val viewType: Int
get() = BaseUiModel.ViewType.URL_PREVIEW.viewType
......
......@@ -14,7 +14,9 @@ data class VideoAttachmentUiModel(
override var reactions: List<ReactionUiModel>,
override var nextDownStreamMessage: BaseUiModel<*>? = 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> {
override val viewType: Int
get() = BaseUiModel.ViewType.VIDEO_ATTACHMENT.viewType
......
......@@ -68,13 +68,15 @@ class RoomUiModelMapper(
val name = mapName(user.username!!, user.name, false)
val status = user.status
val avatar = serverUrl.avatarUrl(user.username!!)
val username = user.username!!
RoomUiModel(
id = user.id,
name = name,
type = roomTypeOf(RoomType.DIRECT_MESSAGE),
avatar = avatar,
status = status
status = status,
username = username
)
}
}
......@@ -97,7 +99,7 @@ class RoomUiModelMapper(
val isUnread = alert || unread > 0
val type = roomTypeOf(type)
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 avatar = if (type is RoomType.DirectMessage) {
serverUrl.avatarUrl(name)
......@@ -117,7 +119,8 @@ class RoomUiModelMapper(
unread = unread,
alert = isUnread,
lastMessage = lastMessage,
status = status
status = status,
username = if (type is RoomType.DirectMessage) name else null
)
}
}
......
......@@ -12,5 +12,6 @@ data class RoomUiModel(
val unread: String? = null,
val alert: Boolean = false,
val lastMessage: CharSequence? = null,
val status: UserStatus? = null
val status: UserStatus? = null,
val username: String? = null
)
\ No newline at end of file
......@@ -9,9 +9,7 @@ import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.infraestructure.ConnectionManager
......@@ -37,13 +35,6 @@ class ChatRoomsFragmentModule {
return frag
}
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides
@PerFragment
fun provideRocketChatClient(
......@@ -53,15 +44,6 @@ class ChatRoomsFragmentModule {
return factory.create(currentServer)
}
@Provides
@PerFragment
fun provideDatabaseManager(
factory: DatabaseManagerFactory,
@Named("currentServer") currentServer: String
): DatabaseManager {
return factory.create(currentServer)
}
@Provides
@PerFragment
fun provideChatRoomDao(manager: DatabaseManager): ChatRoomDao = manager.chatRoomDao()
......
......@@ -70,7 +70,8 @@ class FetchChatRoomsInteractor(
lastSeen = lastSeen,
lastMessageText = lastMessage?.message,
lastMessageUserId = lastMessage?.sender?.id,
lastMessageTimestamp = lastMessage?.timestamp
lastMessageTimestamp = lastMessage?.timestamp,
broadcast = broadcast
)
}
}
......
......@@ -51,7 +51,8 @@ class ChatRoomsPresenter @Inject constructor(
id = id,
subscriptionId = "",
type = type.toString(),
name = name.toString(),
name = username ?: name.toString(),
fullname = name.toString(),
open = false
)
loadChatRoom(entity)
......
......@@ -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.twofactor.di.TwoFAFragmentProvider
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.ChatRoomModule
import chat.rocket.android.chatroom.ui.ChatRoomActivity
......@@ -82,6 +84,8 @@ abstract class ActivityBuilder {
abstract fun bindChangeServerActivity(): ChangeServerActivity
@PerActivity
@ContributesAndroidInjector(modules = [MessageInfoFragmentProvider::class])
abstract fun bindMessageInfoActiviy(): MessageInfoActivity
@ContributesAndroidInjector(modules = [DrawModule::class])
abstract fun bindDrawingActivity(): DrawingActivity
}
......@@ -14,6 +14,8 @@ import chat.rocket.android.authentication.infraestructure.SharedPreferencesToken
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.qualifier.ForAuthentication
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.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository
......@@ -68,6 +70,7 @@ import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.spans.SpannableTheme
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Named
import javax.inject.Singleton
@Module
......@@ -254,7 +257,12 @@ class AppModule {
}
@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()!!
return MessageParser(context, configuration, settingsInteractor.get(url))
}
......@@ -301,4 +309,18 @@ class AppModule {
fun provideJobSchedulerInteractor(jobScheduler: JobScheduler, jobInfo: JobInfo): JobSchedulerInteractor {
return JobSchedulerInteractorImpl(jobScheduler, jobInfo)
}
@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)
}
}
......@@ -115,10 +115,10 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
const val TYPE_ORDER = """
CASE
WHEN type = '${RoomType.CHANNEL}' THEN 1
WHEN type = '${RoomType.PRIVATE_GROUP}' THEN 2
WHEN type = '${RoomType.DIRECT_MESSAGE}' THEN 3
WHEN type = '${RoomType.LIVECHAT}' THEN 4
WHEN type = 'c' THEN 1
WHEN type = 'p' THEN 2
WHEN type = 'd' THEN 3
WHEN type = 'l' THEN 4
ELSE 5
END
"""
......
......@@ -295,7 +295,8 @@ class DatabaseManager(val context: Application,
lastSeen = subscription.lastSeen,
lastMessageText = room.lastMessage?.message,
lastMessageUserId = room.lastMessage?.sender?.id,
lastMessageTimestamp = room.lastMessage?.timestamp
lastMessageTimestamp = room.lastMessage?.timestamp,
broadcast = room.broadcast
)
}
......
......@@ -7,7 +7,7 @@ import chat.rocket.android.db.model.UserEntity
@Database(
entities = [UserEntity::class, ChatRoomEntity::class],
version = 3,
version = 4,
exportSchema = true
)
abstract class RCDatabase : RoomDatabase() {
......
......@@ -19,6 +19,7 @@ import androidx.room.PrimaryKey
ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["lastMessageUserId"])
]
)
data class ChatRoomEntity(
@PrimaryKey var id: String,
var subscriptionId: String,
......@@ -40,7 +41,8 @@ data class ChatRoomEntity(
var lastSeen: Long? = -1,
var lastMessageText: String? = null,
var lastMessageUserId: String? = null,
var lastMessageTimestamp: Long? = null
var lastMessageTimestamp: Long? = null,
var broadcast: Boolean? = false
)
data class ChatRoom(
......
......@@ -22,22 +22,6 @@ class FavoriteMessagesFragmentModule {
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
@PerFragment
fun provideJob() = Job()
......
......@@ -3,15 +3,11 @@ package chat.rocket.android.files.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
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.ui.FilesFragment
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module
class FilesFragmentModule {
......@@ -22,22 +18,6 @@ class FilesFragmentModule {
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
@PerFragment
fun provideJob() = Job()
......
......@@ -5,6 +5,7 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.useRealName
import chat.rocket.common.model.SimpleUser
import chat.rocket.common.model.User
import javax.inject.Inject
......@@ -26,6 +27,10 @@ class UserHelper @Inject constructor(
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.
*
......
......@@ -52,9 +52,10 @@ class SharedPreferencesLocalRepository(
override fun clear(key: String) = preferences.edit { remove(key) }
override fun clearAllFromServer(server: String) {
clear(LocalRepository.KEY_PUSH_TOKEN)
clear(LocalRepository.TOKEN_KEY + server)
clear(LocalRepository.SETTINGS_KEY + server)
clear(LocalRepository.CURRENT_USERNAME_KEY)
preferences.all.keys.forEach { key ->
if (key.startsWith(server, ignoreCase = true)) {
clear(key)
}
}
}
}
......@@ -4,16 +4,12 @@ import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.core.lifecycle.CancelStrategy
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.MembersView
import chat.rocket.android.members.ui.MembersFragment
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module
class MembersFragmentModule {
......@@ -28,22 +24,6 @@ class MembersFragmentModule {
@PerFragment
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
@PerFragment
fun provideJob() = Job()
......
......@@ -5,11 +5,9 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.mentions.presentention.MentionsView
import chat.rocket.android.mentions.ui.MentionsFragment
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module
class MentionsFragmentModule {
......@@ -20,13 +18,6 @@ class MentionsFragmentModule {
return frag
}
@Provides
@PerFragment
@Named("currentServer")
fun provideCurrentServer(currentServerInteractor: GetCurrentServerInteractor): String {
return currentServerInteractor.get()!!
}
@Provides
@PerFragment
fun provideJob() = Job()
......
......@@ -3,15 +3,11 @@ package chat.rocket.android.pinnedmessages.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
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.ui.PinnedMessagesFragment
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import javax.inject.Named
@Module
class PinnedMessagesFragmentModule {
......@@ -22,22 +18,6 @@ class PinnedMessagesFragmentModule {
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
@PerFragment
fun provideJob() = Job()
......
......@@ -25,7 +25,7 @@ class RefreshSettingsInteractor @Inject constructor(
HIDE_USER_JOIN, HIDE_USER_LEAVE,
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,
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) {
withContext(CommonPool) {
......
......@@ -46,6 +46,8 @@ const val SHOW_EDITED_STATUS = "Message_ShowEditedStatus"
const val ALLOW_MESSAGE_PINNING = "Message_AllowPinning"
const val ALLOW_MESSAGE_STARRING = "Message_AllowStarring"
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.
......@@ -87,6 +89,9 @@ fun PublicSettings.allowedMessageDeleting(): Boolean = this[ALLOW_MESSAGE_DELETI
fun PublicSettings.hasShowLastMessage(): Boolean = this[STORE_LAST_MESSAGE] != null
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>? {
val values = this[UPLOAD_WHITELIST_MIMETYPES]?.value as String?
if (!values.isNullOrBlank()) {
......
<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"?>
<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 @@
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:layout_marginTop="2dp"
android:src="@drawable/ic_action_message_star_24dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/text_sender"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/text_edit_indicator"
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" />
<TextView
......@@ -112,9 +124,9 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="2dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="4dp"
app:layout_constraintStart_toStartOf="@+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!" />
......
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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" />
</android.support.constraint.ConstraintLayout>
......@@ -2,6 +2,11 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
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
android:id="@+id/action_message_reply"
android:icon="@drawable/ic_action_message_reply_24dp"
......
......@@ -36,6 +36,8 @@
<string name="action_away">Ausente</string>
<string name="action_busy">Ocupado</string>
<string name="action_invisible">Invisible</string>
<!-- TODO Add translation -->
<string name="action_drawing">Drawing</string>
<string name="action_save_to_gallery">Guardar en la galería</string>
<!-- Settings List -->
......@@ -113,12 +115,19 @@
<string name="msg_is_typing">\u0020esta escribiendo…</string>
<string name="msg_are_typing">\u0020están escribiendo…</string>
<string name="msg_several_users_are_typing">Varios usuarios están escribiendo…</string>
<string name="msg_no_search_found">No se han encontrado resultados</string>
<string name="msg_channel_name">Nombre del Canal</string>
<string name="msg_search">Buscar</string>
<string name="msg_message_copied">Mensaje copiado</string>
<string name="msg_delete_message">Borrar mensaje</string>
<string name="msg_delete_description">Seguro que quieres borrar este mensaje</string>
<string name="msg_no_search_found">No se han encontrado resultados</string>
<string name="msg_log_out">Saliendo de tu cuenta…</string>
<!-- TODO Add translation -->
<string name="msg_upload_file">Upload file</string>
<!-- TODO Add translation -->
<string name="msg_file_description">File description</string>
<!-- TODO Add translation -->
<string name="msg_send">Send</string>
<!-- Create channel messages -->
<string name="msg_private_channel">Privado</string>
......@@ -150,6 +159,7 @@
<!-- Message actions -->
<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_copy">Copiar</string>
<string name="action_msg_quote">Citar</string>
......@@ -157,8 +167,7 @@
<string name="action_msg_pin">Fijar mensaje</string>
<string name="action_msg_unpin">Soltar mensaje</string>
<string name="action_msg_star">Star mensaje</string>
// TODO: Add proper translation.
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_unstar">Unstar mensaje</string>
<string name="action_msg_share">Compartir</string>
<string name="action_title_editing">Edición de mensaje</string>
<string name="action_msg_add_reaction">Añadir una reacción</string>
......@@ -167,9 +176,12 @@
<string name="permission_editing_not_allowed">La edición no és permitida</string>
<string name="permission_deleting_not_allowed">Eliminar no és permitido</string>
<string name="permission_pinning_not_allowed">Fijar no és permitido</string>
// TODO: Add proper translation.
<!-- TODO Add translation -->
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Search message -->
<string name="title_search_message">Search message</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Chat favorito</string>
<string name="title_unfavorite_chat">Deshacer chat favorito</string>
......@@ -238,6 +250,8 @@
<!-- Emoji message-->
<string name="msg_no_recent_emoji">Sin emojis recientes</string>
<!-- TODO Add translation -->
<string name="alert_title_default_skin_tone">Default skin tone</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Ordenar</string>
......@@ -259,4 +273,7 @@
<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_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>
</resources>
......@@ -172,16 +172,15 @@
<!-- Message actions -->
<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_copy">Copier</string>
<string name="action_msg_quote">Citation</string>
<string name="action_msg_delete">Effacer</string>
<string name="action_msg_pin">Épingle message</string>
<string name="action_msg_unpin">Enlever message</string>
// TODO: Add proper translation.
<string name="action_msg_star">Star message</string>
// TODO: Add proper translation.
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_star">Ajouter aux Favoris</string>
<string name="action_msg_unstar">Retirer des favoris</string>
<string name="action_msg_share">Partager</string>
<string name="action_title_editing">Modification du message</string>
<string name="action_msg_add_reaction">Ajouter une réaction</string>
......@@ -291,6 +290,7 @@
<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_success_sending">Message envoyé à %1$s!</string>
<!-- TODO Add proper translation-->
<string name="msg_log_out">Logging out…</string>
<string name="read_by">Lire par</string>
<string name="message_information_title">Informations sur le message</string>
<string name="msg_log_out">Déconnecter…</string>
</resources>
......@@ -156,6 +156,7 @@
<!-- Message actions -->
<string name="action_msg_reply">जवाब दें</string>
<string name="action_msg_info">संदेश जानकारी</string>
<string name="action_msg_edit">संपादन करें</string>
<string name="action_msg_copy">कॉपी</string>
<string name="action_msg_quote">उद्धरण</string>
......@@ -268,6 +269,7 @@
<string name="notif_action_reply_hint">जवाब</string>
<string name="notif_error_sending">उत्तर विफल हुआ है। कृपया फिर से प्रयास करें।</string>
<string name="notif_success_sending">संदेश भेजा गया %1$s!</string>
<!-- TODO Add proper translation-->
<string name="msg_log_out">Logging out…</string>
<string name="read_by">द्वारा पढ़ें</string>
<string name="message_information_title">संदेश की जानकारी</string>
<string name="msg_log_out">लॉग आउट कर रहा हूं…</string>
</resources>
......@@ -157,6 +157,7 @@
<!-- Message actions -->
<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_copy">Copiar</string>
<string name="action_msg_quote">Citar</string>
......@@ -268,5 +269,7 @@
<string name="notif_action_reply_hint">RESPONDER</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="read_by">Lida por</string>
<string name="message_information_title">Informações da mensagem</string>
<string name="msg_log_out">Deslogando…</string>
</resources>
......@@ -37,6 +37,8 @@
<string name="action_away">Отошел</string>
<string name="action_busy">Занят</string>
<string name="action_invisible">Невидимый</string>
<!-- TODO Add translation -->
<string name="action_drawing">Drawing</string>
<string name="action_save_to_gallery">Сохранить в галерею</string>
<!-- Settings List -->
......@@ -153,6 +155,7 @@
<!-- Message actions -->
<string name="action_msg_reply">Ответить</string>
<string name="action_msg_info">Інформація про повідомлення</string>
<string name="action_msg_edit">Редактировать</string>
<string name="action_msg_copy">Копировать</string>
<string name="action_msg_quote">Цитата</string>
......@@ -264,5 +267,7 @@
<string name="notif_action_reply_hint">ОТВЕТИТЬ</string>
<string name="notif_error_sending">Ошибка ответа. Пожалуйста, попробуйте еще раз.</string>
<string name="notif_success_sending">Сообщение отправлено %1$s!</string>
<string name="read_by">Read by</string>
<string name="message_information_title">Message information</string>
<string name="msg_log_out">Выход…</string>
</resources>
......@@ -120,7 +120,7 @@
<string name="msg_are_typing">\u0020are typing…</string>
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">No result found</string>
<string name="msg_log_out">Logging out...</string>
<string name="msg_log_out">Logging out</string>
<string name="msg_upload_file">Upload file</string>
<string name="msg_file_description">File description</string>
<string name="msg_send">Send</string>
......@@ -157,6 +157,7 @@
<!-- Message actions -->
<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_copy">Copy</string>
<string name="action_msg_quote">Quote</string>
......@@ -268,4 +269,6 @@
<string name="notif_action_reply_hint">REPLY</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="read_by">Read by</string>
<string name="message_information_title">Message information</string>
</resources>
\ No newline at end of file
......@@ -10,7 +10,7 @@ buildscript {
}
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.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.2.0'
......
......@@ -59,7 +59,7 @@ ext {
mockito : '2.10.0'
]
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}",
coroutinesAndroid : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutine}",
......
......@@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
distributionSha256Sum=9af7345c199f1731c187c96d3fe3d31f5405192a42046bafa71d846c3d9adacb
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-rc-2-all.zip
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