diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e2cdca7..9d24c63 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -1,12 +1,129 @@ package otus.gpb.recyclerview +import ChatAdapter +import CustomDividerItemDecoration +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RectF import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import otus.gpb.recyclerview.models.Chat +import androidx.core.graphics.toColorInt class MainActivity : AppCompatActivity() { + private val chatAdapter = ChatAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + val recyclerView = findViewById(R.id.recyclerView) + + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = chatAdapter + + val dividerItemDecoration = CustomDividerItemDecoration(this, R.drawable.divider_line) + recyclerView.addItemDecoration(dividerItemDecoration) + + setupSwipe(recyclerView) + + chatAdapter.submitList(getMockData()) + } + + private fun setupSwipe(recyclerView: RecyclerView) { + val swipeCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { + + override fun onMove( + rv: RecyclerView, + vh: RecyclerView.ViewHolder, + t: RecyclerView.ViewHolder + ) = false + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val position = viewHolder.adapterPosition + val currentList = chatAdapter.currentList.toMutableList() + currentList.removeAt(position) + chatAdapter.submitList(currentList) + } + + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + val itemView = viewHolder.itemView + val paint = Paint() + if (dX < 0) { + paint.color = "#66A9E0".toColorInt() + val background = RectF( + itemView.right.toFloat() + dX, + itemView.top.toFloat(), + itemView.right.toFloat(), + itemView.bottom.toFloat() + ) + c.drawRect(background, paint) + + if (Math.abs(dX) > 100) { + val icon = + ContextCompat.getDrawable(this@MainActivity, R.drawable.ic_archive) + icon?.let { + val iconSize = (itemView.height * 0.37).toInt() + val iconMarginRight = 58 + + val iconTop = itemView.top + (itemView.height / 2) - iconSize + val iconBottom = iconTop + iconSize + val iconRight = itemView.right - iconMarginRight + val iconLeft = iconRight - iconSize + + it.setBounds(iconLeft, iconTop, iconRight, iconBottom) + it.setTint(Color.WHITE) + it.draw(c) + + paint.color = Color.WHITE + paint.textSize = 28f + paint.isAntiAlias = true + paint.textAlign = Paint.Align.CENTER + + val textX = (iconLeft + iconRight) / 2f + val textY = iconBottom + 33f + c.drawText("Archive", textX, textY, paint) + } + } + } + + super.onChildDraw( + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive + ) + } + } + + val itemTouchHelper = ItemTouchHelper(swipeCallback) + itemTouchHelper.attachToRecyclerView(recyclerView) + } + + private fun getMockData(): List { + return listOf( + Chat(1, "Elon", "I love X", "14:00", isScam = false,isMuted =false, isOutgoing = false,avatarRes = R.drawable.avatar_elon), + Chat(2, "OTUS Bacic-android-2025-07", "Иди делать домашку", "Wed", isScam = false,isMuted =false,isOutgoing = false,avatarRes = R.drawable.avatar_otus), + Chat(3, "Arnold", "I'll be back", "14:45", isVerified = true, isOutgoing = false,avatarRes = R.drawable.avatar_arnold), + Chat(4, "Telegram Support", "Login code: 123", "12:00", isVerified = true, isRead = true, isOutgoing = true,avatarRes = R.drawable.avatar_telega), + Chat(5, "Crypto King", "Give me money", "Yesterday", isScam = true, isRead = false, isOutgoing = true,avatarRes = R.drawable.avatar_bitcoin), + Chat(6, "Классная гусеница", "Ок", "10:00", isMuted = true, isOutgoing = false,avatarRes = R.drawable.avatar_cool) + ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/otus/gpb/recyclerview/models/Chat.kt b/app/src/main/java/otus/gpb/recyclerview/models/Chat.kt new file mode 100644 index 0000000..603f144 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/models/Chat.kt @@ -0,0 +1,16 @@ +package otus.gpb.recyclerview.models + +data class Chat( + val id: Int, + val title: String, + val lastMessage: String, + val time: String, + val isScam: Boolean = false, + val isMuted: Boolean = false, + val isOutgoing: Boolean, + val avatar: String? = null, + val isVerified: Boolean = false, + val isRead: Boolean = false, + val avatarRes: Int, + +) \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ui/ChatAdapter.kt b/app/src/main/java/otus/gpb/recyclerview/ui/ChatAdapter.kt new file mode 100644 index 0000000..8599c0a --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ui/ChatAdapter.kt @@ -0,0 +1,60 @@ +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import otus.gpb.recyclerview.R +import otus.gpb.recyclerview.models.Chat + +class ChatAdapter : ListAdapter(ChatDiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.item_chat, parent, false) + return ChatViewHolder(view) + } + + override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + class ChatViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val lastMessage = itemView.findViewById(R.id.LastMessage) + private val time = itemView.findViewById(R.id.Time) + private val verified = itemView.findViewById(R.id.Verified) + private val scam = itemView.findViewById(R.id.Scam) + private val mute = itemView.findViewById(R.id.Mute) + + fun bind(chat: Chat) { + val title = itemView.findViewById(R.id.Title) + val statusIcon = itemView.findViewById(R.id.Delivered) + val avatarImageView = itemView.findViewById(R.id.Avatar) + avatarImageView.setImageResource(chat.avatarRes) + title.text = chat.title + lastMessage.text = chat.lastMessage + time.text = chat.time + verified.visibility = if (chat.isVerified) View.VISIBLE else View.GONE + scam.visibility = if (chat.isScam) View.VISIBLE else View.GONE + mute.visibility = if (chat.isMuted) View.VISIBLE else View.GONE + if (chat.isOutgoing) { + statusIcon.visibility = View.VISIBLE + val resId = if (chat.isRead) R.drawable.ic_status_read else R.drawable.ic_status_delivered + statusIcon.setImageResource(resId) + } else { + statusIcon.visibility = View.GONE + } + } + } +} + +class ChatDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean { + return oldItem == newItem + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ui/DividerItemDecoration.kt b/app/src/main/java/otus/gpb/recyclerview/ui/DividerItemDecoration.kt new file mode 100644 index 0000000..182b15d --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ui/DividerItemDecoration.kt @@ -0,0 +1,38 @@ +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.view.View +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView + +class CustomDividerItemDecoration(context: Context, resId: Int) : RecyclerView.ItemDecoration() { + private var divider: Drawable? = ContextCompat.getDrawable(context, resId) + + override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { + val left = parent.paddingLeft + val right = parent.width - parent.paddingRight + + val childCount = parent.childCount + for (i in 0 until childCount - 1) { + val child = parent.getChildAt(i) + val params = child.layoutParams as RecyclerView.LayoutParams + val top = child.bottom + params.bottomMargin + val bottom = top + (divider?.intrinsicHeight ?: 0) + + divider?.let { + it.setBounds(left, top, right, bottom) + it.draw(c) + } + } + } + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + outRect.set(0, 0, 0, divider?.intrinsicHeight ?: 0) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/avatar_arnold.jpeg b/app/src/main/res/drawable/avatar_arnold.jpeg new file mode 100644 index 0000000..25add4e Binary files /dev/null and b/app/src/main/res/drawable/avatar_arnold.jpeg differ diff --git a/app/src/main/res/drawable/avatar_bitcoin.jpg b/app/src/main/res/drawable/avatar_bitcoin.jpg new file mode 100644 index 0000000..0b9409d Binary files /dev/null and b/app/src/main/res/drawable/avatar_bitcoin.jpg differ diff --git a/app/src/main/res/drawable/avatar_cool.jpg b/app/src/main/res/drawable/avatar_cool.jpg new file mode 100644 index 0000000..bd17761 Binary files /dev/null and b/app/src/main/res/drawable/avatar_cool.jpg differ diff --git a/app/src/main/res/drawable/avatar_elon.jpg b/app/src/main/res/drawable/avatar_elon.jpg new file mode 100644 index 0000000..e6e6da2 Binary files /dev/null and b/app/src/main/res/drawable/avatar_elon.jpg differ diff --git a/app/src/main/res/drawable/avatar_otus.jpg b/app/src/main/res/drawable/avatar_otus.jpg new file mode 100644 index 0000000..5b60fdc Binary files /dev/null and b/app/src/main/res/drawable/avatar_otus.jpg differ diff --git a/app/src/main/res/drawable/avatar_telega.jpg b/app/src/main/res/drawable/avatar_telega.jpg new file mode 100644 index 0000000..4d2c0d5 Binary files /dev/null and b/app/src/main/res/drawable/avatar_telega.jpg differ diff --git a/app/src/main/res/drawable/divider_line.xml b/app/src/main/res/drawable/divider_line.xml new file mode 100644 index 0000000..bf174ed --- /dev/null +++ b/app/src/main/res/drawable/divider_line.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_archive.xml b/app/src/main/res/drawable/ic_archive.xml new file mode 100644 index 0000000..d9bec62 --- /dev/null +++ b/app/src/main/res/drawable/ic_archive.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_badge_muted.xml b/app/src/main/res/drawable/ic_badge_muted.xml new file mode 100644 index 0000000..e45c74f --- /dev/null +++ b/app/src/main/res/drawable/ic_badge_muted.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_badge_pinned.xml b/app/src/main/res/drawable/ic_badge_pinned.xml new file mode 100644 index 0000000..eaf2041 --- /dev/null +++ b/app/src/main/res/drawable/ic_badge_pinned.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_badge_scam.xml b/app/src/main/res/drawable/ic_badge_scam.xml new file mode 100644 index 0000000..f6d9704 --- /dev/null +++ b/app/src/main/res/drawable/ic_badge_scam.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_badge_verified.xml b/app/src/main/res/drawable/ic_badge_verified.xml new file mode 100644 index 0000000..dac159e --- /dev/null +++ b/app/src/main/res/drawable/ic_badge_verified.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_status_delivered.xml b/app/src/main/res/drawable/ic_status_delivered.xml new file mode 100644 index 0000000..12e625a --- /dev/null +++ b/app/src/main/res/drawable/ic_status_delivered.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_status_read.xml b/app/src/main/res/drawable/ic_status_read.xml new file mode 100644 index 0000000..8308eef --- /dev/null +++ b/app/src/main/res/drawable/ic_status_read.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/layout/item_chat.xml b/app/src/main/res/layout/item_chat.xml new file mode 100644 index 0000000..e3945b9 --- /dev/null +++ b/app/src/main/res/layout/item_chat.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file