From ba3b4bb62e84029267e67be8e54634e7a196bd02 Mon Sep 17 00:00:00 2001 From: Evgenii Balandin Date: Mon, 13 Jan 2025 11:18:26 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=92=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D0=B5=D0=B5=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20RecyclerView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 14 +- app/src/main/AndroidManifest.xml | 8 + .../main/java/otus/gpb/recyclerview/Chat.kt | 132 +++++ .../java/otus/gpb/recyclerview/ChatAdapter.kt | 152 ++++++ .../otus/gpb/recyclerview/MainActivity.kt | 348 +++++++++++++- .../ResizeToMinSideTransformation.kt | 39 ++ app/src/main/res/drawable/circle_border.xml | 6 + .../res/drawable/circle_border_blue_20.xml | 6 + .../res/drawable/circle_border_silver_7.xml | 6 + app/src/main/res/drawable/ic_archive.xml | 4 + app/src/main/res/drawable/ic_at.xml | 41 ++ .../main/res/drawable/ic_blue_verified.xml | 10 + app/src/main/res/drawable/ic_bubles.xml | 33 ++ app/src/main/res/drawable/ic_green_check.xml | 10 + .../res/drawable/ic_green_double_check.xml | 10 + app/src/main/res/drawable/ic_lock.xml | 10 + app/src/main/res/drawable/ic_main_menu.xml | 10 + app/src/main/res/drawable/ic_mute.xml | 10 + app/src/main/res/drawable/ic_pinned.xml | 24 + .../res/drawable/ic_placeholder_avatar.xml | 10 + app/src/main/res/drawable/ic_scam.xml | 17 + .../main/res/drawable/ic_status_speech.xml | 39 ++ .../res/drawable/recycler_view_devider.xml | 18 + .../main/res/drawable/rounded_background.xml | 5 + app/src/main/res/layout/activity_main.xml | 48 +- app/src/main/res/layout/item_chat.xml | 449 ++++++++++++++++++ app/src/main/res/menu/toolbar_menu.xml | 11 + app/src/main/res/values-night/themes.xml | 88 +++- app/src/main/res/values/attrs.xml | 7 + app/src/main/res/values/colors.xml | 176 +++++++ app/src/main/res/values/strings.xml | 26 + app/src/main/res/values/themes.xml | 144 +++++- .../otus/gpb/recyclerview/ExampleUnitTest.kt | 16 +- build.gradle | 3 +- gradle.properties | 5 +- 35 files changed, 1896 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/otus/gpb/recyclerview/Chat.kt create mode 100644 app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt create mode 100644 app/src/main/java/otus/gpb/recyclerview/ResizeToMinSideTransformation.kt create mode 100644 app/src/main/res/drawable/circle_border.xml create mode 100644 app/src/main/res/drawable/circle_border_blue_20.xml create mode 100644 app/src/main/res/drawable/circle_border_silver_7.xml create mode 100644 app/src/main/res/drawable/ic_archive.xml create mode 100644 app/src/main/res/drawable/ic_at.xml create mode 100644 app/src/main/res/drawable/ic_blue_verified.xml create mode 100644 app/src/main/res/drawable/ic_bubles.xml create mode 100644 app/src/main/res/drawable/ic_green_check.xml create mode 100644 app/src/main/res/drawable/ic_green_double_check.xml create mode 100644 app/src/main/res/drawable/ic_lock.xml create mode 100644 app/src/main/res/drawable/ic_main_menu.xml create mode 100644 app/src/main/res/drawable/ic_mute.xml create mode 100644 app/src/main/res/drawable/ic_pinned.xml create mode 100644 app/src/main/res/drawable/ic_placeholder_avatar.xml create mode 100644 app/src/main/res/drawable/ic_scam.xml create mode 100644 app/src/main/res/drawable/ic_status_speech.xml create mode 100644 app/src/main/res/drawable/recycler_view_devider.xml create mode 100644 app/src/main/res/drawable/rounded_background.xml create mode 100644 app/src/main/res/layout/item_chat.xml create mode 100644 app/src/main/res/menu/toolbar_menu.xml create mode 100644 app/src/main/res/values/attrs.xml diff --git a/app/build.gradle b/app/build.gradle index 54e4eac..09efae7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,10 +26,16 @@ android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 + + } kotlinOptions { jvmTarget = '1.8' } + + viewBinding { + enabled = true + } } dependencies { @@ -38,7 +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' - testImplementation 'junit:junit:4.13.2' + //implementation 'com.github.telegram:telegram-design-system:7.8.1' + implementation 'com.github.bumptech.glide:glide:4.15.1' + //kapt 'com.github.bumptech.glide:compiler:4.15.1' // For annotation processing if required testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} \ No newline at end of file +} + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ef75335..04f2779 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/Chat.kt b/app/src/main/java/otus/gpb/recyclerview/Chat.kt new file mode 100644 index 0000000..55467c0 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/Chat.kt @@ -0,0 +1,132 @@ +package otus.gpb.recyclerview + + +// Parent interface +interface Chat { + val id: Int + val lastUserName: String + val lastMessage: String + val time: String + val isVerified: Boolean + val isScam: Boolean + val unreadMessageCount: Int + val avatarUrl: String? + val lastAnswererUrl: String? + val isPinned: Boolean + var isMute: Boolean + var isSpeeching: Boolean + var isTyping: Boolean + var isUnreadedAnswerToYou: Boolean + var isAnswered: Boolean + var isOpponnentReaded: Boolean + var isLock: Boolean + + + fun isRead(): Boolean { return unreadMessageCount == 0 } + + // Optionally, we can add the equals method in the interface itself + // But it should only be used in specific cases, not common for all types + override fun equals(other: Any?): Boolean +} + +// Child class for group chat +data class GroupChat( + override val id: Int, + val title: String, + override val lastUserName: String, + override val lastMessage: String, + override val time: String, + override val isVerified: Boolean = true, + override val isScam: Boolean = false, + override val unreadMessageCount: Int = 0, + override val avatarUrl: String? = null, + override val lastAnswererUrl: String? = null, + override val isPinned: Boolean = false, + override var isMute: Boolean = false, + override var isSpeeching: Boolean = false, + override var isTyping: Boolean = false, + override var isUnreadedAnswerToYou: Boolean = false, + override var isAnswered: Boolean = false, + override var isOpponnentReaded: Boolean = false, + override var isLock: Boolean = false + + +) : Chat { + + // Custom equals implementation for GroupChat + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GroupChat) return false + return id == other.id && + lastUserName == other.lastUserName && + lastMessage == other.lastMessage && + time == other.time && + isVerified == other.isVerified && + isScam == other.isScam && + unreadMessageCount == other.unreadMessageCount && + avatarUrl == other.avatarUrl && + lastAnswererUrl == other.lastAnswererUrl && + isPinned == other.isPinned && + isMute == other.isMute && + isSpeeching == other.isSpeeching && + isTyping == other.isTyping && + isUnreadedAnswerToYou == other.isUnreadedAnswerToYou && + isAnswered == other.isAnswered && + isOpponnentReaded == other.isOpponnentReaded && + isLock == other.isLock + } + + override fun hashCode(): Int { + return id.hashCode() + } +} + +// Child class for user chat +data class UserChat( + override val id: Int, + override val lastUserName: String, + override val lastMessage: String, + override val time: String, + override val isVerified: Boolean = true, + override val isScam: Boolean = false, + override val unreadMessageCount: Int = 0, + override val avatarUrl: String? = null, + override val lastAnswererUrl: String? = null, + override val isPinned: Boolean = false, + override var isMute: Boolean = false, + override var isSpeeching: Boolean = false, + override var isTyping: Boolean = false, + override var isUnreadedAnswerToYou: Boolean = false, + override var isAnswered: Boolean = false, + override var isOpponnentReaded: Boolean = false, + override var isLock: Boolean = false +) : Chat { + + // Custom equals implementation for UserChat + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is UserChat) return false + return id == other.id && + lastUserName == other.lastUserName && + lastMessage == other.lastMessage && + time == other.time && + isVerified == other.isVerified && + isScam == other.isScam && + unreadMessageCount == other.unreadMessageCount && + avatarUrl == other.avatarUrl && + lastAnswererUrl == other.lastAnswererUrl && + isPinned == other.isPinned && + isMute == other.isMute && + isSpeeching == other.isSpeeching && + isTyping == other.isTyping && + isUnreadedAnswerToYou == other.isUnreadedAnswerToYou && + isAnswered == other.isAnswered && + isOpponnentReaded == other.isOpponnentReaded && + isLock == other.isLock + } + + override fun hashCode(): Int { + return id.hashCode() + } +} + 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..ac88d03 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -0,0 +1,152 @@ +package otus.gpb.recyclerview + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CircleCrop +import otus.gpb.recyclerview.databinding.ItemChatBinding + + +class ChatAdapter( + //private val onDeleteChat: (Chat) -> Unit, + private val onArchiveChat: (Chat) -> Unit // Новый callback для архивирования +) : ListAdapter(ChatDiffCallback()) { + + inner class ChatViewHolder(private val binding: ItemChatBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(chat: Chat) { + // Установка данных в зависимости от типа чата + when (chat) { + is GroupChat -> { + binding.chatTopic.text = chat.title + binding.chatLastUser.text = chat.lastUserName + binding.chatLastUser.visibility = View.VISIBLE + } + is UserChat -> { + binding.chatTopic.text = chat.lastUserName + binding.chatLastUser.visibility = View.GONE + } + } + + binding.chatLastMessage.text = chat.lastMessage + binding.chatTime.text = chat.time + + // Отображение иконок статусов + binding.speechingStatusIcon.visibility = if (chat.isSpeeching) View.VISIBLE else View.GONE + binding.muteChannelIcon.visibility = if (chat.isMute) View.VISIBLE else View.GONE + + binding.lockIcon.visibility = if (chat.isLock) View.VISIBLE else View.GONE + binding.scamIcon.visibility = if (chat.isScam) View.VISIBLE else View.GONE + + binding.verifiedIcon.visibility = if (chat.isVerified) View.VISIBLE else View.GONE + + binding.unreadCount.visibility = if (chat.unreadMessageCount > 0) View.VISIBLE else View.GONE + chat.unreadMessageCount.toString().also { binding.unreadCount.text = it } + + binding.typingIcon.visibility = if (chat.isTyping) View.VISIBLE else View.GONE + binding.typing.visibility = if (chat.isTyping) View.VISIBLE else View.GONE + binding.chatLastMessage.visibility = if (chat.isTyping) View.GONE else View.VISIBLE + + binding.answeredToYouStatus.visibility = if (chat.isUnreadedAnswerToYou) View.VISIBLE else View.GONE + + binding.answeredIcon.visibility = if (chat.isAnswered && !chat.isOpponnentReaded) View.VISIBLE else View.GONE + binding.answeredAndReadedIcon.visibility = if (chat.isAnswered && chat.isOpponnentReaded) View.VISIBLE else View.GONE + binding.pinnedStatus.visibility = if (chat.isPinned) View.VISIBLE else View.GONE + + val targetSize = 500 // Размер в пикселях, до которого нужно увеличить изображение + // Загрузка аватара + if (chat.avatarUrl != null) { + Glide.with(binding.chatAvatar.context) + .load(chat.avatarUrl) + .transform(ResizeAndCropTransformation(targetSize), CircleCrop()) // Обрезаем и применяем круг + .into(binding.chatAvatar) + } else { + binding.chatAvatar.setImageResource(R.drawable.ic_placeholder_avatar) + } + + // Загрузка аватара ответившего + if (chat.lastAnswererUrl != null) { + Glide.with(binding.answererIcon.context) + .load(chat.lastAnswererUrl) + //.circleCrop() + .into(binding.answererIcon) + binding.answererIcon.visibility = View.VISIBLE + } else { + binding.answererIcon.setImageResource(android.R.color.transparent) + binding.answererIcon.visibility = View.GONE + } + + +// // Обработчик длинного нажатия для удаления +// binding.root.setOnLongClickListener { +// val position = adapterPosition +// if (position != RecyclerView.NO_POSITION) { +// val chatObj = currentList[position] +// onDeleteChat(chatObj) +// } +// true +// } + + // Обработчик нажатия на кнопку архивирования + binding.archiveButton.setOnClickListener { + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val chatObj = currentList[position] + // Обработка архивации + onArchiveChat(chatObj) + // Скрыть шторку + val archiveLayout = binding.root.findViewById(R.id.archiveButtonLayout) + archiveLayout.visibility = View.GONE + notifyItemChanged(position) + } + } + + + binding.root.setOnClickListener { + val archiveLayout = binding.root.findViewById(R.id.archiveButtonLayout) + val itemLayout = binding.root.findViewById(R.id.chatItemLayout) + if (archiveLayout.visibility == View.VISIBLE) { + //archiveLayout.visibility = View.GONE + //notifyItemChanged(adapterPosition) + + // Анимация возврата в исходное положение + itemLayout.animate() + .translationX(0f) + .setDuration(500) + .withEndAction { + // После завершения анимации скрываем шторку + archiveLayout.visibility = View.GONE + // Обновляем элемент только после завершения анимации + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + notifyItemChanged(position) + } + } + .start() + + } + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ItemChatBinding.inflate(inflater, parent, false) + return ChatViewHolder(binding) + } + + override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} + +class ChatDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean = oldItem.id == newItem.id + override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean = oldItem == newItem +} diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e2cdca7..f694f9a 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -1,12 +1,358 @@ package otus.gpb.recyclerview -import androidx.appcompat.app.AppCompatActivity +import android.graphics.Canvas import android.os.Bundle +import android.view.View +import android.widget.FrameLayout +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlin.math.abs class MainActivity : AppCompatActivity() { + private lateinit var chatAdapter: ChatAdapter + private var itemTouchHelper: ItemTouchHelper? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + val recyclerView: RecyclerView = findViewById(R.id.recycler_view) + + val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL) + divider.setDrawable(ContextCompat.getDrawable(this, R.drawable.recycler_view_devider)!!) + recyclerView.addItemDecoration(divider) + + chatAdapter = ChatAdapter( +// onDeleteChat = { chat -> +// val updatedList = chatAdapter.currentList.toMutableList() +// updatedList.remove(chat) +// chatAdapter.submitList(updatedList) +// }, + onArchiveChat = { chat -> + // Обработка архивирования + val updatedList = chatAdapter.currentList.toMutableList() + updatedList.remove(chat) + chatAdapter.submitList(updatedList) + Toast.makeText(this, "$chat archived", Toast.LENGTH_SHORT).show() + } + ) + + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = chatAdapter + + +// // Set up ItemTouchHelper for swipe actions +// val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( +// ItemTouchHelper.UP or ItemTouchHelper.DOWN, +// ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT +// ) { +// override fun onMove( +// recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder +// ): Boolean = false +// +// override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { +// val position = viewHolder.adapterPosition +// //val chat = chatAdapter.getItem(position) +// // Handle swipe action, e.g., delete chat +// //chatAdapter.onDeleteChat(chat) +// +// +// //val itemLayout = viewHolder.itemView.findViewById(R.id.chatItemLayout) +// val archiveLayout = viewHolder.itemView.findViewById(R.id.archiveButtonLayout) +// +// // Показать шторку с архивом при свайпе +// archiveLayout.visibility = View.VISIBLE +// +// // Сдвигаем основной элемент (chatItemLayout) влево, чтобы показать архив +// //itemLayout.animate().translationX(-itemLayout.width.toFloat()).setDuration(200).start() +// +// // Кнопка архивирования +// val archiveButton = viewHolder.itemView.findViewById(R.id.archiveButton) +// archiveButton.setOnClickListener { +// // Обработать архивирование +// //onArchiveChat(chat) +// archiveLayout.visibility = View.GONE // Скрыть архивный элемент +// } +// } +// +// override fun isItemViewSwipeEnabled(): Boolean = true +// } + + //val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback) + //itemTouchHelper.attachToRecyclerView(recyclerView) + + val itemTouchHelperCallback = object : ItemTouchHelper.Callback() { + + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + // Позволяем свайпы влево + return makeMovementFlags(0, ItemTouchHelper.LEFT) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + // Перемещение элементов отключено + return false + } + +// override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { +// itemTouchHelper?.attachToRecyclerView(null) +// itemTouchHelper?.attachToRecyclerView(recyclerView) +// +// val archiveLayout = viewHolder.itemView.findViewById(R.id.archiveButtonLayout) +// val itemLayout = viewHolder.itemView.findViewById(R.id.chatItemLayout) +// +// val position = viewHolder.adapterPosition +// if (position == RecyclerView.NO_POSITION) return +// +// if (direction == ItemTouchHelper.LEFT && archiveLayout.visibility != View.VISIBLE) { +// +// +// // Проверяем расстояние свайпа +// val translationX = abs(viewHolder.itemView.translationX); +// if (translationX >= viewHolder.itemView.width * 0.8) { +// +// // Длинный свайп: Удаление +// //val position = viewHolder.adapterPosition +// val chatObj = chatAdapter.currentList[position]; +// val updatedList = chatAdapter.currentList.toMutableList() +// updatedList.remove(chatObj) +// chatAdapter.submitList(updatedList) +// //val item: String = chatAdapter.getData().get(position) +// //chatAdapter.removeItem(position) +// Toast.makeText(this@MainActivity, "Deleted: $chatObj", Toast.LENGTH_SHORT).show() +// chatAdapter.notifyItemRemoved(position) +// } else { +// // Показать шторку +// archiveLayout.visibility = View.VISIBLE +// itemLayout.animate().translationX(-archiveLayout.width.toFloat()).setDuration(500).start() +// } +// +// // Сбросить состояние ViewHolder после анимации +// //itemLayout.postDelayed({ +// //itemTouchHelper.attachToRecyclerView(null) +// //chatAdapter.notifyItemChanged(position) +// //itemTouchHelper.attachToRecyclerView(recyclerView) +// //}, 500) +// } +// +//// // Сброс состояния +//// itemLayout.postDelayed({ +//// archiveLayout.visibility = View.GONE +//// itemLayout.animate().translationX(0f).setDuration(1000).start() +//// chatAdapter.notifyItemChanged(position) +//// }, 3000) +// } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val position = viewHolder.adapterPosition + if (position == RecyclerView.NO_POSITION) return + + if (direction == ItemTouchHelper.LEFT) { + // Длинный свайп (удаление) + val updatedList = chatAdapter.currentList.toMutableList() + updatedList.removeAt(position) + chatAdapter.submitList(updatedList) + Toast.makeText(this@MainActivity, "Deleted item at position $position", Toast.LENGTH_SHORT).show() + } + } + + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + val itemView = viewHolder.itemView + + // Получаем смещение dX (оно будет отрицательным для свайпа влево) + val translationX = abs(dX) + + // Устанавливаем шторку в видимое состояние, если свайп достаточно длинный + val archiveLayout = itemView.findViewById(R.id.archiveButtonLayout) + if (translationX >= itemView.width * 0.8f) { + // Длинный свайп (удаление) + archiveLayout.visibility = View.GONE + } else { + // Полусвайп (показ шторки) + archiveLayout.visibility = View.VISIBLE + + } + + // Выполняем стандартный рендеринг + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + } + + +// override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { +// super.clearView(recyclerView, viewHolder) +// // Возвращаем элемент в исходное положение +// val archiveLayout = viewHolder.itemView.findViewById(R.id.archiveButtonLayout) +// val itemLayout = viewHolder.itemView.findViewById(R.id.chatItemLayout) +// //archiveLayout.visibility = View.GONE +// // Скрываем шторку и сбрасываем позицию +// if (archiveLayout.visibility != View.GONE) { +// archiveLayout.visibility = View.GONE +// // itemLayout.translationX = 0f +// itemLayout.animate().translationX(0f).setDuration(500).start() +// +// val position = viewHolder.adapterPosition +// // Сбросить состояние ViewHolder после анимации +// itemLayout.postDelayed({ +// //itemTouchHelper.attachToRecyclerView(null) +// chatAdapter.notifyItemChanged(position) +// //itemTouchHelper.attachToRecyclerView(recyclerView) +// }, 500) +// +// +// //val position = viewHolder.adapterPosition +// //chatAdapter.notifyItemChanged(position) +// } +// +// } + + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + + val archiveLayout = viewHolder.itemView.findViewById(R.id.archiveButtonLayout) + val itemLayout = viewHolder.itemView.findViewById(R.id.chatItemLayout) + + // Проверяем, видима ли шторка + if (archiveLayout.visibility != View.GONE) { + // Анимация возврата в исходное положение + // Сброс состояния + itemLayout.postDelayed({ + itemLayout.animate() + .translationX(0f) + .setDuration(500) + .withEndAction { + // После завершения анимации скрываем шторку + archiveLayout.visibility = View.GONE + + // Обновляем элемент только после завершения анимации + val position = viewHolder.adapterPosition + if (position != RecyclerView.NO_POSITION) { + chatAdapter.notifyItemChanged(position) + } + } + .start() + }, 3000) + + } + } + + override fun isItemViewSwipeEnabled(): Boolean = true + } + + itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback) + itemTouchHelper?.attachToRecyclerView(recyclerView) + + // Загрузить данные + chatAdapter.submitList(loadChats()) } + +} + + +private fun loadChats(): List { + return listOf( + UserChat( + id = 1, + lastUserName = "Alice", + lastMessage = "Hi there!", + time = "12:30", + isVerified = true, + avatarUrl = "https://gravatar.com/avatar/478f6b94ddcde12416b90f22d7588cab?s=400&d=robohash&r=x", + isScam = false, + unreadMessageCount = 0, + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isPinned = true, + isOpponnentReaded = false, + isAnswered = true + ), + GroupChat( + id = 2, + title = "Group topic", + lastUserName = "Bob", + lastMessage = "How are you?", + time = "11:15", + isVerified = false, + avatarUrl = "https://gravatar.com/avatar/1ae9a8dbb50baebe44d7b96d012a73d5?s=400&d=robohash&r=x", + isScam = true, + unreadMessageCount = 0, + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = true, + isAnswered = true, + isLock = true, + lastAnswererUrl = "https://i.pinimg.com/736x/c9/fe/fb/c9fefb489d5fdc792ae324103255edd2.jpg" + + ), + UserChat( + id = 3, + lastUserName = "Charlie", + lastMessage = "Meeting at 3?", + time = "Yesterday", + isVerified = true, + avatarUrl = "https://gravatar.com/avatar/f07d785a9ee32506a7a077f25466f98b?s=400&d=robohash&r=x", + isScam = false, + unreadMessageCount = 11, + isMute = true, + isSpeeching = true, + isTyping = false, + isUnreadedAnswerToYou = true + ), + UserChat( + id = 4, + lastUserName = "Marilyn", + lastMessage = "Tomorow?", + time = "Yesterday", + isVerified = false, + avatarUrl = "https://i.pinimg.com/736x/75/26/5b/75265b439fb335a418e79b13b447b1a6.jpg", + isScam = false, + unreadMessageCount = 1, + isMute = false, + isSpeeching = false, + isTyping = true, + isUnreadedAnswerToYou = true, + isAnswered = false, + isOpponnentReaded = false + ), + UserChat( + id = 4, + lastUserName = "Elvis", + lastMessage = "Dear! Not today.", + time = "Yesterday", + isVerified = true, + avatarUrl = "https://www.kino-teatr.ru/news/20390/183753.jpg", + isScam = false, + unreadMessageCount = 0, + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isOpponnentReaded = true + ) + + + ) } \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ResizeToMinSideTransformation.kt b/app/src/main/java/otus/gpb/recyclerview/ResizeToMinSideTransformation.kt new file mode 100644 index 0000000..0b79e9a --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ResizeToMinSideTransformation.kt @@ -0,0 +1,39 @@ +package otus.gpb.recyclerview + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Paint +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import java.security.MessageDigest + +class ResizeAndCropTransformation(private val targetSize: Int) : BitmapTransformation() { + + override fun transform( + pool: BitmapPool, + toTransform: Bitmap, + outWidth: Int, + outHeight: Int + ): Bitmap { + // Определяем масштаб для увеличения изображения до нужного размера + val scale = targetSize.toFloat() / minOf(toTransform.width, toTransform.height) + + val resizedWidth = (toTransform.width * scale).toInt() + val resizedHeight = (toTransform.height * scale).toInt() + + // Масштабируем изображение + val resizedBitmap = Bitmap.createScaledBitmap(toTransform, resizedWidth, resizedHeight, true) + + // Обрезаем до квадрата + val minSide = minOf(resizedWidth, resizedHeight) + val left = (resizedWidth - minSide) / 2 + val top = (resizedHeight - minSide) / 2 + + return Bitmap.createBitmap(resizedBitmap, left, top, minSide, minSide) + } + + override fun updateDiskCacheKey(messageDigest: MessageDigest) { + messageDigest.update(("ResizeAndCropTransformation:$targetSize").toByteArray()) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_border.xml b/app/src/main/res/drawable/circle_border.xml new file mode 100644 index 0000000..8311f13 --- /dev/null +++ b/app/src/main/res/drawable/circle_border.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_border_blue_20.xml b/app/src/main/res/drawable/circle_border_blue_20.xml new file mode 100644 index 0000000..8532863 --- /dev/null +++ b/app/src/main/res/drawable/circle_border_blue_20.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_border_silver_7.xml b/app/src/main/res/drawable/circle_border_silver_7.xml new file mode 100644 index 0000000..9a0da3e --- /dev/null +++ b/app/src/main/res/drawable/circle_border_silver_7.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_archive.xml b/app/src/main/res/drawable/ic_archive.xml new file mode 100644 index 0000000..8bbe872 --- /dev/null +++ b/app/src/main/res/drawable/ic_archive.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_at.xml b/app/src/main/res/drawable/ic_at.xml new file mode 100644 index 0000000..321a3f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_at.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_blue_verified.xml b/app/src/main/res/drawable/ic_blue_verified.xml new file mode 100644 index 0000000..552bd9e --- /dev/null +++ b/app/src/main/res/drawable/ic_blue_verified.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bubles.xml b/app/src/main/res/drawable/ic_bubles.xml new file mode 100644 index 0000000..3033425 --- /dev/null +++ b/app/src/main/res/drawable/ic_bubles.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_green_check.xml b/app/src/main/res/drawable/ic_green_check.xml new file mode 100644 index 0000000..0950241 --- /dev/null +++ b/app/src/main/res/drawable/ic_green_check.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_green_double_check.xml b/app/src/main/res/drawable/ic_green_double_check.xml new file mode 100644 index 0000000..320454b --- /dev/null +++ b/app/src/main/res/drawable/ic_green_double_check.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..ab2a883 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_main_menu.xml b/app/src/main/res/drawable/ic_main_menu.xml new file mode 100644 index 0000000..32b6ead --- /dev/null +++ b/app/src/main/res/drawable/ic_main_menu.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_mute.xml b/app/src/main/res/drawable/ic_mute.xml new file mode 100644 index 0000000..7d09270 --- /dev/null +++ b/app/src/main/res/drawable/ic_mute.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_pinned.xml b/app/src/main/res/drawable/ic_pinned.xml new file mode 100644 index 0000000..bc08f6f --- /dev/null +++ b/app/src/main/res/drawable/ic_pinned.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_placeholder_avatar.xml b/app/src/main/res/drawable/ic_placeholder_avatar.xml new file mode 100644 index 0000000..03d0044 --- /dev/null +++ b/app/src/main/res/drawable/ic_placeholder_avatar.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scam.xml b/app/src/main/res/drawable/ic_scam.xml new file mode 100644 index 0000000..95df387 --- /dev/null +++ b/app/src/main/res/drawable/ic_scam.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_status_speech.xml b/app/src/main/res/drawable/ic_status_speech.xml new file mode 100644 index 0000000..df60df5 --- /dev/null +++ b/app/src/main/res/drawable/ic_status_speech.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/recycler_view_devider.xml b/app/src/main/res/drawable/recycler_view_devider.xml new file mode 100644 index 0000000..d7068a4 --- /dev/null +++ b/app/src/main/res/drawable/recycler_view_devider.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_background.xml b/app/src/main/res/drawable/rounded_background.xml new file mode 100644 index 0000000..b834524 --- /dev/null +++ b/app/src/main/res/drawable/rounded_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2d026df..8f566ac 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,13 +1,57 @@ + + + + + + + + + + + android:layout_height="0dp" + android:padding="0dp" + app:layout_constraintTop_toBottomOf="@id/toolbar" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_chat.xml b/app/src/main/res/layout/item_chat.xml new file mode 100644 index 0000000..ab1871f --- /dev/null +++ b/app/src/main/res/layout/item_chat.xml @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/toolbar_menu.xml b/app/src/main/res/menu/toolbar_menu.xml new file mode 100644 index 0000000..a0b9352 --- /dev/null +++ b/app/src/main/res/menu/toolbar_menu.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 114f376..279e289 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,16 +1,80 @@ - \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..78880e1 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,7 @@ + + + + + + + \ 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..1022759 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,180 @@ #FF018786 #FF000000 #FFFFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #2196F3 + #1976D2 + #81C784 + #388E3C + #64B5F6 + #A5D6A7 + #4CAF50 + + + + #2c75b0 + #1a5b8e + #ffffff + + #F5F5F5 + #66A9E0 + #F5F5F5 + + #f5f5f5 + #000000 + #757575 + + #d3d3d3 + + + + #9e9e9e + + + + + #1f2a36 + #0c1520 + #ffffff + + #1a1a1a + #a0a0a0 + #ffffff + + #1a1a1a + #e0e0e0 + #a0a0a0 + + #212121 + #9e9e9e + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3d78b1f..e0b716f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,29 @@ RecyclerView + description + User Name + 12:34 + Last message in chat + Speech status + Mute channel + Last user + Archive + End of shadow + Answered + Pinned + Verified + Answered + Answered and opponent readed + Scam + Lock channel + Topic + Margin topic + Typing + typing + Answerer + 101011 + Main menu + Telegram + Home + Exit \ 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..bb7681b 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,16 +1,136 @@ - \ No newline at end of file diff --git a/app/src/test/java/otus/gpb/recyclerview/ExampleUnitTest.kt b/app/src/test/java/otus/gpb/recyclerview/ExampleUnitTest.kt index 5ce306c..863a7c8 100644 --- a/app/src/test/java/otus/gpb/recyclerview/ExampleUnitTest.kt +++ b/app/src/test/java/otus/gpb/recyclerview/ExampleUnitTest.kt @@ -1,17 +1,17 @@ package otus.gpb.recyclerview -import org.junit.Test +//import org.junit.Test -import org.junit.Assert.* +//import org.junit.Assert.* /** * Example local unit test, which will execute on the development machine (host). * * See [testing documentation](http://d.android.com/tools/testing). */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file +//class ExampleUnitTest { +//// @Test +//// fun addition_isCorrect() { +//// assertEquals(4, 2 + 2) +//// } +//} \ No newline at end of file diff --git a/build.gradle b/build.gradle index bd20018..55fd564 100644 --- a/build.gradle +++ b/build.gradle @@ -7,4 +7,5 @@ plugins { task clean(type: Delete) { delete rootProject.buildDir -} \ No newline at end of file +} + diff --git a/gradle.properties b/gradle.properties index cd0519b..b793761 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,7 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true + + +android.suppressUnsupportedCompileSdk=1 \ No newline at end of file From 43ab08557cb05af435867df0c6e9d859dc760d7d Mon Sep 17 00:00:00 2001 From: Evgenii Balandin Date: Mon, 13 Jan 2025 13:29:36 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=BE=D1=87=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BC=D1=83=D1=81=D0=BE=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/otus/gpb/recyclerview/Chat.kt | 6 - .../java/otus/gpb/recyclerview/ChatAdapter.kt | 15 -- .../otus/gpb/recyclerview/MainActivity.kt | 177 +++++------------- .../ResizeToMinSideTransformation.kt | 3 - app/src/main/res/drawable/circle_border.xml | 6 +- .../res/drawable/circle_border_blue_20.xml | 6 +- .../res/drawable/circle_border_silver_7.xml | 6 +- app/src/main/res/drawable/ic_archive.xml | 10 +- app/src/main/res/drawable/ic_at.xml | 19 -- app/src/main/res/drawable/ic_bubles.xml | 36 ++-- .../main/res/drawable/ic_status_speech.xml | 18 +- .../res/drawable/recycler_view_devider.xml | 8 - .../main/res/drawable/rounded_background.xml | 4 +- app/src/main/res/layout/activity_main.xml | 26 ++- app/src/main/res/layout/item_chat.xml | 106 ++--------- ...ar_menu.xml => navigation_drawer_menu.xml} | 0 app/src/main/res/values-night/themes.xml | 65 +------ app/src/main/res/values/colors.xml | 134 ------------- app/src/main/res/values/themes.xml | 123 +----------- .../otus/gpb/recyclerview/ExampleUnitTest.kt | 17 +- 20 files changed, 132 insertions(+), 653 deletions(-) rename app/src/main/res/menu/{toolbar_menu.xml => navigation_drawer_menu.xml} (100%) diff --git a/app/src/main/java/otus/gpb/recyclerview/Chat.kt b/app/src/main/java/otus/gpb/recyclerview/Chat.kt index 55467c0..cb5c2f6 100644 --- a/app/src/main/java/otus/gpb/recyclerview/Chat.kt +++ b/app/src/main/java/otus/gpb/recyclerview/Chat.kt @@ -1,6 +1,5 @@ package otus.gpb.recyclerview - // Parent interface interface Chat { val id: Int @@ -21,11 +20,6 @@ interface Chat { var isOpponnentReaded: Boolean var isLock: Boolean - - fun isRead(): Boolean { return unreadMessageCount == 0 } - - // Optionally, we can add the equals method in the interface itself - // But it should only be used in specific cases, not common for all types override fun equals(other: Any?): Boolean } diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt index ac88d03..399057f 100644 --- a/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -13,7 +13,6 @@ import otus.gpb.recyclerview.databinding.ItemChatBinding class ChatAdapter( - //private val onDeleteChat: (Chat) -> Unit, private val onArchiveChat: (Chat) -> Unit // Новый callback для архивирования ) : ListAdapter(ChatDiffCallback()) { @@ -82,17 +81,6 @@ class ChatAdapter( binding.answererIcon.visibility = View.GONE } - -// // Обработчик длинного нажатия для удаления -// binding.root.setOnLongClickListener { -// val position = adapterPosition -// if (position != RecyclerView.NO_POSITION) { -// val chatObj = currentList[position] -// onDeleteChat(chatObj) -// } -// true -// } - // Обработчик нажатия на кнопку архивирования binding.archiveButton.setOnClickListener { val position = adapterPosition @@ -112,9 +100,6 @@ class ChatAdapter( val archiveLayout = binding.root.findViewById(R.id.archiveButtonLayout) val itemLayout = binding.root.findViewById(R.id.chatItemLayout) if (archiveLayout.visibility == View.VISIBLE) { - //archiveLayout.visibility = View.GONE - //notifyItemChanged(adapterPosition) - // Анимация возврата в исходное положение itemLayout.animate() .translationX(0f) diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index f694f9a..e45c2ea 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -28,11 +28,6 @@ class MainActivity : AppCompatActivity() { recyclerView.addItemDecoration(divider) chatAdapter = ChatAdapter( -// onDeleteChat = { chat -> -// val updatedList = chatAdapter.currentList.toMutableList() -// updatedList.remove(chat) -// chatAdapter.submitList(updatedList) -// }, onArchiveChat = { chat -> // Обработка архивирования val updatedList = chatAdapter.currentList.toMutableList() @@ -45,47 +40,6 @@ class MainActivity : AppCompatActivity() { recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = chatAdapter - -// // Set up ItemTouchHelper for swipe actions -// val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( -// ItemTouchHelper.UP or ItemTouchHelper.DOWN, -// ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT -// ) { -// override fun onMove( -// recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder -// ): Boolean = false -// -// override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { -// val position = viewHolder.adapterPosition -// //val chat = chatAdapter.getItem(position) -// // Handle swipe action, e.g., delete chat -// //chatAdapter.onDeleteChat(chat) -// -// -// //val itemLayout = viewHolder.itemView.findViewById(R.id.chatItemLayout) -// val archiveLayout = viewHolder.itemView.findViewById(R.id.archiveButtonLayout) -// -// // Показать шторку с архивом при свайпе -// archiveLayout.visibility = View.VISIBLE -// -// // Сдвигаем основной элемент (chatItemLayout) влево, чтобы показать архив -// //itemLayout.animate().translationX(-itemLayout.width.toFloat()).setDuration(200).start() -// -// // Кнопка архивирования -// val archiveButton = viewHolder.itemView.findViewById(R.id.archiveButton) -// archiveButton.setOnClickListener { -// // Обработать архивирование -// //onArchiveChat(chat) -// archiveLayout.visibility = View.GONE // Скрыть архивный элемент -// } -// } -// -// override fun isItemViewSwipeEnabled(): Boolean = true -// } - - //val itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback) - //itemTouchHelper.attachToRecyclerView(recyclerView) - val itemTouchHelperCallback = object : ItemTouchHelper.Callback() { override fun getMovementFlags( @@ -105,55 +59,6 @@ class MainActivity : AppCompatActivity() { return false } -// override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { -// itemTouchHelper?.attachToRecyclerView(null) -// itemTouchHelper?.attachToRecyclerView(recyclerView) -// -// val archiveLayout = viewHolder.itemView.findViewById(R.id.archiveButtonLayout) -// val itemLayout = viewHolder.itemView.findViewById(R.id.chatItemLayout) -// -// val position = viewHolder.adapterPosition -// if (position == RecyclerView.NO_POSITION) return -// -// if (direction == ItemTouchHelper.LEFT && archiveLayout.visibility != View.VISIBLE) { -// -// -// // Проверяем расстояние свайпа -// val translationX = abs(viewHolder.itemView.translationX); -// if (translationX >= viewHolder.itemView.width * 0.8) { -// -// // Длинный свайп: Удаление -// //val position = viewHolder.adapterPosition -// val chatObj = chatAdapter.currentList[position]; -// val updatedList = chatAdapter.currentList.toMutableList() -// updatedList.remove(chatObj) -// chatAdapter.submitList(updatedList) -// //val item: String = chatAdapter.getData().get(position) -// //chatAdapter.removeItem(position) -// Toast.makeText(this@MainActivity, "Deleted: $chatObj", Toast.LENGTH_SHORT).show() -// chatAdapter.notifyItemRemoved(position) -// } else { -// // Показать шторку -// archiveLayout.visibility = View.VISIBLE -// itemLayout.animate().translationX(-archiveLayout.width.toFloat()).setDuration(500).start() -// } -// -// // Сбросить состояние ViewHolder после анимации -// //itemLayout.postDelayed({ -// //itemTouchHelper.attachToRecyclerView(null) -// //chatAdapter.notifyItemChanged(position) -// //itemTouchHelper.attachToRecyclerView(recyclerView) -// //}, 500) -// } -// -//// // Сброс состояния -//// itemLayout.postDelayed({ -//// archiveLayout.visibility = View.GONE -//// itemLayout.animate().translationX(0f).setDuration(1000).start() -//// chatAdapter.notifyItemChanged(position) -//// }, 3000) -// } - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition if (position == RecyclerView.NO_POSITION) return @@ -196,35 +101,6 @@ class MainActivity : AppCompatActivity() { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } - -// override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { -// super.clearView(recyclerView, viewHolder) -// // Возвращаем элемент в исходное положение -// val archiveLayout = viewHolder.itemView.findViewById(R.id.archiveButtonLayout) -// val itemLayout = viewHolder.itemView.findViewById(R.id.chatItemLayout) -// //archiveLayout.visibility = View.GONE -// // Скрываем шторку и сбрасываем позицию -// if (archiveLayout.visibility != View.GONE) { -// archiveLayout.visibility = View.GONE -// // itemLayout.translationX = 0f -// itemLayout.animate().translationX(0f).setDuration(500).start() -// -// val position = viewHolder.adapterPosition -// // Сбросить состояние ViewHolder после анимации -// itemLayout.postDelayed({ -// //itemTouchHelper.attachToRecyclerView(null) -// chatAdapter.notifyItemChanged(position) -// //itemTouchHelper.attachToRecyclerView(recyclerView) -// }, 500) -// -// -// //val position = viewHolder.adapterPosition -// //chatAdapter.notifyItemChanged(position) -// } -// -// } - - override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { super.clearView(recyclerView, viewHolder) @@ -289,20 +165,21 @@ private fun loadChats(): List { ), GroupChat( id = 2, - title = "Group topic", + title = "Mentors", lastUserName = "Bob", lastMessage = "How are you?", time = "11:15", isVerified = false, avatarUrl = "https://gravatar.com/avatar/1ae9a8dbb50baebe44d7b96d012a73d5?s=400&d=robohash&r=x", - isScam = true, - unreadMessageCount = 0, + isScam = false, + unreadMessageCount = 7, isMute = false, isSpeeching = false, isTyping = false, isUnreadedAnswerToYou = true, isAnswered = true, - isLock = true, + isLock = false, + isPinned = true, lastAnswererUrl = "https://i.pinimg.com/736x/c9/fe/fb/c9fefb489d5fdc792ae324103255edd2.jpg" ), @@ -328,16 +205,16 @@ private fun loadChats(): List { isVerified = false, avatarUrl = "https://i.pinimg.com/736x/75/26/5b/75265b439fb335a418e79b13b447b1a6.jpg", isScam = false, - unreadMessageCount = 1, + unreadMessageCount = 15, isMute = false, isSpeeching = false, isTyping = true, - isUnreadedAnswerToYou = true, + isUnreadedAnswerToYou = false, isAnswered = false, isOpponnentReaded = false ), UserChat( - id = 4, + id = 5, lastUserName = "Elvis", lastMessage = "Dear! Not today.", time = "Yesterday", @@ -351,6 +228,44 @@ private fun loadChats(): List { isUnreadedAnswerToYou = false, isAnswered = true, isOpponnentReaded = true + ), + GroupChat( + id = 6, + title = "Cat's and mouse", + lastUserName = "Catzilla", + lastMessage = "Good morning!", + time = "11:15", + isVerified = false, + avatarUrl = "https://s9.travelask.ru/uploads/post/000/031/157/main_image/facebook-bf918145c8d2cee688d53ee7112500d3.jpg", + isScam = true, + unreadMessageCount = 0, + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = true, + isAnswered = true, + isLock = true, + lastAnswererUrl = "https://i.pinimg.com/originals/52/c6/65/52c665df0515dd447eb92544374cf543.jpg" + + ), + GroupChat( + id = 7, + title = "StarLine Tours", + lastUserName = "Bear Grils", + lastMessage = "Let's go!", + time = "11:15", + isVerified = false, + avatarUrl = "https://avatars.mds.yandex.net/i?id=c7269ba0c5fc64e968daedd67f497d1d82453fcf-7760894-images-thumbs&n=13", + isScam = false, + unreadMessageCount = 101, + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isLock = false, + lastAnswererUrl = "https://avatars.mds.yandex.net/get-kinopoisk-image/1898899/78473e64-0a54-46ba-87ad-94b2822e9aaf/1920x" + ) diff --git a/app/src/main/java/otus/gpb/recyclerview/ResizeToMinSideTransformation.kt b/app/src/main/java/otus/gpb/recyclerview/ResizeToMinSideTransformation.kt index 0b79e9a..65a12f1 100644 --- a/app/src/main/java/otus/gpb/recyclerview/ResizeToMinSideTransformation.kt +++ b/app/src/main/java/otus/gpb/recyclerview/ResizeToMinSideTransformation.kt @@ -1,9 +1,6 @@ package otus.gpb.recyclerview import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Matrix -import android.graphics.Paint import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import java.security.MessageDigest diff --git a/app/src/main/res/drawable/circle_border.xml b/app/src/main/res/drawable/circle_border.xml index 8311f13..b4b96d0 100644 --- a/app/src/main/res/drawable/circle_border.xml +++ b/app/src/main/res/drawable/circle_border.xml @@ -1,6 +1,8 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_border_blue_20.xml b/app/src/main/res/drawable/circle_border_blue_20.xml index 8532863..ef218a2 100644 --- a/app/src/main/res/drawable/circle_border_blue_20.xml +++ b/app/src/main/res/drawable/circle_border_blue_20.xml @@ -1,6 +1,8 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_border_silver_7.xml b/app/src/main/res/drawable/circle_border_silver_7.xml index 9a0da3e..f601597 100644 --- a/app/src/main/res/drawable/circle_border_silver_7.xml +++ b/app/src/main/res/drawable/circle_border_silver_7.xml @@ -1,6 +1,8 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_archive.xml b/app/src/main/res/drawable/ic_archive.xml index 8bbe872..bee2e39 100644 --- a/app/src/main/res/drawable/ic_archive.xml +++ b/app/src/main/res/drawable/ic_archive.xml @@ -1,4 +1,10 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_at.xml b/app/src/main/res/drawable/ic_at.xml index 321a3f8..20b2727 100644 --- a/app/src/main/res/drawable/ic_at.xml +++ b/app/src/main/res/drawable/ic_at.xml @@ -1,23 +1,4 @@ - - - - - - - - - - - - - - - - - - - - + + - + + + + - - + + - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_status_speech.xml b/app/src/main/res/drawable/ic_status_speech.xml index df60df5..4f367f0 100644 --- a/app/src/main/res/drawable/ic_status_speech.xml +++ b/app/src/main/res/drawable/ic_status_speech.xml @@ -17,23 +17,23 @@ android:pathData="M8,1A7,7 0 1,0 15,8A7,7 0 0,0 8,1Z" /> + android:strokeColor="#FFFFFF" + android:strokeLineCap="round" /> + android:strokeColor="#FFFFFF" + android:strokeLineCap="round" /> + android:strokeColor="#FFFFFF" + android:strokeLineCap="round" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/recycler_view_devider.xml b/app/src/main/res/drawable/recycler_view_devider.xml index d7068a4..8972b1a 100644 --- a/app/src/main/res/drawable/recycler_view_devider.xml +++ b/app/src/main/res/drawable/recycler_view_devider.xml @@ -1,17 +1,9 @@ - - - - - - - - diff --git a/app/src/main/res/drawable/rounded_background.xml b/app/src/main/res/drawable/rounded_background.xml index b834524..c553e70 100644 --- a/app/src/main/res/drawable/rounded_background.xml +++ b/app/src/main/res/drawable/rounded_background.xml @@ -1,5 +1,7 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8f566ac..29cdfae 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,7 +1,7 @@ @@ -10,12 +10,11 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - app:layout_scrollFlags="scroll|enterAlways" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" android:background="?attr/colorPrimary" - > + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_scrollFlags="scroll|enterAlways"> + tools:ignore="TouchTargetSizeCheck" /> + android:textColor="?attr/colorOnPrimary" /> @@ -48,10 +45,9 @@ android:layout_width="match_parent" android:layout_height="0dp" android:padding="0dp" - app:layout_constraintTop_toBottomOf="@id/toolbar" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" - /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_chat.xml b/app/src/main/res/layout/item_chat.xml index ab1871f..26e0c97 100644 --- a/app/src/main/res/layout/item_chat.xml +++ b/app/src/main/res/layout/item_chat.xml @@ -12,7 +12,6 @@ android:id="@+id/chatItemLayout" > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -313,18 +282,26 @@ android:id="@+id/pinned_status" android:layout_width="20dp" android:layout_height="20dp" - android:layout_marginEnd="8dp" + android:layout_marginEnd="2dp" android:layout_marginBottom="10dp" android:contentDescription="@string/pinned_status" android:scaleType="centerInside" android:src="@drawable/ic_pinned" - app:layout_constraintEnd_toStartOf="@id/archiveButtonLayout" + app:layout_constraintEnd_toStartOf="@id/right_bottom_anchor" app:layout_constraintBottom_toBottomOf="parent" /> - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -440,8 +364,6 @@ - - diff --git a/app/src/main/res/menu/toolbar_menu.xml b/app/src/main/res/menu/navigation_drawer_menu.xml similarity index 100% rename from app/src/main/res/menu/toolbar_menu.xml rename to app/src/main/res/menu/navigation_drawer_menu.xml diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 279e289..adcf19f 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,55 +1,6 @@ - + \ 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 1022759..c32b5ee 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -8,64 +8,6 @@ #FF000000 #FFFFFFFF - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #2196F3 #1976D2 #81C784 @@ -74,7 +16,6 @@ #A5D6A7 #4CAF50 - #2c75b0 #1a5b8e @@ -90,12 +31,8 @@ #d3d3d3 - - #9e9e9e - - #1f2a36 #0c1520 @@ -112,75 +49,4 @@ #212121 #9e9e9e - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ 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 bb7681b..83c09d4 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,109 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/test/java/otus/gpb/recyclerview/ExampleUnitTest.kt b/app/src/test/java/otus/gpb/recyclerview/ExampleUnitTest.kt index 863a7c8..9d2c85f 100644 --- a/app/src/test/java/otus/gpb/recyclerview/ExampleUnitTest.kt +++ b/app/src/test/java/otus/gpb/recyclerview/ExampleUnitTest.kt @@ -1,17 +1,14 @@ package otus.gpb.recyclerview -//import org.junit.Test - -//import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * * See [testing documentation](http://d.android.com/tools/testing). + * + * class ExampleUnitTest { + * @Test + * fun addition_isCorrect() { + * assertEquals(4, 2 + 2) + * } + * } */ -//class ExampleUnitTest { -//// @Test -//// fun addition_isCorrect() { -//// assertEquals(4, 2 + 2) -//// } -//} \ No newline at end of file From fc9db0b4c61d7e1bec3ef9bf6ba635476f8f4bf1 Mon Sep 17 00:00:00 2001 From: Evgenii Balandin Date: Mon, 13 Jan 2025 16:33:34 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=BE=D1=87=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BC=D1=83=D1=81=D0=BE=D1=80=D0=B0=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otus/gpb/recyclerview/MainActivity.kt | 48 ++++++++++ .../res/drawable/circle_border_silver_7.xml | 2 +- app/src/main/res/drawable/ic_at.xml | 1 - app/src/main/res/drawable/ic_home.xml | 10 ++ app/src/main/res/drawable/ic_info.xml | 10 ++ .../res/drawable/ic_nav_header_avatar.xml | 10 ++ app/src/main/res/drawable/ic_scam.xml | 2 +- app/src/main/res/drawable/ic_settings.xml | 12 +++ app/src/main/res/layout/activity_main.xml | 96 ++++++++++--------- app/src/main/res/layout/item_chat.xml | 4 +- app/src/main/res/layout/nav_header.xml | 25 +++++ .../main/res/menu/navigation_drawer_menu.xml | 11 --- app/src/main/res/menu/navigation_menu.xml | 15 +++ app/src/main/res/values/strings.xml | 10 ++ app/src/main/res/values/styles.xml | 4 + build.gradle | 15 +-- 16 files changed, 208 insertions(+), 67 deletions(-) create mode 100644 app/src/main/res/drawable/ic_home.xml create mode 100644 app/src/main/res/drawable/ic_info.xml create mode 100644 app/src/main/res/drawable/ic_nav_header_avatar.xml create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/layout/nav_header.xml delete mode 100644 app/src/main/res/menu/navigation_drawer_menu.xml create mode 100644 app/src/main/res/menu/navigation_menu.xml create mode 100644 app/src/main/res/values/styles.xml diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e45c2ea..4ffd1d5 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -5,22 +5,70 @@ import android.os.Bundle import android.view.View import android.widget.FrameLayout import android.widget.Toast +import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.view.GravityCompat +import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.appbar.MaterialToolbar +import com.google.android.material.navigation.NavigationView import kotlin.math.abs class MainActivity : AppCompatActivity() { private lateinit var chatAdapter: ChatAdapter private var itemTouchHelper: ItemTouchHelper? = null + private lateinit var drawerLayout: DrawerLayout + private lateinit var navigationView: NavigationView + private lateinit var toolbar: MaterialToolbar + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + this.title = getString(R.string.app_title) + + // Initialize views + drawerLayout = findViewById(R.id.drawer_layout) + navigationView = findViewById(R.id.navigation_view) + toolbar = findViewById(R.id.toolbar) + + // Set up toolbar + setSupportActionBar(toolbar) + + // Add navigation drawer toggle + val toggle = ActionBarDrawerToggle( + this, drawerLayout, toolbar, + R.string.navigation_drawer_open, + R.string.navigation_drawer_close + ) + toggle.drawerArrowDrawable.color = ContextCompat.getColor(this, R.color.colorOnPrimary) + drawerLayout.addDrawerListener(toggle) + toggle.syncState() + + // Handle navigation menu item clicks + navigationView.setNavigationItemSelectedListener { menuItem -> + when (menuItem.itemId) { + R.id.nav_home -> { + Toast.makeText(this, getString(R.string.home_selected), Toast.LENGTH_SHORT).show() + } + R.id.nav_settings -> { + Toast.makeText(this, getString(R.string.settings_selected), Toast.LENGTH_SHORT).show() + } + R.id.nav_about -> { + Toast.makeText(this,getString(R.string.about_selected), Toast.LENGTH_SHORT).show() + } + } + drawerLayout.closeDrawer(GravityCompat.START) + true + } + + val recyclerView: RecyclerView = findViewById(R.id.recycler_view) val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL) diff --git a/app/src/main/res/drawable/circle_border_silver_7.xml b/app/src/main/res/drawable/circle_border_silver_7.xml index f601597..ebdb013 100644 --- a/app/src/main/res/drawable/circle_border_silver_7.xml +++ b/app/src/main/res/drawable/circle_border_silver_7.xml @@ -4,5 +4,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_at.xml b/app/src/main/res/drawable/ic_at.xml index 20b2727..690f919 100644 --- a/app/src/main/res/drawable/ic_at.xml +++ b/app/src/main/res/drawable/ic_at.xml @@ -1,6 +1,5 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..475684d --- /dev/null +++ b/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_nav_header_avatar.xml b/app/src/main/res/drawable/ic_nav_header_avatar.xml new file mode 100644 index 0000000..9d783a5 --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_header_avatar.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scam.xml b/app/src/main/res/drawable/ic_scam.xml index 95df387..b739235 100644 --- a/app/src/main/res/drawable/ic_scam.xml +++ b/app/src/main/res/drawable/ic_scam.xml @@ -1,5 +1,5 @@ - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 29cdfae..0e20eaa 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,53 +1,59 @@ - - + - - - - - - - - - - - - \ No newline at end of file + android:layout_height="match_parent"> + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_chat.xml b/app/src/main/res/layout/item_chat.xml index 26e0c97..d56647c 100644 --- a/app/src/main/res/layout/item_chat.xml +++ b/app/src/main/res/layout/item_chat.xml @@ -260,7 +260,9 @@ app:layout_constraintEnd_toStartOf="@id/answered_to_you_status" app:layout_constraintBottom_toBottomOf="@id/right_bottom_anchor" app:layout_constraintTop_toTopOf="@id/right_bottom_anchor" - android:text="@string/unread_count" /> + android:text="@string/unread_count" + android:minWidth="20dp" + /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/navigation_drawer_menu.xml b/app/src/main/res/menu/navigation_drawer_menu.xml deleted file mode 100644 index a0b9352..0000000 --- a/app/src/main/res/menu/navigation_drawer_menu.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/navigation_menu.xml b/app/src/main/res/menu/navigation_menu.xml new file mode 100644 index 0000000..46b7a58 --- /dev/null +++ b/app/src/main/res/menu/navigation_menu.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0b716f..ee58ba3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,4 +26,14 @@ Telegram Home Exit + Username + Avatar + Navigation drawer open + Navigation drawer close + Settings + About + Telegram + Home selected + Settings selected + About selected \ 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..0d2c4cc --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 55fd564..e0271e9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,12 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.7.2' apply false - id 'com.android.library' version '8.7.2' apply false + id 'com.android.application' version '8.7.3' apply false + id 'com.android.library' version '8.7.3' apply false id 'org.jetbrains.kotlin.android' version '2.0.21' apply false -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +}//task clean(type: Delete) { +// delete rootProject.buildDir +//} +tasks.register("clean", Delete) { + delete(rootProject.layout.buildDirectory) +} \ No newline at end of file From ad34817088b9603d3ad8dbdb785c24dd167fbabf Mon Sep 17 00:00:00 2001 From: Evgenii Balandin Date: Mon, 13 Jan 2025 16:57:00 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=BE=D1=87=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BC=D1=83=D1=81=D0=BE=D1=80=D0=B0=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...border_silver_7.xml => circle_border_silver_10.xml} | 0 app/src/main/res/layout/activity_main.xml | 6 +++--- app/src/main/res/layout/item_chat.xml | 2 +- build.gradle | 10 ++++------ 4 files changed, 8 insertions(+), 10 deletions(-) rename app/src/main/res/drawable/{circle_border_silver_7.xml => circle_border_silver_10.xml} (100%) diff --git a/app/src/main/res/drawable/circle_border_silver_7.xml b/app/src/main/res/drawable/circle_border_silver_10.xml similarity index 100% rename from app/src/main/res/drawable/circle_border_silver_7.xml rename to app/src/main/res/drawable/circle_border_silver_10.xml diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0e20eaa..cfaf856 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -42,17 +42,17 @@ + diff --git a/app/src/main/res/layout/item_chat.xml b/app/src/main/res/layout/item_chat.xml index d56647c..98ff43b 100644 --- a/app/src/main/res/layout/item_chat.xml +++ b/app/src/main/res/layout/item_chat.xml @@ -255,7 +255,7 @@ android:padding="2dp" android:gravity="center" - android:background="@drawable/circle_border_silver_7" + android:background="@drawable/circle_border_silver_10" android:textColor="?attr/colorOnPrimary" app:layout_constraintEnd_toStartOf="@id/answered_to_you_status" app:layout_constraintBottom_toBottomOf="@id/right_bottom_anchor" diff --git a/build.gradle b/build.gradle index e0271e9..c1baa7e 100644 --- a/build.gradle +++ b/build.gradle @@ -3,10 +3,8 @@ plugins { id 'com.android.application' version '8.7.3' apply false id 'com.android.library' version '8.7.3' apply false id 'org.jetbrains.kotlin.android' version '2.0.21' apply false -}//task clean(type: Delete) { -// delete rootProject.buildDir -//} +} -tasks.register("clean", Delete) { - delete(rootProject.layout.buildDirectory) -} \ No newline at end of file +task clean(type: Delete) { + delete rootProject.buildDir +} From 332410bcbf4d15960b24e76cdede3c594c554196 Mon Sep 17 00:00:00 2001 From: Evgenii Balandin Date: Wed, 15 Jan 2025 03:06:29 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=BE=D1=87=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BC=D1=83=D1=81=D0=BE=D1=80=D0=B0=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/otus/gpb/recyclerview/Chat.kt | 20 +- .../java/otus/gpb/recyclerview/ChatAdapter.kt | 241 ++++++---- .../otus/gpb/recyclerview/MainActivity.kt | 411 ++++++++++++------ app/src/main/res/drawable/ic_add.xml | 10 + .../main/res/drawable/rounded_background.xml | 2 +- app/src/main/res/layout/activity_main.xml | 14 + app/src/main/res/layout/item_chat.xml | 2 +- app/src/main/res/layout/item_loading.xml | 20 + app/src/main/res/values-night/themes.xml | 1 + app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 1 + 13 files changed, 501 insertions(+), 227 deletions(-) create mode 100644 app/src/main/res/drawable/ic_add.xml create mode 100644 app/src/main/res/layout/item_loading.xml diff --git a/app/src/main/java/otus/gpb/recyclerview/Chat.kt b/app/src/main/java/otus/gpb/recyclerview/Chat.kt index cb5c2f6..1c3fa89 100644 --- a/app/src/main/java/otus/gpb/recyclerview/Chat.kt +++ b/app/src/main/java/otus/gpb/recyclerview/Chat.kt @@ -1,7 +1,11 @@ package otus.gpb.recyclerview -// Parent interface interface Chat { + override fun equals(other: Any?): Boolean +} + +// Parent interface +interface DataChat: Chat { val id: Int val lastUserName: String val lastMessage: String @@ -20,7 +24,6 @@ interface Chat { var isOpponnentReaded: Boolean var isLock: Boolean - override fun equals(other: Any?): Boolean } // Child class for group chat @@ -45,7 +48,7 @@ data class GroupChat( override var isLock: Boolean = false -) : Chat { +) : DataChat { // Custom equals implementation for GroupChat override fun equals(other: Any?): Boolean { @@ -94,7 +97,7 @@ data class UserChat( override var isAnswered: Boolean = false, override var isOpponnentReaded: Boolean = false, override var isLock: Boolean = false -) : Chat { +) : DataChat { // Custom equals implementation for UserChat override fun equals(other: Any?): Boolean { @@ -124,3 +127,12 @@ data class UserChat( } } +object ChatLoading : Chat { + override fun equals(other: Any?): Boolean { + return this === other + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } +} \ 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 index 399057f..a63266f 100644 --- a/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -1,5 +1,6 @@ package otus.gpb.recyclerview +import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -7,131 +8,179 @@ import android.widget.FrameLayout import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.CircleCrop import otus.gpb.recyclerview.databinding.ItemChatBinding class ChatAdapter( + private val context: Context, private val onArchiveChat: (Chat) -> Unit // Новый callback для архивирования -) : ListAdapter(ChatDiffCallback()) { +) : ListAdapter(ChatDiffCallback()) { + + companion object { + private const val VIEW_TYPE_DATA = 0 + private const val VIEW_TYPE_LOADING = 1 + } + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is DataChat -> VIEW_TYPE_DATA + is ChatLoading -> VIEW_TYPE_LOADING + else -> throw IllegalArgumentException(context.getString(R.string.invalid_view_type)) + } + } inner class ChatViewHolder(private val binding: ItemChatBinding) : - RecyclerView.ViewHolder(binding.root) { + ViewHolder(binding.root) { fun bind(chat: Chat) { - // Установка данных в зависимости от типа чата - when (chat) { - is GroupChat -> { - binding.chatTopic.text = chat.title - binding.chatLastUser.text = chat.lastUserName - binding.chatLastUser.visibility = View.VISIBLE - } - is UserChat -> { - binding.chatTopic.text = chat.lastUserName - binding.chatLastUser.visibility = View.GONE + if (chat is DataChat) { + // Установка данных в зависимости от типа чата + when (chat) { + is GroupChat -> { + binding.chatTopic.text = chat.title + binding.chatLastUser.text = chat.lastUserName + binding.chatLastUser.visibility = View.VISIBLE + } + + is UserChat -> { + binding.chatTopic.text = chat.lastUserName + binding.chatLastUser.visibility = View.GONE + } } - } - - binding.chatLastMessage.text = chat.lastMessage - binding.chatTime.text = chat.time - - // Отображение иконок статусов - binding.speechingStatusIcon.visibility = if (chat.isSpeeching) View.VISIBLE else View.GONE - binding.muteChannelIcon.visibility = if (chat.isMute) View.VISIBLE else View.GONE - - binding.lockIcon.visibility = if (chat.isLock) View.VISIBLE else View.GONE - binding.scamIcon.visibility = if (chat.isScam) View.VISIBLE else View.GONE - - binding.verifiedIcon.visibility = if (chat.isVerified) View.VISIBLE else View.GONE - - binding.unreadCount.visibility = if (chat.unreadMessageCount > 0) View.VISIBLE else View.GONE - chat.unreadMessageCount.toString().also { binding.unreadCount.text = it } - - binding.typingIcon.visibility = if (chat.isTyping) View.VISIBLE else View.GONE - binding.typing.visibility = if (chat.isTyping) View.VISIBLE else View.GONE - binding.chatLastMessage.visibility = if (chat.isTyping) View.GONE else View.VISIBLE - - binding.answeredToYouStatus.visibility = if (chat.isUnreadedAnswerToYou) View.VISIBLE else View.GONE - binding.answeredIcon.visibility = if (chat.isAnswered && !chat.isOpponnentReaded) View.VISIBLE else View.GONE - binding.answeredAndReadedIcon.visibility = if (chat.isAnswered && chat.isOpponnentReaded) View.VISIBLE else View.GONE - binding.pinnedStatus.visibility = if (chat.isPinned) View.VISIBLE else View.GONE - - val targetSize = 500 // Размер в пикселях, до которого нужно увеличить изображение - // Загрузка аватара - if (chat.avatarUrl != null) { - Glide.with(binding.chatAvatar.context) - .load(chat.avatarUrl) - .transform(ResizeAndCropTransformation(targetSize), CircleCrop()) // Обрезаем и применяем круг - .into(binding.chatAvatar) - } else { - binding.chatAvatar.setImageResource(R.drawable.ic_placeholder_avatar) - } + binding.chatLastMessage.text = chat.lastMessage + binding.chatTime.text = chat.time + + // Отображение иконок статусов + binding.speechingStatusIcon.visibility = + if (chat.isSpeeching) View.VISIBLE else View.GONE + binding.muteChannelIcon.visibility = if (chat.isMute) View.VISIBLE else View.GONE + + binding.lockIcon.visibility = if (chat.isLock) View.VISIBLE else View.GONE + binding.scamIcon.visibility = if (chat.isScam) View.VISIBLE else View.GONE + + binding.verifiedIcon.visibility = if (chat.isVerified) View.VISIBLE else View.GONE + + binding.unreadCount.visibility = + if (chat.unreadMessageCount > 0) View.VISIBLE else View.GONE + chat.unreadMessageCount.toString().also { binding.unreadCount.text = it } + + binding.typingIcon.visibility = if (chat.isTyping) View.VISIBLE else View.GONE + binding.typing.visibility = if (chat.isTyping) View.VISIBLE else View.GONE + binding.chatLastMessage.visibility = if (chat.isTyping) View.GONE else View.VISIBLE + + binding.answeredToYouStatus.visibility = + if (chat.isUnreadedAnswerToYou) View.VISIBLE else View.GONE + + binding.answeredIcon.visibility = + if (chat.isAnswered && !chat.isOpponnentReaded) View.VISIBLE else View.GONE + binding.answeredAndReadedIcon.visibility = + if (chat.isAnswered && chat.isOpponnentReaded) View.VISIBLE else View.GONE + binding.pinnedStatus.visibility = if (chat.isPinned) View.VISIBLE else View.GONE + + val targetSize = 500 // Размер в пикселях, до которого нужно увеличить изображение + // Загрузка аватара + if (chat.avatarUrl != null) { + Glide.with(binding.chatAvatar.context) + .load(chat.avatarUrl) + .transform( + ResizeAndCropTransformation(targetSize), + CircleCrop() + ) // Обрезаем и применяем круг + .into(binding.chatAvatar) + } else { + binding.chatAvatar.setImageResource(R.drawable.ic_placeholder_avatar) + } - // Загрузка аватара ответившего - if (chat.lastAnswererUrl != null) { - Glide.with(binding.answererIcon.context) - .load(chat.lastAnswererUrl) - //.circleCrop() - .into(binding.answererIcon) - binding.answererIcon.visibility = View.VISIBLE - } else { - binding.answererIcon.setImageResource(android.R.color.transparent) - binding.answererIcon.visibility = View.GONE - } + // Загрузка аватара ответившего + if (chat.lastAnswererUrl != null) { + Glide.with(binding.answererIcon.context) + .load(chat.lastAnswererUrl) + //.circleCrop() + .into(binding.answererIcon) + binding.answererIcon.visibility = View.VISIBLE + } else { + binding.answererIcon.setImageResource(android.R.color.transparent) + binding.answererIcon.visibility = View.GONE + } - // Обработчик нажатия на кнопку архивирования - binding.archiveButton.setOnClickListener { - val position = adapterPosition - if (position != RecyclerView.NO_POSITION) { - val chatObj = currentList[position] - // Обработка архивации - onArchiveChat(chatObj) - // Скрыть шторку - val archiveLayout = binding.root.findViewById(R.id.archiveButtonLayout) - archiveLayout.visibility = View.GONE - notifyItemChanged(position) + // Обработчик нажатия на кнопку архивирования + binding.archiveButton.setOnClickListener { + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val chatObj = currentList[position] + // Обработка архивации + onArchiveChat(chatObj) + // Скрыть шторку + val archiveLayout = + binding.root.findViewById(R.id.archiveButtonLayout) + archiveLayout.visibility = View.GONE + notifyItemChanged(position) + } } - } - binding.root.setOnClickListener { - val archiveLayout = binding.root.findViewById(R.id.archiveButtonLayout) - val itemLayout = binding.root.findViewById(R.id.chatItemLayout) - if (archiveLayout.visibility == View.VISIBLE) { - // Анимация возврата в исходное положение - itemLayout.animate() - .translationX(0f) - .setDuration(500) - .withEndAction { - // После завершения анимации скрываем шторку - archiveLayout.visibility = View.GONE - // Обновляем элемент только после завершения анимации - val position = adapterPosition - if (position != RecyclerView.NO_POSITION) { - notifyItemChanged(position) + binding.root.setOnClickListener { + val archiveLayout = + binding.root.findViewById(R.id.archiveButtonLayout) + val itemLayout = binding.root.findViewById(R.id.chatItemLayout) + if (archiveLayout.visibility == View.VISIBLE) { + // Анимация возврата в исходное положение + itemLayout.animate() + .translationX(0f) + .setDuration(500) + .withEndAction { + // После завершения анимации скрываем шторку + archiveLayout.visibility = View.GONE + // Обновляем элемент только после завершения анимации + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + notifyItemChanged(position) + } } - } - .start() + .start() + } } } } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder { - val inflater = LayoutInflater.from(parent.context) - val binding = ItemChatBinding.inflate(inflater, parent, false) - return ChatViewHolder(binding) + inner class LoadingViewHolder(view: View) : ViewHolder(view) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return when (viewType) { + VIEW_TYPE_DATA -> { + val binding = ItemChatBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ChatViewHolder(binding) + } + VIEW_TYPE_LOADING -> { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_loading, parent, false) + LoadingViewHolder(view) + } + else -> throw IllegalArgumentException(context.getString(R.string.invalid_view_type)) + } } - override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { - holder.bind(getItem(position)) + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + when (holder) { + is ChatViewHolder -> holder.bind(getItem(position) as DataChat) + is LoadingViewHolder -> { + // Здесь ничего не нужно делать, если загрузка не требует биндинга + } + else -> throw IllegalArgumentException(context.getString(R.string.invalid_view_type)) + } } + } class ChatDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean = oldItem.id == newItem.id - override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean = oldItem == newItem + override fun areItemsTheSame(oldItem: Chat, newItem: Chat): Boolean + = (oldItem as? DataChat)?.id == (newItem as? DataChat)?.id + override fun areContentsTheSame(oldItem: Chat, newItem: Chat): Boolean + = oldItem == newItem } diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index 4ffd1d5..2067e67 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -2,6 +2,8 @@ package otus.gpb.recyclerview import android.graphics.Canvas import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.View import android.widget.FrameLayout import android.widget.Toast @@ -15,8 +17,11 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.appbar.MaterialToolbar +import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.navigation.NavigationView import kotlin.math.abs +import kotlin.random.Random + class MainActivity : AppCompatActivity() { private lateinit var chatAdapter: ChatAdapter @@ -26,6 +31,10 @@ class MainActivity : AppCompatActivity() { private lateinit var navigationView: NavigationView private lateinit var toolbar: MaterialToolbar + private var isLoading = false + private var lastChatId: Int = 1; + + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -68,6 +77,17 @@ class MainActivity : AppCompatActivity() { true } + val fab = findViewById(R.id.fab) + fab.setOnClickListener { + // Обработка нажатия на кнопку + Toast.makeText( + this, + getString(R.string.write_new_message), + Toast.LENGTH_SHORT + ).show() + // Здесь вы можете добавить логику для открытия нового экрана или действия + } + val recyclerView: RecyclerView = findViewById(R.id.recycler_view) @@ -76,6 +96,7 @@ class MainActivity : AppCompatActivity() { recyclerView.addItemDecoration(divider) chatAdapter = ChatAdapter( + context = this, onArchiveChat = { chat -> // Обработка архивирования val updatedList = chatAdapter.currentList.toMutableList() @@ -185,137 +206,277 @@ class MainActivity : AppCompatActivity() { itemTouchHelper = ItemTouchHelper(itemTouchHelperCallback) itemTouchHelper?.attachToRecyclerView(recyclerView) + + // Слушатель для пагинации + 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 (!isLoading && (visibleItemCount + firstVisibleItemPosition) >= totalItemCount + && firstVisibleItemPosition >= 0 + ) { + loadMoreItems() + } + } + }) + // Загрузить данные - chatAdapter.submitList(loadChats()) + loadMoreItems() + + } + + + private fun loadMoreItems() { + // Проверяем, не загружаются ли уже данные + if (isLoading) return + isLoading = true + val list = chatAdapter.currentList.toMutableList() + // Добавляем элемент загрузки в список + if (!list.contains(ChatLoading)) { + list.add(ChatLoading) + chatAdapter.submitList(list.toList()) // Обновляем адаптер + } + // Эмуляция загрузки данных + Handler(Looper.getMainLooper()).postDelayed({ + // Убираем индикатор загрузки + list.remove(ChatLoading) + + list.addAll(loadChats()) + // Используем submitList для обновления адаптера + chatAdapter.submitList(list.toList()) + isLoading = false + }, 1500) // Задержка для эмуляции сети } -} + private fun loadChats(): List { + return listOf( + UserChat( + id = lastChatId++, + lastUserName = "Alice", + lastMessage = "Hi there!", + time = "12:30", + isVerified = true, + avatarUrl = "https://gravatar.com/avatar/478f6b94ddcde12416b90f22d7588cab?s=400&d=robohash&r=x", + isScam = false, + unreadMessageCount = 0, + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isPinned = true, + isOpponnentReaded = false, + isAnswered = true + ), + GroupChat( + id = lastChatId++, + title = "Mentors", + lastUserName = "Bob", + lastMessage = "How are you?", + time = "11:15", + isVerified = false, + avatarUrl = "https://gravatar.com/avatar/1ae9a8dbb50baebe44d7b96d012a73d5?s=400&d=robohash&r=x", + isScam = false, + unreadMessageCount = Random.nextInt(30), + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = true, + isAnswered = true, + isLock = false, + isPinned = true, + lastAnswererUrl = "https://i.pinimg.com/736x/c9/fe/fb/c9fefb489d5fdc792ae324103255edd2.jpg" + + ), + UserChat( + id = lastChatId++, + lastUserName = "Charlie", + lastMessage = "Meeting at 3?", + time = "Yesterday", + isVerified = true, + avatarUrl = "https://gravatar.com/avatar/f07d785a9ee32506a7a077f25466f98b?s=400&d=robohash&r=x", + isScam = false, + unreadMessageCount = Random.nextInt(15), + isMute = true, + isSpeeching = true, + isTyping = false, + isUnreadedAnswerToYou = true + ), + UserChat( + id = lastChatId++, + lastUserName = "Marilyn", + lastMessage = "Tomorow?", + time = "Yesterday", + isVerified = false, + avatarUrl = "https://i.pinimg.com/736x/75/26/5b/75265b439fb335a418e79b13b447b1a6.jpg", + isScam = false, + unreadMessageCount = Random.nextInt(50), + isMute = false, + isSpeeching = false, + isTyping = true, + isUnreadedAnswerToYou = false, + isAnswered = false, + isOpponnentReaded = false + ), + UserChat( + id = lastChatId++, + lastUserName = "Elvis", + lastMessage = "Dear! Not today.", + time = "Yesterday", + isVerified = true, + avatarUrl = "https://www.kino-teatr.ru/news/20390/183753.jpg", + isScam = false, + unreadMessageCount = 0, + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isOpponnentReaded = true + ), + GroupChat( + id = lastChatId++, + title = "Cat's and mouse", + lastUserName = "Catzilla", + lastMessage = "Good morning!", + time = "06:07", + isVerified = false, + avatarUrl = "https://s9.travelask.ru/uploads/post/000/031/157/main_image/facebook-bf918145c8d2cee688d53ee7112500d3.jpg", + isScam = true, + unreadMessageCount = 0, + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = true, + isAnswered = true, + isLock = true, + lastAnswererUrl = "https://i.pinimg.com/originals/52/c6/65/52c665df0515dd447eb92544374cf543.jpg" + + ), + GroupChat( + id = lastChatId++, + title = "StarLine Tours", + lastUserName = "Bear Grils", + lastMessage = "Let's go!", + time = "10:09", + isVerified = false, + avatarUrl = "https://avatars.mds.yandex.net/i?id=c7269ba0c5fc64e968daedd67f497d1d82453fcf-7760894-images-thumbs&n=13", + isScam = false, + unreadMessageCount = Random.nextInt(150), + isMute = false, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isLock = false, + lastAnswererUrl = "https://avatars.mds.yandex.net/get-kinopoisk-image/1898899/78473e64-0a54-46ba-87ad-94b2822e9aaf/1920x" + + ), + GroupChat( + id = lastChatId++, + title = "News & Facts", + lastUserName = "Truth Reporter", + lastMessage = "Aliens already here!", + time = "23:20", + isVerified = false, + avatarUrl = "https://avatars.mds.yandex.net/i?id=a81133d9a76c76f1504ca65334107711af3e3b92-10465625-images-thumbs&n=13", + isScam = false, + unreadMessageCount = Random.nextInt(301), + isMute = true, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isLock = false, + lastAnswererUrl = "https://www.kino-teatr.ru/news/20390/183753.jpg" + + ), + GroupChat( + id = lastChatId++, + title = "Food", + lastUserName = "Supplier", + lastMessage = "Food for wealth.", + time = "21:41", + isVerified = false, + avatarUrl = "https://avatars.mds.yandex.net/i?id=e1258c5d321183ada452bdb6f7283115a439ce89-10246451-images-thumbs&n=13", + isScam = false, + unreadMessageCount = Random.nextInt(350), + isMute = true, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isLock = false, + lastAnswererUrl = "https://www.axial.net/wp-content/uploads/2016/07/naturalfood.jpg" + + ), + GroupChat( + id = lastChatId++, + title = "Fitness", + lastUserName = "Trainer", + lastMessage = "Let's go to training!", + time = "22:15", + isVerified = false, + avatarUrl = "https://cdn.culture.ru/images/cf6e22be-dddf-55cf-bef4-4dde1e64fa63", + isScam = false, + unreadMessageCount = Random.nextInt(360), + isMute = true, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isLock = false, + lastAnswererUrl = "https://avatars.mds.yandex.net/i?id=6ce40ad4578dddf9bf81d1a52f44b5f027ee78a7-4078102-images-thumbs&n=13" + ), + GroupChat( + id = lastChatId++, + title = "Technology News", + lastUserName = "Bober", + lastMessage = "Ready for new apple!", + time = "Fri", + isVerified = true, + avatarUrl = "https://avatars.mds.yandex.net/i?id=fc179096458914d6908cd807b942bf73cf8b74cc-3663718-images-thumbs&n=13", + isScam = false, + unreadMessageCount = Random.nextInt(3000), + isMute = true, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isLock = false, + lastAnswererUrl = "https://avatars.mds.yandex.net/i?id=67e2837139254b993171edce157f551949602e2b-12714815-images-thumbs&n=13" + ), + GroupChat( + id = lastChatId++, + title = "Fashion News", + lastUserName = "Pavlin", + lastMessage = "Exclusive for you!", + time = "Fri", + isVerified = true, + avatarUrl = "https://avatars.mds.yandex.net/i?id=cc4788280c0d75f6882102161b08737ac79c7973-4262069-images-thumbs&n=13", + isScam = false, + unreadMessageCount = Random.nextInt(40), + isMute = true, + isSpeeching = false, + isTyping = false, + isUnreadedAnswerToYou = false, + isAnswered = true, + isLock = false, + lastAnswererUrl = "https://avatars.mds.yandex.net/i?id=3e4a09ab3c6b398ac222fe28ef03953e043d48b7b0a55b2b-12999039-images-thumbs&n=13" + + ) -private fun loadChats(): List { - return listOf( - UserChat( - id = 1, - lastUserName = "Alice", - lastMessage = "Hi there!", - time = "12:30", - isVerified = true, - avatarUrl = "https://gravatar.com/avatar/478f6b94ddcde12416b90f22d7588cab?s=400&d=robohash&r=x", - isScam = false, - unreadMessageCount = 0, - isMute = false, - isSpeeching = false, - isTyping = false, - isUnreadedAnswerToYou = false, - isPinned = true, - isOpponnentReaded = false, - isAnswered = true - ), - GroupChat( - id = 2, - title = "Mentors", - lastUserName = "Bob", - lastMessage = "How are you?", - time = "11:15", - isVerified = false, - avatarUrl = "https://gravatar.com/avatar/1ae9a8dbb50baebe44d7b96d012a73d5?s=400&d=robohash&r=x", - isScam = false, - unreadMessageCount = 7, - isMute = false, - isSpeeching = false, - isTyping = false, - isUnreadedAnswerToYou = true, - isAnswered = true, - isLock = false, - isPinned = true, - lastAnswererUrl = "https://i.pinimg.com/736x/c9/fe/fb/c9fefb489d5fdc792ae324103255edd2.jpg" - - ), - UserChat( - id = 3, - lastUserName = "Charlie", - lastMessage = "Meeting at 3?", - time = "Yesterday", - isVerified = true, - avatarUrl = "https://gravatar.com/avatar/f07d785a9ee32506a7a077f25466f98b?s=400&d=robohash&r=x", - isScam = false, - unreadMessageCount = 11, - isMute = true, - isSpeeching = true, - isTyping = false, - isUnreadedAnswerToYou = true - ), - UserChat( - id = 4, - lastUserName = "Marilyn", - lastMessage = "Tomorow?", - time = "Yesterday", - isVerified = false, - avatarUrl = "https://i.pinimg.com/736x/75/26/5b/75265b439fb335a418e79b13b447b1a6.jpg", - isScam = false, - unreadMessageCount = 15, - isMute = false, - isSpeeching = false, - isTyping = true, - isUnreadedAnswerToYou = false, - isAnswered = false, - isOpponnentReaded = false - ), - UserChat( - id = 5, - lastUserName = "Elvis", - lastMessage = "Dear! Not today.", - time = "Yesterday", - isVerified = true, - avatarUrl = "https://www.kino-teatr.ru/news/20390/183753.jpg", - isScam = false, - unreadMessageCount = 0, - isMute = false, - isSpeeching = false, - isTyping = false, - isUnreadedAnswerToYou = false, - isAnswered = true, - isOpponnentReaded = true - ), - GroupChat( - id = 6, - title = "Cat's and mouse", - lastUserName = "Catzilla", - lastMessage = "Good morning!", - time = "11:15", - isVerified = false, - avatarUrl = "https://s9.travelask.ru/uploads/post/000/031/157/main_image/facebook-bf918145c8d2cee688d53ee7112500d3.jpg", - isScam = true, - unreadMessageCount = 0, - isMute = false, - isSpeeching = false, - isTyping = false, - isUnreadedAnswerToYou = true, - isAnswered = true, - isLock = true, - lastAnswererUrl = "https://i.pinimg.com/originals/52/c6/65/52c665df0515dd447eb92544374cf543.jpg" - - ), - GroupChat( - id = 7, - title = "StarLine Tours", - lastUserName = "Bear Grils", - lastMessage = "Let's go!", - time = "11:15", - isVerified = false, - avatarUrl = "https://avatars.mds.yandex.net/i?id=c7269ba0c5fc64e968daedd67f497d1d82453fcf-7760894-images-thumbs&n=13", - isScam = false, - unreadMessageCount = 101, - isMute = false, - isSpeeching = false, - isTyping = false, - isUnreadedAnswerToYou = false, - isAnswered = true, - isLock = false, - lastAnswererUrl = "https://avatars.mds.yandex.net/get-kinopoisk-image/1898899/78473e64-0a54-46ba-87ad-94b2822e9aaf/1920x" ) + } + +} + + - ) -} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..43eb47e --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_background.xml b/app/src/main/res/drawable/rounded_background.xml index c553e70..d320df6 100644 --- a/app/src/main/res/drawable/rounded_background.xml +++ b/app/src/main/res/drawable/rounded_background.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index cfaf856..82ab7a4 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -40,6 +40,20 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/toolbar" /> + + + + diff --git a/app/src/main/res/layout/item_chat.xml b/app/src/main/res/layout/item_chat.xml index 98ff43b..4529823 100644 --- a/app/src/main/res/layout/item_chat.xml +++ b/app/src/main/res/layout/item_chat.xml @@ -308,7 +308,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index adcf19f..1849f80 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -13,5 +13,6 @@ @color/textSecondary @color/DarkDivider + @color/colorDarkRecyclerItem \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 78880e1..08d9831 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -3,5 +3,6 @@ + \ 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 c32b5ee..873f527 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -32,6 +32,7 @@ #d3d3d3 #9e9e9e + #FFFFFF #1f2a36 @@ -48,5 +49,6 @@ #212121 #9e9e9e + #121212 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee58ba3..791ec94 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,4 +36,7 @@ Home selected Settings selected About selected + Invalid view type + New message + Write new message \ 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 83c09d4..d35017b 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -13,5 +13,6 @@ @color/textSecondary @color/divider + @color/colorRecyclerItem \ No newline at end of file