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
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures {
viewBinding true
}
}

dependencies {
Expand Down
82 changes: 82 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,94 @@
package otus.gpb.recyclerview

import android.graphics.Paint
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import otus.gpb.recyclerview.callback.SwipeCallBack
import otus.gpb.recyclerview.converter.ContactConverter
import otus.gpb.recyclerview.databinding.ActivityMainBinding
import otus.gpb.recyclerview.model.ContactViewModel

class MainActivity : AppCompatActivity() {

val viewModel = ContactViewModel()
private lateinit var binding: ActivityMainBinding
private lateinit var converter: ContactConverter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
subscribe()
}

fun subscribe() {
viewModel.contacts.observe(this) { contacts ->
// Update UI with the new count value
converter.addList(contacts)
}

configureRecycler()
setupSwipe(binding.recyclerView)
viewModel.load()
}

fun configureRecycler() {
converter = ContactConverter()
binding.recyclerView.addItemDecoration(getListRecyclerDecoration())
binding.recyclerView.adapter = converter
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()

if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0
) {
viewModel.load()
}
}
})

}

private fun setupSwipe(recyclerView: RecyclerView) {
val iconMarginDpRight = 29
val iconMarginDpTop = 16
val background = Paint()
val icon = getDrawable(R.drawable.archive_24px)

background.color = getColor(R.color.backgroundDelete)
val callback = SwipeCallBack(
density = resources.displayMetrics.density,
scaledDensity = resources.displayMetrics.scaledDensity,
iconMarginDpRight = iconMarginDpRight,
iconMarginDpTop = iconMarginDpTop,
swipeAction = {
val position = it.adapterPosition
converter.remove(position)
},
background = background,
icon = icon
)
val itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper.attachToRecyclerView(recyclerView)
}

private fun getListRecyclerDecoration(): RecyclerView.ItemDecoration {
val dividerDrawable =
AppCompatResources.getDrawable(this, R.drawable.separator)
return DividerItemDecoration(this, DividerItemDecoration.VERTICAL).apply {
dividerDrawable?.let {
setDrawable(it)
}
}
}
}
113 changes: 113 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/callback/SwipeCallBack.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package otus.gpb.recyclerview.callback

import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.Drawable
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView

class SwipeCallBack(
private val density: Float,
private val scaledDensity: Float,
private val iconMarginDpRight: Int,
private val iconMarginDpTop: Int,
private val swipeAction: (viewHolder: RecyclerView.ViewHolder) -> Unit,
private val background: Paint,
private val icon: Drawable?,
dragDirs: Int = 0,
swipeDirs: Int = ItemTouchHelper.LEFT
) : ItemTouchHelper.SimpleCallback(dragDirs, swipeDirs) {

private fun sizeDpInPixel(sizeDp: Int): Int {
return (density * sizeDp + 0.5f).toInt()
}

private fun sizePixelInDp(sizePixel: Int): Int {
return (sizePixel / density + 0.5f).toInt()
}

private fun pxToSp(px: Float): Float{
return px/scaledDensity
}
private fun spToPx(sp: Float): Float{
return sp * scaledDensity
}

// не передвигаем вверх вниз
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
swipeAction.invoke(viewHolder)
}

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

override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {

val itemView = viewHolder.itemView
// val itemHeight = itemView.bottom - itemView.top

if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
if (dX <= 0f) {
var iconTop = 0
var iconLeft = 0
var iconRight = 0
var iconBottom = 0
icon?.let {

iconTop = itemView.top + sizeDpInPixel(iconMarginDpTop)
iconRight = itemView.right - sizeDpInPixel(iconMarginDpRight)
iconBottom = iconTop + icon.intrinsicHeight
iconLeft = iconRight - icon.intrinsicWidth
c.drawRect(
itemView.right.toFloat() + dX,
itemView.top.toFloat(),
itemView.right.toFloat(),
itemView.bottom.toFloat(),
background
)
icon.setBounds(iconLeft,iconTop,iconRight,iconBottom)
icon.draw(c)
}
val textPaint = Paint()
textPaint.color = Color.WHITE
val deleteTextSize = spToPx(13F)
textPaint.textSize = deleteTextSize


val textWidth = textPaint.measureText("Archive")
val textY = itemView.bottom - sizeDpInPixel(16).toFloat()
val textX = itemView.right - sizeDpInPixel(20).toFloat() - textWidth


c.drawText("Archive", textX, textY, textPaint)
}
}
super.onChildDraw(
c,
recyclerView,
viewHolder,
dX,
dY,
actionState,
isCurrentlyActive
)
}
}
133 changes: 133 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/converter/ContactConverter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package otus.gpb.recyclerview.converter

import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import otus.gpb.recyclerview.databinding.ChatBinding
import otus.gpb.recyclerview.model.Contact
import otus.gpb.recyclerview.model.MessageStatusEnum
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale

class ContactConverter : RecyclerView.Adapter<ContactConverter.ChatItemViewHolder>() {
private var items: MutableList<Contact> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ChatBinding.inflate(inflater, parent, false)
return ChatItemViewHolder(binding)
}

override fun onBindViewHolder(holder: ChatItemViewHolder, position: Int) {
holder.bind(items[position])
}

override fun getItemCount(): Int {
return items.size
}

fun setList(list: List<Contact>) {
items.clear()
addList(list)
}
fun addList(list: List<Contact>) {
items.addAll(list)
notifyDataSetChanged()
}

fun remove(position: Int) {
items.removeAt(position)
notifyItemRemoved(position)
}

inner class ChatItemViewHolder(private val binding: ChatBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Contact) {
binding.title.text = item.title
binding.img.setImageResource(item.imgRes)
binding.isMuted.visibility = if (item.isMuted) View.VISIBLE else View.GONE

binding.status.visibility = if (item.isScum ) View.VISIBLE else View.GONE
binding.statusValue.visibility = if (item.isScum) View.VISIBLE else View.GONE

if (item.message != null) {
binding.subject.visibility = View.VISIBLE
binding.lastMessageContainer.visibility = View.VISIBLE

if (!item.subject.isEmpty()) {
binding.subject.visibility = View.VISIBLE
binding.subject.text = item.subject
} else {
binding.subject.visibility = View.GONE
}
binding.lastMessageText.text = item.message.title

if (item.message.hasImg) {
binding.lastMessageImg.visibility = View.VISIBLE
} else {
binding.lastMessageImg.visibility = View.GONE
}
} else {
binding.subject.visibility = View.GONE
binding.lastMessageContainer.visibility = View.GONE
}

when (item.message?.status) {

MessageStatusEnum.SEND -> {
binding.messageSendAndRead.visibility = View.GONE
binding.messageSendAndUnread.visibility = View.VISIBLE
}

MessageStatusEnum.DELIVERED -> {
binding.messageSendAndUnread.visibility = View.GONE
binding.messageSendAndRead.visibility = View.GONE
}

MessageStatusEnum.READ -> {
binding.messageSendAndRead.visibility = View.VISIBLE
binding.messageSendAndUnread.visibility = View.GONE
}
}



if (item.unReadCount > 0) {
binding.unreadBudge.visibility = View.VISIBLE
binding.unreadValue.text = "${item.unReadCount}"
} else {
binding.unreadBudge.visibility = View.GONE
}

binding.date.text = item.message?.date?.let { getTime(it) }
}
}

fun getTime(date: Date): String {


if (DateUtils.isToday(date.time)) {
return SimpleDateFormat("hh:mm a", Locale.ENGLISH).format(date)
} else if (isDateInCurrentWeek(date)) {
val outFormat = SimpleDateFormat("EEE",Locale.ENGLISH)
return outFormat.format(date)
} else {
return SimpleDateFormat("MMM dd",Locale.ENGLISH).format(date)
}
}

fun isDateInCurrentWeek(date: Date?): Boolean {
val currentCalendar: Calendar = Calendar.getInstance()
val week: Int = currentCalendar.get(Calendar.WEEK_OF_YEAR)
val year: Int = currentCalendar.get(Calendar.YEAR)
val targetCalendar: Calendar = Calendar.getInstance()
if (date != null) {
targetCalendar.time = date
}
val targetWeek: Int = targetCalendar.get(Calendar.WEEK_OF_YEAR)
val targetYear: Int = targetCalendar.get(Calendar.YEAR)
return week == targetWeek && year == targetYear
}
}
33 changes: 33 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/model/Contact.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package otus.gpb.recyclerview.model

import androidx.annotation.DrawableRes
import otus.gpb.recyclerview.R
import kotlin.random.Random

data class Contact(
val id: String,
@DrawableRes val imgRes: Int,
val title: String,
val subject: String,
val isMuted: Boolean,
val message: Message?,
val unReadCount: Int,
val isScum: Boolean,
){
companion object {
fun getRandom(): Contact {
return Contact(
id = java.util.UUID.randomUUID().toString(),
imgRes = R.drawable.avatar,
title = listOf("Very", "bad", "title").random(),
subject = listOf("John", "Mary", "Beaver").random()
.plus(" ")
.plus(listOf("Smith", "Telegram", "Apple").random()),
isMuted = Random.nextBoolean(),
message = Message.getRandom(),
unReadCount = (0..1000).random(),
isScum = Random.nextBoolean()
)
}
}
}
Loading