diff --git a/app/build.gradle b/app/build.gradle index 54e4eac..3302ed5 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 { @@ -41,4 +47,6 @@ 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 'androidx.databinding:databinding-runtime:8.10.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..a5c91aa --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -0,0 +1,47 @@ +package otus.gpb.recyclerview + +import android.content.res.ColorStateList +import android.graphics.Color +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import otus.gpb.recyclerview.databinding.ChatItemBinding +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import kotlin.random.Random + +class ChatAdapter: ListAdapter(ChatDiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder { + val binding = ChatItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ChatViewHolder(binding) + } + + override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { + val chatItem = getItem(position) + val binding = holder.binding + + when (chatItem.id % 5) { + 0-> binding.imageAvatar.setImageResource(R.drawable.avatar_1) + 1-> binding.imageAvatar.setImageResource(R.drawable.avatar_2) + 2-> binding.imageAvatar.setImageResource(R.drawable.avatar_3) + 3-> binding.imageAvatar.setImageResource(R.drawable.avatar_4) + 4-> binding.imageAvatar.setImageResource(R.drawable.avatar_5) + else -> binding.imageAvatar.setImageResource(R.drawable.avatar_1) + } + + binding.title.setText(chatItem.title) + binding.author.setText(chatItem.author) + binding.message.setText(chatItem.message) + if (chatItem.author == "...typing") binding.message.visibility = INVISIBLE + binding.verifiedIcon.visibility = if (chatItem.verifiedIcon) VISIBLE else INVISIBLE + binding.muteIcon.visibility = if (chatItem.muteIcon) VISIBLE else INVISIBLE + binding.scamPatch.visibility = if (chatItem.scamPatch) VISIBLE else INVISIBLE + binding.counter.visibility = if (chatItem.counter > 0) VISIBLE else INVISIBLE + binding.counter.text = if (binding.counter.visibility == VISIBLE) chatItem.counter.toString() else "" + if (Random.nextBoolean()) binding.counter.backgroundTintList = ColorStateList.valueOf(Color.parseColor("#C5C9CC")) + binding.checkIcon.visibility = if (chatItem.check && !chatItem.read) VISIBLE else INVISIBLE + binding.readIcon.visibility = if (!chatItem.check && chatItem.read) VISIBLE else INVISIBLE + binding.time.setText(chatItem.time) + } + } diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatDiffCallback.kt b/app/src/main/java/otus/gpb/recyclerview/ChatDiffCallback.kt new file mode 100644 index 0000000..2721371 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatDiffCallback.kt @@ -0,0 +1,13 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.DiffUtil + +class ChatDiffCallback: 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 + } +} 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..6f54af9 --- /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 title: String, + val author: String, + val message: String, + val verifiedIcon: Boolean, + val muteIcon: Boolean, + val scamPatch: Boolean, + val counter: Int, + val check: Boolean, + val read: Boolean, + val time: String, +) diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt b/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt new file mode 100644 index 0000000..23c42b3 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatViewHolder.kt @@ -0,0 +1,6 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import otus.gpb.recyclerview.databinding.ChatItemBinding + +class ChatViewHolder(val binding: ChatItemBinding): ViewHolder(binding.root) diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e2cdca7..2fabfba 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -2,11 +2,126 @@ package otus.gpb.recyclerview import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import otus.gpb.recyclerview.databinding.ActivityMainBinding +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator +import android.graphics.Canvas +import androidx.recyclerview.widget.LinearLayoutManager +import kotlin.random.Random class MainActivity : AppCompatActivity() { + private val binding: ActivityMainBinding by lazy { + ActivityMainBinding.inflate(layoutInflater) + } + private var chatList: MutableList = mutableListOf() + private lateinit var adapter: ChatAdapter + var id = 0 + var previousItemsCount = 0 + private var pageLoad = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + setContentView(binding.root) + adapter = ChatAdapter() + binding.recyclerView.adapter = adapter + + 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 -> { + removeItem(position) + } + 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 + ).create().decorate() + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + } + }) + itemTouchHelper.attachToRecyclerView(binding.recyclerView) + createItems(100) + setScrollListener() + } + + private fun setScrollListener(){ + 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 itemsCount = layoutManager.itemCount + val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() + if (pageLoad && itemsCount > previousItemsCount) { + pageLoad = false + previousItemsCount = itemsCount + } + if (!pageLoad && lastVisibleItem >= itemsCount - 5) { + getNextPage() + } + } + }) + } + + + private fun getNextPage() { + pageLoad = true + createItems(100) + } + + private fun removeItem(position: Int){ + val item = adapter.currentList[position] + chatList.apply { + remove(item) + } + adapter.notifyItemRemoved(position) + } + + private fun createItems(size: Int){ + val title = arrayOf("Dima Murantsev" , "SnejUgal News","Catbird","just design", "R4IN80W", "Yes. No") + val author = arrayOf("Nikolay", "Anno", "You", "...typing"," ") + val message = arrayOf("you are welcome :)", "F", "I want pizza", "du biest mein sonnechein", "I see all :))", "That`s better") + for (i in 1..size) { + chatList.add( + ChatItem(id++,title[Random.nextInt(title.size)],author[Random.nextInt(author.size)],message[Random.nextInt(author.size)], + Random.nextBoolean(), Random.nextBoolean(), Random.nextBoolean(), Random.nextInt(100), + Random.nextBoolean(),Random.nextBoolean(), + Random.nextInt(24).toString() + ":" + (Random.nextInt(60).toString().padStart(2, '0')) + ) + ) + } + adapter.submitList(chatList) + adapter.notifyDataSetChanged() } } \ No newline at end of file diff --git a/app/src/main/res/drawable/avatar_1.png b/app/src/main/res/drawable/avatar_1.png new file mode 100644 index 0000000..078d44f Binary files /dev/null and b/app/src/main/res/drawable/avatar_1.png differ diff --git a/app/src/main/res/drawable/avatar_2.png b/app/src/main/res/drawable/avatar_2.png new file mode 100644 index 0000000..ba2eebd Binary files /dev/null and b/app/src/main/res/drawable/avatar_2.png differ diff --git a/app/src/main/res/drawable/avatar_3.png b/app/src/main/res/drawable/avatar_3.png new file mode 100644 index 0000000..28455e6 Binary files /dev/null and b/app/src/main/res/drawable/avatar_3.png differ diff --git a/app/src/main/res/drawable/avatar_4.png b/app/src/main/res/drawable/avatar_4.png new file mode 100644 index 0000000..da371d6 Binary files /dev/null and b/app/src/main/res/drawable/avatar_4.png differ diff --git a/app/src/main/res/drawable/avatar_5.png b/app/src/main/res/drawable/avatar_5.png new file mode 100644 index 0000000..f7937e8 Binary files /dev/null and b/app/src/main/res/drawable/avatar_5.png differ diff --git a/app/src/main/res/drawable/check_icon.png b/app/src/main/res/drawable/check_icon.png new file mode 100644 index 0000000..f2fc178 Binary files /dev/null and b/app/src/main/res/drawable/check_icon.png differ diff --git a/app/src/main/res/drawable/counter.png b/app/src/main/res/drawable/counter.png new file mode 100644 index 0000000..7027076 Binary files /dev/null and b/app/src/main/res/drawable/counter.png differ diff --git a/app/src/main/res/drawable/mute_icon.png b/app/src/main/res/drawable/mute_icon.png new file mode 100644 index 0000000..5da8244 Binary files /dev/null and b/app/src/main/res/drawable/mute_icon.png differ diff --git a/app/src/main/res/drawable/read_icon.png b/app/src/main/res/drawable/read_icon.png new file mode 100644 index 0000000..9349ced Binary files /dev/null and b/app/src/main/res/drawable/read_icon.png differ diff --git a/app/src/main/res/drawable/scam_patch.png b/app/src/main/res/drawable/scam_patch.png new file mode 100644 index 0000000..fe61146 Binary files /dev/null and b/app/src/main/res/drawable/scam_patch.png differ diff --git a/app/src/main/res/drawable/verified_icon.png b/app/src/main/res/drawable/verified_icon.png new file mode 100644 index 0000000..5cf7f76 Binary files /dev/null and b/app/src/main/res/drawable/verified_icon.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2d026df..14b87f2 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..dd9f930 --- /dev/null +++ b/app/src/main/res/layout/chat_item.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file