Unverified Commit 5e5e565d authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge pull request #1287 from RocketChat/beta

[RELEASE] Merge beta into master
parents b0817ace 744cdeb2
......@@ -13,8 +13,8 @@ android {
applicationId "chat.rocket.android"
minSdkVersion 21
targetSdkVersion versions.targetSdk
versionCode 2020
versionName "2.1.1"
versionCode 2021
versionName "2.2.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
......
......@@ -18,13 +18,12 @@ import com.google.android.flexbox.FlexboxLayoutManager
import ru.whalemare.sheetmenu.extension.inflate
import ru.whalemare.sheetmenu.extension.toList
abstract class BaseViewHolder<T : BaseViewModel<*>>(
itemView: View,
private val listener: ActionsListener,
var reactionListener: EmojiReactionListener? = null
itemView: View,
private val listener: ActionsListener,
var reactionListener: EmojiReactionListener? = null
) : RecyclerView.ViewHolder(itemView),
MenuItem.OnMenuItemClickListener {
MenuItem.OnMenuItemClickListener {
var data: T? = null
init {
......@@ -76,22 +75,29 @@ abstract class BaseViewHolder<T : BaseViewModel<*>>(
fun onActionSelected(item: MenuItem, message: Message)
}
private val longClickListener = { view: View ->
private val onClickListener = { view: View ->
if (data?.message?.isSystemMessage() == false) {
val menuItems = view.context.inflate(R.menu.message_actions).toList()
menuItems.find { it.itemId == R.id.action_menu_msg_pin_unpin }?.apply {
val isPinned = data?.message?.pinned ?: false
setTitle(if (isPinned) R.string.action_msg_unpin else R.string.action_msg_pin)
isChecked = isPinned
data?.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)
isChecked = it.pinned
}
menuItems.find { it.itemId == R.id.action_message_star }?.apply {
val isStarred = it.starred?.isNotEmpty() ?: false
setTitle(if (isStarred) R.string.action_msg_unstar else R.string.action_msg_star)
isChecked = isStarred
}
val adapter = ActionListAdapter(menuItems, this@BaseViewHolder)
BottomSheetMenu(adapter).show(view.context)
}
val adapter = ActionListAdapter(menuItems, this@BaseViewHolder)
BottomSheetMenu(adapter).show(view.context)
}
true
}
internal fun setupActionMenu(view: View) {
if (listener.isActionsEnabled()) {
view.setOnClickListener(onClickListener)
if (view is ViewGroup) {
for (child in view.children) {
if (child !is RecyclerView && child.id != R.id.recycler_view_reactions) {
......@@ -99,7 +105,6 @@ abstract class BaseViewHolder<T : BaseViewModel<*>>(
}
}
}
view.setOnLongClickListener(longClickListener)
}
}
......
......@@ -5,7 +5,21 @@ import android.view.MenuItem
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.viewmodel.*
import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.chatroom.viewmodel.AudioAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.AuthorAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.BaseFileAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.android.chatroom.viewmodel.ColorAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.GenericFileAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.ImageAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.MessageAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.MessageReplyViewModel
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.chatroom.viewmodel.UrlPreviewViewModel
import chat.rocket.android.chatroom.viewmodel.VideoAttachmentViewModel
import chat.rocket.android.chatroom.viewmodel.toViewType
import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.widget.emoji.EmojiReactionListener
import chat.rocket.core.model.Message
......@@ -65,6 +79,12 @@ class ChatRoomAdapter(
val view = parent.inflate(R.layout.item_file_attachment)
GenericFileAttachmentViewHolder(view, actionsListener, reactionListener)
}
BaseViewModel.ViewType.MESSAGE_REPLY -> {
val view = parent.inflate(R.layout.item_message_reply)
MessageReplyViewHolder(view, actionsListener, reactionListener) { roomName, permalink ->
presenter?.openDirectMessage(roomName, permalink)
}
}
else -> {
throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}")
}
......@@ -98,15 +118,26 @@ class ChatRoomAdapter(
}
when (holder) {
is MessageViewHolder -> holder.bind(dataSet[position] as MessageViewModel)
is ImageAttachmentViewHolder -> holder.bind(dataSet[position] as ImageAttachmentViewModel)
is AudioAttachmentViewHolder -> holder.bind(dataSet[position] as AudioAttachmentViewModel)
is VideoAttachmentViewHolder -> holder.bind(dataSet[position] as VideoAttachmentViewModel)
is UrlPreviewViewHolder -> holder.bind(dataSet[position] as UrlPreviewViewModel)
is MessageAttachmentViewHolder -> holder.bind(dataSet[position] as MessageAttachmentViewModel)
is AuthorAttachmentViewHolder -> holder.bind(dataSet[position] as AuthorAttachmentViewModel)
is ColorAttachmentViewHolder -> holder.bind(dataSet[position] as ColorAttachmentViewModel)
is GenericFileAttachmentViewHolder -> holder.bind(dataSet[position] as GenericFileAttachmentViewModel)
is MessageViewHolder ->
holder.bind(dataSet[position] as MessageViewModel)
is ImageAttachmentViewHolder ->
holder.bind(dataSet[position] as ImageAttachmentViewModel)
is AudioAttachmentViewHolder ->
holder.bind(dataSet[position] as AudioAttachmentViewModel)
is VideoAttachmentViewHolder ->
holder.bind(dataSet[position] as VideoAttachmentViewModel)
is UrlPreviewViewHolder ->
holder.bind(dataSet[position] as UrlPreviewViewModel)
is MessageAttachmentViewHolder ->
holder.bind(dataSet[position] as MessageAttachmentViewModel)
is AuthorAttachmentViewHolder ->
holder.bind(dataSet[position] as AuthorAttachmentViewModel)
is ColorAttachmentViewHolder ->
holder.bind(dataSet[position] as ColorAttachmentViewModel)
is GenericFileAttachmentViewHolder ->
holder.bind(dataSet[position] as GenericFileAttachmentViewModel)
is MessageReplyViewHolder ->
holder.bind(dataSet[position] as MessageReplyViewModel)
}
}
......@@ -181,25 +212,39 @@ class ChatRoomAdapter(
}
private val actionsListener = object : BaseViewHolder.ActionsListener {
override fun isActionsEnabled(): Boolean = enableActions
override fun onActionSelected(item: MenuItem, message: Message) {
message.apply {
when (item.itemId) {
R.id.action_menu_msg_delete -> presenter?.deleteMessage(roomId, id)
R.id.action_menu_msg_quote -> presenter?.citeMessage(roomType, id, false)
R.id.action_menu_msg_reply -> presenter?.citeMessage(roomType, id, true)
R.id.action_menu_msg_copy -> presenter?.copyMessage(id)
R.id.action_menu_msg_edit -> presenter?.editMessage(roomId, id, message.message)
R.id.action_menu_msg_pin_unpin -> {
with(item) {
if (!isChecked) {
presenter?.pinMessage(id)
} else {
presenter?.unpinMessage(id)
}
R.id.action_message_reply -> {
presenter?.citeMessage(roomName, roomType, id, true)
}
R.id.action_message_quote -> {
presenter?.citeMessage(roomName, roomType, id, false)
}
R.id.action_message_copy -> {
presenter?.copyMessage(id)
}
R.id.action_message_edit -> {
presenter?.editMessage(roomId, id, message.message)
}
R.id.action_message_star -> {
if (!item.isChecked) {
presenter?.starMessage(id)
} else {
presenter?.unstarMessage(id)
}
}
R.id.action_message_unpin -> {
if (!item.isChecked) {
presenter?.pinMessage(id)
} else {
presenter?.unpinMessage(id)
}
}
R.id.action_message_delete -> presenter?.deleteMessage(roomId, id)
R.id.action_menu_msg_react -> presenter?.showReactions(id)
else -> TODO("Not implemented")
}
......
......@@ -37,11 +37,11 @@ import timber.log.Timber
import java.io.File
class ImageAttachmentViewHolder(itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null)
: BaseViewHolder<ImageAttachmentViewModel>(itemView, listener, reactionListener) {
class ImageAttachmentViewHolder(
itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null
) : BaseViewHolder<ImageAttachmentViewModel>(itemView, listener, reactionListener) {
private var cacheKey: CacheKey? = null
init {
......@@ -63,16 +63,19 @@ class ImageAttachmentViewHolder(itemView: View,
// TODO - implement a proper image viewer with a proper Transition
// TODO - We should definitely write our own ImageViewer
var imageViewer: ImageViewer? = null
val request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(data.attachmentUrl))
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.DISK_CACHE)
.build()
val request =
ImageRequestBuilder.newBuilderWithSource(Uri.parse(data.attachmentUrl))
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.DISK_CACHE)
.build()
cacheKey = DefaultCacheKeyFactory.getInstance()
.getEncodedCacheKey(request, null)
val pad = context.resources
.getDimensionPixelSize(R.dimen.viewer_toolbar_padding)
val lparams = AppBarLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
val lparams = AppBarLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
val toolbar = Toolbar(context).also {
it.inflateMenu(R.menu.image_actions)
it.overflowIcon?.setTint(Color.WHITE)
......@@ -98,7 +101,7 @@ class ImageAttachmentViewHolder(itemView: View,
val backArrowView = ImageView(context).also {
it.setImageResource(R.drawable.ic_arrow_back_white_24dp)
it.setOnClickListener { imageViewer?.onDismiss() }
it.setPadding(0, pad ,pad, pad)
it.setPadding(0, pad, pad, pad)
}
val layoutParams = AppBarLayout.LayoutParams(
......@@ -113,10 +116,12 @@ class ImageAttachmentViewHolder(itemView: View,
val appBarLayout = AppBarLayout(context).also {
it.layoutParams = lparams
it.setBackgroundColor(Color.BLACK)
it.addView(toolbar, AppBarLayout.LayoutParams(
AppBarLayout.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
))
it.addView(
toolbar, AppBarLayout.LayoutParams(
AppBarLayout.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
)
}
val builder = ImageViewer.createPipelineDraweeControllerBuilder()
......@@ -144,12 +149,17 @@ class ImageAttachmentViewHolder(itemView: View,
val imageFormat = ImageFormatChecker.getImageFormat(resource.openStream())
val imageDir = "${Environment.DIRECTORY_PICTURES}/Rocket.Chat Images/"
val imagePath = Environment.getExternalStoragePublicDirectory(imageDir)
val imageFile = File(imagePath, "${cachedFile.nameWithoutExtension}.${imageFormat.fileExtension}")
val imageFile =
File(imagePath, "${cachedFile.nameWithoutExtension}.${imageFormat.fileExtension}")
imagePath.mkdirs()
imageFile.createNewFile()
try {
cachedFile.copyTo(imageFile, true)
MediaScannerConnection.scanFile(context, arrayOf(imageFile.absolutePath), null) { path, uri ->
MediaScannerConnection.scanFile(
context,
arrayOf(imageFile.absolutePath),
null
) { path, uri ->
Timber.i("Scanned $path:")
Timber.i("-> uri=$uri")
}
......@@ -166,16 +176,21 @@ class ImageAttachmentViewHolder(itemView: View,
}
private fun canWriteToExternalStorage(): Boolean {
return AndroidPermissionsHelper.checkPermission(itemView.context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
return AndroidPermissionsHelper.checkPermission(
itemView.context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
private fun checkWritingPermission() {
val context = itemView.context
if (context is ContextThemeWrapper && context.baseContext is Activity) {
val activity = context.baseContext as Activity
AndroidPermissionsHelper.requestPermission(activity,
AndroidPermissionsHelper.requestPermission(
activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE)
AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE
)
}
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.adapter
import android.view.View
import chat.rocket.android.chatroom.viewmodel.MessageReplyViewModel
import chat.rocket.android.widget.emoji.EmojiReactionListener
import kotlinx.android.synthetic.main.item_message_reply.view.*
class MessageReplyViewHolder(
itemView: View,
listener: ActionsListener,
reactionListener: EmojiReactionListener? = null,
private val replyCallback: (roomName: String, permalink: String) -> Unit
) : BaseViewHolder<MessageReplyViewModel>(itemView, listener, reactionListener) {
init {
with(itemView) {
setupActionMenu(itemView)
}
}
override fun bindViews(data: MessageReplyViewModel) {
with(itemView) {
button_message_reply.setOnClickListener {
with(data.rawData) {
replyCallback.invoke(roomName, permalink)
}
}
}
}
}
\ No newline at end of file
......@@ -3,8 +3,11 @@ package chat.rocket.android.chatroom.adapter
import android.graphics.Color
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.core.view.isVisible
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.widget.emoji.EmojiReactionListener
import chat.rocket.core.model.isSystemMessage
import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_message.view.*
......@@ -33,6 +36,10 @@ class MessageViewHolder(
text_content.setTextColor(
if (data.isTemporary) Color.GRAY else Color.BLACK
)
data.message.let {
text_edit_indicator.isVisible = it.isSystemMessage() && it.editedBy != null
image_star_indicator.isVisible = it.starred?.isNotEmpty() ?: false
}
}
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.domain
data class MessageReply(
val roomName: String,
val permalink: String
)
\ No newline at end of file
......@@ -2,6 +2,7 @@ package chat.rocket.android.chatroom.presentation
import chat.rocket.android.R
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.members.ui.newInstance
import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.util.extensions.addFragmentBackStack
......@@ -15,8 +16,14 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
}
fun toPinnedMessageList(chatRoomId: String, chatRoomType: String) {
activity.addFragmentBackStack("PinnedMessages", R.id.fragment_container){
chat.rocket.android.pinnedmessages.ui.newInstance(chatRoomId,chatRoomType)
activity.addFragmentBackStack("PinnedMessages", R.id.fragment_container) {
chat.rocket.android.pinnedmessages.ui.newInstance(chatRoomId, chatRoomType)
}
}
fun toFavoriteMessageList(chatRoomId: String, chatRoomType: String) {
activity.addFragmentBackStack("FavoriteMessages", R.id.fragment_container) {
chat.rocket.android.favoritemessages.ui.newInstance(chatRoomId, chatRoomType)
}
}
......@@ -24,4 +31,17 @@ class ChatRoomNavigator(internal val activity: ChatRoomActivity) {
activity.startActivity(activity.changeServerIntent())
activity.finish()
}
fun toDirectMessage(chatRoomId: String,
chatRoomName: String,
chatRoomType: String,
isChatRoomReadOnly: Boolean,
chatRoomLastSeen: Long,
isChatRoomSubscribed: Boolean,
isChatRoomCreator: Boolean,
chatRoomMessage: String) {
activity.startActivity(activity.chatRoomIntent(chatRoomId, chatRoomName, chatRoomType,
isChatRoomReadOnly, chatRoomLastSeen, isChatRoomSubscribed, isChatRoomCreator, chatRoomMessage))
activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
}
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import chat.rocket.android.chatroom.viewmodel.suggestion.PeopleSuggestionViewMod
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.model.ChatRoom
interface ChatRoomView : LoadingView, MessageView {
......@@ -25,6 +26,18 @@ interface ChatRoomView : LoadingView, MessageView {
*/
fun sendMessage(text: String)
/**
* Shows the username(s) of the user(s) who is/are typing in the chat room.
*
* @param usernameList The list of username to show.
*/
fun showTypingStatus(usernameList: ArrayList<String>)
/**
* Hides the typing status view.
*/
fun hideTypingStatusView()
/**
* Perform file selection with the mime type [filter]
*/
......@@ -109,8 +122,10 @@ interface ChatRoomView : LoadingView, MessageView {
fun populateRoomSuggestions(chatRooms: List<ChatRoomSuggestionViewModel>)
/**
* This user has joined the chat callback.
*
* @param userCanPost Whether the user can post a message or not.
*/
fun onJoined()
fun onJoined(userCanPost: Boolean)
fun showReactionsPopup(messageId: String)
......@@ -120,4 +135,15 @@ interface ChatRoomView : LoadingView, MessageView {
* @param commands The list of available commands.
*/
fun populateCommandSuggestions(commands: List<CommandSuggestionViewModel>)
/**
* Communicate whether it's a broadcast channel and if current user can post to it.
*/
fun onRoomUpdated(userCanPost: Boolean, channelIsBroadcast: Boolean, userCanMod: Boolean)
/**
* Open a DM with the user in the given [chatRoom] and pass the [permalink] for the message
* to reply.
*/
fun openDirectMessage(chatRoom: ChatRoom, permalink: String)
}
\ No newline at end of file
......@@ -60,13 +60,22 @@ class MessageService : JobService() {
)
messageRepository.save(message.copy(isTemporary = false))
Timber.d("Sent scheduled message given by id: ${message.id}")
} catch (ex: RocketChatException) {
} catch (ex: Exception) {
Timber.e(ex)
if (ex.message?.contains("E11000", true) == true) {
// XXX: Temporary solution. We need proper error codes from the api.
messageRepository.save(message.copy(isTemporary = false))
// TODO - remove the generic message when we implement :userId:/message subscription
if (ex is IllegalStateException) {
Timber.d(ex, "Probably a read-only problem...")
// TODO: For now we are only going to reschedule when api is fixed.
messageRepository.removeById(message.id)
jobFinished(params, false)
} else {
// some other error
if (ex.message?.contains("E11000", true) == true) {
// XXX: Temporary solution. We need proper error codes from the api.
messageRepository.save(message.copy(isTemporary = false))
}
jobFinished(params, true)
}
jobFinished(params, true)
}
}
}
......
package chat.rocket.android.chatroom.ui
import DrawableHelper
import android.content.Context
import android.content.Intent
import android.os.Bundle
......@@ -27,38 +26,49 @@ fun Context.chatRoomIntent(
chatRoomType: String,
isChatRoomReadOnly: Boolean,
chatRoomLastSeen: Long,
isChatRoomSubscribed: Boolean = true
isChatRoomSubscribed: Boolean = true,
isChatRoomCreator: Boolean = false,
chatRoomMessage: String? = null
): Intent {
return Intent(this, ChatRoomActivity::class.java).apply {
putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
putExtra(INTENT_CHAT_ROOM_NAME, chatRoomName)
putExtra(INTENT_CHAT_ROOM_TYPE, chatRoomType)
putExtra(INTENT_IS_CHAT_ROOM_READ_ONLY, isChatRoomReadOnly)
putExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, isChatRoomReadOnly)
putExtra(INTENT_CHAT_ROOM_LAST_SEEN, chatRoomLastSeen)
putExtra(INTENT_CHAT_IS_SUBSCRIBED, isChatRoomSubscribed)
putExtra(INTENT_CHAT_ROOM_IS_CREATOR, isChatRoomCreator)
putExtra(INTENT_CHAT_ROOM_MESSAGE, chatRoomMessage)
}
}
private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
private const val INTENT_CHAT_ROOM_NAME = "chat_room_name"
private const val INTENT_CHAT_ROOM_TYPE = "chat_room_type"
private const val INTENT_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
private const val INTENT_CHAT_ROOM_IS_READ_ONLY = "chat_room_is_read_only"
private const val INTENT_CHAT_ROOM_IS_CREATOR = "chat_room_is_creator"
private const val INTENT_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen"
private const val INTENT_CHAT_IS_SUBSCRIBED = "is_chat_room_subscribed"
private const val INTENT_CHAT_ROOM_MESSAGE = "chat_room_message"
class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
@Inject
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
// TODO - workaround for now... We will move to a single activity
@Inject lateinit var serverInteractor: GetCurrentServerInteractor
@Inject lateinit var navigator: ChatRoomNavigator
@Inject lateinit var managerFactory: ConnectionManagerFactory
@Inject
lateinit var serverInteractor: GetCurrentServerInteractor
@Inject
lateinit var navigator: ChatRoomNavigator
@Inject
lateinit var managerFactory: ConnectionManagerFactory
private lateinit var chatRoomId: String
private lateinit var chatRoomName: String
private lateinit var chatRoomType: String
private var isChatRoomReadOnly: Boolean = false
private var isChatRoomSubscribed: Boolean = true
private var isChatRoomCreator: Boolean = false
private var chatRoomLastSeen: Long = -1L
override fun onCreate(savedInstanceState: Bundle?) {
......@@ -84,8 +94,13 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE)
requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" }
isChatRoomReadOnly = intent.getBooleanExtra(INTENT_IS_CHAT_ROOM_READ_ONLY, true)
requireNotNull(chatRoomType) { "no is_chat_room_read_only provided in Intent extras" }
isChatRoomReadOnly = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, true)
requireNotNull(isChatRoomReadOnly) { "no chat_room_is_read_only provided in Intent extras" }
isChatRoomCreator = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_CREATOR, false)
requireNotNull(isChatRoomCreator) { "no chat_room_is_creator provided in Intent extras" }
val chatRoomMessage = intent.getStringExtra(INTENT_CHAT_ROOM_MESSAGE)
setupToolbar()
......@@ -96,7 +111,7 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
if (supportFragmentManager.findFragmentByTag(TAG_CHAT_ROOM_FRAGMENT) == null) {
addFragment(TAG_CHAT_ROOM_FRAGMENT, R.id.fragment_container) {
newInstance(chatRoomId, chatRoomName, chatRoomType, isChatRoomReadOnly, chatRoomLastSeen,
isChatRoomSubscribed)
isChatRoomSubscribed, isChatRoomCreator, chatRoomMessage)
}
}
}
......@@ -109,6 +124,17 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
return fragmentDispatchingAndroidInjector
}
private fun setupToolbar() {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
text_room_name.textContent = chatRoomName
showRoomTypeIcon(true)
toolbar.setNavigationOnClickListener { finishActivity() }
}
fun showRoomTypeIcon(showRoomTypeIcon: Boolean) {
if (showRoomTypeIcon) {
val roomType = roomTypeOf(chatRoomType)
......@@ -136,17 +162,6 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
}
}
private fun setupToolbar() {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
text_room_name.textContent = chatRoomName
showRoomTypeIcon(true)
toolbar.setNavigationOnClickListener {
finishActivity()
}
}
fun setupToolbarTitle(toolbarTitle: String) {
text_room_name.textContent = toolbarTitle
......
......@@ -7,8 +7,10 @@ import chat.rocket.android.util.extensions.setVisible
/**
* An adapter for bottomsheet menu that lists all the actions that could be taken over a chat message.
*/
class ActionListAdapter(menuItems: List<MenuItem> = emptyList(), callback: MenuItem.OnMenuItemClickListener) :
ListBottomSheetAdapter(menuItems = menuItems, callback = callback) {
class ActionListAdapter(
menuItems: List<MenuItem> = emptyList(),
callback: MenuItem.OnMenuItemClickListener
) : ListBottomSheetAdapter(menuItems = menuItems, callback = callback) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = menuItems[position]
......@@ -25,7 +27,11 @@ class ActionListAdapter(menuItems: List<MenuItem> = emptyList(), callback: MenuI
callback?.onMenuItemClick(item)
}
val deleteTextColor = holder.itemView.context.resources.getColor(R.color.red)
val color = if (item.itemId == R.id.action_menu_msg_delete) deleteTextColor else textColors.get(item.itemId)
val color = if (item.itemId == R.id.action_message_delete) {
deleteTextColor
} else {
textColors.get(item.itemId)
}
holder.textTitle.setTextColor(color)
}
}
\ No newline at end of file
......@@ -24,7 +24,8 @@ interface BaseViewModel<out T> {
MESSAGE_ATTACHMENT(6),
AUTHOR_ATTACHMENT(7),
COLOR_ATTACHMENT(8),
GENERIC_FILE_ATTACHMENT(9)
GENERIC_FILE_ATTACHMENT(9),
MESSAGE_REPLY(10)
}
}
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.R
import chat.rocket.android.chatroom.domain.MessageReply
import chat.rocket.core.model.Message
data class MessageReplyViewModel(
override val rawData: MessageReply,
override val messageId: String,
override var reactions: List<ReactionViewModel>,
override var nextDownStreamMessage: BaseViewModel<*>?,
override var preview: Message?,
override var isTemporary: Boolean = false,
override val message: Message
) : BaseViewModel<MessageReply> {
override val viewType: Int
get() = BaseViewModel.ViewType.MESSAGE_REPLY.viewType
override val layoutId: Int
get() = R.layout.item_message_reply
}
\ No newline at end of file
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.core.model.ChatRoomRole
data class RoomViewModel(
val roles: List<ChatRoomRole>,
val isBroadcast: Boolean = false,
val isRoom: Boolean = false
)
\ No newline at end of file
......@@ -13,16 +13,32 @@ import androidx.core.text.buildSpannedString
import androidx.core.text.color
import androidx.core.text.scale
import chat.rocket.android.R
import chat.rocket.android.chatroom.domain.MessageReply
import chat.rocket.android.helper.MessageHelper
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.ChatRoomsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.baseUrl
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.isNotNullNorEmpty
import chat.rocket.android.widget.emoji.EmojiParser
import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType
import chat.rocket.core.model.Value
import chat.rocket.core.model.attachment.*
import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.attachment.AudioAttachment
import chat.rocket.core.model.attachment.AuthorAttachment
import chat.rocket.core.model.attachment.ColorAttachment
import chat.rocket.core.model.attachment.FileAttachment
import chat.rocket.core.model.attachment.GenericFileAttachment
import chat.rocket.core.model.attachment.ImageAttachment
import chat.rocket.core.model.attachment.MessageAttachment
import chat.rocket.core.model.attachment.VideoAttachment
import chat.rocket.core.model.isSystemMessage
import chat.rocket.core.model.url.Url
import kotlinx.coroutines.experimental.CommonPool
......@@ -34,6 +50,8 @@ import javax.inject.Inject
class ViewModelMapper @Inject constructor(
private val context: Context,
private val parser: MessageParser,
private val roomsInteractor: ChatRoomsInteractor,
private val messageHelper: MessageHelper,
tokenRepository: TokenRepository,
serverInteractor: GetCurrentServerInteractor,
getSettingsInteractor: GetSettingsInteractor,
......@@ -47,21 +65,24 @@ class ViewModelMapper @Inject constructor(
private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY)
private val secondaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
suspend fun map(message: Message): List<BaseViewModel<*>> {
return translate(message)
suspend fun map(message: Message, roomViewModel: RoomViewModel = RoomViewModel(
roles = emptyList(), isBroadcast = true)): List<BaseViewModel<*>> {
return translate(message, roomViewModel)
}
suspend fun map(messages: List<Message>): List<BaseViewModel<*>> = withContext(CommonPool) {
suspend fun map(messages: List<Message>, roomViewModel: RoomViewModel = RoomViewModel(
roles = emptyList(), isBroadcast = true)): List<BaseViewModel<*>> = withContext(CommonPool) {
val list = ArrayList<BaseViewModel<*>>(messages.size)
messages.forEach {
list.addAll(translate(it))
list.addAll(translate(it, roomViewModel))
}
return@withContext list
}
private suspend fun translate(message: Message): List<BaseViewModel<*>> = withContext(CommonPool) {
private suspend fun translate(message: Message, roomViewModel: RoomViewModel)
: List<BaseViewModel<*>> = withContext(CommonPool) {
val list = ArrayList<BaseViewModel<*>>()
message.urls?.forEach {
......@@ -86,9 +107,40 @@ class ViewModelMapper @Inject constructor(
list[i].nextDownStreamMessage = next
}
if (isBroadcastReplyAvailable(roomViewModel, message)) {
roomsInteractor.getById(currentServer, message.roomId)?.let { chatRoom ->
val replyViewModel = mapMessageReply(message, chatRoom)
list.first().nextDownStreamMessage = replyViewModel
list.add(0, replyViewModel)
}
}
return@withContext list
}
private fun isBroadcastReplyAvailable(roomViewModel: RoomViewModel, message: Message): Boolean {
val senderUsername = message.sender?.username
return roomViewModel.isRoom && roomViewModel.isBroadcast &&
!message.isSystemMessage() &&
senderUsername != currentUsername
}
private fun mapMessageReply(message: Message, chatRoom: ChatRoom): MessageReplyViewModel {
val name = message.sender?.name
val roomName = if (settings.useRealName() && name != null) name else message.sender?.username
?: ""
val permalink = messageHelper.createPermalink(message, chatRoom)
return MessageReplyViewModel(
messageId = message.id,
isTemporary = false,
reactions = emptyList(),
message = message,
preview = mapMessagePreview(message),
rawData = MessageReply(roomName = roomName, permalink = permalink),
nextDownStreamMessage = null
)
}
private fun mapUrl(message: Message, url: Url): BaseViewModel<*>? {
if (url.ignoreParse || url.meta == null) return null
......@@ -322,6 +374,10 @@ class ViewModelMapper @Inject constructor(
is MessageType.RoomNameChanged -> context.getString(R.string.message_room_name_changed, message.message, message.sender?.username)
is MessageType.UserRemoved -> context.getString(R.string.message_user_removed_by, message.message, message.sender?.username)
is MessageType.MessagePinned -> context.getString(R.string.message_pinned)
is MessageType.UserMuted -> context.getString(R.string.message_muted, message.message, message.sender?.username)
is MessageType.UserUnMuted -> context.getString(R.string.message_unmuted, message.message, message.sender?.username)
is MessageType.SubscriptionRoleAdded -> context.getString(R.string.message_role_add, message.message, message.role, message.sender?.username)
is MessageType.SubscriptionRoleRemoved -> context.getString(R.string.message_role_removed, message.message, message.role, message.sender?.username)
else -> {
throw InvalidParameterException("Invalid message type: ${message.type}")
}
......
......@@ -25,10 +25,13 @@ import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.item_chat.view.*
import kotlinx.android.synthetic.main.unread_messages_badge.view.*
class ChatRoomsAdapter(private val context: Context,
private val settings: PublicSettings,
private val localRepository: LocalRepository,
private val listener: (ChatRoom) -> Unit) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
class ChatRoomsAdapter(
private val context: Context,
private val settings: PublicSettings,
private val localRepository: LocalRepository,
private val listener: (ChatRoom) -> Unit
) : RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder>() {
var dataSet: MutableList<ChatRoom> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent.inflate(R.layout.item_chat))
......
......@@ -14,6 +14,7 @@ import android.support.v7.widget.SearchView
import android.view.*
import android.widget.CheckBox
import android.widget.RadioGroup
import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView
......@@ -23,7 +24,6 @@ import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.showLastMessage
import chat.rocket.android.util.extensions.*
import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.common.model.RoomType
......@@ -37,11 +37,14 @@ import timber.log.Timber
import javax.inject.Inject
class ChatRoomsFragment : Fragment(), ChatRoomsView {
@Inject lateinit var presenter: ChatRoomsPresenter
@Inject lateinit var serverInteractor: GetCurrentServerInteractor
@Inject lateinit var settingsRepository: SettingsRepository
@Inject lateinit var localRepository: LocalRepository
private lateinit var preferences: SharedPreferences
@Inject
lateinit var presenter: ChatRoomsPresenter
@Inject
lateinit var serverInteractor: GetCurrentServerInteractor
@Inject
lateinit var settingsRepository: SettingsRepository
@Inject
lateinit var localRepository: LocalRepository
private var searchView: SearchView? = null
private val handler = Handler()
......@@ -56,7 +59,6 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
preferences = context?.getSharedPreferences("temp", Context.MODE_PRIVATE)!!
}
override fun onDestroy() {
......@@ -136,9 +138,9 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
})
val dialogSort = AlertDialog.Builder(context)
.setTitle(R.string.dialog_sort_title)
.setView(dialogLayout)
.setPositiveButton("Done", { dialog, _ -> dialog.dismiss() })
.setTitle(R.string.dialog_sort_title)
.setView(dialogLayout)
.setPositiveButton("Done", { dialog, _ -> dialog.dismiss() })
dialogSort.show()
}
......@@ -146,9 +148,9 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
return super.onOptionsItemSelected(item)
}
private fun invalidateQueryOnSearch(){
private fun invalidateQueryOnSearch() {
searchView?.let {
if (!searchView!!.isIconified){
if (!searchView!!.isIconified) {
queryChatRoomsByName(searchView!!.query.toString())
}
}
......@@ -163,7 +165,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
/*val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await()*/
text_no_search.isVisible = newDataSet.isEmpty()
if (isActive) {
adapter.baseAdapter.updateRooms(newDataSet)
// TODO - fix crash to re-enable diff.dispatchUpdatesTo(adapter)
......@@ -179,7 +181,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
ui { text_no_data_to_display.setVisible(true) }
}
override fun showLoading(){
override fun showLoading() {
ui { view_loading.setVisible(true) }
}
......@@ -236,19 +238,18 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
ui {
recycler_view.layoutManager = LinearLayoutManager(it, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(it,
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_start),
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end)))
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_start),
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end)))
recycler_view.itemAnimator = DefaultItemAnimator()
// TODO - use a ViewModel Mapper instead of using settings on the adapter
println(serverInteractor.get() + " -> ${settingsRepository.get(serverInteractor.get()!!).showLastMessage()}")
val baseAdapter = ChatRoomsAdapter(it,
settingsRepository.get(serverInteractor.get()!!), localRepository) {
chatRoom -> presenter.loadChatRoom(chatRoom)
settingsRepository.get(serverInteractor.get()!!), localRepository) { chatRoom ->
presenter.loadChatRoom(chatRoom)
}
sectionedAdapter = SimpleSectionedRecyclerViewAdapter(it,
R.layout.item_chatroom_header, R.id.text_chatroom_header, baseAdapter)
R.layout.item_chatroom_header, R.id.text_chatroom_header, baseAdapter)
recycler_view.adapter = sectionedAdapter
}
}
......
......@@ -10,6 +10,7 @@ import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatroom.di.ChatRoomFragmentProvider
import chat.rocket.android.chatroom.di.ChatRoomModule
import chat.rocket.android.chatroom.di.FavoriteMessagesFragmentProvider
import chat.rocket.android.chatroom.di.PinnedMessagesFragmentProvider
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatrooms.di.ChatRoomsFragmentProvider
......@@ -47,10 +48,15 @@ abstract class ActivityBuilder {
abstract fun bindMainActivity(): MainActivity
@PerActivity
@ContributesAndroidInjector(modules = [ChatRoomModule::class,
ChatRoomFragmentProvider::class,
MembersFragmentProvider::class,
PinnedMessagesFragmentProvider::class])
@ContributesAndroidInjector(
modules = [
ChatRoomModule::class,
ChatRoomFragmentProvider::class,
MembersFragmentProvider::class,
PinnedMessagesFragmentProvider::class,
FavoriteMessagesFragmentProvider::class
]
)
abstract fun bindChatRoomActivity(): ChatRoomActivity
@PerActivity
......
......@@ -14,12 +14,10 @@ import chat.rocket.android.app.RocketChatDatabase
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.qualifier.ForFresco
import chat.rocket.android.dagger.qualifier.ForMessages
import chat.rocket.android.helper.FrescoAuthInterceptor
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPrefsLocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository
import chat.rocket.android.push.GroupedPush
import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.*
......@@ -43,7 +41,6 @@ import com.squareup.moshi.Moshi
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import ru.noties.markwon.SpannableConfiguration
......@@ -111,32 +108,16 @@ class AppModule {
@Singleton
fun provideOkHttpClient(logger: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(logger)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
}
@Provides
@ForFresco
@Singleton
fun provideFrescoAuthInterceptor(tokenRepository: TokenRepository, currentServerInteractor: GetCurrentServerInteractor): Interceptor {
return FrescoAuthInterceptor(tokenRepository, currentServerInteractor)
}
@Provides
@ForFresco
@Singleton
fun provideFrescoOkHttpClient(okHttpClient: OkHttpClient, @ForFresco authInterceptor: Interceptor): OkHttpClient {
return okHttpClient.newBuilder().apply {
//addInterceptor(authInterceptor)
}.build()
.addInterceptor(logger)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideImagePipelineConfig(context: Context, @ForFresco okHttpClient: OkHttpClient): ImagePipelineConfig {
fun provideImagePipelineConfig(context: Context, okHttpClient: OkHttpClient): ImagePipelineConfig {
val listeners = setOf(RequestLoggingListener())
return OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient)
......@@ -177,8 +158,8 @@ class AppModule {
@Provides
@Singleton
fun provideLocalRepository(prefs: SharedPreferences): LocalRepository {
return SharedPrefsLocalRepository(prefs)
fun provideLocalRepository(prefs: SharedPreferences, moshi: Moshi): LocalRepository {
return SharedPreferencesLocalRepository(prefs, moshi)
}
@Provides
......@@ -193,6 +174,12 @@ class AppModule {
return SharedPreferencesSettingsRepository(localRepository)
}
@Provides
@Singleton
fun providePermissionsRepository(localRepository: LocalRepository, moshi: Moshi): PermissionsRepository {
return SharedPreferencesPermissionsRepository(localRepository, moshi)
}
@Provides
@Singleton
fun provideRoomRepository(): RoomRepository {
......@@ -258,7 +245,7 @@ class AppModule {
@Provides
@Singleton
fun provideConfiguration(context: Application, client: OkHttpClient): SpannableConfiguration {
fun provideConfiguration(context: Application): SpannableConfiguration {
val res = context.resources
return SpannableConfiguration.builder(context)
.theme(SpannableTheme.builder()
......@@ -273,12 +260,6 @@ class AppModule {
return MessageParser(context, configuration, settingsInteractor.get(url))
}
@Provides
@Singleton
fun providePermissionInteractor(settingsRepository: SettingsRepository, serverRepository: CurrentServerRepository): GetPermissionsInteractor {
return GetPermissionsInteractor(settingsRepository, serverRepository)
}
@Provides
@Singleton
fun provideAccountsRepository(preferences: SharedPreferences, moshi: Moshi): AccountsRepository =
......
......@@ -3,7 +3,21 @@ package chat.rocket.android.dagger.module
import android.content.Context
import android.content.SharedPreferences
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPrefsLocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.TimberLogger
import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date
import chat.rocket.common.model.TimestampAdapter
import chat.rocket.common.util.CalendarISO8601Converter
import chat.rocket.common.util.Logger
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.internal.AttachmentAdapterFactory
import chat.rocket.core.internal.ReactionsAdapter
import com.squareup.moshi.Moshi
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
......@@ -11,14 +25,53 @@ import javax.inject.Singleton
@Module
class LocalModule {
@Provides
@Singleton
fun providePlatformLogger(): PlatformLogger {
return TimberLogger
}
@Provides
@Singleton
fun provideCurrentServerRepository(prefs: SharedPreferences): CurrentServerRepository {
return SharedPrefsCurrentServerRepository(prefs)
}
@Provides
@Singleton
fun provideMoshi(
logger: PlatformLogger,
currentServerInteractor: GetCurrentServerInteractor
): Moshi {
val url = currentServerInteractor.get() ?: ""
return Moshi.Builder()
.add(FallbackSealedClassJsonAdapter.ADAPTER_FACTORY)
.add(AppJsonAdapterFactory.INSTANCE)
.add(AttachmentAdapterFactory(Logger(logger, url)))
.add(
java.lang.Long::class.java,
ISO8601Date::class.java,
TimestampAdapter(CalendarISO8601Converter())
)
.add(
Long::class.java,
ISO8601Date::class.java,
TimestampAdapter(CalendarISO8601Converter())
)
.add(ReactionsAdapter())
.build()
}
@Provides
fun provideSharedPreferences(context: Context): SharedPreferences {
return context.getSharedPreferences("rocket.chat", Context.MODE_PRIVATE)
}
@Provides
@Singleton
fun provideLocalRepository(prefs: SharedPreferences): LocalRepository {
return SharedPrefsLocalRepository(prefs)
fun provideLocalRepository(sharedPreferences: SharedPreferences, moshi: Moshi): LocalRepository {
return SharedPreferencesLocalRepository(sharedPreferences, moshi)
}
}
\ No newline at end of file
package chat.rocket.android.dagger.qualifier
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class ForFresco
\ No newline at end of file
package chat.rocket.android.chatroom.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.favoritemessages.presentation.FavoriteMessagesView
import chat.rocket.android.favoritemessages.ui.FavoriteMessagesFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class FavoriteMessagesFragmentModule {
@Provides
fun provideLifecycleOwner(frag: FavoriteMessagesFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
@Provides
fun provideFavoriteMessagesView(frag: FavoriteMessagesFragment): FavoriteMessagesView {
return frag
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.di
import chat.rocket.android.favoritemessages.ui.FavoriteMessagesFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class FavoriteMessagesFragmentProvider {
@ContributesAndroidInjector(modules = [FavoriteMessagesFragmentModule::class])
abstract fun provideFavoriteMessageFragment(): FavoriteMessagesFragment
}
\ No newline at end of file
package chat.rocket.android.favoritemessages.presentation
import chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.ChatRoomsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.getFavoriteMessages
import timber.log.Timber
import javax.inject.Inject
class FavoriteMessagesPresenter @Inject constructor(
private val view: FavoriteMessagesView,
private val strategy: CancelStrategy,
private val serverInteractor: GetCurrentServerInteractor,
private val roomsInteractor: ChatRoomsInteractor,
private val mapper: ViewModelMapper,
factory: RocketChatClientFactory
) {
private val client = factory.create(serverInteractor.get()!!)
private var offset: Int = 0
/**
* Loads all favorite messages for room. the given room id.
*
* @param roomId The id of the room to get its favorite messages.
*/
fun loadFavoriteMessages(roomId: String) {
launchUI(strategy) {
try {
val serverUrl = serverInteractor.get()!!
val chatRoom = roomsInteractor.getById(serverUrl, roomId)
chatRoom?.let { room ->
view.showLoading()
val favoriteMessages =
client.getFavoriteMessages(roomId, room.type, offset)
offset = favoriteMessages.offset.toInt()
val messageList = mapper.map(favoriteMessages.result)
view.showFavoriteMessages(messageList)
view.hideLoading()
}.ifNull {
Timber.e("Couldn't find a room with id: $roomId at current server.")
}
} catch (e: RocketChatException) {
Timber.e(e)
}
}
}
}
\ No newline at end of file
package chat.rocket.android.favoritemessages.presentation
import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface FavoriteMessagesView : MessageView, LoadingView {
/**
* Shows the list of favorite messages for the current room.
*
* @param favoriteMessages The list of favorite messages to show.
*/
fun showFavoriteMessages(favoriteMessages: List<BaseViewModel<*>>)
}
\ No newline at end of file
package chat.rocket.android.favoritemessages.ui
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.ChatRoomAdapter
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.android.favoritemessages.presentation.FavoriteMessagesPresenter
import chat.rocket.android.favoritemessages.presentation.FavoriteMessagesView
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_favorite_messages.*
import javax.inject.Inject
fun newInstance(chatRoomId: String, chatRoomType: String): Fragment {
return FavoriteMessagesFragment().apply {
arguments = Bundle(1).apply {
putString(INTENT_CHAT_ROOM_ID, chatRoomId)
putString(INTENT_CHAT_ROOM_TYPE, chatRoomType)
}
}
}
private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
private const val INTENT_CHAT_ROOM_TYPE = "chat_room_type"
class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
private lateinit var chatRoomId: String
private lateinit var chatRoomType: String
private lateinit var adapter: ChatRoomAdapter
@Inject
lateinit var presenter: FavoriteMessagesPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(INTENT_CHAT_ROOM_ID)
chatRoomType = bundle.getString(INTENT_CHAT_ROOM_TYPE)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_favorite_messages)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
presenter.loadFavoriteMessages(chatRoomId)
}
override fun showFavoriteMessages(favoriteMessages: List<BaseViewModel<*>>) {
ui {
if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(chatRoomType, "", null, false)
recycler_view.adapter = adapter
val linearLayoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recycler_view.layoutManager = linearLayoutManager
recycler_view.itemAnimator = DefaultItemAnimator()
if (favoriteMessages.size > 10) {
recycler_view.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(
page: Int,
totalItemsCount: Int,
recyclerView: RecyclerView?
) {
presenter.loadFavoriteMessages(chatRoomId)
}
})
}
no_messages_view.isVisible = favoriteMessages.isEmpty()
}
adapter.appendData(favoriteMessages)
}
}
override fun showMessage(resId: Int) {
ui { showToast(resId) }
}
override fun showMessage(message: String) {
ui { showToast(message) }
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showLoading() {
ui { view_loading.isVisible = true }
}
override fun hideLoading() {
ui { view_loading.isVisible = false }
}
private fun setupToolbar() {
(activity as ChatRoomActivity).setupToolbarTitle(getString(R.string.title_favorite_messages))
}
}
\ No newline at end of file
package chat.rocket.android.helper
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.TokenRepository
import okhttp3.Interceptor
import okhttp3.Response
class FrescoAuthInterceptor(
private val tokenRepository: TokenRepository,
private val currentServerInteractor: GetCurrentServerInteractor
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
currentServerInteractor.get()?.let { serverUrl ->
val token = tokenRepository.get(serverUrl)
return@let token?.let {
val url = request.url().newBuilder().apply {
addQueryParameter("rc_uid", token.userId)
addQueryParameter("rc_token", token.authToken)
}.build()
request = request.newBuilder().apply {
url(url)
}.build()
}
}
return chain.proceed(request)
}
}
\ No newline at end of file
package chat.rocket.android.helper
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.useRealName
import chat.rocket.common.model.RoomType
import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.Message
import javax.inject.Inject
class MessageHelper @Inject constructor(
getSettingsInteractor: GetSettingsInteractor,
serverInteractor: GetCurrentServerInteractor
) {
private val currentServer = serverInteractor.get()!!
private val settings: PublicSettings = getSettingsInteractor.get(currentServer)
fun createPermalink(message: Message, chatRoom: ChatRoom): String {
val type = when (chatRoom.type) {
is RoomType.PrivateGroup -> "group"
is RoomType.Channel -> "channel"
is RoomType.DirectMessage -> "direct"
is RoomType.Livechat -> "livechat"
else -> "custom"
}
val name = if (settings.useRealName()) chatRoom.fullName ?: chatRoom.name else chatRoom.name
return "[ ]($currentServer/$type/$name?msg=${message.id}) "
}
fun messageIdFromPermalink(permalink: String): String? {
PERMALINK_REGEX.find(permalink.trim())?.let {
if (it.groupValues.size == 5) {
return it.groupValues[MESSAGE_ID]
}
}
return null
}
fun roomNameFromPermalink(permalink: String): String? {
PERMALINK_REGEX.find(permalink.trim())?.let {
if (it.groupValues.size == 5) {
return it.groupValues[ROOM_NAME]
}
}
return null
}
fun roomTypeFromPermalink(permalink: String): String? {
PERMALINK_REGEX.find(permalink.trim())?.let {
if (it.groupValues.size == 5) {
val type = it.groupValues[ROOM_TYPE]
return when(type) {
"group" -> "p"
"channel" -> "c"
"direct" -> "d"
"livechat" -> "l"
else -> type
}
}
}
return null
}
companion object {
private const val ROOM_TYPE = 2
private const val ROOM_NAME = 3
private const val MESSAGE_ID = 4
val PERMALINK_REGEX = "(?:__|[*#])|\\[(.+?)\\]\\(.+?//.+?/(.+)/(.+)\\?.*=(.*)\\)".toRegex()
}
}
\ No newline at end of file
package chat.rocket.android.helper
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.domain.useRealName
import chat.rocket.common.model.User
import javax.inject.Inject
class UserHelper @Inject constructor(
private val localRepository: LocalRepository,
private val getCurrentServerInteractor: GetCurrentServerInteractor,
settingsRepository: SettingsRepository
) {
private val settings: PublicSettings = settingsRepository.get(getCurrentServerInteractor.get()!!)
/**
* Return the display name for the given [user].
* If setting 'Use_Real_Name' is true then the real name will be given, or else
* the username without the '@' is yielded. The fallback for any case is the username, which
* could be null.
*/
fun displayName(user: User): String? {
return if (settings.useRealName()) user.name ?: user.username else user.username
}
/**
* Return current logged user's display name.
*
* @see displayName
*/
fun displayName(): String? {
user()?.let {
return displayName(it)
}
return null
}
/**
* Return current logged [User].
*/
fun user(): User? {
return localRepository.getCurrentUser(serverUrl())
}
/**
* Return the username for the current logged [User].
*/
fun username(): String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY, null)
/**
* Whether current [User] is admin on the current server.
*/
fun isAdmin(): Boolean {
return user()?.roles?.find { it.equals("admin", ignoreCase = true) } != null
}
private fun serverUrl(): String {
return getCurrentServerInteractor.get()!!
}
}
\ No newline at end of file
package chat.rocket.android.infrastructure
import chat.rocket.common.model.User
interface LocalRepository {
fun save(key: String, value: String?)
......@@ -14,12 +16,16 @@ interface LocalRepository {
fun getLong(key: String, defValue: Long = -1L): Long
fun clear(key: String)
fun clearAllFromServer(server: String)
fun getCurrentUser(url: String): User?
fun saveCurrentUser(url: String, user: User)
companion object {
const val KEY_PUSH_TOKEN = "KEY_PUSH_TOKEN"
const val MIGRATION_FINISHED_KEY = "MIGRATION_FINISHED_KEY"
const val TOKEN_KEY = "token_"
const val SETTINGS_KEY = "settings_"
const val PERMISSIONS_KEY = "permissions_"
const val USER_KEY = "user_"
const val CURRENT_USERNAME_KEY = "username_"
}
}
......
......@@ -2,8 +2,26 @@ package chat.rocket.android.infrastructure
import android.content.SharedPreferences
import androidx.core.content.edit
import chat.rocket.common.model.User
import com.squareup.moshi.Moshi
class SharedPreferencesLocalRepository(
private val preferences: SharedPreferences,
moshi: Moshi
) : LocalRepository {
private val userAdapter = moshi.adapter(User::class.java)
override fun getCurrentUser(url: String): User? {
return get("${url}_${LocalRepository.USER_KEY}", null)?.let {
userAdapter.fromJson(it)
}
}
override fun saveCurrentUser(url: String, user: User) {
save("${url}_${LocalRepository.USER_KEY}", userAdapter.toJson(user))
}
class SharedPrefsLocalRepository(private val preferences: SharedPreferences) : LocalRepository {
override fun getBoolean(key: String, defValue: Boolean) = preferences.getBoolean(key, defValue)
override fun getFloat(key: String, defValue: Float) = preferences.getFloat(key, defValue)
......
package chat.rocket.android.main.presentation
import android.content.Context
import chat.rocket.android.R
import chat.rocket.android.authentication.ui.newServerIntent
import chat.rocket.android.chatroom.ui.chatRoomIntent
......@@ -36,9 +35,10 @@ class MainNavigator(internal val activity: MainActivity) {
chatRoomType: String,
isChatRoomReadOnly: Boolean,
chatRoomLastSeen: Long,
isChatRoomSubscribed: Boolean) {
isChatRoomSubscribed: Boolean,
isChatRoomCreator: Boolean) {
activity.startActivity(activity.chatRoomIntent(chatRoomId, chatRoomName, chatRoomType,
isChatRoomReadOnly, chatRoomLastSeen, isChatRoomSubscribed))
isChatRoomReadOnly, chatRoomLastSeen, isChatRoomSubscribed, isChatRoomCreator))
activity.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
}
......
......@@ -2,7 +2,6 @@ package chat.rocket.android.main.viewmodel
import chat.rocket.common.model.UserStatus
data class NavHeaderViewModel(
val userDisplayName: String?,
val userStatus: UserStatus?,
......
......@@ -6,7 +6,6 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import chat.rocket.android.R
......@@ -38,9 +37,12 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
class MembersFragment : Fragment(), MembersView {
@Inject lateinit var presenter: MembersPresenter
private val adapter: MembersAdapter = MembersAdapter { memberViewModel -> presenter.toMemberDetails(memberViewModel) }
private val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
@Inject
lateinit var presenter: MembersPresenter
private val adapter: MembersAdapter =
MembersAdapter { memberViewModel -> presenter.toMemberDetails(memberViewModel) }
private val linearLayoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
private lateinit var chatRoomId: String
private lateinit var chatRoomType: String
......@@ -58,13 +60,15 @@ class MembersFragment : Fragment(), MembersView {
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_members)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_members)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as AppCompatActivity).supportActionBar?.title = ""
setupRecyclerView()
presenter.loadChatRoomsMembers(chatRoomId, chatRoomType)
}
......@@ -75,8 +79,13 @@ class MembersFragment : Fragment(), MembersView {
if (adapter.itemCount == 0) {
adapter.prependData(dataSet)
if (dataSet.size >= 59) { // TODO Check why the API retorns the specified count -1
recycler_view.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
recycler_view.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(
page: Int,
totalItemsCount: Int,
recyclerView: RecyclerView?
) {
presenter.loadChatRoomsMembers(chatRoomId, chatRoomType, page * 60L)
}
})
......@@ -84,18 +93,7 @@ class MembersFragment : Fragment(), MembersView {
} else {
adapter.appendData(dataSet)
}
if (it is ChatRoomActivity) {
it.showRoomTypeIcon(false)
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
(activity as ChatRoomActivity).showRoomTypeIcon(true)
return super.onOptionsItemSelected(item)
}
return super.onOptionsItemSelected(item)
}
override fun showLoading() {
......@@ -129,6 +127,8 @@ class MembersFragment : Fragment(), MembersView {
}
private fun setupToolbar(totalMembers: Long) {
(activity as ChatRoomActivity?)?.setupToolbarTitle(getString(R.string.title_members, totalMembers))
(activity as ChatRoomActivity?)?.setupToolbarTitle(
getString(R.string.title_members, totalMembers)
)
}
}
\ No newline at end of file
......@@ -2,13 +2,13 @@ package chat.rocket.android.pinnedmessages.presentation
import chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetChatRoomsInteractor
import chat.rocket.android.server.domain.ChatRoomsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.getRoomPinnedMessages
import chat.rocket.core.internal.rest.getPinnedMessages
import chat.rocket.core.model.isSystemMessage
import timber.log.Timber
import javax.inject.Inject
......@@ -17,7 +17,7 @@ class PinnedMessagesPresenter @Inject constructor(
private val view: PinnedMessagesView,
private val strategy: CancelStrategy,
private val serverInteractor: GetCurrentServerInteractor,
private val roomsInteractor: GetChatRoomsInteractor,
private val roomsInteractor: ChatRoomsInteractor,
private val mapper: ViewModelMapper,
factory: RocketChatClientFactory
) {
......@@ -37,7 +37,7 @@ class PinnedMessagesPresenter @Inject constructor(
chatRoom?.let { room ->
view.showLoading()
val pinnedMessages =
client.getRoomPinnedMessages(roomId, room.type, pinnedMessagesListOffset)
client.getPinnedMessages(roomId, room.type, pinnedMessagesListOffset)
pinnedMessagesListOffset = pinnedMessages.offset.toInt()
val messageList = mapper.map(pinnedMessages.result.filterNot { it.isSystemMessage() })
view.showPinnedMessages(messageList)
......
......@@ -2,13 +2,13 @@ package chat.rocket.android.pinnedmessages.ui
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.ChatRoomAdapter
import chat.rocket.android.chatroom.ui.ChatRoomActivity
......@@ -17,14 +17,13 @@ import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.pinnedmessages.presentation.PinnedMessagesPresenter
import chat.rocket.android.pinnedmessages.presentation.PinnedMessagesView
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_pinned_messages.*
import javax.inject.Inject
fun newInstance(chatRoomId: String, chatRoomType: String) : Fragment {
fun newInstance(chatRoomId: String, chatRoomType: String): Fragment {
return PinnedMessagesFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
......@@ -49,15 +48,19 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null){
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE)
}else{
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_pinned_messages)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_pinned_messages)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
......@@ -69,21 +72,27 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
override fun showPinnedMessages(pinnedMessages: List<BaseViewModel<*>>) {
ui {
if (recycler_view_pinned.adapter == null){
adapter = ChatRoomAdapter(chatRoomType,"",null,false)
if (recycler_view_pinned.adapter == null) {
adapter = ChatRoomAdapter(chatRoomType, "", null, false)
recycler_view_pinned.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL,false)
val linearLayoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
recycler_view_pinned.layoutManager = linearLayoutManager
recycler_view_pinned.itemAnimator = DefaultItemAnimator()
if (pinnedMessages.size > 10){
recycler_view_pinned.addOnScrollListener(object : EndlessRecyclerViewScrollListener(linearLayoutManager){
override fun onLoadMore(page: Int, totalItemsCount: Int, recyclerView: RecyclerView?) {
if (pinnedMessages.size > 10) {
recycler_view_pinned.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(
page: Int,
totalItemsCount: Int,
recyclerView: RecyclerView?
) {
presenter.loadPinnedMessages(chatRoomId)
}
})
}
togglePinView(pinnedMessages.size)
pin_view.isVisible = pinnedMessages.isEmpty()
}
adapter.appendData(pinnedMessages)
}
......@@ -104,22 +113,14 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showLoading() {
ui{ view_loading.setVisible(true) }
ui { view_loading.isVisible = true }
}
override fun hideLoading() {
ui { view_loading.setVisible(false) }
ui { view_loading.isVisible = false }
}
private fun setupToolbar() {
(activity as ChatRoomActivity).setupToolbarTitle(getString(R.string.title_pinned_messages))
}
private fun togglePinView(size : Int){
if (size == 0){
pin_view.setVisible(true)
}else{
pin_view.setVisible(false)
}
}
}
\ No newline at end of file
......@@ -291,7 +291,7 @@ class PushManager @Inject constructor(
.setLabel(replyTextHint)
.build()
val pendingIntent = getReplyPendingIntent(pushMessage)
val replyAction = NotificationCompat.Action.Builder(R.drawable.ic_reply_black_24px, replyTextHint, pendingIntent)
val replyAction = NotificationCompat.Action.Builder(R.drawable.ic_action_message_reply_24dp, replyTextHint, pendingIntent)
.addRemoteInput(replyRemoteInput)
.setAllowGeneratedReplies(true)
.build()
......
......@@ -5,7 +5,7 @@ import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import javax.inject.Inject
class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsRepository) {
class ChatRoomsInteractor @Inject constructor(private val repository: ChatRoomsRepository) {
/**
* Get all [ChatRoom].
......@@ -41,8 +41,7 @@ class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoo
* @return The [ChatRoom] object or null if we couldn't find any.
*/
suspend fun getById(serverUrl: String, roomId: String): ChatRoom? = withContext(CommonPool) {
val allChatRooms = repository.get(serverUrl)
return@withContext allChatRooms.first {
return@withContext repository.get(serverUrl).find {
it.id == roomId
}
}
......@@ -55,7 +54,7 @@ class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoo
* @return The [ChatRoom] object or null if we couldn't find any.
*/
fun getByName(serverUrl: String, name: String): ChatRoom? {
return getAll(serverUrl).toMutableList().find { chatRoom -> chatRoom.name == name }
return getAll(serverUrl).firstOrNull { it.name == name || it.fullName == name }
}
/**
......
package chat.rocket.android.server.domain
import javax.inject.Inject
class GetPermissionsInteractor @Inject constructor(private val settingsRepository: SettingsRepository,
private val currentServerRepository: CurrentServerRepository) {
private fun publicSettings(): PublicSettings? = settingsRepository.get(currentServerRepository.get()!!)
/**
* Check whether user is allowed to delete a message.
*/
fun allowedMessageDeleting() = publicSettings()?.allowedMessageDeleting() ?: false
/**
* Checks whether user is allowed to edit a message.
*/
fun allowedMessageEditing() = publicSettings()?.allowedMessageEditing() ?: false
/**
* Checks whether user is allowed to pin a message to a channel.
*/
fun allowedMessagePinning() = publicSettings()?.allowedMessagePinning() ?: false
/**
* Checks whether should show deleted message status.
*/
fun showDeletedStatus() = publicSettings()?.showDeletedStatus() ?: false
/**
* Checks whether should show edited message status.
*/
fun showEditedStatus() = publicSettings()?.showEditedStatus() ?: false
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.core.model.Permission
import javax.inject.Inject
// Creating rooms
const val CREATE_PUBLIC_CHANNELS = "create-c"
const val CREATE_DIRECT_MESSAGES = "create-d"
const val CREATE_PRIVATE_CHANNELS = "create-p"
// Messages
const val DELETE_MESSAGE = "delete-message"
const val FORCE_DELETE_MESSAGE = "force-delete-message"
const val EDIT_MESSAGE = "edit-message"
const val PIN_MESSAGE = "pin-message"
const val POST_READONLY = "post-readonly"
class PermissionsInteractor @Inject constructor(
private val settingsRepository: SettingsRepository,
private val permissionsRepository: PermissionsRepository,
private val getCurrentServerInteractor: GetCurrentServerInteractor,
private val userHelper: UserHelper
) {
private fun publicSettings(): PublicSettings? = settingsRepository.get(currentServerUrl()!!)
fun saveAll(permissions: List<Permission>) {
val url = currentServerUrl()!!
permissions.forEach { permissionsRepository.save(url, it) }
}
/**
* Check whether the user is allowed to delete a message.
*/
fun allowedMessageDeleting() = publicSettings()?.allowedMessageDeleting() ?: false
/**
* Checks whether the user is allowed to edit a message.
*/
fun allowedMessageEditing() = publicSettings()?.allowedMessageEditing() ?: false
/**
* Checks whether the user is allowed to pin a message to a channel.
*/
fun allowedMessagePinning() = publicSettings()?.allowedMessagePinning() ?: false
/**
* Checks whether the user is allowed to star a message.
*/
fun allowedMessageStarring() = publicSettings()?.allowedMessageStarring() ?: false
/**
* Checks whether should show deleted message status.
*/
fun showDeletedStatus() = publicSettings()?.showDeletedStatus() ?: false
/**
* Checks whether should show edited message status.
*/
fun showEditedStatus() = publicSettings()?.showEditedStatus() ?: false
fun canPostToReadOnlyChannels(): Boolean {
val url = getCurrentServerInteractor.get()!!
val currentUserRoles = userHelper.user()?.roles
return permissionsRepository.get(url, POST_READONLY)?.let { permission ->
currentUserRoles?.isNotEmpty() == true && permission.roles.any {
currentUserRoles.contains(it)
}
} == true || userHelper.isAdmin()
}
private fun currentServerUrl(): String? {
return getCurrentServerInteractor.get()
}
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.core.model.Permission
interface PermissionsRepository {
/**
* Store [permission] locally.
*
* @param url The server url from where we're interest to store the permission.
* @param permission The permission to store.
*/
fun save(url: String, permission: Permission)
/**
* Get permission given by the [permissionId] and for the server [url].
*
* @param url The server url from where we're interested on getting the permissions.
* @param permissionId the id of the permission to get.
*
* @return The interested [Permission] or null if not found.
*/
fun get(url: String, permissionId: String): Permission?
}
\ No newline at end of file
......@@ -24,7 +24,7 @@ class RefreshSettingsInteractor @Inject constructor(
FAVORITE_ROOMS, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE, UPLOAD_WHITELIST_MIMETYPES,
HIDE_USER_JOIN, HIDE_USER_LEAVE,
HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING,
ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, 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)
suspend fun refresh(server: String) {
......
......@@ -29,7 +29,8 @@ class SaveActiveUsersInteractor @Inject constructor(
username = user.username ?: it.username,
status = user.status ?: it.status,
emails = user.emails ?: it.emails,
utcOffset = user.utcOffset ?: it.utcOffset
utcOffset = user.utcOffset ?: it.utcOffset,
roles = user.roles ?: it.roles
)
val activeUserList: MutableList<User> =
......
......@@ -49,6 +49,7 @@ const val ALLOW_MESSAGE_EDITING = "Message_AllowEditing"
const val SHOW_DELETED_STATUS = "Message_ShowDeletedStatus"
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"
/*
......@@ -83,6 +84,7 @@ fun PublicSettings.wideTile(): String? = this[WIDE_TILE_310]?.value as String?
fun PublicSettings.showDeletedStatus(): Boolean = this[SHOW_DELETED_STATUS]?.value == true
fun PublicSettings.showEditedStatus(): Boolean = this[SHOW_EDITED_STATUS]?.value == true
fun PublicSettings.allowedMessagePinning(): Boolean = this[ALLOW_MESSAGE_PINNING]?.value == true
fun PublicSettings.allowedMessageStarring(): Boolean = this[ALLOW_MESSAGE_STARRING]?.value == true
fun PublicSettings.allowedMessageEditing(): Boolean = this[ALLOW_MESSAGE_EDITING]?.value == true
fun PublicSettings.allowedMessageDeleting(): Boolean = this[ALLOW_MESSAGE_DELETING]?.value == true
......
......@@ -110,7 +110,6 @@ class ConnectionManager(internal val client: RocketChatClient) {
Timber.d("Received new Message for room ${message.roomId}")
val channel = roomMessagesChannels[message.roomId]
channel?.send(message)
}
}
......@@ -131,6 +130,7 @@ class ConnectionManager(internal val client: RocketChatClient) {
}
}
}
client.connect()
// Broadcast initial state...
......
package chat.rocket.android.server.infraestructure
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.PermissionsRepository
import chat.rocket.core.model.Permission
import com.squareup.moshi.Moshi
class SharedPreferencesPermissionsRepository(
private val localRepository: LocalRepository,
moshi: Moshi
) : PermissionsRepository {
private val adapter = moshi.adapter(Permission::class.java)
override fun save(url: String, permission: Permission) {
localRepository.save(getPermissionKey(url, permission.id), adapter.toJson(permission))
}
override fun get(url: String, permissionId: String): Permission? {
return localRepository.get(getPermissionKey(url, permissionId))?.let {
adapter.fromJson(it)
}
}
// Create a key following the pattern: settings_[url]_[permission id]
// eg.: 'settings_https://open.rocket.chat_create-p'
private fun getPermissionKey(url: String, permissionId: String): String {
return "${LocalRepository.PERMISSIONS_KEY}${url}_$permissionId"
}
}
\ No newline at end of file
......@@ -6,7 +6,9 @@ import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.core.internal.SettingsAdapter
class SharedPreferencesSettingsRepository(private val localRepository: LocalRepository) : SettingsRepository {
class SharedPreferencesSettingsRepository(
private val localRepository: LocalRepository
) : SettingsRepository {
private val adapter = SettingsAdapter().lenient()
......
......@@ -6,9 +6,8 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
fun EditText.asObservable(debounceTimeout: Long = 100): Observable<CharSequence> {
fun EditText.asObservable(): Observable<CharSequence> {
return RxTextView.textChanges(this)
.debounce(debounceTimeout, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(AndroidSchedulers.mainThread())
}
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"
android:fillColor="@color/actionMenuColor"/>
</vector>
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/actionMenuColor"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"
android:fillColor="#FF0000"/>
android:fillColor="#FF0000"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"
android:fillColor="@color/actionMenuColor"/>
</vector>
android:fillColor="@color/actionMenuColor"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@color/actionMenuColor"
android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:pathData="M6,17h3l2,-4L11,7L5,7v6h3zM14,17h3l2,-4L19,7h-6v6h3z"
android:fillColor="@color/actionMenuColor"/>
</vector>
android:fillColor="@color/actionMenuColor"
android:pathData="M6,17h3l2,-4L11,7L5,7v6h3zM14,17h3l2,-4L19,7h-6v6h3z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"
android:fillColor="@color/actionMenuColor"/>
</vector>
android:fillColor="@color/actionMenuColor"
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
</vector>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"
android:fillColor="@color/actionMenuColor"/>
</vector>
android:fillColor="@color/actionMenuColor"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
</vector>
\ No newline at end of file
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="13dp"
android:height="16dp"
android:viewportWidth="13.0"
android:viewportHeight="16.0">
<path
android:pathData="M6.626,11.495L4.505,11.495L3.714,16L1.703,16L2.495,11.495L0,11.495L0,9.604L2.824,9.604L3.374,6.484L0.824,6.484L0.824,4.571L3.714,4.571L4.516,0L6.516,0L5.714,4.571L7.846,4.571L8.648,0L10.659,0L9.857,4.571L12.264,4.571L12.264,6.484L9.516,6.484L8.967,9.604L11.429,9.604L11.429,11.495L8.637,11.495L7.846,16L5.835,16L6.626,11.495ZM4.835,9.604L6.956,9.604L7.505,6.484L5.374,6.484L4.835,9.604Z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#000000"
android:strokeWidth="1"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="197.218"
android:viewportHeight="197.218"
android:width="197.218dp"
android:height="197.218dp">
<group
android:translateX="-570.396"
android:translateY="-306.782">
<path
android:pathData="M704.445 306.782l-6.785 6.785c-6.084 6.084 -7.622 14.712 -4.309 21.871l-44.068 35.44 -3.086 -3.086c-7.889 -7.889 -19.525 -7.889 -27.414 0l-8.944 8.953 87.821 87.811 8.934 -8.933c7.899 -7.899 7.899 -19.525 0 -27.433l-3.076 -3.077 36.051 -44.68c6.824 2.466 14.367 1.036 20.037 -4.624l8.008 -5.858 -63.169 -63.169zm-66.867 116.487l-67.182 66.857 0 13.874 13.864 0 66.867 -67.182 -13.549 -13.549z"
android:fillColor="@color/actionMenuColor"
/>
</group>
</vector>
\ No newline at end of file
......@@ -6,4 +6,4 @@
<path
android:fillColor="#FF000000"
android:pathData="M22.548,9l0.452,-2h-5.364l1.364,-6h-2l-1.364,6h-5l1.364,-6h-2l-1.364,6h-6.184l-0.452,2h6.182l-1.364,6h-5.36l-0.458,2h5.364l-1.364,6h2l1.364,-6h5l-1.364,6h2l1.364,-6h6.185l0.451,-2h-6.182l1.364,-6h5.366zM13.818,15h-5l1.364,-6h5l-1.364,6z" />
</vector>
</vector>
\ No newline at end of file
......@@ -6,4 +6,4 @@
<path
android:fillColor="#FF000000"
android:pathData="M13.6,13.47c-0.91,0.953 -2.191,1.545 -3.61,1.545 -2.756,0 -4.99,-2.234 -4.99,-4.99 0,-0.009 0,-0.018 0,-0.026v0.001c0,-2.761 2.239,-5 5,-5 1.131,0 2.175,0.376 3.013,1.009l-0.013,-0.009v-1h2v6.5c0,0.828 0.672,1.5 1.5,1.5s1.5,-0.672 1.5,-1.5v0,-1.5c-0.003,-4.416 -3.584,-7.994 -8,-7.994 -4.418,0 -8,3.582 -8,8s3.582,8 8,8c1.305,0 2.537,-0.312 3.625,-0.867l-0.045,0.021 0.9,1.79c-1.305,0.668 -2.847,1.06 -4.48,1.06 -5.523,0 -10,-4.477 -10,-10s4.477,-10 10,-10c5.519,0 9.994,4.472 10,9.99v0.001h-0.01v1.5c0,0.003 0,0.007 0,0.01 0,1.933 -1.567,3.5 -3.5,3.5 -1.202,0 -2.262,-0.606 -2.892,-1.528l-0.008,-0.012zM10,13c1.657,0 3,-1.343 3,-3s-1.343,-3 -3,-3v0c-1.657,0 -3,1.343 -3,3s1.343,3 3,3v0z" />
</vector>
</vector>
\ No newline at end of file
......@@ -6,4 +6,4 @@
<path
android:fillColor="#FF000000"
android:pathData="M18,10v-4c0,-3.313 -2.687,-6 -6,-6s-6,2.687 -6,6v4h-3v14h18v-14h-3zM8,6c0,-2.206 1.794,-4 4,-4s4,1.794 4,4v4h-8v-4zM19,22h-14v-10h14v10z" />
</vector>
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="4dp" />
<stroke android:color="#1D74F5" android:width="2dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppTheme"
tools:context=".chatroom.ui.PinnedMessagesActivity">
<include
android:id="@+id/layout_app_bar"
layout="@layout/app_bar_chat_room" />
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/layout_app_bar" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<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:id="@+id/root_layout"
......@@ -12,23 +11,23 @@
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"
app:indicatorColor="@color/black"
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" />
<FrameLayout
android:id="@+id/message_list_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_typing_status"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
android:id="@+id/layout_message_list"
......@@ -44,52 +43,65 @@
android:layout_height="100dp"
android:src="@drawable/ic_chat_black_24dp"
android:tint="@color/icon_grey"
app:layout_constraintStart_toStartOf="parent"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/text_chat_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_chat_title"
app:layout_constraintVertical_chainStyle="packed"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/text_chat_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/msg_no_chat_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_chat_icon"
app:layout_constraintBottom_toTopOf="@id/text_chat_description"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp"
android:layout_marginTop="24dp"
android:textStyle="bold"
android:textColor="@color/colorSecondaryText"
android:visibility="gone"
tools:visibility="visible"/>
app:layout_constraintBottom_toTopOf="@id/text_chat_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_chat_icon"
tools:visibility="visible" />
<TextView
android:id="@+id/text_chat_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/msg_no_chat_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_chat_title"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:layout_marginTop="16dp"
android:text="@string/msg_no_chat_description"
android:textAlignment="center"
android:textSize="16sp"
android:textColor="@color/colorSecondaryTextLight"
android:textSize="16sp"
android:visibility="gone"
tools:visibility="visible"/>
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_chat_title"
tools:visibility="visible" />
<chat.rocket.android.widget.autocompletion.ui.SuggestionsView
android:id="@+id/suggestions_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/suggestion_background_color"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer" />
<TextView
android:id="@+id/text_typing_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:maxLines="2"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:background="@color/suggestion_background_color" />
app:layout_constraintEnd_toStartOf="parent" />
<include
android:id="@+id/layout_message_composer"
......@@ -102,18 +114,18 @@
android:id="@+id/view_dim"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:background="@color/colorDim"
android:visibility="gone" />
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer" />
<include
android:id="@+id/layout_message_attachment_options"
layout="@layout/message_attachment_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer"
android:layout_margin="5dp"
android:visibility="gone" />
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/layout_message_composer" />
<TextView
android:id="@+id/connection_status_text"
......
......@@ -45,4 +45,15 @@
tools:text="connected"
tools:visibility="visible" />
<TextView
android:id="@+id/text_no_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="56dp"
android:text="@string/msg_no_search_found"
android:textSize="20sp"
android:layout_centerHorizontal="true"
android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout>
\ No newline at end of file
<?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="match_parent"
tools:context=".favoritemessages.ui.FavoriteMessagesFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
app:indicatorColor="@color/black"
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" />
<ImageView
android:id="@+id/image_star"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_action_message_star_24dp"
android:tint="@color/icon_grey"
app:layout_constraintBottom_toTopOf="@+id/text_no_favorite_messages"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/text_no_favorite_messages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/no_favorite_messages"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/text_no_favorite_messages_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_star" />
<TextView
android:id="@+id/text_no_favorite_messages_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/no_favorite_description"
android:textAlignment="center"
android:textColor="@color/colorSecondaryTextLight"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_no_favorite_messages" />
<android.support.constraint.Group
android:id="@+id/no_messages_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="text_no_favorite_messages_description,image_star,text_no_favorite_messages"
tools:visibility="visible" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -3,18 +3,14 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:context=".pinnedmessages.ui.PinnedMessagesFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view_pinned"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
android:scrollbars="vertical" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
......@@ -36,48 +32,48 @@
android:id="@+id/iv_pin_icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_pin_black_24dp"
android:src="@drawable/ic_action_message_pin_24dp"
android:tint="@color/icon_grey"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/tv_pin_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/tv_pin_title"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/tv_pin_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/no_pinned_messages"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_pin_icon"
app:layout_constraintBottom_toTopOf="@id/tv_pin_description"
android:textColor="@color/colorSecondaryText"
android:textSize="20sp"
android:layout_marginTop="24dp"
android:textStyle="bold"
android:textColor="@color/colorSecondaryText"/>
app:layout_constraintBottom_toTopOf="@id/tv_pin_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_pin_icon" />
<TextView
android:id="@+id/tv_pin_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_pinned_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_pin_title"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="16dp"
android:text="@string/no_pinned_description"
android:textAlignment="center"
android:textColor="@color/colorSecondaryTextLight"
android:textSize="16sp"
android:textColor="@color/colorSecondaryTextLight"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_pin_title" />
<android.support.constraint.Group
android:id="@+id/pin_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_pin_description,iv_pin_icon,tv_pin_title"
android:visibility="gone"
app:constraint_referenced_ids="tv_pin_description,iv_pin_icon,tv_pin_title"
tools:visibility="visible" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -7,11 +7,11 @@
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding"
android:focusable="true"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"
android:focusable="true">
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding">
<include
android:id="@+id/layout_avatar"
......@@ -20,36 +20,39 @@
android:layout_height="40dp"
android:layout_marginTop="5dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/new_messages_notif" />
app:layout_constraintTop_toBottomOf="@+id/new_messages_notif" />
<LinearLayout
android:id="@+id/new_messages_notif"
tools:visibility="visible"
android:visibility="gone"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<View
android:layout_gravity="center"
android:layout_height="1dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp"
android:layout_gravity="center"
android:layout_marginEnd="4dp"
android:background="@color/red"/>
android:layout_weight="1"
android:background="@color/red" />
<TextView
android:layout_width="wrap_content"
android:text="@string/msg_unread_messages"
android:layout_height="wrap_content"
android:text="@string/msg_unread_messages"
android:textColor="@color/red" />
<View
android:layout_gravity="center"
android:layout_height="1dp"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="1dp"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:background="@color/red"/>
android:layout_weight="1"
android:background="@color/red" />
</LinearLayout>
<LinearLayout
......@@ -58,8 +61,8 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/new_messages_notif"
app:layout_constraintLeft_toRightOf="@+id/layout_avatar">
app:layout_constraintLeft_toRightOf="@+id/layout_avatar"
app:layout_constraintTop_toBottomOf="@+id/new_messages_notif">
<TextView
android:id="@+id/text_sender"
......@@ -75,6 +78,27 @@
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
tools:text="11:45 PM" />
<TextView
android:id="@+id/text_edit_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/msg_edited"
android:textStyle="italic"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/image_star_indicator"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginTop="2dp"
android:src="@drawable/ic_action_message_star_24dp"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
<TextView
......@@ -84,12 +108,13 @@
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_marginTop="5dp"
app:layout_constraintLeft_toLeftOf="@id/top_container"
app:layout_constraintLeft_toLeftOf="@+id/top_container"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/top_container"
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!" />
<include layout="@layout/layout_reactions"
<include
layout="@layout/layout_reactions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/text_content"
......
<?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"
android:id="@+id/message_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingBottom="@dimen/message_item_top_and_bottom_padding"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:paddingTop="@dimen/message_item_top_and_bottom_padding">
<Button
android:id="@+id/button_message_reply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_marginTop="5dp"
android:layout_marginStart="56dp"
android:background="@drawable/message_reply_button_bg"
android:text="@string/action_msg_reply"
android:textAllCaps="false"
android:textColor="#1D74F5"
android:textSize="14sp"
app:layout_constraintLeft_toLeftOf="parent" />
<include
layout="@layout/layout_reactions"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/button_message_reply"
app:layout_constraintStart_toStartOf="@+id/button_message_reply"
app:layout_constraintTop_toBottomOf="@+id/button_message_reply" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -11,4 +11,9 @@
android:id="@+id/action_pinned_messages"
android:title="@string/title_pinned_messages"
app:showAsAction="never" />
<item
android:id="@+id/action_favorite_messages"
android:title="@string/title_favorite_messages"
app:showAsAction="never" />
</menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/common_actions">
<item
android:id="@+id/action_menu_msg_reply"
android:icon="@drawable/ic_reply_black_24px"
android:title="@string/action_msg_reply" />
<item
android:id="@+id/action_menu_msg_quote"
android:icon="@drawable/ic_quote_black_24px"
android:title="@string/action_msg_quote" />
<item
android:id="@+id/action_menu_msg_edit"
android:icon="@drawable/ic_edit_black_24px"
android:title="@string/action_msg_edit" />
<item
android:id="@+id/action_menu_msg_copy"
android:icon="@drawable/ic_content_copy_black_24px"
android:title="@string/action_msg_copy" />
<item
android:id="@+id/action_menu_msg_react"
android:icon="@drawable/ic_add_reaction"
android:title="@string/action_msg_add_reaction" />
<!--<item-->
<!--android:id="@+id/action_menu_msg_share"-->
<!--andrtextIconicon="@drawable/ic_share_black_24px"-->
<!--android:title="@string/action_msg_share" />-->
<item
android:id="@+id/action_menu_msg_pin_unpin"
android:icon="@drawable/ic_pin_black_24dp"
android:title="@string/action_msg_pin" />
<!--<item-->
<!--android:id="@+id/action_menu_msg_star"-->
<!--andrtextIconicon="@drawable/ic_star_black_24px"-->
<!--android:title="@string/action_msg_star" />-->
</group>
<group android:id="@+id/dangerous_actions">
<item
android:id="@+id/action_menu_msg_delete"
android:icon="@drawable/ic_delete_black_24px"
android:title="@string/action_msg_delete" />
</group>
<item
android:id="@+id/action_message_reply"
android:icon="@drawable/ic_action_message_reply_24dp"
android:title="@string/action_msg_reply" />
<item
android:id="@+id/action_message_quote"
android:icon="@drawable/ic_action_message_quote_24dp"
android:title="@string/action_msg_quote" />
<item
android:id="@+id/action_message_copy"
android:icon="@drawable/ic_action_message_copy_24dp"
android:title="@string/action_msg_copy" />
<item
android:id="@+id/action_menu_msg_react"
android:icon="@drawable/ic_add_reaction"
android:title="@string/action_msg_add_reaction" />
<!--<item-->
<!--android:id="@+id/action_menu_msg_share"-->
<!--andrtextIconicon="@drawable/ic_share_black_24px"-->
<!--android:title="@string/action_msg_share" />-->
<item
android:id="@+id/action_message_star"
android:icon="@drawable/ic_action_message_star_24dp"
android:title="@string/action_msg_star" />
<item
android:id="@+id/action_message_unpin"
android:icon="@drawable/ic_action_message_pin_24dp"
android:title="@string/action_msg_pin" />
<item
android:id="@+id/action_message_edit"
android:icon="@drawable/ic_action_message_edit_24dp"
android:title="@string/action_msg_edit" />
<item
android:id="@+id/action_message_delete"
android:icon="@drawable/ic_action_message_delete_24dp"
android:title="@string/action_msg_delete" />
</menu>
\ No newline at end of file
......@@ -100,7 +100,6 @@
<string name="msg_ver_not_minimum">
Parece que la versión del servidor está por debajo de la versión mínima requerida %1$s.\nActualice su servidor para iniciar sesión!
</string>
<string name="msg_proceed">PROCEDER</string>
<string name="msg_cancel">CANCELAR</string>
<string name="msg_warning">ADVERTENCIA</string>
......@@ -111,6 +110,16 @@
<string name="msg_image_saved_failed">Error al guardar la imagen</string>
<string name="msg_no_chat_title">Sin mensajes de chat</string>
<string name="msg_no_chat_description">Comience a conversar para ver\nsus mensajes aquí.</string>
<string name="msg_edited">(editado)</string>
// TODO: Add proper translation.
<string name="msg_and">\u0020and\u0020</string>
// TODO: Add proper translation.
<string name="msg_is_typing">\u0020is typing…</string>
// TODO: Add proper translation.
<string name="msg_are_typing">\u0020are typing…</string>
// TODO: Add proper translation.
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">No se han encontrado resultados</string>
<!-- System messages -->
<string name="message_room_name_changed">Nombre de la sala cambiado para: %1$s por %2$s</string>
......@@ -121,6 +130,10 @@
<string name="message_welcome">Bienvenido %s</string>
<string name="message_removed">Mensaje eliminado</string>
<string name="message_pinned">Fijado una mensaje:</string>
<string name="message_muted">Usuario %1$s silenciado por %2$s</string>
<string name="message_unmuted">Usuario %1$s no silenciado por %2$s</string>
<string name="message_role_add">%1$s fue establecido %2$s por %3$s</string>
<string name="message_role_removed">%1$s ya no es %2$s por %3$s</string>
<!-- Message actions -->
<string name="action_msg_reply">Respuesta</string>
......@@ -131,6 +144,8 @@
<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_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>
......@@ -139,6 +154,8 @@
<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.
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">Lista de miembros</string>
......@@ -146,7 +163,13 @@
<!-- Pinned Messages -->
<string name="title_pinned_messages">Mensajes fijados</string>
<string name="no_pinned_messages">Sin mensajes fijadas</string>
<string name="no_pinned_description">Todas las mensajes fijadas\naparecen aquí.</string>
<string name="no_pinned_description">Todas las mensajes fijadas\naparecen aquí</string>
<!-- Favorite Messages -->
<!-- TODO Add proper translation-->
<string name="title_favorite_messages">Favorite Messages</string>
<string name="no_favorite_messages">No favorite messages</string>
<string name="no_favorite_description">All the favorite messages\nappear here</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">Tamaño del archivo (%1$d bytes) excedió el tamaño máximo de carga de %2$d bytes</string>
......
......@@ -110,6 +110,16 @@
<string name="msg_image_saved_failed">Échec de l\'enregistrement de l\'image</string>
<string name="msg_no_chat_title">Aucun message de discussion</string>
<string name="msg_no_chat_description">Commencez à converser pour voir\nvos messages ici.</string>
<string name="msg_edited">(édité)</string>
// TODO: Add proper translation.
<string name="msg_and">\u0020and\u0020</string>
// TODO: Add proper translation.
<string name="msg_is_typing">\u0020is typing…</string>
// TODO: Add proper translation.
<string name="msg_are_typing">\u0020are typing…</string>
// TODO: Add proper translation.
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">Aucun résultat trouvé</string>
<!-- System messages -->
<string name="message_room_name_changed">Le nom de le salle a changé à: %1$s par %2$s</string>
......@@ -120,6 +130,10 @@
<string name="message_welcome">Bienvenue %s</string>
<string name="message_removed">Message supprimé</string>
<string name="message_pinned">Épinglé un message:</string>
<string name="message_muted">Utilisateur %1$s mis en sourdine par %2$s</string>
<string name="message_unmuted">Utilisateur %1$s non muté par %2$s</string>
<string name="message_role_add">%1$s a été défini %2$s par %3$s</string>
<string name="message_role_removed">%1$s is no longer %2$s par %3$s</string>
<!-- Message actions -->
<string name="action_msg_reply">Répondre</string>
......@@ -129,7 +143,10 @@
<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_share">Partager</string>
<string name="action_title_editing">Modification du message</string>
<string name="action_msg_add_reaction">Ajouter une réaction</string>
......@@ -138,6 +155,8 @@
<string name="permission_editing_not_allowed">L\'édition n\'est pas autorisée</string>
<string name="permission_deleting_not_allowed">La suppression n\'est pas autorisée</string>
<string name="permission_pinning_not_allowed">L\'épinglage n\'est pas autorisé</string>
// TODO: Add proper translation.
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">Liste des membres</string>
......@@ -145,7 +164,13 @@
<!-- Pinned Messages -->
<string name="title_pinned_messages">Messages épinglés</string>
<string name="no_pinned_messages">Aucun message épinglé</string>
<string name="no_pinned_description">Tous les messages épinglés\napparaissent ici.</string>
<string name="no_pinned_description">Tous les messages épinglés\napparaissent ici</string>
<!-- Favorite Messages -->
<!-- TODO Add proper translation-->
<string name="title_favorite_messages">Favorite Messages</string>
<string name="no_favorite_messages">No favorite messages</string>
<string name="no_favorite_description">All the favorite messages\nappear here</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">Taille du fichier (%1$d bytes) dépassé la taille de téléchargement maximale de %2$d bytes</string>
......
......@@ -112,6 +112,16 @@
<string name="msg_image_saved_failed">छवि को सहेजने में विफल</string>
<string name="msg_no_chat_title">कोई चैट संदेश नहीं</string>
<string name="msg_no_chat_description">यहां अपने संदेश देखने के लिए\nबातचीत शुरू करें।</string>
<string name="msg_edited">(संपादित)</string>
// TODO: Add proper translation.
<string name="msg_and">\u0020and\u0020</string>
// TODO: Add proper translation.
<string name="msg_is_typing">\u0020is typing…</string>
// TODO: Add proper translation.
<string name="msg_are_typing">\u0020are typing…</string>
// TODO: Add proper translation.
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">कोई परिणाम नहीं मिला</string>
<!-- System messages -->
<string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string>
......@@ -122,6 +132,10 @@
<string name="message_welcome">%s का स्वागत करते हैं</string>
<string name="message_removed">संदेश हटाया गया</string>
<string name="message_pinned">एक संदेश पिन किया:</string>
<string name="message_muted">उपयोगकर्ता %1$s %2$s द्वारा म्यूट किया गया</string>
<string name="message_unmuted">उपयोगकर्ता %1$s %2$s द्वारा अनम्यूट किया गया</string>
<string name="message_role_add">%1$s %3$s द्वारा %2$s सेट किया गया था</string>
<string name="message_role_removed">%1$s अब %3$s द्वारा %2$s नहीं है</string>
<!-- Message actions -->
<string name="action_msg_reply">जवाब दें</string>
......@@ -132,6 +146,8 @@
<string name="action_msg_pin">संदेश को पिन करें</string>
<string name="action_msg_unpin">संदेश को पिन से हटाएँ</string>
<string name="action_msg_star">संदेश को स्टार करें</string>
// TODO: Add proper translation.
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">शेयर करें</string>
<string name="action_title_editing">संपादन संदेश</string>
<string name="action_msg_add_reaction">प्रतिक्रिया जोड़ें</string>
......@@ -140,6 +156,8 @@
<string name="permission_editing_not_allowed">संपादन की अनुमति नहीं है</string>
<string name="permission_deleting_not_allowed">हटाने की अनुमति नहीं है</string>
<string name="permission_pinning_not_allowed">पिनि करने की अनुमति नहीं है</string>
// TODO: Add proper translation.
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">सदस्यों की सूची</string>
......@@ -149,6 +167,12 @@
<string name="no_pinned_messages">कोई पिन संदेश नहीं</string>
<string name="no_pinned_description">सभी पिन किए गए संदेश यहां\nदिखाई देते हैं।</string>
<!-- Favorite Messages -->
<!-- TODO Add proper translation-->
<string name="title_favorite_messages">Favorite Messages</string>
<string name="no_favorite_messages">No favorite messages</string>
<string name="no_favorite_description">All the favorite messages\nappear here</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">फ़ाइल का आकार %1$d बाइट्स ने %2$d बाइट्स के अधिकतम अपलोड आकार को पार कर लिया है</string>
......
......@@ -106,16 +106,26 @@
<string name="msg_invalid_server_protocol">O protocolo selecionado não é suportado pelo servidor, por favor utilize HTTPS e tente novamente</string>
<string name="msg_image_saved_successfully">Imagem salva na galeria</string>
<string name="msg_image_saved_failed">Falha ao salvar a imagem</string>
<string name="msg_edited">(editado)</string>
<string name="msg_and">\u0020e\u0020</string>
<string name="msg_is_typing">\u0020está digitando…</string>
<string name="msg_are_typing">\u0020estão digitando…</string>
<string name="msg_several_users_are_typing">Vários usuários estão digitando…</string>
<string name="msg_no_search_found">nenhum resultado encontrado</string>
<!-- System messages -->
<string name="message_room_name_changed">Nome da sala alterado para: %1$s por %2$s</string>
<string name="message_user_added_by">Usuário %1$s adicionado por %2$s</string>
<string name="message_user_removed_by">Usuário %1$s removido por %2$s</string>
<string name="message_user_left">Saiu da sala.</string>
<string name="message_user_joined_channel">Entrou na sala.</string>
<string name="message_user_left">Saiu da sala</string>
<string name="message_user_joined_channel">Entrou na sala</string>
<string name="message_welcome">Bem-vindo, %s</string>
<string name="message_removed">Mensagem removida</string>
<string name="message_pinned">Pinou uma mensagem:</string>
<string name="message_muted">Usuário %1$s entrou no modo mudo por %2$s</string>
<string name="message_unmuted">Usuário %1$s saiu do modo mudo por %2$s</string>
<string name="message_role_add">%1$s foi definido %2$s por %3$s</string>
<string name="message_role_removed">%1$s não é mais %2$s por %3$s</string>
<!-- Message actions -->
<string name="action_msg_reply">Responder</string>
......@@ -123,17 +133,19 @@
<string name="action_msg_copy">Copiar</string>
<string name="action_msg_quote">Citar</string>
<string name="action_msg_delete">Remover</string>
<string name="action_msg_pin">Fixar Mensagem</string>
<string name="action_msg_unpin">Desafixar Mensagem</string>
<string name="action_msg_star">Favoritar Mensagem</string>
<string name="action_msg_pin">Pinar mensagem</string>
<string name="action_msg_unpin">Despinar mensagem</string>
<string name="action_msg_star">Favoritar mensagem</string>
<string name="action_msg_unstar">Desfavoritar messagem</string>
<string name="action_msg_share">Compartilhar</string>
<string name="action_title_editing">Editando Mensagem</string>
<string name="action_title_editing">Editando mensagem</string>
<string name="action_msg_add_reaction">Adicionar reação</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">Edição não permitida</string>
<string name="permission_deleting_not_allowed">Remoção não permitida</string>
<string name="permission_pinning_not_allowed">Pinagem não permitida</string>
<string name="permission_starring_not_allowed">Favoritar não permitido</string>
<!-- Members List -->
<string name="title_members_list">Lista de Membros</string>
......@@ -143,12 +155,18 @@
<string name="no_pinned_messages">Nenhuma mensagem pinada</string>
<string name="no_pinned_description">Todas as mensagens pinadas\naparecerão aqui</string>
<!-- Favorite Messages -->
<!-- TODO Add proper translation-->
<string name="title_favorite_messages">Messagens Favoritas</string>
<string name="no_favorite_messages">Nenhuma messagem favorita</string>
<string name="no_favorite_description">Todas as mensagens favoritas\naparecerão aqui</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">Tamanho de arquivo (%1$d bytes) excedeu tamanho máximo de upload (%2$d bytes)</string>
<!-- Socket status -->
<string name="status_connected">Conectado</string>
<string name="status_disconnected">Desconetado</string>
<string name="status_disconnected">Desconectado</string>
<string name="status_connecting">Conectando</string>
<string name="status_authenticating">Autenticando</string>
<string name="status_disconnecting">Desconectando</string>
......@@ -165,7 +183,7 @@
<string name="Slash_Tableflip_Description">Exibir (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Exibir ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Criar um novo canal</string>
<string name="Show_the_keyboard_shortcut_list">Show the keyboard shortcut list</string>
<string name="Show_the_keyboard_shortcut_list">Exibir a lista de atalhos do teclado</string>
<string name="Invite_user_to_join_channel_all_from"> do [#canal] para entrar neste</string>
<string name="Invite_user_to_join_channel_all_to">Convidar todos os usuários deste canal para entrar no [#canal]</string>
<string name="Archive">Arquivar</string>
......@@ -173,8 +191,8 @@
<string name="Leave_the_current_channel">Sair do canal atual</string>
<string name="Displays_action_text">Exibir texto de ação</string>
<string name="Direct_message_someone">Enviar DM para alguém</string>
<string name="Mute_someone_in_room">Silenciar alguém</string>
<string name="Unmute_someone_in_room">De-silenciar alguém na sala</string>
<string name="Mute_someone_in_room">Ativar o modo mudo em alguém</string>
<string name="Unmute_someone_in_room">Desativar o modo mudo em alguém na sala</string>
<string name="Invite_user_to_join_channel">Convidar algum usuário para entrar neste canal</string>
<string name="Unarchive">Desarquivar</string>
<string name="Join_the_given_channel">Entrar no canal especificado</string>
......
......@@ -107,6 +107,12 @@
<string name="msg_invalid_server_protocol">The selected protocol is not accepted by this server, try using HTTPS</string>
<string name="msg_image_saved_successfully">Image has been saved to gallery</string>
<string name="msg_image_saved_failed">Failed to save image</string>
<string name="msg_edited">(edited)</string>
<string name="msg_and">\u0020and\u0020</string>
<string name="msg_is_typing">\u0020is typing…</string>
<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>
<!-- System messages -->
<string name="message_room_name_changed">Room name changed to: %1$s by %2$s</string>
......@@ -117,6 +123,10 @@
<string name="message_welcome">Welcome %s</string>
<string name="message_removed">Message removed</string>
<string name="message_pinned">Pinned a message:</string>
<string name="message_muted">User %1$s muted by %2$s</string>
<string name="message_unmuted">User %1$s unmuted by %2$s</string>
<string name="message_role_add">%1$s was set %2$s by %3$s</string>
<string name="message_role_removed">%1$s is no longer %2$s by %3$s</string>
<!-- Message actions -->
<string name="action_msg_reply">Reply</string>
......@@ -127,6 +137,7 @@
<string name="action_msg_pin">Pin Message</string>
<string name="action_msg_unpin">Unpin Message</string>
<string name="action_msg_star">Star Message</string>
<string name="action_msg_unstar">Unstar Message</string>
<string name="action_msg_share">Share</string>
<string name="action_title_editing">Editing Message</string>
<string name="action_msg_add_reaction">Add reaction</string>
......@@ -135,6 +146,7 @@
<string name="permission_editing_not_allowed">Editing is not allowed</string>
<string name="permission_deleting_not_allowed">Deleting is not allowed</string>
<string name="permission_pinning_not_allowed">Pinning is not allowed</string>
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Members List -->
<string name="title_members_list">Members List</string>
......@@ -142,7 +154,12 @@
<!-- Pinned Messages -->
<string name="title_pinned_messages">Pinned Messages</string>
<string name="no_pinned_messages">No pinned messages</string>
<string name="no_pinned_description">All the pinned messages\nappear here.</string>
<string name="no_pinned_description">All the pinned messages\nappear here</string>
<!-- Favorite Messages -->
<string name="title_favorite_messages">Favorite Messages</string>
<string name="no_favorite_messages">No favorite messages</string>
<string name="no_favorite_description">All the favorite messages\nappear here</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">File size %1$d bytes exceeded max upload size of %2$d bytes</string>
......
package chat.rocket.android
import chat.rocket.android.server.infraestructure.MemoryMessagesRepository
import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType
import kotlinx.coroutines.experimental.runBlocking
import org.hamcrest.CoreMatchers.notNullValue
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Before
import org.junit.Test
import org.hamcrest.CoreMatchers.`is` as isEqualTo
class MemoryMessagesRepositoryTest {
val repository = MemoryMessagesRepository()
val msg = Message(
id = "messageId",
roomId = "GENERAL",
message = "Beam me up, Scotty.",
timestamp = 1511443964815,
attachments = null,
sender = null,
avatar = null,
channels = null,
editedAt = null,
editedBy = null,
groupable = true,
mentions = null,
parseUrls = false,
senderAlias = null,
type = MessageType.MessageRemoved(),
updatedAt = 1511443964815,
urls = null,
pinned = false,
reactions = null
)
val msg2 = Message(
id = "messageId2",
roomId = "sandbox",
message = "Highly Illogical",
timestamp = 1511443964818,
attachments = null,
sender = null,
avatar = null,
channels = null,
editedAt = null,
editedBy = null,
groupable = true,
mentions = null,
parseUrls = false,
senderAlias = null,
type = MessageType.MessageRemoved(),
updatedAt = 1511443964818,
urls = null,
pinned = false,
reactions = null
)
@Before
fun setup() {
runBlocking {
repository.clear()
}
}
@Test
fun `save() should save a single message`() {
runBlocking {
assertThat(repository.getAll().size, isEqualTo(0))
repository.save(msg)
val allMessages = repository.getAll()
assertThat(allMessages.size, isEqualTo(1))
allMessages[0].apply {
assertThat(id, isEqualTo("messageId"))
assertThat(message, isEqualTo("Beam me up, Scotty."))
assertThat(roomId, isEqualTo("GENERAL"))
}
}
}
@Test
fun `saveAll() should all saved messages`() {
runBlocking {
assertThat(repository.getAll().size, isEqualTo(0))
repository.saveAll(listOf(msg, msg2))
val allMessages = repository.getAll()
assertThat(allMessages.size, isEqualTo(2))
allMessages[0].apply {
assertThat(id, isEqualTo("messageId"))
assertThat(message, isEqualTo("Beam me up, Scotty."))
assertThat(roomId, isEqualTo("GENERAL"))
}
allMessages[1].apply {
assertThat(id, isEqualTo("messageId2"))
assertThat(message, isEqualTo("Highly Illogical"))
assertThat(roomId, isEqualTo("sandbox"))
}
}
}
@Test
fun `getById() should return a single message`() {
runBlocking {
repository.saveAll(listOf(msg, msg2))
var singleMsg = repository.getById("messageId")
assertThat(singleMsg, notNullValue())
singleMsg!!.apply {
assertThat(id, isEqualTo("messageId"))
assertThat(message, isEqualTo("Beam me up, Scotty."))
assertThat(roomId, isEqualTo("GENERAL"))
}
singleMsg = repository.getById("messageId2")
assertThat(singleMsg, notNullValue())
singleMsg!!.apply {
assertThat(id, isEqualTo("messageId2"))
assertThat(message, isEqualTo("Highly Illogical"))
assertThat(roomId, isEqualTo("sandbox"))
}
}
}
@Test
fun `getByRoomId() should return all messages for room id or an empty list`() {
runBlocking {
repository.saveAll(listOf(msg, msg2))
var roomMessages = repository.getByRoomId("faAad32fkasods2")
assertThat(roomMessages.isEmpty(), isEqualTo(true))
roomMessages = repository.getByRoomId("sandbox")
assertThat(roomMessages.size, isEqualTo(1))
roomMessages[0].apply {
assertThat(id, isEqualTo("messageId2"))
assertThat(message, isEqualTo("Highly Illogical"))
assertThat(roomId, isEqualTo("sandbox"))
}
roomMessages = repository.getByRoomId("GENERAL")
assertThat(roomMessages.size, isEqualTo(1))
roomMessages[0].apply {
assertThat(id, isEqualTo("messageId"))
assertThat(message, isEqualTo("Beam me up, Scotty."))
assertThat(roomId, isEqualTo("GENERAL"))
}
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment