diff --git a/app/build.gradle b/app/build.gradle index c1b2a86..79657f5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,6 +16,9 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + viewBinding = true + } buildTypes { release { minifyEnabled false @@ -40,4 +43,5 @@ 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 '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..3d707d2 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -0,0 +1,91 @@ +package otus.gpb.recyclerview + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.imageview.ShapeableImageView + +class ChatAdapter (private val items: MutableList) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder { + val view: View = LayoutInflater.from(parent.context).inflate(R.layout.chat_item, parent, false) + return ChatViewHolder(view) + } + + fun removeAt(index: Int) { + items.removeAt(index) + notifyItemRemoved(index) + } + + override fun getItemCount(): Int { + return items.size + } + + override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { + holder.bind(items[position]) + } + + fun onLoadMore(context: Context) { + Toast.makeText(context, "Load more", Toast.LENGTH_LONG).show() + val newChatItems = GenerateChatItems().getList(10) + val getCountItems = items.size + items.addAll(newChatItems) + this.notifyItemRangeInserted(getCountItems, newChatItems.size) + } + + class ChatViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + private val mainImg: ShapeableImageView = itemView.findViewById(R.id.image_icon) + private val mainName: TextView = itemView.findViewById(R.id.text_view_name) + private val bottomName: TextView = itemView.findViewById(R.id.text_view_name_bottom) + private val imgMessage: ImageView = itemView.findViewById(R.id.img_message) + private val message: TextView = itemView.findViewById(R.id.text_view_message) + private val imgVerified: ImageView = itemView.findViewById(R.id.img_verified) + private val imgMuted: ImageView = itemView.findViewById(R.id.img_noise) + private val time: TextView = itemView.findViewById(R.id.text_view_time) + private val imgFixed: ImageButton = itemView.findViewById(R.id.img_button_fixed) + private val imgStateMessage: ImageView = itemView.findViewById(R.id.img_readed) + + fun bind(itemChat: ChatItem) { + mainName.text = itemChat.mainName + mainImg.setImageResource(itemChat.mainImg) + message.text = itemChat.textMessage + time.text = itemChat.textTime + imgStateMessage.setImageResource(itemChat.stateMessage) + + if (itemChat.isVerified) { + imgVerified.setImageResource(R.drawable.verif) + } + else { + imgVerified.visibility = View.GONE + } + if (itemChat.isMuted) { + imgMuted.setImageResource(R.drawable.muted) + } + if (itemChat.isFavour) { + imgFixed.setImageResource(R.drawable.favour) + } + if (itemChat.secondaryName != null) { + bottomName.text = itemChat.secondaryName + } + else { + bottomName.visibility = View.GONE + } + if (itemChat.messageImg != null) { + imgMessage.setImageResource(itemChat.messageImg) + } + else { + imgMessage.visibility = View.GONE + } + } + } + + +} \ No newline at end of file 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..6751ba7 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatItem.kt @@ -0,0 +1,16 @@ +package otus.gpb.recyclerview + +import androidx.annotation.DrawableRes + +data class ChatItem( + val mainName: String, + val secondaryName: String?, + val textMessage: String, + val textTime: String, + val isMuted: Boolean, + val isVerified: Boolean, + @DrawableRes val messageImg: Int?, + @DrawableRes val stateMessage: Int, + val isFavour: Boolean, + @DrawableRes val mainImg: Int +) diff --git a/app/src/main/java/otus/gpb/recyclerview/GenerateChatItems.kt b/app/src/main/java/otus/gpb/recyclerview/GenerateChatItems.kt new file mode 100644 index 0000000..d6e6ff1 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/GenerateChatItems.kt @@ -0,0 +1,55 @@ +package otus.gpb.recyclerview + +import kotlin.random.Random + +class GenerateChatItems { + private val list = mutableListOf() + + + + fun getList(count: Int): MutableList{ + for (i in 1..count) { + val mainName = getRandomMainName() + val secondaryName = getRandomSecondaryName() + val textMessage = getRandomMessage() + val textTime = getTextTime() + val isMuted = Random.nextBoolean() + val isVerified = Random.nextBoolean() + val messageImg = getRandomImgMessage() + val stateMessage = getRandomState() + val isFavour = Random.nextBoolean() + val mainImg = getRandomAvatar() + list.add(ChatItem(mainName, secondaryName, textMessage, textTime, isMuted, isVerified, messageImg, stateMessage, isFavour, mainImg)) + } + return list + } + + private fun getRandomMainName(): String { + val list = listOf("Denis Milkov", "Elton", "Ivan Ivanov", "Elon Mask", "Oliver", "Grozny", "Kotlin", "Petr", "Hozy") + return list.random() + } + private fun getRandomSecondaryName() : String? { + val list = listOf("Boss", "Friend", "No name", "Doctor", "Developer", "Funny man", null, null, null) + return list.random() + } + private fun getRandomMessage() : String { + val list = listOf("Hello!", "How are you?", "Cant answer right now", "What are you doing?", "Im watching TV", "...") + return list.random() + } + private fun getTextTime() : String { + val list = listOf("12:46", "Fri", "Mon", "18:56", "00:00", "Wen", "15:33", "19:00", "09:00") + return list.random() + } + private fun getRandomAvatar(): Int { + val list = listOf(R.drawable.avatar4, R.drawable.avata2r, R.drawable.avata22r, R.drawable.ava) + return list.random() + } + private fun getRandomState(): Int { + val list = listOf(R.drawable.readed, R.drawable.not_readed, R.drawable.state_null) + return list.random() + } + private fun getRandomImgMessage(): Int? { + val list = listOf(R.drawable.avatar, null, null, null) + return list.random() + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e2cdca7..f7719e8 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -1,12 +1,54 @@ package otus.gpb.recyclerview -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.widget.SimpleAdapter +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +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 class MainActivity : AppCompatActivity() { + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: ChatAdapter + private var items = GenerateChatItems().getList(20) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + val manager = LinearLayoutManager(this) + + recyclerView = findViewById(R.id.recyclerView) + adapter = ChatAdapter(items) + recyclerView.adapter = adapter + recyclerView.layoutManager = manager + recyclerView.setItemViewCacheSize(100) + val swipeHandler = object : SwipeCallback(this) { + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + adapter.removeAt(viewHolder.adapterPosition) + } + } + val itemTouchHelper = ItemTouchHelper(swipeHandler) + itemTouchHelper.attachToRecyclerView(recyclerView) + + val divider = AppCompatResources.getDrawable(this, R.drawable.decoration) + + recyclerView.addItemDecoration( + DividerItemDecoration(this, DividerItemDecoration.VERTICAL).apply { + divider?.let { setDrawable(it) } + } + ) + + val paging = PageScrollListener(manager).apply { + onLoadMore = { + adapter.onLoadMore(this@MainActivity) + isLoading = false + } + } + recyclerView.addOnScrollListener(paging) } } \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/PageScrollListener.kt b/app/src/main/java/otus/gpb/recyclerview/PageScrollListener.kt new file mode 100644 index 0000000..aed2e2a --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/PageScrollListener.kt @@ -0,0 +1,25 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +class PageScrollListener( + private val layoutManager: LinearLayoutManager +): RecyclerView.OnScrollListener() { + + var isLoading = false + var onLoadMore: (() -> Unit)? = null + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + val totalItemCount = layoutManager.itemCount + val visibleItemCount = layoutManager.childCount + val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition() + if (!isLoading) { + if ((visibleItemCount + firstVisiblePosition) >= totalItemCount) { + isLoading = true + onLoadMore?.invoke() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/SwipeCallback.kt b/app/src/main/java/otus/gpb/recyclerview/SwipeCallback.kt new file mode 100644 index 0000000..bff08f9 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/SwipeCallback.kt @@ -0,0 +1,77 @@ +package otus.gpb.recyclerview + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import android.graphics.drawable.ColorDrawable +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder + + +abstract class SwipeCallback (context: Context) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { + private val deleteIcon = ContextCompat.getDrawable(context, R.drawable.archive) + private val intrinsicWidth = deleteIcon?.intrinsicWidth + private val intrinsicHeight = deleteIcon?.intrinsicHeight + private val background = ColorDrawable() + private val backgroundColor = Color.parseColor("#66A9E0") + private val clearPaint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) } + + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: ViewHolder): Int { + if (viewHolder.adapterPosition == 10) return 0 + return super.getMovementFlags(recyclerView, viewHolder) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: ViewHolder, + target: ViewHolder + ): Boolean { + return false + } + + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + + val itemView = viewHolder.itemView + val itemHeight = itemView.bottom - itemView.top + val isCanceled = dX == 0f && !isCurrentlyActive + + if (isCanceled) { + clearCanvas(c, itemView.right + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat()) + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + return + } + background.color = backgroundColor + background.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom) + background.draw(c) + + val deleteIconTop = itemView.top + (itemHeight - intrinsicHeight!!) / 2 + val deleteIconMargin = (itemHeight - intrinsicHeight) / 2 + val deleteIconLeft = itemView.right - deleteIconMargin - intrinsicWidth!! + val deleteIconRight = itemView.right - deleteIconMargin + val deleteIconBottom = deleteIconTop + intrinsicHeight + + deleteIcon?.setBounds(deleteIconLeft, deleteIconTop, deleteIconRight, deleteIconBottom) + deleteIcon?.draw(c) + + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + } + + private fun clearCanvas(c: Canvas?, left: Float, top: Float, right: Float, bottom: Float) { + c?.drawRect(left, top, right, bottom, clearPaint) + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/archive.xml b/app/src/main/res/drawable/archive.xml new file mode 100644 index 0000000..a450808 --- /dev/null +++ b/app/src/main/res/drawable/archive.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ava.jpg b/app/src/main/res/drawable/ava.jpg new file mode 100644 index 0000000..39e174d Binary files /dev/null and b/app/src/main/res/drawable/ava.jpg differ diff --git a/app/src/main/res/drawable/avata22r.jpg b/app/src/main/res/drawable/avata22r.jpg new file mode 100644 index 0000000..0a7b687 Binary files /dev/null and b/app/src/main/res/drawable/avata22r.jpg differ diff --git a/app/src/main/res/drawable/avata2r.jpg b/app/src/main/res/drawable/avata2r.jpg new file mode 100644 index 0000000..bb9d08a Binary files /dev/null and b/app/src/main/res/drawable/avata2r.jpg differ diff --git a/app/src/main/res/drawable/avatar.png b/app/src/main/res/drawable/avatar.png new file mode 100644 index 0000000..73c7bfe Binary files /dev/null and b/app/src/main/res/drawable/avatar.png differ diff --git a/app/src/main/res/drawable/avatar4.jpg b/app/src/main/res/drawable/avatar4.jpg new file mode 100644 index 0000000..87b07b3 Binary files /dev/null and b/app/src/main/res/drawable/avatar4.jpg differ diff --git a/app/src/main/res/drawable/decoration.xml b/app/src/main/res/drawable/decoration.xml new file mode 100644 index 0000000..7d69318 --- /dev/null +++ b/app/src/main/res/drawable/decoration.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/favour.xml b/app/src/main/res/drawable/favour.xml new file mode 100644 index 0000000..e270350 --- /dev/null +++ b/app/src/main/res/drawable/favour.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/muted.xml b/app/src/main/res/drawable/muted.xml new file mode 100644 index 0000000..bd102cc --- /dev/null +++ b/app/src/main/res/drawable/muted.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/not_readed.xml b/app/src/main/res/drawable/not_readed.xml new file mode 100644 index 0000000..314a6bd --- /dev/null +++ b/app/src/main/res/drawable/not_readed.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/readed.xml b/app/src/main/res/drawable/readed.xml new file mode 100644 index 0000000..75abe9e --- /dev/null +++ b/app/src/main/res/drawable/readed.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/state_null.xml b/app/src/main/res/drawable/state_null.xml new file mode 100644 index 0000000..bdcebd7 --- /dev/null +++ b/app/src/main/res/drawable/state_null.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/verif.xml b/app/src/main/res/drawable/verif.xml new file mode 100644 index 0000000..47c07b3 --- /dev/null +++ b/app/src/main/res/drawable/verif.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/font/font.xml b/app/src/main/res/font/font.xml new file mode 100644 index 0000000..8f51456 --- /dev/null +++ b/app/src/main/res/font/font.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/font/roboto_black.ttf b/app/src/main/res/font/roboto_black.ttf new file mode 100644 index 0000000..0112e7d Binary files /dev/null and b/app/src/main/res/font/roboto_black.ttf differ diff --git a/app/src/main/res/font/roboto_light.ttf b/app/src/main/res/font/roboto_light.ttf new file mode 100644 index 0000000..e7307e7 Binary files /dev/null and b/app/src/main/res/font/roboto_light.ttf differ diff --git a/app/src/main/res/font/roboto_medium.ttf b/app/src/main/res/font/roboto_medium.ttf new file mode 100644 index 0000000..ac0f908 Binary files /dev/null and b/app/src/main/res/font/roboto_medium.ttf differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2d026df..27bc33d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,13 +1,17 @@ - - \ No newline at end of file + \ 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..370177a --- /dev/null +++ b/app/src/main/res/layout/chat_item.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f8c6127..04e744a 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #FF018786 #FF000000 #FFFFFFFF + #66A9E0 \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cf54973 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 2187cf1..1365a3f 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,6 +1,6 @@ -