diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 8a0a00a..17ca97e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,7 +10,7 @@ android {
defaultConfig {
applicationId "com.example.h_mal.messengerapp"
- minSdkVersion 23
+ minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
@@ -42,6 +42,8 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation "androidx.cardview:cardview:1.0.0"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/application/AppClass.kt b/app/src/main/java/com/example/h_mal/messengerapp/application/AppClass.kt
index 7f1adf7..86d9602 100644
--- a/app/src/main/java/com/example/h_mal/messengerapp/application/AppClass.kt
+++ b/app/src/main/java/com/example/h_mal/messengerapp/application/AppClass.kt
@@ -39,7 +39,7 @@ class AppClass: Application(), KodeinAware{
)
}
bind() from singleton { AppRoomDatabase(instance()) }
- bind() from singleton { RepositoryImpl(instance()) }
+ bind() from singleton { RepositoryImpl(instance(), instance()) }
bind() from provider { MainViewModelFactory(instance()) }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/data/network/MessengerApi.kt b/app/src/main/java/com/example/h_mal/messengerapp/data/network/MessengerApi.kt
index a49749b..5b6537a 100644
--- a/app/src/main/java/com/example/h_mal/messengerapp/data/network/MessengerApi.kt
+++ b/app/src/main/java/com/example/h_mal/messengerapp/data/network/MessengerApi.kt
@@ -1,5 +1,6 @@
package com.example.h_mal.messengerapp.data.network
+import com.example.h_mal.messengerapp.data.network.model.MessageItem
import com.tinder.scarlet.Scarlet
import com.tinder.scarlet.Stream
import com.tinder.scarlet.WebSocket
@@ -14,14 +15,16 @@ import okhttp3.OkHttpClient
interface MessengerApi {
+ // Receive websocket messages in the form of string
@Receive
- fun observerMessage(): ReceiveChannel
+ fun observerMessage(): ReceiveChannel
@Receive
- fun observerFailure(): ReceiveChannel
+ fun observerEvent(): ReceiveChannel
+ // Send message to websocket and return pass/fail boolean result
@Send
- fun send(message: Any): Boolean
+ fun send(message: MessageItem): Boolean
// invoke method creating an invocation of the api call
@@ -32,7 +35,7 @@ interface MessengerApi {
val webSocketUrl = "ws://echo.websocket.org/"
- // creation of retrofit class
+ // creation of Api class for websocket
return Scarlet.Builder()
.webSocketFactory(okkHttpClient.newWebSocketFactory(webSocketUrl))
.addMessageAdapterFactory(GsonMessageAdapter.Factory())
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/data/network/model/MessageItem.kt b/app/src/main/java/com/example/h_mal/messengerapp/data/network/model/MessageItem.kt
new file mode 100644
index 0000000..7833424
--- /dev/null
+++ b/app/src/main/java/com/example/h_mal/messengerapp/data/network/model/MessageItem.kt
@@ -0,0 +1,16 @@
+package com.example.h_mal.messengerapp.data.network.model
+
+import com.google.gson.annotations.Expose
+import com.google.gson.annotations.SerializedName
+
+data class MessageItem(
+ @SerializedName("Message")
+ @Expose
+ var message: String? = null,
+ @SerializedName("IsSender")
+ @Expose
+ var isSender: Boolean? = null,
+ @SerializedName("TimeStamp")
+ @Expose
+ var timeStamp: Long = System.currentTimeMillis()
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/data/repository/Repository.kt b/app/src/main/java/com/example/h_mal/messengerapp/data/repository/Repository.kt
index 8ac1ef7..e65b222 100644
--- a/app/src/main/java/com/example/h_mal/messengerapp/data/repository/Repository.kt
+++ b/app/src/main/java/com/example/h_mal/messengerapp/data/repository/Repository.kt
@@ -1,11 +1,19 @@
package com.example.h_mal.messengerapp.data.repository
+import androidx.lifecycle.LiveData
+import com.example.h_mal.messengerapp.data.network.model.MessageItem
+import com.example.h_mal.messengerapp.data.room.MessageEntity
import com.tinder.scarlet.WebSocket
import kotlinx.coroutines.channels.ReceiveChannel
interface Repository {
- fun observeText(): ReceiveChannel
- fun submitMessage(message: String)
- fun getEvent(): ReceiveChannel
+
+ fun observeWebsocketMessage(): ReceiveChannel
+ fun observeWebsocketEvent(): ReceiveChannel
+ fun submitMessageToApi(message: String, isSender: Boolean)
+ fun getMessagesLiveData(): LiveData>
+ suspend fun addMessageItemToDatabase(messageItem: MessageItem)
+ suspend fun addTimeStamp(timestampMessage: String)
+ suspend fun getLastEntry(): MessageEntity?
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/data/repository/RepositoryImpl.kt b/app/src/main/java/com/example/h_mal/messengerapp/data/repository/RepositoryImpl.kt
index f91cabc..401fcda 100644
--- a/app/src/main/java/com/example/h_mal/messengerapp/data/repository/RepositoryImpl.kt
+++ b/app/src/main/java/com/example/h_mal/messengerapp/data/repository/RepositoryImpl.kt
@@ -2,28 +2,54 @@ package com.example.h_mal.messengerapp.data.repository
import android.util.Log
import com.example.h_mal.messengerapp.data.network.MessengerApi
+import com.example.h_mal.messengerapp.data.network.model.MessageItem
+import com.example.h_mal.messengerapp.data.room.AppRoomDatabase
+import com.example.h_mal.messengerapp.data.room.MessageEntity
import com.tinder.scarlet.WebSocket
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.consumeEach
import java.io.IOException
class RepositoryImpl(
- private val messengerApi: MessengerApi
+ private val messengerApi: MessengerApi,
+ private val database: AppRoomDatabase
) : Repository{
- override fun observeText(): ReceiveChannel {
+ override fun observeWebsocketMessage(): ReceiveChannel {
return messengerApi.observerMessage()
}
- override fun submitMessage(message: String){
- val send = messengerApi.send(message)
+ override fun observeWebsocketEvent(): ReceiveChannel {
+ return messengerApi.observerEvent()
+ }
+
+ // Send message to websocket or throw error if no connection
+ override fun submitMessageToApi(message: String, isSender: Boolean){
+ val messageItem = MessageItem(message, isSender)
+ val send = messengerApi.send(messageItem)
if (!send){
throw IOException("Could not send message")
}
}
- override fun getEvent(): ReceiveChannel {
- return messengerApi.observerFailure()
+ override fun getMessagesLiveData() = database.getMessageDao().getAllItems()
+
+ override suspend fun addMessageItemToDatabase(
+ messageItem: MessageItem
+ ){
+ val entity = MessageEntity(messageItem)
+ database.getMessageDao().saveSingleItem(entity)
+ }
+
+ override suspend fun addTimeStamp(
+ timestampMessage: String
+ ){
+ val entity = MessageEntity(timestampMessage)
+ database.getMessageDao().saveSingleItem(entity)
+ }
+
+ override suspend fun getLastEntry(): MessageEntity? {
+ return database.getMessageDao().getLastEntry()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/data/room/MessageDao.kt b/app/src/main/java/com/example/h_mal/messengerapp/data/room/MessageDao.kt
index e1d1aab..4db5b7b 100644
--- a/app/src/main/java/com/example/h_mal/messengerapp/data/room/MessageDao.kt
+++ b/app/src/main/java/com/example/h_mal/messengerapp/data/room/MessageDao.kt
@@ -23,4 +23,7 @@ interface MessageDao {
@Delete
fun deleteSingleEntry(message: MessageEntity)
+
+ @Query("SELECT * FROM MessageEntity ORDER BY timestamp DESC LIMIT 1")
+ suspend fun getLastEntry(): MessageEntity?
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/data/room/MessageEntity.kt b/app/src/main/java/com/example/h_mal/messengerapp/data/room/MessageEntity.kt
index 986d6e5..a406f0a 100644
--- a/app/src/main/java/com/example/h_mal/messengerapp/data/room/MessageEntity.kt
+++ b/app/src/main/java/com/example/h_mal/messengerapp/data/room/MessageEntity.kt
@@ -2,11 +2,32 @@ package com.example.h_mal.messengerapp.data.room
import androidx.room.Entity
import androidx.room.PrimaryKey
+import com.example.h_mal.messengerapp.data.network.model.MessageItem
+const val TWO_HOURS = 20 * 1000
@Entity
data class MessageEntity(
@PrimaryKey(autoGenerate = true)
val id: Int,
- val sender: Boolean,
- val message: String
-)
\ No newline at end of file
+ var isSender: Boolean? = null,
+ val message: String?,
+ val timeStamp: Long = System.currentTimeMillis()
+){
+
+ constructor(timeStampMessage: String): this(
+ 0, null, timeStampMessage
+ )
+
+ constructor(messageItem: MessageItem): this(
+ 0,
+ messageItem.isSender,
+ messageItem.message,
+ messageItem.timeStamp
+ )
+
+ fun isGreaterThanTwoHours(): Boolean{
+ val current = System.currentTimeMillis()
+ val time = current - timeStamp
+ return time > TWO_HOURS
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MainFragment.kt b/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MainFragment.kt
index 2e92761..b643cfc 100644
--- a/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MainFragment.kt
+++ b/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MainFragment.kt
@@ -5,16 +5,21 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import com.example.h_mal.messengerapp.R
+import com.example.h_mal.messengerapp.utils.showToast
import kotlinx.android.synthetic.main.main_fragment.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.InternalCoroutinesApi
import org.kodein.di.KodeinAware
import org.kodein.di.android.x.kodein
import org.kodein.di.generic.instance
class MainFragment : Fragment(), KodeinAware {
+ // Kodein DI to retrieve @MainViewModelFactory
override val kodein by kodein()
private val factory by instance()
@@ -22,8 +27,6 @@ class MainFragment : Fragment(), KodeinAware {
fun newInstance() = MainFragment()
}
-
- @InternalCoroutinesApi
@ExperimentalCoroutinesApi
private lateinit var viewModel: MainViewModel
@@ -32,17 +35,53 @@ class MainFragment : Fragment(), KodeinAware {
return inflater.inflate(R.layout.main_fragment, container, false)
}
- @InternalCoroutinesApi
@ExperimentalCoroutinesApi
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
-
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)
+ // Toast viewmodel error messages
+ viewModel.operationFailure.observe(viewLifecycleOwner, Observer {
+ it.getContentIfNotHandled()?.let { operationFailure ->
+ context?.showToast(operationFailure)
+ }
+ })
- message.setOnClickListener {
- viewModel.submitMessage("dsfsdfsdjf")
+ message_recycler.apply {
+ layoutManager = LinearLayoutManager(requireContext()).apply {
+ stackFromEnd = true
+ }
+ setHasFixedSize(true)
+
+ // Apply adapter to recycler view
+ // anchor to the bottom when chats are being populated
+ adapter = MessageRecyclerAdapter(viewModel.messagesLiveData).apply {
+ registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver(){
+ override fun onChanged() {
+ super.onChanged()
+ scrollToPosition(itemCount - 1)
+ }
+ })
+ }
+
+ }
+
+ message_box_et.apply {
+ setOnEditorActionListener { _, actionId, _ ->
+ when (actionId) {
+ EditorInfo.IME_ACTION_DONE ->{
+ viewModel.submitMessage(text.toString())
+ text.clear()
+ }
+ }
+ true
+ }
+
+ submit.setOnClickListener {
+ viewModel.submitMessage(text.toString())
+ text.clear()
+ }
}
}
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MainViewModel.kt b/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MainViewModel.kt
index eb8dd61..64c97d0 100644
--- a/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MainViewModel.kt
+++ b/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MainViewModel.kt
@@ -1,32 +1,95 @@
package com.example.h_mal.messengerapp.ui.main
-import android.util.Log
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.example.h_mal.messengerapp.data.network.model.MessageItem
import com.example.h_mal.messengerapp.data.repository.Repository
-import com.tinder.scarlet.Stream
+import com.example.h_mal.messengerapp.utils.Event
+import com.example.h_mal.messengerapp.utils.convertDateEpoch
import com.tinder.scarlet.WebSocket
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.*
import kotlinx.coroutines.channels.consumeEach
-import kotlinx.coroutines.launch
+import java.io.IOException
+import java.util.*
+import kotlin.concurrent.schedule
+import kotlin.random.Random
-@InternalCoroutinesApi
@ExperimentalCoroutinesApi
class MainViewModel(
private val repository: Repository
) : ViewModel() {
+ // get live data from the database
+ val messagesLiveData = repository.getMessagesLiveData()
+ // Event live data to push error messages to the UI
+ val operationFailure = MutableLiveData>()
+
init {
viewModelScope.launch {
- repository.observeText().consumeEach {
- Log.i("WebsocketOut", it)
+ repository.observeWebsocketMessage().consumeEach {
+ // add message to room database
+ addMessageItemToDatabase(it)
+ }
+ }
+ viewModelScope.launch {
+ repository.observeWebsocketEvent().consumeEach {
+ when(it){
+ is WebSocket.Event.OnConnectionOpened<*> ->{
+ saveDateStamp()
+ }
+ }
+ // Can handle different websocket events here
}
}
}
fun submitMessage(message: String){
- repository.submitMessage(message)
+ if (message.isBlank()) return
+
+ try {
+ // Submit my message
+ repository.submitMessageToApi(message.trim(), true)
+ // Submit a random reply message with a delay of a second
+ Timer().schedule(1000){
+ repository.submitMessageToApi(randomString(), false)
+ }
+ }catch (exception: IOException){
+ val failureMessage = exception.message ?: "Failed to send"
+ operationFailure.postValue(Event(failureMessage))
+ }
+ }
+
+ private fun addMessageItemToDatabase(
+ messageItem: MessageItem
+ ){
+ CoroutineScope(Dispatchers.IO).launch {
+ repository.addMessageItemToDatabase(messageItem)
+ }
+ }
+
+ private fun saveDateStamp(){
+ CoroutineScope(Dispatchers.IO).launch {
+ // get last entry from room database
+ val lastEntry = repository.getLastEntry() ?: return@launch
+ // if its greater than 2 hours
+ if (lastEntry.isGreaterThanTwoHours()){
+ lastEntry.timeStamp.convertDateEpoch()?.let {
+ repository.addTimeStamp(it)
+ }
+ }
+ }
+ }
+
+ // Creates a random string of 15 characters to simulate a reply
+ private fun randomString(): String {
+ val charPool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ val randomString = (1..15)
+ .map { i -> Random.nextInt(0, charPool.length) }
+ .map(charPool::get)
+ .joinToString("");
+
+ return randomString
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MessageRecyclerAdapter.kt b/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MessageRecyclerAdapter.kt
new file mode 100644
index 0000000..ece4e35
--- /dev/null
+++ b/app/src/main/java/com/example/h_mal/messengerapp/ui/main/MessageRecyclerAdapter.kt
@@ -0,0 +1,108 @@
+package com.example.h_mal.messengerapp.ui.main
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.cardview.widget.CardView
+import androidx.lifecycle.LiveData
+import androidx.recyclerview.widget.RecyclerView
+import com.example.h_mal.messengerapp.R
+import com.example.h_mal.messengerapp.data.room.MessageEntity
+import com.example.h_mal.messengerapp.utils.setColour
+import kotlinx.android.synthetic.main.simple_message_layout.view.*
+import kotlinx.android.synthetic.main.timestamp_item_layout.view.*
+
+const val SENT_CONST = 101
+const val RECEIVED_CONST = 102
+const val TIMESTAMP_CONST = 103
+class MessageRecyclerAdapter(
+ val messageLiveData: LiveData>
+) : RecyclerView.Adapter() {
+
+ var messages: List? = null
+
+ init {
+ // Observer live data and update list on change
+ messageLiveData.observeForever{
+ messages = it
+ notifyDataSetChanged()
+ }
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): RecyclerView.ViewHolder {
+ return when(viewType){
+ SENT_CONST ->{
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.message_right, parent, false)
+ CustomViewHolder(view)
+ }
+ RECEIVED_CONST ->{
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.message_left, parent, false)
+ CustomViewHolder(view)
+ }
+ TIMESTAMP_CONST ->{
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.timestamp_item_layout, parent, false)
+ TimestampViewHolder(view)
+ }
+ else -> EmptyViewHolder(parent.context)
+ }
+
+ }
+
+ override fun getItemCount(): Int {
+ return messages?.size ?: 0
+ }
+
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ when (holder){
+ is CustomViewHolder ->{
+ messages?.get(position)?.let {
+ holder.populateMessage(it)
+ }
+ }
+ is TimestampViewHolder ->{
+ messages?.get(position)?.let {
+ holder.populateMessage(it)
+ }
+ }
+ }
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ messages?.get(position)?.isSender?.let {
+ return when(it){
+ true -> SENT_CONST
+ false -> RECEIVED_CONST
+ }
+ }
+ return TIMESTAMP_CONST
+ }
+
+ class CustomViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
+ private val messageTv: TextView = itemView.inner_text
+ private val card: CardView = itemView.card
+
+ fun populateMessage(messageEntity: MessageEntity){
+ messageTv.text = messageEntity.message
+ if (messageEntity.isSender!!){
+ card.setColour(R.color.senderColour)
+ }else{
+ card.setColour(R.color.receiverColour)
+ }
+ }
+ }
+
+ class TimestampViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
+ private val timestampTV: TextView = itemView.title
+
+ fun populateMessage(messageEntity: MessageEntity){
+ timestampTV.text = messageEntity.message
+ }
+ }
+
+ class EmptyViewHolder(context: Context): RecyclerView.ViewHolder(View(context))
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/utils/DateUtils.kt b/app/src/main/java/com/example/h_mal/messengerapp/utils/DateUtils.kt
new file mode 100644
index 0000000..faaac0d
--- /dev/null
+++ b/app/src/main/java/com/example/h_mal/messengerapp/utils/DateUtils.kt
@@ -0,0 +1,16 @@
+package com.example.h_mal.messengerapp.utils
+
+import java.lang.Exception
+import java.text.SimpleDateFormat
+import java.util.*
+
+fun Long.convertDateEpoch(): String? {
+ return try {
+ val sdf = SimpleDateFormat("dd/MM/yy hh:mm a", Locale.getDefault())
+ val date = Date(this)
+ sdf.format(date)
+ }catch (e: Exception){
+ e.printStackTrace()
+ null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/utils/Event.kt b/app/src/main/java/com/example/h_mal/messengerapp/utils/Event.kt
new file mode 100644
index 0000000..4204004
--- /dev/null
+++ b/app/src/main/java/com/example/h_mal/messengerapp/utils/Event.kt
@@ -0,0 +1,24 @@
+package com.example.h_mal.messengerapp.utils
+
+open class Event(private val content: T) {
+
+ var hasBeenHandled = false
+ private set // Allow external read but not write
+
+ /**
+ * Returns the content and prevents its use again.
+ */
+ fun getContentIfNotHandled(): T? {
+ return if (hasBeenHandled) {
+ null
+ } else {
+ hasBeenHandled = true
+ content
+ }
+ }
+
+ /**
+ * Returns the content, even if it's already been handled.
+ */
+ fun peekContent(): T = content
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/h_mal/messengerapp/utils/ViewUtils.kt b/app/src/main/java/com/example/h_mal/messengerapp/utils/ViewUtils.kt
new file mode 100644
index 0000000..7faf11f
--- /dev/null
+++ b/app/src/main/java/com/example/h_mal/messengerapp/utils/ViewUtils.kt
@@ -0,0 +1,39 @@
+package com.example.h_mal.messengerapp.utils
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import androidx.annotation.StringRes
+import androidx.cardview.widget.CardView
+import com.example.h_mal.messengerapp.R
+
+fun View.show() {
+ visibility = View.VISIBLE
+}
+
+fun View.hide() {
+ visibility = View.GONE
+}
+
+fun Context.showToast(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+}
+
+fun Context.showToast(@StringRes resourceId: Int) {
+ Toast.makeText(this, resourceId, Toast.LENGTH_LONG).show()
+}
+
+fun View.hideKeyboard() {
+ val inputMethodManager =
+ context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
+ inputMethodManager.hideSoftInputFromWindow(windowToken, 0)
+}
+
+fun CardView.setColour(@ColorRes id: Int){
+ val colour = context.getColor(id)
+ setCardBackgroundColor(colour)
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/main_fragment.xml b/app/src/main/res/layout/main_fragment.xml
index 1196542..b5246e4 100644
--- a/app/src/main/res/layout/main_fragment.xml
+++ b/app/src/main/res/layout/main_fragment.xml
@@ -5,16 +5,50 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.main.MainFragment">
-
+
+
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent">
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/message_left.xml b/app/src/main/res/layout/message_left.xml
new file mode 100644
index 0000000..668e6f8
--- /dev/null
+++ b/app/src/main/res/layout/message_left.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/message_right.xml b/app/src/main/res/layout/message_right.xml
new file mode 100644
index 0000000..5d66a77
--- /dev/null
+++ b/app/src/main/res/layout/message_right.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/simple_message_layout.xml b/app/src/main/res/layout/simple_message_layout.xml
new file mode 100644
index 0000000..7806187
--- /dev/null
+++ b/app/src/main/res/layout/simple_message_layout.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/timestamp_item_layout.xml b/app/src/main/res/layout/timestamp_item_layout.xml
new file mode 100644
index 0000000..6bfebb6
--- /dev/null
+++ b/app/src/main/res/layout/timestamp_item_layout.xml
@@ -0,0 +1,23 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 4faecfa..4405807 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,4 +3,7 @@
#6200EE
#3700B3
#03DAC5
+
+ #BB8AFF
+ #EBE2F6
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 68d1041..6e7e7f1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
MessengerApp
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
\ No newline at end of file