diff --git a/app/build.gradle b/app/build.gradle index 54e4eac..3302ed5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,12 @@ android { kotlinOptions { jvmTarget = '1.8' } + buildFeatures { + viewBinding = true + } + dataBinding { + enabled = true + } } dependencies { @@ -41,4 +47,6 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation 'androidx.databinding:databinding-runtime:8.10.0' + implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4' } \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt new file mode 100644 index 0000000..a2aaf83 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -0,0 +1,56 @@ +package otus.gpb.recyclerview + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import otus.gpb.recyclerview.databinding.ChatItemBinding +import android.view.View.INVISIBLE +import android.view.View.VISIBLE + +class ChatAdapter : ListAdapter(ChatDiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder { + val binding = ChatItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return ChatViewHolder(binding) + } + + override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { + val item = getItem(position) + + with(holder.binding) { + + textName.text = item.name + textStatus.text = item.status + message.text = item.message + time.text = item.time + + iconVerified.visibility = if (item.verified) VISIBLE else INVISIBLE + iconScam.visibility = if (item.scam) VISIBLE else INVISIBLE + iconMute.visibility = if (item.mute) VISIBLE else INVISIBLE + + counter.apply { + visibility = if (item.counter > 0) VISIBLE else INVISIBLE + text = item.counter.toString() + } + + val iconRes = when { + item.delivered && item.read -> R.drawable.doublecheck + item.delivered -> R.drawable.check + item.read -> R.drawable.doublecheck + else -> null + } + + if (iconRes != null) { + delivered.setImageResource(iconRes) + delivered.visibility = VISIBLE + } else { + delivered.visibility = INVISIBLE + } + } + } +} diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatDiffCallback.kt b/app/src/main/java/otus/gpb/recyclerview/ChatDiffCallback.kt new file mode 100644 index 0000000..2721371 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatDiffCallback.kt @@ -0,0 +1,13 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.DiffUtil + +class ChatDiffCallback: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean { + return oldItem == newItem + } +} diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatItem.kt b/app/src/main/java/otus/gpb/recyclerview/ChatItem.kt new file mode 100644 index 0000000..0d30d6a --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatItem.kt @@ -0,0 +1,15 @@ +package otus.gpb.recyclerview + +data class ChatItem( + val id: Int, + val name: String, + val status: String, + val message: String, + val counter: Int, + val verified: Boolean, + val mute: Boolean, + val scam: Boolean, + val delivered: Boolean, + val read: Boolean, + val time: String, +) diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt b/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt new file mode 100644 index 0000000..23c42b3 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt @@ -0,0 +1,6 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import otus.gpb.recyclerview.databinding.ChatItemBinding + +class ChatViewHolder(val binding: ChatItemBinding): ViewHolder(binding.root) diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e2cdca7..8b1d435 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -1,12 +1,136 @@ package otus.gpb.recyclerview -import androidx.appcompat.app.AppCompatActivity +import android.graphics.Canvas import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.* +import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator +import kotlin.random.Random +import otus.gpb.recyclerview.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { + private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } + + private val chatList = mutableListOf() + private val adapter = ChatAdapter() + + private var lastId = 0 + private var previousItemsCount = 0 + private var pageLoad = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + setContentView(binding.root) + + binding.recyclerView.adapter = adapter + binding.recyclerView.layoutManager = LinearLayoutManager(this) + + setupSwipe() + setScrollListener() + + createItems(20) + } + + private fun setupSwipe() { + + val helper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + 0, + ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT + ) { + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ) = false + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + removeItem(viewHolder.adapterPosition) + } + + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + RecyclerViewSwipeDecorator.Builder( + c, recyclerView, viewHolder, + dX, dY, actionState, isCurrentlyActive + ).create().decorate() + + super.onChildDraw( + c, recyclerView, viewHolder, + dX, dY, actionState, isCurrentlyActive + ) + } + }) + + helper.attachToRecyclerView(binding.recyclerView) + } + + private fun setScrollListener() { + + binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + + val layoutManager = recyclerView.layoutManager as LinearLayoutManager + + val itemsCount = layoutManager.itemCount + val lastVisibleItem = layoutManager.findLastVisibleItemPosition() + + if (pageLoad && itemsCount > previousItemsCount) { + pageLoad = false + previousItemsCount = itemsCount + } + + if (!pageLoad && lastVisibleItem >= itemsCount - 5) { + getNextPage() + } + } + }) + } + + private fun getNextPage() { + pageLoad = true + createItems(20) + } + + private fun removeItem(position: Int) { + chatList.removeAt(position) + adapter.submitList(chatList.toList()) + } + + private fun createItems(size: Int) { + + repeat(size) { + + val hour = Random.nextInt(0, 24) + val minute = Random.nextInt(0, 60) + val time = "%02d:%02d".format(hour, minute) + + chatList.add( + ChatItem( + id = lastId++, + name = "Mr. Meeseeks $lastId", + status = "status $lastId", + message = "Message $lastId", + counter = Random.nextInt(10), + verified = Random.nextBoolean(), + scam = Random.nextBoolean(), + mute = Random.nextBoolean(), + delivered = Random.nextBoolean(), + read = Random.nextBoolean(), + time = time + ) + ) + } + + adapter.submitList(chatList.toList()) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/avatar.png b/app/src/main/res/drawable/avatar.png new file mode 100644 index 0000000..d1724b9 Binary files /dev/null and b/app/src/main/res/drawable/avatar.png differ diff --git a/app/src/main/res/drawable/check.xml b/app/src/main/res/drawable/check.xml new file mode 100644 index 0000000..e8b21b0 --- /dev/null +++ b/app/src/main/res/drawable/check.xml @@ -0,0 +1,2 @@ + + diff --git a/app/src/main/res/drawable/check1.xml b/app/src/main/res/drawable/check1.xml new file mode 100644 index 0000000..48b7d4d --- /dev/null +++ b/app/src/main/res/drawable/check1.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/circle.xml b/app/src/main/res/drawable/circle.xml new file mode 100644 index 0000000..27cde7b --- /dev/null +++ b/app/src/main/res/drawable/circle.xml @@ -0,0 +1,2 @@ + + diff --git a/app/src/main/res/drawable/doublecheck.xml b/app/src/main/res/drawable/doublecheck.xml new file mode 100644 index 0000000..c71c8ba --- /dev/null +++ b/app/src/main/res/drawable/doublecheck.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/mute.xml b/app/src/main/res/drawable/mute.xml new file mode 100644 index 0000000..dc59ee4 --- /dev/null +++ b/app/src/main/res/drawable/mute.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/scam.xml b/app/src/main/res/drawable/scam.xml new file mode 100644 index 0000000..b77d9ab --- /dev/null +++ b/app/src/main/res/drawable/scam.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2d026df..14b87f2 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -3,11 +3,15 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@color/white" + xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".MainActivity"> + android:layout_height="match_parent" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/chat_item" /> \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item.xml b/app/src/main/res/layout/chat_item.xml new file mode 100644 index 0000000..92efb23 --- /dev/null +++ b/app/src/main/res/layout/chat_item.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index cd0519b..bd6aecb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,5 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.overridePathCheck=true \ No newline at end of file