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
44 changes: 0 additions & 44 deletions app/build.gradle

This file was deleted.

56 changes: 56 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
}

android {
namespace = "otus.gpb.recyclerview"
compileSdk = 36

defaultConfig {
applicationId = "otus.gpb.recyclerview"
minSdk = 26
targetSdk = 36
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
viewBinding = true
}

kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
}
}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.recyclerview)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<application
android:allowBackup="true"
android:name=".App"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
Expand Down
28 changes: 28 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package otus.gpb.recyclerview

import android.app.Application
import otus.gpb.recyclerview.prefs.KeyValueStorage
import otus.gpb.recyclerview.prefs.SharedPreferencesStorage
import otus.gpb.recyclerview.repository.ChatRepository
import otus.gpb.recyclerview.stat.StatService


class App : Application() {
/**
* Stat service
*/
val statService = StatService.Logger()

/**
* Preferences storage
* Можно подписаться через LiveData на изменение по ключу и получасть изменения в разных activities
*/
val preferences: KeyValueStorage by lazy {
SharedPreferencesStorage(this, "prefs")
}

val chatRepository by lazy {
ChatRepository()
}

}
35 changes: 35 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,35 @@
package otus.gpb.recyclerview

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import otus.gpb.recyclerview.databinding.ChatItemBinding
import otus.gpb.recyclerview.model.ChatItem

class ChatAdapter(
private val dataSet: MutableList<ChatItem>,
private val itemListener: ItemListener,
) : RecyclerView.Adapter<ChatViewHolder>() {

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ChatViewHolder {
val chatItemBinding =
ChatItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return ChatViewHolder(chatItemBinding, itemListener)
}

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

override fun getItemCount() = dataSet.size
}
37 changes: 37 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/ChatDiffAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package otus.gpb.recyclerview

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import otus.gpb.recyclerview.databinding.ChatItemBinding
import otus.gpb.recyclerview.model.ChatItem

class ChatDiffAdapter(
private val itemListener: ItemListener,
) : ListAdapter<ChatItem, ChatViewHolder>(DiffUtilItem()) {

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ChatViewHolder {
val chatItemBinding =
ChatItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ChatViewHolder(chatItemBinding, itemListener)
}

override fun onBindViewHolder(holder: ChatViewHolder, position: Int) =
holder.bind(getItem(position))

}

private class DiffUtilItem : DiffUtil.ItemCallback<ChatItem>() {
override fun areItemsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean {
return oldItem::class == newItem::class && oldItem.id == newItem.id
}

override fun areContentsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean {
return oldItem == newItem
}

}
121 changes: 121 additions & 0 deletions app/src/main/java/otus/gpb/recyclerview/ChatItemTouchHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package otus.gpb.recyclerview

import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.drawable.VectorDrawable
import android.util.DisplayMetrics
import androidx.core.content.ContextCompat
import androidx.core.graphics.createBitmap
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import java.util.UUID
import kotlin.math.roundToInt


class ChatItemTouchHelper(private val listener: ItemListener) : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.ACTION_STATE_IDLE,
ItemTouchHelper.LEFT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
) = false

override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int
) {
viewHolder.getElementId()?.let { listener.onSwipe(it) }
}

/**
* source: https://stackoverflow.com/questions/30820806/adding-a-colored-background-with-text-icon-under-swiped-row-when-using-androids
* Источник из ДЗ (не загружается без vpn) https://www.digitalocean.com/community/tutorials/android-recyclerview-swipe-to-delete-undo
* */
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {

// Get RecyclerView item from the ViewHolder
val itemView = viewHolder.itemView

val p = Paint()
val context = itemView.context
val resources: Resources = context.resources

if (dX < 0) {
/* Set your color for negative displacement */
p.setColor(ContextCompat.getColor(context, R.color.light_blue))
// Draw Rect with varying left side, equal to the item's right side plus negative displacement dX

val itemViewTop = itemView.top.toFloat()
val itemViewRight = itemView.right.toFloat()
val itemViewBottom = itemView.bottom.toFloat()
c.drawRect(
/* left = */ itemViewRight + dX,
/* top = */ itemViewTop,
/* right = */ itemViewRight,
/* bottom = */ itemViewBottom,
/* paint = */ p
)

// fixme magic numbers to dimensions, add relative counters
getVectorBitmap(context, R.drawable.archive)?.let { bitmap ->
val left = itemViewRight - convertDpToPx(50, resources) - bitmap.getWidth()
val top = itemViewTop + (itemViewBottom - itemViewTop - bitmap.getHeight()) * 0.4f
c.drawBitmap(
/* bitmap = */ bitmap,
/* left = */ left,
/* top = */ top,
/* paint = */ p
)

c.drawText(
"Archive",
left - convertDpToPx(30, resources),
top + convertDpToPx(50, resources),
Paint().apply {
color = ContextCompat.getColor(context, R.color.white)
textSize = 36f
}
)
}
}

super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}

/**
* source https://stackoverflow.com/questions/44612353/using-vector-drawable-by-drawing-in-canvas
* */
private fun getVectorBitmap(context: Context, drawableId: Int) =
ContextCompat.getDrawable(context, drawableId)?.asClass<VectorDrawable>()
?.let { drawable ->
val bitmap: Bitmap = createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
bitmap
}

private fun convertDpToPx(dp: Int, resources: Resources): Int {
return (dp * (resources.displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)).roundToInt()
}

private fun RecyclerView.ViewHolder.getElementId(): UUID? {
return bindingAdapter
?.asClass<ChatDiffAdapter>()
?.currentList
?.getOrNull(bindingAdapterPosition)?.id
}
}
49 changes: 49 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,49 @@
package otus.gpb.recyclerview

import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import otus.gpb.recyclerview.databinding.ChatItemBinding
import otus.gpb.recyclerview.model.ChatItem

class ChatViewHolder(
private val cartItemBinding: ChatItemBinding,
private val itemListener: ItemListener
) : RecyclerView.ViewHolder(cartItemBinding.root) {

val chatImage: ImageView = cartItemBinding.chatImage
val chatNameText: TextView = cartItemBinding.chatName
val lastUserNameText: TextView = cartItemBinding.lastUserName
val lastMessageText: TextView = cartItemBinding.lastMessage
val verifiedImage: ImageView = cartItemBinding.verified
val muteImage: ImageView = cartItemBinding.mute
val messageStatusImage: ImageView = cartItemBinding.messageStatus
val lastMessageTimeText: TextView = cartItemBinding.lastMessageTime
val chatPinnedImage: ImageView = cartItemBinding.pinned



fun bind(chatItem: ChatItem) {
with(chatItem) {
chatImage.setImageResource(imageId)
chatNameText.text = chatName
lastUserNameText.text = lastUserName
lastMessageText.text = lastMessage
verifiedImage.visibility = if (isVerified) View.VISIBLE else View.GONE
muteImage.visibility = if (isMuted) View.VISIBLE else View.GONE

messageStatusImage.setImageResource(messageStatus.iconId)
messageStatusImage.setColorFilter(ContextCompat.getColor(cartItemBinding.root.context, messageStatus.colorId),
android.graphics.PorterDuff.Mode.SRC_IN)
lastMessageTimeText.text = lastMessageViewTime
chatPinnedImage.visibility = if (isPinned) View.VISIBLE else View.GONE
cartItemBinding.root.setOnClickListener {
itemListener.onItemClick(id)
}
}

}

}
Loading