Commit f4ea66f8 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Add PinnedMessagesActivity

parent 4d499f5b
......@@ -47,6 +47,10 @@
android:name=".chatroom.ui.ChatRoomActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".chatroom.ui.PinnedMessagesActivity"
android:theme="@style/AppTheme" />
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
......
package chat.rocket.android.chatroom.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.presentation.PinnedMessagesView
import chat.rocket.android.chatroom.ui.PinnedMessagesFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class PinnedMessagesFragmentModule {
@Provides
fun provideLifecycleOwner(frag: PinnedMessagesFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
@Provides
fun providePinnedMessageView(frag: PinnedMessagesFragment): PinnedMessagesView {
return frag
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.di
import chat.rocket.android.chatroom.ui.PinnedMessagesFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class PinnedMessagesFragmentProvider {
@ContributesAndroidInjector(modules = [PinnedMessagesFragmentModule::class])
abstract fun providePinnedMessageFragment(): PinnedMessagesFragment
}
\ No newline at end of file
package chat.rocket.android.chatroom.presentation
import chat.rocket.android.chatroom.viewmodel.MessageViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetChatRoomsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.getRoomPinnedMessages
import chat.rocket.core.model.Value
import timber.log.Timber
import javax.inject.Inject
class PinnedMessagesPresenter @Inject constructor(private val view: PinnedMessagesView,
private val strategy: CancelStrategy,
private val serverInteractor: GetCurrentServerInteractor,
private val roomsInteractor: GetChatRoomsInteractor,
private val mapper: MessageViewModelMapper,
factory: RocketChatClientFactory,
getSettingsInteractor: GetSettingsInteractor) {
private val client = factory.create(serverInteractor.get()!!)
private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(serverInteractor.get()!!)!!
private var pinnedMessagesListOffset: Int = 0
/**
* Load all pinned messages for the given room id.
*
* @param roomId The id of the room to get pinned messages from.
*/
fun loadPinnedMessages(roomId: String) {
launchUI(strategy) {
try {
val serverUrl = serverInteractor.get()!!
val chatRoom = roomsInteractor.getById(serverUrl, roomId)
chatRoom?.let { room ->
view.showLoading()
val pinnedMessages = client.getRoomPinnedMessages(roomId, room.type, pinnedMessagesListOffset)
pinnedMessagesListOffset = pinnedMessages.offset.toInt()
val messageList = mapper.mapToViewModelList(pinnedMessages.result, settings)
view.showPinnedMessages(messageList, serverUrl)
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.chatroom.presentation
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
interface PinnedMessagesView : MessageView, LoadingView {
/**
* Show list of pinned messages for the current room.
*
* @param pinnedMessages The list of pinned messages.
* @param serverUrl The url of the current server.
*/
fun showPinnedMessages(pinnedMessages: List<MessageViewModel>, serverUrl: String)
}
\ No newline at end of file
......@@ -15,6 +15,7 @@ import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.app_bar_chat_room.*
import javax.inject.Inject
fun Context.chatRoomIntent(chatRoomId: String, chatRoomName: String, chatRoomType: String, isChatRoomReadOnly: Boolean): Intent {
return Intent(this, ChatRoomActivity::class.java).apply {
putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
......@@ -67,6 +68,8 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
}
private fun setupToolbar(chatRoomName: String) {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
text_room_name.textContent = chatRoomName
toolbar.setNavigationOnClickListener {
finishActivity()
......
......@@ -3,14 +3,13 @@ package chat.rocket.android.chatroom.ui
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
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 android.view.*
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
......@@ -66,6 +65,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = container?.inflate(R.layout.fragment_chat_room)
......@@ -82,6 +82,25 @@ class ChatRoomFragment : Fragment(), ChatRoomView {
super.onDestroyView()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.chatroom_actions, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_pinned_messages -> {
val intent = Intent(activity, PinnedMessagesActivity::class.java).apply {
putExtra(BUNDLE_CHAT_ROOM_ID, chatRoomId)
putExtra(BUNDLE_CHAT_ROOM_TYPE, chatRoomType)
putExtra(BUNDLE_CHAT_ROOM_NAME, chatRoomName)
}
startActivity(intent)
}
}
return true
}
override fun showMessages(dataSet: List<MessageViewModel>, serverUrl: String) {
activity?.apply {
if (recycler_view.adapter == null) {
......
package chat.rocket.android.chatroom.ui
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.util.addFragment
import chat.rocket.android.util.textContent
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.app_bar_chat_room.*
import javax.inject.Inject
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"
class PinnedMessagesActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
private lateinit var chatRoomId: String
private lateinit var chatRoomName: String
private lateinit var chatRoomType: String
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pinned_messages)
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" }
chatRoomName = intent.getStringExtra(INTENT_CHAT_ROOM_NAME)
requireNotNull(chatRoomName) { "no chat_room_name provided in Intent extras" }
chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE)
requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" }
setupToolbar()
addFragment("PinnedMessagesFragment", R.id.fragment_container) {
newPinnedMessagesFragment(chatRoomId, chatRoomName, chatRoomType)
}
}
override fun onBackPressed() = finishActivity()
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentDispatchingAndroidInjector
}
private fun setupToolbar() {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
text_room_name.textContent = getString(R.string.title_pinned_messages)
toolbar.setNavigationOnClickListener {
finishActivity()
}
}
private fun finishActivity() {
super.onBackPressed()
overridePendingTransition(R.anim.close_enter, R.anim.close_exit)
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.ui
import android.support.v7.widget.RecyclerView
import android.text.method.LinkMovementMethod
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.android.chatroom.viewmodel.AttachmentType
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.content
import chat.rocket.android.util.inflate
import chat.rocket.android.util.setVisible
import chat.rocket.common.util.ifNull
import com.facebook.drawee.view.SimpleDraweeView
import com.stfalcon.frescoimageviewer.ImageViewer
import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_message.view.*
import kotlinx.android.synthetic.main.message_attachment.view.*
class PinnedMessagesAdapter(private val serverUrl: String) : RecyclerView.Adapter<PinnedMessagesAdapter.ViewHolder>() {
init {
setHasStableIds(true)
}
val dataSet = ArrayList<MessageViewModel>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(parent.inflate(R.layout.item_message), serverUrl)
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(dataSet[position])
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
onBindViewHolder(holder, position)
}
override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int = position
fun addDataSet(dataSet: List<MessageViewModel>) {
val previousDataSetSize = this.dataSet.size
this.dataSet.addAll(previousDataSetSize, dataSet)
notifyItemRangeInserted(previousDataSetSize, dataSet.size)
}
fun addItem(message: MessageViewModel) {
dataSet.add(0, message)
notifyItemInserted(0)
}
fun updateItem(message: MessageViewModel) {
val index = dataSet.indexOfFirst { it.id == message.id }
if (index > -1) {
dataSet[index] = message
notifyItemChanged(index)
}
}
fun removeItem(messageId: String) {
val index = dataSet.indexOfFirst { it.id == messageId }
if (index > -1) {
dataSet.removeAt(index)
notifyItemRemoved(index)
}
}
override fun getItemId(position: Int): Long {
return dataSet[position].id.hashCode().toLong()
}
class ViewHolder(itemView: View,
val serverUrl: String) : RecyclerView.ViewHolder(itemView) {
private lateinit var messageViewModel: MessageViewModel
fun bind(message: MessageViewModel) = with(itemView) {
messageViewModel = message
bindUserAvatar(message, image_avatar, image_unknown_avatar)
text_user_name.content = message.sender
text_message_time.content = message.time
text_content.content = message.content
text_content.movementMethod = LinkMovementMethod()
bindAttachment(message, message_attachment, image_attachment, audio_video_attachment,
file_name)
}
private fun bindAttachment(message: MessageViewModel,
attachment_container: View,
image_attachment: SimpleDraweeView,
audio_video_attachment: View,
file_name: TextView) {
with(message) {
if (attachmentUrl == null || attachmentType == null) {
attachment_container.setVisible(false)
return
}
var imageVisible = false
var videoVisible = false
attachment_container.setVisible(true)
when (message.attachmentType) {
is AttachmentType.Image -> {
imageVisible = true
image_attachment.setImageURI(message.attachmentUrl)
image_attachment.setOnClickListener { view ->
// TODO - implement a proper image viewer with a proper Transition
ImageViewer.Builder(view.context, listOf(message.attachmentUrl))
.setStartPosition(0)
.show()
}
}
is AttachmentType.Video,
is AttachmentType.Audio -> {
videoVisible = true
audio_video_attachment.setOnClickListener { view ->
message.attachmentUrl?.let { url ->
PlayerActivity.play(view.context, url)
}
}
}
}
image_attachment.setVisible(imageVisible)
audio_video_attachment.setVisible(videoVisible)
file_name.text = message.attachmentTitle
}
}
private fun bindUserAvatar(message: MessageViewModel, drawee: SimpleDraweeView, imageUnknownAvatar: ImageView) = message.getAvatarUrl(serverUrl).let {
drawee.setImageURI(it.toString())
drawee.setVisible(true)
imageUnknownAvatar.setVisible(false)
}.ifNull {
drawee.setVisible(false)
imageUnknownAvatar.setVisible(true)
}
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.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 android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.PinnedMessagesPresenter
import chat.rocket.android.chatroom.presentation.PinnedMessagesView
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.util.setVisible
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_pinned_messages.*
import javax.inject.Inject
private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
private const val BUNDLE_CHAT_ROOM_NAME = "chat_room_name"
private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
fun newPinnedMessagesFragment(chatRoomId: String, chatRoomType: String, chatRoomName: String): Fragment {
return PinnedMessagesFragment().apply {
arguments = Bundle(3).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
putString(BUNDLE_CHAT_ROOM_NAME, chatRoomName)
putString(BUNDLE_CHAT_ROOM_TYPE, chatRoomType)
}
}
}
class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
@Inject lateinit var presenter: PinnedMessagesPresenter
private lateinit var chatRoomId: String
private lateinit var chatRoomName: String
private lateinit var chatRoomType: String
private lateinit var adapter: PinnedMessagesAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomName = bundle.getString(BUNDLE_CHAT_ROOM_NAME)
chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
inflater.inflate(R.layout.fragment_pinned_messages, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.loadPinnedMessages(chatRoomId)
}
override fun showLoading() = view_loading.setVisible(true)
override fun hideLoading() = view_loading.setVisible(false)
override fun showMessage(resId: Int) = showMessage(getString(resId))
override fun showMessage(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showPinnedMessages(pinnedMessages: List<MessageViewModel>, serverUrl: String) {
activity?.apply {
if (recycler_view_pinned.adapter == null) {
adapter = PinnedMessagesAdapter(serverUrl)
recycler_view_pinned.adapter = adapter
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?) {
presenter.loadPinnedMessages(chatRoomId)
}
})
}
}
adapter.addDataSet(pinnedMessages)
}
}
}
\ No newline at end of file
......@@ -7,11 +7,13 @@ import chat.rocket.android.authentication.signup.di.SignupFragmentProvider
import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.chatroom.di.ChatRoomFragmentProvider
import chat.rocket.android.chatroom.di.PinnedMessagesFragmentProvider
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatroom.ui.PinnedMessagesActivity
import chat.rocket.android.chatrooms.di.ChatRoomsFragmentProvider
import chat.rocket.android.chatrooms.di.ChatRoomsModule
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.profile.di.ProfileFragmentProvider
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -35,4 +37,8 @@ abstract class ActivityBuilder {
@PerActivity
@ContributesAndroidInjector(modules = [ChatRoomFragmentProvider::class])
abstract fun bindChatRoomActivity(): ChatRoomActivity
@PerActivity
@ContributesAndroidInjector(modules = [PinnedMessagesFragmentProvider::class])
abstract fun bindPinnedMessagesActivity(): PinnedMessagesActivity
}
\ No newline at end of file
......@@ -29,7 +29,7 @@ class GetChatRoomsInteractor @Inject constructor(private val repository: ChatRoo
*
* @return The ChatRoom object or null if we couldn't find any.
*/
suspend fun getByRoomId(serverUrl: String, roomId: String): ChatRoom {
suspend fun getById(serverUrl: String, roomId: String): ChatRoom? {
return async(CommonPool) {
val allChatRooms = repository.get(serverUrl)
return@async allChatRooms.first {
......
<?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"
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.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" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:visibility="gone"
app:indicatorColor="@color/black"
app:indicatorName="BallPulseIndicator"
app:layout_constraintBottom_toBottomOf="@+id/recycler_view_pinned"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_pinned_messages"
android:title="@string/title_pinned_messages"
app:showAsAction="never" />
</menu>
\ No newline at end of file
......@@ -70,4 +70,7 @@
<!-- Permission messages -->
<string name="permission_editing_not_allowed">Edição não permitida</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">Mensagens Pinadas</string>
</resources>
\ No newline at end of file
......@@ -72,4 +72,7 @@
<!-- Permission messages -->
<string name="permission_editing_not_allowed">Editing not allowed</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">Pinned Messages</string>
</resources>
\ 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