Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ android {
dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation 'androidx.activity:activity-ktx:1.9.3'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
Expand Down
54 changes: 54 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package otus.gpb.recyclerview

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import java.util.Collections

class ChatAdapter() : ListAdapter<ChatItem, RecyclerView.ViewHolder>(DiffUtilItem()) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.chat, parent, false)
return ChatViewHolder(view)
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = currentList.getOrNull(position)
if (item != null) {
(holder as ChatViewHolder).bind(item)
}
}

fun exchange(fromPosition: Int, toPosition: Int) {
val list = currentList.toMutableList()
if (fromPosition < toPosition) {
for (index in fromPosition until toPosition) {
Collections.swap(list, index, index + 1)
}
} else {
for (index in fromPosition downTo toPosition + 1) {
Collections.swap(list, index, index - 1)
}
}
submitList(list)
}

fun removeItem(fromPosition: Int) {
val list = currentList.toMutableList()
list.removeAt(fromPosition)
submitList(list)
}
}

class DiffUtilItem : DiffUtil.ItemCallback<ChatItem>() {

override fun areItemsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean {
return oldItem == newItem
}
}
9 changes: 9 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/ChatItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package otus.gpb.recyclerview
data class ChatItem(
val id: Int,
val date: String,
val name: String,
val surname: String,
val message: String,
val background: Int
)
30 changes: 30 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package otus.gpb.recyclerview

import android.annotation.SuppressLint
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ChatViewHolder(
private val view: View,
) : RecyclerView.ViewHolder(view) {

init {
println(this)
}

private val name: TextView by lazy { view.findViewById(R.id.name) }
private val avatar: TextView by lazy { view.findViewById(R.id.avatar) }
private val message: TextView by lazy { view.findViewById(R.id.message) }
private val date: TextView by lazy { view.findViewById(R.id.date) }

@SuppressLint("SetTextI18n")
fun bind(item: ChatItem) {
println("bind item ${item.id}")
name.text = "${item.name} ${item.surname}"
message.text = item.message
date.text = item.date
avatar.text = item.name.substring(0, 1) + item.surname.substring(0,1)
avatar.setBackgroundColor(item.background)
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/ColorGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package otus.gpb.recyclerview

import android.graphics.Color
import java.util.Random

object ColorGenerator {

fun generateColor(): Int {
val nextInt = Random().nextInt(0xffffff + 1)
val colorCode = String.format("#%06x", nextInt)
return Color.parseColor(colorCode)
}
}
40 changes: 40 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/CustomDecorator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package otus.gpb.recyclerview

import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration

class CustomDecorator : ItemDecoration() {

private val bounds = Rect()
private val paint = Paint().apply {
color = Color.GRAY
strokeWidth = 1f
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(canvas, parent, state)

val childCount = parent.childCount
for (index in 0 until childCount) {
val child = parent.getChildAt(index)
parent.getDecoratedBoundsWithMargins(child, bounds)

val positionCurrent = parent.getChildAdapterPosition(child)
if (positionCurrent != RecyclerView.NO_POSITION) {
val lastElementPosition = parent.adapter?.itemCount?.minus(1)
if (positionCurrent != lastElementPosition) {
canvas.drawLine(
bounds.left.toFloat() + 150f,
bounds.bottom.toFloat(),
bounds.right.toFloat(),
bounds.bottom.toFloat(),
paint
)
}
}
}
}
}
37 changes: 37 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/ItemTouchHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package otus.gpb.recyclerview

import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

class ItemTouchCallback : ItemTouchHelper.Callback() {

override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT
)
}

override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
viewHolder.chatAdapter.exchange(
viewHolder.bindingAdapterPosition,
target.bindingAdapterPosition
)
return true
}

override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
return 0.25f
}

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
viewHolder.chatAdapter.removeItem(viewHolder.absoluteAdapterPosition)
}

private val RecyclerView.ViewHolder.chatAdapter: ChatAdapter
get() = bindingAdapter as? ChatAdapter ?: error("Not ChatAdapter")
}
58 changes: 57 additions & 1 deletion app/src/main/java/otus/gpb/recyclerview/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,68 @@
package otus.gpb.recyclerview

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlin.random.Random

class MainActivity : AppCompatActivity() {

private val adapter: ChatAdapter by lazy { ChatAdapter() }
private var list: List<ChatItem> = emptyList()
private val names: List<String> = listOf("Sam", "Paul", "Jean", "Jo", "Jacob", "Winter", "Summer", "Olly")
private val surnames: List<String> = listOf("Brown", "Potter", "Sol", "Dew", "Moss", "Triple", "Kenzie", "Martin")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}

val recyclerView = findViewById<RecyclerView>(R.id.list)
recyclerView.addItemDecoration(
CustomDecorator()
)

ItemTouchHelper(ItemTouchCallback()).attachToRecyclerView(recyclerView)

recyclerView.adapter = adapter
list = generateList(15)
adapter.submitList(list)

findViewById<FloatingActionButton>(R.id.loadMore).setOnClickListener { addChats() }
}

private fun addChats() {
val currentList = list.toMutableList()
currentList.addAll(generateList(5))
adapter.submitList(currentList)
}

private fun generateList(number: Int) = run {
val list = mutableListOf<ChatItem>()
repeat(number) {
val nameIdx = Random.nextInt(0, names.size - 1)
val surnameIdx = Random.nextInt(0, surnames.size - 1)
val personItem = ChatItem(
id = it,
name = names[nameIdx],
surname = surnames[surnameIdx],
message = "Some message from ${names[nameIdx]}",
date = "12:05",
background = ColorGenerator.generateColor()
)
list.add(personItem)
}

list.toList()
}
}
29 changes: 25 additions & 4 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"


<androidx.constraintlayout.widget.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/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="5"
tools:listitem="@layout/chat" />

</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/loadMore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:contentDescription="@string/load_more"
android:src="@android:drawable/ic_dialog_email"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout >
59 changes: 59 additions & 0 deletions app/src/main/res/layout/chat.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:padding="8dp"
tools:background="@color/white">

<TextView
android:id="@+id/avatar"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textColor="@color/white"
android:gravity="center"
android:textSize="28sp"
/>

<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/date"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="Name" />

<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/date"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toBottomOf="@id/name"
tools:text="Hello world" />

<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="12:04" />


</androidx.constraintlayout.widget.ConstraintLayout>
2 changes: 1 addition & 1 deletion app/src/main/res/values-night/themes.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.RecyclerView" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.RecyclerView" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<resources>
<string name="app_name">RecyclerView</string>
<string name="load_more">Load more</string>
</resources>
Loading