diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index de9e212c02..16fe887abf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,30 +10,31 @@ + - - @@ -42,92 +43,76 @@ - - - - - - - - - - - - - - - - @@ -139,7 +124,7 @@ - + android:name=".utils.fcm.RegistrationIntentService" + android:exported="false" /> - + - + \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatActivity.kt b/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatActivity.kt new file mode 100644 index 0000000000..569103a08e --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatActivity.kt @@ -0,0 +1,14 @@ +package org.mifos.mobile.rocketchat + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import org.mifos.mobile.databinding.ActivityRocketChatBinding + +class RocketChatActivity : AppCompatActivity() { + private lateinit var binding : ActivityRocketChatBinding + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityRocketChatBinding.inflate(layoutInflater) + setContentView(binding.root) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatFragment.kt b/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatFragment.kt new file mode 100644 index 0000000000..f4d7802f4e --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatFragment.kt @@ -0,0 +1,67 @@ +package org.mifos.mobile.rocketchat + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import org.mifos.mobile.databinding.FragmentRocketChatBinding +import org.mifos.mobile.rocketchat.adapter.RocketChatAdapter +import org.mifos.mobile.rocketchat.model.SupportChatMessage + +class RocketChatFragment : Fragment() { + private var _binding: FragmentRocketChatBinding? = null + private val binding get() = _binding!! + private lateinit var viewModel: RocketChatViewModel + private var webSocket: WebSocket? = null + private lateinit var webSocketListener: WebSocketListener + private lateinit var adapter: RocketChatAdapter + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentRocketChatBinding.inflate(inflater, container, false) + viewModel = ViewModelProvider(this)[RocketChatViewModel::class.java] + webSocketListener = RocketChatService(viewModel) + adapter = RocketChatAdapter() + binding.rvSupportChat.adapter = adapter + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + with(binding) { + viewModel.socketStatus.observe(viewLifecycleOwner) { + statusTV.text = if (it) "Connected" else "Disconnected" + } + + viewModel.messages.observe(viewLifecycleOwner) { + adapter.addNewMessage(it) + } + + connectButton.setOnClickListener { + webSocket = viewModel.createWebSocketConnection(webSocketListener) + } + + disconnectButton.setOnClickListener { + webSocket?.close(1000, "Canceled manually.") + } + + sendButton.setOnClickListener { + viewModel.sendCustomerMessage(webSocket, messageET.text.toString()) + viewModel.addMessage( + SupportChatMessage( + messageET.text.toString().trim(), + SupportChatMessage.MessageType.USER + ) + ) + messageET.text = null + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatService.kt b/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatService.kt new file mode 100644 index 0000000000..75ca49fbad --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatService.kt @@ -0,0 +1,92 @@ +package org.mifos.mobile.rocketchat + +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import org.json.JSONObject +import org.mifos.mobile.rocketchat.model.SupportChatMessage +import javax.inject.Inject + +class RocketChatService @Inject constructor( + private val viewModel: RocketChatViewModel +) : WebSocketListener() { + override fun onOpen(webSocket: WebSocket, response: Response) { + super.onOpen(webSocket, response) + viewModel.setStatus(true) + val connectMessage = """ + { + "msg": "connect", + "version": "1", + "support": ["1"] + } + """.trimIndent() + webSocket.send(connectMessage) + + val loginRequest = """ + { + "msg": "method", + "method": "login", + "id": "42", + "params": [ + { "resume": "EDFOiOPiwS5k-ptDA9-bDQ7qM2DVXiDDh-5YrzrLfxA" } + ] + } + """.trimIndent() + webSocket.send(loginRequest) + + val subscribeRequest = """ + { + "msg": "sub", + "id": "sub1", + "name": "stream-room-messages", + "params": ["GENERAL", false] + } + """.trimIndent() + webSocket.send(subscribeRequest) + + } + + override fun onMessage(webSocket: WebSocket, text: String) { + super.onMessage(webSocket, text) + + if (text.contains("\"msg\":\"changed\"") && text.contains("\"collection\":\"stream-room-messages\"") && !text.contains( + "\"username\":\"CustomerTestMifosRCWorkspace\"" + ) + ) { + val jsonObject = JSONObject(text) + val fieldsObject = jsonObject.getJSONObject("fields") + val argsArray = fieldsObject.getJSONArray("args") + val messageObject = argsArray.getJSONObject(0) + val message = messageObject.getString("msg") + + viewModel.addMessage( + SupportChatMessage( + message, + SupportChatMessage.MessageType.SUPPORT + ) + ) + } + + if (text.contains("\"msg\":\"ping\"")) { + val pongMessage = """ + { + "msg": "pong" + } + """.trimIndent() + webSocket.send(pongMessage) + } + } + + override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { + super.onClosing(webSocket, code, reason) + } + + override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { + super.onClosed(webSocket, code, reason) + viewModel.setStatus(false) + } + + override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { + super.onFailure(webSocket, t, response) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatViewModel.kt b/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatViewModel.kt new file mode 100644 index 0000000000..5770a2a2ba --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/rocketchat/RocketChatViewModel.kt @@ -0,0 +1,64 @@ +package org.mifos.mobile.rocketchat + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import org.mifos.mobile.rocketchat.model.SupportChatMessage +import javax.inject.Inject + +@HiltViewModel +class RocketChatViewModel @Inject constructor( +) : ViewModel() { + private val webSocketURL = "wss://testrcmifosworkspace.rocket.chat/websocket" + private val okHttpClient = OkHttpClient() + private val _socketStatus = MutableLiveData(false) + val socketStatus: LiveData = _socketStatus + private val _messages = MutableLiveData() + val messages: LiveData = _messages + + fun createWebSocketConnection(webSocketListener: WebSocketListener): WebSocket? { + return okHttpClient.newWebSocket(createRequest(), webSocketListener) + } + + fun sendCustomerMessage(webSocket: WebSocket?, message: String) { + val messageObject = """ + { + "msg": "method", + "method": "sendMessage", + "id": "42", + "params": [ + { + "_id": "${System.currentTimeMillis()}", + "rid": "GENERAL", + "msg": "$message" + } + ] + } + """.trimIndent() + webSocket?.send(messageObject) + } + + private fun createRequest(): Request { + return Request.Builder() + .url(webSocketURL) + .build() + } + + fun addMessage(message: SupportChatMessage) = viewModelScope.launch(Dispatchers.Main) { + if (_socketStatus.value == true) { + _messages.value = message + } + } + + fun setStatus(status: Boolean) = viewModelScope.launch(Dispatchers.Main) { + _socketStatus.value = status + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/rocketchat/adapter/RocketChatAdapter.kt b/app/src/main/java/org/mifos/mobile/rocketchat/adapter/RocketChatAdapter.kt new file mode 100644 index 0000000000..a04d4d2c47 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/rocketchat/adapter/RocketChatAdapter.kt @@ -0,0 +1,85 @@ +package org.mifos.mobile.rocketchat.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.item_chat_support.view.tv_message_support +import kotlinx.android.synthetic.main.item_chat_user.view.tv_message_user +import org.mifos.mobile.R +import org.mifos.mobile.rocketchat.model.SupportChatMessage + +class RocketChatAdapter : RecyclerView.Adapter() { + private val messages = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + SupportChatMessage.MessageType.USER.ordinal -> { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_chat_user, parent, false) + + UserMessageViewHolder(view) + } + + SupportChatMessage.MessageType.SUPPORT.ordinal -> { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_chat_support, parent, false) + SupportMessageViewHolder(view) + } + + else -> throw IllegalArgumentException("Invalid view type") + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val message = messages[position] + when (holder) { + is UserMessageViewHolder -> holder.bind(message) + is SupportMessageViewHolder -> holder.bind(message) + } + } + + override fun getItemViewType(position: Int): Int { + return messages[position].messageType.ordinal + } + + override fun getItemCount(): Int { + return messages.size + } + + fun addNewMessage(supportChatMessage: SupportChatMessage) { + messages.add(supportChatMessage) + notifyDataSetChanged() + } + + inner class UserMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(message: SupportChatMessage) { + itemView.tv_message_user.tv_message_user.text = message.text + } + } + + inner class SupportMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(message: SupportChatMessage) { + itemView.tv_message_support.text = message.text + } + } + + +} + +class MessageDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: SupportChatMessage, + newItem: SupportChatMessage + ): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame( + oldItem: SupportChatMessage, + newItem: SupportChatMessage + ): Boolean { + return oldItem == newItem + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/rocketchat/model/SupportChatMessage.kt b/app/src/main/java/org/mifos/mobile/rocketchat/model/SupportChatMessage.kt new file mode 100644 index 0000000000..1ae2e20934 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/rocketchat/model/SupportChatMessage.kt @@ -0,0 +1,10 @@ +package org.mifos.mobile.rocketchat.model + +data class SupportChatMessage( + val text : String, + val messageType : MessageType +) { + enum class MessageType{ + USER, SUPPORT + } +} diff --git a/app/src/main/java/org/mifos/mobile/ui/fragments/HomeOldFragment.kt b/app/src/main/java/org/mifos/mobile/ui/fragments/HomeOldFragment.kt index ea8a7ee229..efc7d4cfca 100644 --- a/app/src/main/java/org/mifos/mobile/ui/fragments/HomeOldFragment.kt +++ b/app/src/main/java/org/mifos/mobile/ui/fragments/HomeOldFragment.kt @@ -19,6 +19,7 @@ import org.mifos.mobile.R import org.mifos.mobile.api.local.PreferencesHelper import org.mifos.mobile.databinding.FragmentHomeOldBinding import org.mifos.mobile.models.client.Client +import org.mifos.mobile.rocketchat.RocketChatActivity import org.mifos.mobile.ui.activities.HomeActivity import org.mifos.mobile.ui.activities.LoanApplicationActivity import org.mifos.mobile.ui.activities.NotificationActivity @@ -328,6 +329,14 @@ class HomeOldFragment : BaseFragment(), OnRefreshListener { binding.llSurveys.setOnClickListener { surveys() } + + binding.btnContactUs.setOnClickListener { + contactUs() + } + } + + private fun contactUs() { + startActivity(Intent(activity, RocketChatActivity::class.java)) } private fun toggleVisibilityButton( diff --git a/app/src/main/res/drawable/ic_send.xml b/app/src/main/res/drawable/ic_send.xml new file mode 100644 index 0000000000..3abc6cb33b --- /dev/null +++ b/app/src/main/res/drawable/ic_send.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_rocket_chat.xml b/app/src/main/res/layout/activity_rocket_chat.xml new file mode 100644 index 0000000000..64715a476c --- /dev/null +++ b/app/src/main/res/layout/activity_rocket_chat.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home_old.xml b/app/src/main/res/layout/fragment_home_old.xml index fa4664f368..84734871c5 100644 --- a/app/src/main/res/layout/fragment_home_old.xml +++ b/app/src/main/res/layout/fragment_home_old.xml @@ -375,12 +375,14 @@ android:orientation="horizontal" android:padding="@dimen/Mifos.DesignSystem.Spacing.CardInnerPaddingLarger"> - - - - - diff --git a/app/src/main/res/layout/fragment_rocket_chat.xml b/app/src/main/res/layout/fragment_rocket_chat.xml new file mode 100644 index 0000000000..8f3b29c4c6 --- /dev/null +++ b/app/src/main/res/layout/fragment_rocket_chat.xml @@ -0,0 +1,85 @@ + + + + + +