From 2ec4ce4eb500b8b77a8740a2fbe8b333dd2fc748 Mon Sep 17 00:00:00 2001 From: Huawei Date: Thu, 5 Jun 2025 19:30:46 +0300 Subject: [PATCH] My homework is done --- app/build.gradle | 11 ++ .../java/otus/gpb/recyclerview/ChatAdapter.kt | 62 ++++++ .../java/otus/gpb/recyclerview/ChatItem.kt | 15 ++ .../gpb/recyclerview/ChatItemDiffCallback.kt | 13 ++ .../gpb/recyclerview/ChatItemViewHolder.kt | 6 + .../java/otus/gpb/recyclerview/CheckMark.kt | 5 + .../otus/gpb/recyclerview/CustomDecorator.kt | 52 +++++ .../otus/gpb/recyclerview/MainActivity.kt | 186 +++++++++++++++++- app/src/main/res/drawable/at.xml | 9 + .../res/drawable/background_image_view.xml | 4 + app/src/main/res/drawable/check.xml | 9 + app/src/main/res/drawable/check_all.xml | 9 + app/src/main/res/drawable/circle_blue.xml | 10 + app/src/main/res/drawable/circle_white.xml | 14 ++ app/src/main/res/drawable/food_steak.xml | 9 + app/src/main/res/drawable/package_down.xml | 9 + app/src/main/res/drawable/pin.xml | 9 + .../main/res/drawable/trash_can_outline.xml | 9 + app/src/main/res/drawable/volume_off.xml | 9 + app/src/main/res/layout/activity_main.xml | 6 +- app/src/main/res/layout/chat_item.xml | 142 +++++++++++++ app/src/main/res/values/colors.xml | 8 + 22 files changed, 603 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt create mode 100644 app/src/main/java/otus/gpb/recyclerview/ChatItem.kt create mode 100644 app/src/main/java/otus/gpb/recyclerview/ChatItemDiffCallback.kt create mode 100644 app/src/main/java/otus/gpb/recyclerview/ChatItemViewHolder.kt create mode 100644 app/src/main/java/otus/gpb/recyclerview/CheckMark.kt create mode 100644 app/src/main/java/otus/gpb/recyclerview/CustomDecorator.kt create mode 100644 app/src/main/res/drawable/at.xml create mode 100644 app/src/main/res/drawable/background_image_view.xml create mode 100644 app/src/main/res/drawable/check.xml create mode 100644 app/src/main/res/drawable/check_all.xml create mode 100644 app/src/main/res/drawable/circle_blue.xml create mode 100644 app/src/main/res/drawable/circle_white.xml create mode 100644 app/src/main/res/drawable/food_steak.xml create mode 100644 app/src/main/res/drawable/package_down.xml create mode 100644 app/src/main/res/drawable/pin.xml create mode 100644 app/src/main/res/drawable/trash_can_outline.xml create mode 100644 app/src/main/res/drawable/volume_off.xml create mode 100644 app/src/main/res/layout/chat_item.xml diff --git a/app/build.gradle b/app/build.gradle index 54e4eac..675d561 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,12 @@ android { kotlinOptions { jvmTarget = '1.8' } + buildFeatures { + viewBinding = true + } + dataBinding { + enabled = true + } } dependencies { @@ -38,6 +44,11 @@ dependencies { 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.databinding:databinding-runtime:8.10.0' + + implementation "androidx.paging:paging-runtime:3.2.0" + implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 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..1d4063b --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -0,0 +1,62 @@ +package otus.gpb.recyclerview + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import otus.gpb.recyclerview.databinding.ChatItemBinding + +class ChatAdapter: ListAdapter(ChatItemDiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatItemViewHolder { + val binding = ChatItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ChatItemViewHolder(binding) + } + + override fun onBindViewHolder(holder: ChatItemViewHolder, position: Int) { + val chatItem = getItem(position) + val binding = holder.binding + binding.textViewAuthor.setText(chatItem.author) + binding.textViewTittle.setText(chatItem.title) + binding.textViewMessage.setText(chatItem.messageLast) + binding.textViewTime.setText(chatItem.date) + when(chatItem.checkMark){ + CheckMark.SENT_CHECKED.name -> { + binding.imageViewCheck.visibility = View.GONE + binding.imageViewTwoCheck.visibility = View.VISIBLE + } + CheckMark.SENT_UNCHECKED.name -> { + binding.imageViewCheck.visibility = View.VISIBLE + binding.imageViewTwoCheck.visibility = View.GONE + } + else -> { + binding.imageViewCheck.visibility = View.GONE + binding.imageViewTwoCheck.visibility = View.GONE + } + } + if (chatItem.isMentioned){ + binding.imageViewAt.visibility = View.VISIBLE + } else { + binding.imageViewAt.visibility = View.GONE + } + if (chatItem.isPinned){ + binding.imageViewPin.visibility = View.VISIBLE + } else { + binding.imageViewPin.visibility = View.GONE + } + if (chatItem.soundIsOn){ + binding.imageViewVolume.visibility = View.GONE + } else { + binding.imageViewVolume.visibility = View.VISIBLE + } + binding.imageViewAvatar.setImageResource(chatItem.image) + } + + fun submitAntSortList(list: List){ + val sortedList = list.sortedByDescending { + it.id + it.isPinned + } + submitList(sortedList) + } +} \ 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..0c23772 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatItem.kt @@ -0,0 +1,15 @@ +package otus.gpb.recyclerview + + +data class ChatItem( + val id: Int, + val checkMark: String, + val isPinned: Boolean, + val soundIsOn: Boolean, + val isMentioned: Boolean, + val date: String, + val title: String, + val author: String, + val messageLast: String, + val image: Int, +) diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatItemDiffCallback.kt b/app/src/main/java/otus/gpb/recyclerview/ChatItemDiffCallback.kt new file mode 100644 index 0000000..ca35420 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatItemDiffCallback.kt @@ -0,0 +1,13 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.DiffUtil + +class ChatItemDiffCallback: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: ChatItem, newItem: ChatItem): Boolean { + return oldItem == newItem + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatItemViewHolder.kt b/app/src/main/java/otus/gpb/recyclerview/ChatItemViewHolder.kt new file mode 100644 index 0000000..bddbade --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatItemViewHolder.kt @@ -0,0 +1,6 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import otus.gpb.recyclerview.databinding.ChatItemBinding + +class ChatItemViewHolder(val binding: ChatItemBinding): ViewHolder(binding.root) \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/CheckMark.kt b/app/src/main/java/otus/gpb/recyclerview/CheckMark.kt new file mode 100644 index 0000000..27c672b --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/CheckMark.kt @@ -0,0 +1,5 @@ +package otus.gpb.recyclerview + +enum class CheckMark { + SENT_CHECKED, SENT_UNCHECKED, NOTHING_WAS_SENT +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/CustomDecorator.kt b/app/src/main/java/otus/gpb/recyclerview/CustomDecorator.kt new file mode 100644 index 0000000..b888157 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/CustomDecorator.kt @@ -0,0 +1,52 @@ +package otus.gpb.recyclerview + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.util.TypedValue +import android.view.View +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ItemDecoration + +class CustomDecorator(context: Context): ItemDecoration() { + + + private val bounds = Rect() + private val paint = Paint().apply { + color = ContextCompat.getColor(context, R.color.gray_light) + strokeWidth = 1f + } + val leftMarginInPx = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 78f, + context.resources.displayMetrics + ).toInt() + + override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(c, parent, state) + + val childCount = parent.childCount + for (i in 0 until childCount) { + val child = parent.getChildAt(i) + parent.getDecoratedBoundsWithMargins(child, bounds) + val positionCurrent = parent.getChildAdapterPosition(child) + if (positionCurrent != RecyclerView.NO_POSITION) { + val lastElementPosition = parent.adapter?.itemCount?.minus(1) + if (positionCurrent != lastElementPosition) { + c.drawLine( + leftMarginInPx.toFloat(), + bounds.bottom.toFloat(), + bounds.right.toFloat(), + bounds.bottom.toFloat(), + paint, + ) + } + } + } + } +} + + + diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e2cdca7..120d0cb 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -1,12 +1,194 @@ package otus.gpb.recyclerview -import androidx.appcompat.app.AppCompatActivity +import android.graphics.Canvas import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator +import otus.gpb.recyclerview.databinding.ActivityMainBinding +import kotlin.random.Random + class MainActivity : AppCompatActivity() { + private lateinit var adapter: ChatAdapter + private var currentPage = 1 + private val pageSize = 30 + var id = 1 + var previousTotalItemCount = 0 + private var isLoading = false + private var chatList: MutableList = mutableListOf() + private val archiveList: MutableList = mutableListOf() + private val binding: ActivityMainBinding by lazy { + ActivityMainBinding.inflate(layoutInflater) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + setContentView(binding.root) + initRecyclerView() + setFirstChats() + generateRandomChats(pageSize) + setScrolling() + } + + + private fun setScrolling(){ + 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 totalItemCount = layoutManager.itemCount + val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() + if (isLoading && totalItemCount > previousTotalItemCount) { + isLoading = false + previousTotalItemCount = totalItemCount + } + if (!isLoading && lastVisibleItem >= totalItemCount - 5) { + Log.d("MainActivity", currentPage.toString()) + loadNextPage() + } + } + }) + } + private fun removeItem(position: Int){ + val itemToDelete = adapter.currentList[position] + val newList = chatList.apply { + remove(itemToDelete) + } + adapter.submitAntSortList(newList) + } + + private fun setFirstChats(){ + chatList = generateRandomChats(currentPage).toMutableList() + adapter.submitAntSortList(chatList) + } + + private fun addInArchiveList(position: Int){ + val item = adapter.currentList[position] + archiveList.add(item) + } + + private fun loadNextPage() { + isLoading = true + val newChats = generateRandomChats(pageSize) + chatList.addAll(newChats) + adapter.submitAntSortList(chatList.toList()) + currentPage++ + } + + private fun generateRandomChats(size: Int): List{ + val listRandomChats = mutableListOf() + for (i in 1..size) { + listRandomChats.add( + ChatItem( + id++, + getRandomCheckMark(), + true, + Random.nextBoolean(), + Random.nextBoolean(), + "12:10", + "Just design", + "Nicolay", + "I want pizza", + R.drawable.food_steak + ) + ) + } + return listRandomChats + } + + private fun getRandomCheckMark(): String { + val values = CheckMark.values().toList() + val randomNumber = Random.nextInt(3) + return values[randomNumber].name + } + + private fun initRecyclerView() { + adapter = ChatAdapter() + binding.recyclerView.adapter = adapter + binding.recyclerView.addItemDecoration(CustomDecorator(this)) + val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + 0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return false + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val position = viewHolder.adapterPosition + when(direction){ + ItemTouchHelper.LEFT -> { + addInArchiveList(position) + removeItem(position) + Log.d("MainActivity", archiveList.toString()) + } + ItemTouchHelper.RIGHT -> { + removeItem(position) + } + } + } + + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + RecyclerViewSwipeDecorator.Builder( + c, + recyclerView, + viewHolder, + dX, + dY, + actionState, + isCurrentlyActive + ) + .addSwipeRightBackgroundColor( + ContextCompat.getColor( + this@MainActivity, + R.color.red + ) + ) + .addSwipeRightActionIcon(R.drawable.trash_can_outline) + .addSwipeRightLabel("Delete") + .setSwipeRightLabelColor( + ContextCompat.getColor( + this@MainActivity, + R.color.white + ) + ) + .addSwipeLeftBackgroundColor( + ContextCompat.getColor( + this@MainActivity, + R.color.blue + ) + ) + .addSwipeLeftActionIcon(R.drawable.package_down) + .addSwipeLeftLabel("Archive") + .setSwipeLeftLabelColor( + ContextCompat.getColor( + this@MainActivity, + R.color.white + ) + ) + .create() + .decorate() + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + } + }) + itemTouchHelper.attachToRecyclerView(binding.recyclerView) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/at.xml b/app/src/main/res/drawable/at.xml new file mode 100644 index 0000000..2322b5f --- /dev/null +++ b/app/src/main/res/drawable/at.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/background_image_view.xml b/app/src/main/res/drawable/background_image_view.xml new file mode 100644 index 0000000..84bb266 --- /dev/null +++ b/app/src/main/res/drawable/background_image_view.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/check.xml b/app/src/main/res/drawable/check.xml new file mode 100644 index 0000000..c849be3 --- /dev/null +++ b/app/src/main/res/drawable/check.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/check_all.xml b/app/src/main/res/drawable/check_all.xml new file mode 100644 index 0000000..0a8cb7d --- /dev/null +++ b/app/src/main/res/drawable/check_all.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/circle_blue.xml b/app/src/main/res/drawable/circle_blue.xml new file mode 100644 index 0000000..efb60dd --- /dev/null +++ b/app/src/main/res/drawable/circle_blue.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_white.xml b/app/src/main/res/drawable/circle_white.xml new file mode 100644 index 0000000..5999953 --- /dev/null +++ b/app/src/main/res/drawable/circle_white.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/food_steak.xml b/app/src/main/res/drawable/food_steak.xml new file mode 100644 index 0000000..493a5cd --- /dev/null +++ b/app/src/main/res/drawable/food_steak.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/package_down.xml b/app/src/main/res/drawable/package_down.xml new file mode 100644 index 0000000..9240df0 --- /dev/null +++ b/app/src/main/res/drawable/package_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/pin.xml b/app/src/main/res/drawable/pin.xml new file mode 100644 index 0000000..254bae6 --- /dev/null +++ b/app/src/main/res/drawable/pin.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/trash_can_outline.xml b/app/src/main/res/drawable/trash_can_outline.xml new file mode 100644 index 0000000..0d2840f --- /dev/null +++ b/app/src/main/res/drawable/trash_can_outline.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/volume_off.xml b/app/src/main/res/drawable/volume_off.xml new file mode 100644 index 0000000..7777c06 --- /dev/null +++ b/app/src/main/res/drawable/volume_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2d026df..4f91270 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -3,11 +3,15 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@color/white" + xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".MainActivity"> + android:layout_height="match_parent" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/chat_item"/> \ 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..785a06c --- /dev/null +++ b/app/src/main/res/layout/chat_item.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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..aa87d2b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,5 +6,13 @@ #FF03DAC5 #FF018786 #FF000000 + #474747 #FFFFFFFF + #878787 + #CCCCCC + #BDBDBD + #737373 + #0FABDB + #056F00 + #CA0909 \ No newline at end of file