diff --git a/app/build.gradle b/app/build.gradle index c1b2a86..99ff079 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,12 +4,12 @@ plugins { } android { - compileSdk 32 + compileSdk 34 defaultConfig { applicationId "otus.gpb.recyclerview" - minSdk 23 - targetSdk 32 + minSdk 24 + targetSdk 34 versionCode 1 versionName "1.0" @@ -29,6 +29,7 @@ android { kotlinOptions { jvmTarget = '1.8' } + namespace 'otus.gpb.recyclerview' } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23344c5..ef75335 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> { + return mutableListOf( + Item( + avatar = R.drawable.avatar_pizza, + name = "Pizza", + message = "Yes,they are necessary", + comment = "jija", + date = "11.38 AM", + mute = true, + verify = false, + unreadMessages = 0, + check = false, + doubleCheck = false, + scum = false, + pictureInMessage = true + ), + Item( + avatar = R.drawable.avatar_elon, + name = "Elon", + message = "I love /r/Reddit!", + comment = "", + date = "12.44 AM", + mute = true, + verify = false, + unreadMessages = 0, + check = false, + doubleCheck = false, + scum = false, + pictureInMessage = false + ), + Item( + avatar = R.drawable.avatar_pasha, + name = "Pasha", + message = "How are u?", + comment = "", + date = "Fri", + mute = true, + verify = false, + unreadMessages = 0, + check = false, + doubleCheck = false, + scum = false, + pictureInMessage = false + ), + Item( + avatar = R.drawable.kitty, + name = "Kitty", + message = "The time is now", + comment = "", + date = "Thu", + mute = true, + verify = false, + unreadMessages = 0, + check = false, + doubleCheck = true, + scum = false, + pictureInMessage = false + ), + + Item( + avatar = R.drawable.avatar_telegram, + name = "Telegram Support", + message = "Yes it happened", + comment = "Support", + date = "Thu", + mute = false, + verify = true, + unreadMessages = 1, + check = false, + doubleCheck = false, + scum = false, + pictureInMessage = false + ), + + Item( + avatar = R.drawable.avatar_karina, + name = "Karina", + message = "Okey", + comment = "", + date = "Wed", + mute = false, + verify = false, + unreadMessages = 0, + check = true, + doubleCheck = false, + scum = false, + pictureInMessage = false + ), + + Item( + avatar = R.drawable.avatar_marilyn, + name = "Marilyn", + message = "Will it ever happen", + comment = "", + date = "May 02", + mute = false, + verify = false, + unreadMessages = 0, + check = false, + doubleCheck = true, + scum = true, + pictureInMessage = false + ), + Item( + avatar = R.drawable.avatar_pizza, + name = "Pizza", + message = "Yes,they are necessary", + comment = "jija", + date = "11.38 AM", + mute = true, + verify = false, + unreadMessages = 0, + check = false, + doubleCheck = false, + scum = false, + pictureInMessage = false + ), + Item( + avatar = R.drawable.avatar_elon, + name = "Elon", + message = "I love r/r/Reddit!", + comment = "", + date = "12.44 AM", + mute = true, + verify = false, + unreadMessages = 0, + check = false, + doubleCheck = false, + scum = false, + pictureInMessage = false + ), + Item( + avatar = R.drawable.avatar_pasha, + name = "Pasha", + message = "How are u?", + comment = "", + date = "Fri", + mute = true, + verify = false, + unreadMessages = 0, + check = false, + doubleCheck = false, + scum = false, + pictureInMessage = false + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt new file mode 100644 index 0000000..aaf608b --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ChatAdapter.kt @@ -0,0 +1,39 @@ +package otus.gpb.recyclerview + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView + +class ChatAdapter: RecyclerView.Adapter() { + private var chatList = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder { + val itemView = LayoutInflater.from(parent.context) + .inflate(R.layout.person_item, parent, false) + return PersonViewHolder(itemView) + } + + override fun getItemCount() = chatList.size + + override fun onBindViewHolder(holder: PersonViewHolder, position: Int) { + val chat = chatList[position] + holder.bind(chat) + } + + fun setItems(items: MutableList) { + chatList = items + notifyDataSetChanged() + } + + fun addItems(items: MutableList) { + chatList.addAll(items) + notifyItemRangeChanged(1,10) + } + + fun removeItem(adapterPosition: Int) { + val newList = chatList.toMutableList().apply { + removeAt(adapterPosition) + } + setItems(newList) + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/DividerItemDecoration.kt b/app/src/main/java/otus/gpb/recyclerview/DividerItemDecoration.kt new file mode 100644 index 0000000..ac86033 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/DividerItemDecoration.kt @@ -0,0 +1,33 @@ +package otus.gpb.recyclerview + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import androidx.recyclerview.widget.RecyclerView + +class DividerItemDecoration: RecyclerView.ItemDecoration() { + + private val bounds = Rect() + private val paint = Paint().apply { + color = Color.parseColor("#8D8E90") + strokeWidth = 2f + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(canvas, parent, state) + + val childCount = parent.childCount + for (i in 0 until childCount) { + val child = parent.getChildAt(i) + parent.getDecoratedBoundsWithMargins(child, bounds) + canvas.drawLine( + 210f, + bounds.top.toFloat(), + bounds.right.toFloat(), + bounds.top.toFloat(), + paint + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/Item.kt b/app/src/main/java/otus/gpb/recyclerview/Item.kt new file mode 100644 index 0000000..d04db76 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/Item.kt @@ -0,0 +1,16 @@ +package otus.gpb.recyclerview + +data class Item( + val avatar: Int, + val name: String, + val message: String, + val comment: String = "", + val date: String, + val mute: Boolean = false, + val verify: Boolean = false, + val unreadMessages: Int, + val check: Boolean = false, + val doubleCheck: Boolean = false, + val scum: Boolean = false, + val pictureInMessage: Boolean = false +) \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/ItemTouchHelperCallback.kt b/app/src/main/java/otus/gpb/recyclerview/ItemTouchHelperCallback.kt new file mode 100644 index 0000000..985c366 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/ItemTouchHelperCallback.kt @@ -0,0 +1,83 @@ +package otus.gpb.recyclerview + +import android.content.res.Resources +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.drawable.ColorDrawable +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView + +class ItemTouchHelperCallback( + private val chatAdapter: ChatAdapter, + private val resources: Resources +) : ItemTouchHelper.Callback() { + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + return makeMovementFlags( + ItemTouchHelper.DOWN, + ItemTouchHelper.LEFT + ) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return true + } + + override fun onChildDraw( + canvas: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + val itemView = viewHolder.itemView + val background = ColorDrawable(Color.parseColor("#66A9E0")) + + background.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom) + background.draw(canvas) + + val scaledDensity = resources.displayMetrics.scaledDensity + val density = resources.displayMetrics.density + + fun spToPx(sp: Float): Float = sp * scaledDensity + fun dpToPx(dp:Int): Int = (density * dp + 0.5f).toInt() + + val archiveIcon = ContextCompat.getDrawable(itemView.context, R.drawable.archive) + val iconHeight = archiveIcon?.intrinsicHeight!! + + val iconTop = itemView.top + dpToPx(16) + val iconBottom = iconTop + iconHeight + val iconRight = itemView.right - dpToPx(29) + val iconLeft = iconRight - archiveIcon.intrinsicWidth + + archiveIcon.setBounds(iconLeft, iconTop, iconRight, iconBottom) + archiveIcon.draw(canvas) + + val textPaint = Paint().apply { + color = Color.WHITE + textSize = spToPx(13F) + } + val text = "Archive" + val textWidth = textPaint.measureText(text) + val textX = itemView.right - dpToPx(20).toFloat() - textWidth + val textY = itemView.bottom - dpToPx(16).toFloat() + + canvas.drawText(text, textX, textY, textPaint) + super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val position = viewHolder.adapterPosition + chatAdapter.removeItem(position) + } +} diff --git a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt index e2cdca7..d480d29 100644 --- a/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt +++ b/app/src/main/java/otus/gpb/recyclerview/MainActivity.kt @@ -1,12 +1,39 @@ package otus.gpb.recyclerview -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { + private val chatAdapter: ChatAdapter by lazy { + ChatAdapter() + } + private val recyclerView: RecyclerView by lazy { + findViewById(R.id.recyclerView) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + val layoutManager = LinearLayoutManager(this@MainActivity) + val paging = PageScrollListener(layoutManager).apply{ + this.setOnLoadMoreListener { loadMoreData() } + } + recyclerView.apply { + this.layoutManager = layoutManager + this.adapter = chatAdapter + this.addItemDecoration(DividerItemDecoration()) + this.addOnScrollListener(paging) + } + chatAdapter.setItems(Chat().fillData()) + val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback(chatAdapter, resources)) + itemTouchHelper.attachToRecyclerView(recyclerView) + } + + private fun loadMoreData() { + chatAdapter.addItems(Chat().fillData()) } } \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/PageScrollListner.kt b/app/src/main/java/otus/gpb/recyclerview/PageScrollListner.kt new file mode 100644 index 0000000..5fb7121 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/PageScrollListner.kt @@ -0,0 +1,26 @@ +package otus.gpb.recyclerview + +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +class PageScrollListener (private val layoutManager:LinearLayoutManager +) : RecyclerView.OnScrollListener() { + + private var onLoadMore: (() -> Unit)? = null + + override fun onScrolled(recycleView:RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recycleView, dx, dy) + + val totalItemCount = layoutManager.itemCount + val visibleItemCount = layoutManager.childCount + val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition() + + if ((visibleItemCount + firstVisiblePosition) >= totalItemCount) { + onLoadMore?.invoke() + } + } + + fun setOnLoadMoreListener(callback: () -> Unit) { + onLoadMore = callback + } +} \ No newline at end of file diff --git a/app/src/main/java/otus/gpb/recyclerview/PersonViewHolder.kt b/app/src/main/java/otus/gpb/recyclerview/PersonViewHolder.kt new file mode 100644 index 0000000..e3a06d8 --- /dev/null +++ b/app/src/main/java/otus/gpb/recyclerview/PersonViewHolder.kt @@ -0,0 +1,40 @@ +package otus.gpb.recyclerview + +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView + +open class PersonViewHolder(view: View) : RecyclerView.ViewHolder(view) { + + fun bind(item: Item) { + itemView.findViewById(R.id.avatar).setImageResource(item.avatar) + itemView.findViewById(R.id.name).text = item.name + itemView.findViewById(R.id.message).text = item.message + itemView.findViewById(R.id.date).text = item.date + itemView.findViewById(R.id.mute).isVisible = item.mute + itemView.findViewById(R.id.verify).isVisible = item.verify + + val comment = itemView.findViewById(R.id.comment) + if (item.comment !="") { + comment.text = item.comment + comment.isVisible = true + } else { + comment.isVisible = false + } + + itemView.findViewById(R.id.check).isVisible = item.check + itemView.findViewById(R.id.doublecheck).isVisible = item.doubleCheck + itemView.findViewById(R.id.scum).isVisible = item.scum + + val unreadCount = itemView.findViewById(R.id.unreadcount) + if (item.unreadMessages > 0) { + unreadCount.text = item.unreadMessages.toString() + unreadCount.isVisible = true + } else { + unreadCount.isVisible = false + } + itemView.findViewById(R.id.pictureInMessage).isVisible = item.pictureInMessage + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/archive.xml b/app/src/main/res/drawable/archive.xml new file mode 100644 index 0000000..868843e --- /dev/null +++ b/app/src/main/res/drawable/archive.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/avatar_elon.png b/app/src/main/res/drawable/avatar_elon.png new file mode 100644 index 0000000..a628335 Binary files /dev/null and b/app/src/main/res/drawable/avatar_elon.png differ diff --git a/app/src/main/res/drawable/avatar_karina.png b/app/src/main/res/drawable/avatar_karina.png new file mode 100644 index 0000000..f396114 Binary files /dev/null and b/app/src/main/res/drawable/avatar_karina.png differ diff --git a/app/src/main/res/drawable/avatar_marilyn.png b/app/src/main/res/drawable/avatar_marilyn.png new file mode 100644 index 0000000..95fa722 Binary files /dev/null and b/app/src/main/res/drawable/avatar_marilyn.png differ diff --git a/app/src/main/res/drawable/avatar_pasha.png b/app/src/main/res/drawable/avatar_pasha.png new file mode 100644 index 0000000..83f690c Binary files /dev/null and b/app/src/main/res/drawable/avatar_pasha.png differ diff --git a/app/src/main/res/drawable/avatar_pizza.png b/app/src/main/res/drawable/avatar_pizza.png new file mode 100644 index 0000000..1a82ec7 Binary files /dev/null and b/app/src/main/res/drawable/avatar_pizza.png differ diff --git a/app/src/main/res/drawable/avatar_telegram.png b/app/src/main/res/drawable/avatar_telegram.png new file mode 100644 index 0000000..dbd9667 Binary files /dev/null and b/app/src/main/res/drawable/avatar_telegram.png differ diff --git a/app/src/main/res/drawable/check.xml b/app/src/main/res/drawable/check.xml new file mode 100644 index 0000000..45327ce --- /dev/null +++ b/app/src/main/res/drawable/check.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/circle.xml b/app/src/main/res/drawable/circle.xml new file mode 100644 index 0000000..a4831d2 --- /dev/null +++ b/app/src/main/res/drawable/circle.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/doublecheck.xml b/app/src/main/res/drawable/doublecheck.xml new file mode 100644 index 0000000..248c046 --- /dev/null +++ b/app/src/main/res/drawable/doublecheck.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/img.png b/app/src/main/res/drawable/img.png new file mode 100644 index 0000000..661777e Binary files /dev/null and b/app/src/main/res/drawable/img.png differ diff --git a/app/src/main/res/drawable/kitty.png b/app/src/main/res/drawable/kitty.png new file mode 100644 index 0000000..3012a47 Binary files /dev/null and b/app/src/main/res/drawable/kitty.png differ diff --git a/app/src/main/res/drawable/mute.png b/app/src/main/res/drawable/mute.png new file mode 100644 index 0000000..3dd5ef0 Binary files /dev/null and b/app/src/main/res/drawable/mute.png differ diff --git a/app/src/main/res/drawable/scum.png b/app/src/main/res/drawable/scum.png new file mode 100644 index 0000000..b3e9480 Binary files /dev/null and b/app/src/main/res/drawable/scum.png differ diff --git a/app/src/main/res/drawable/verify.png b/app/src/main/res/drawable/verify.png new file mode 100644 index 0000000..0fc21fd Binary files /dev/null and b/app/src/main/res/drawable/verify.png differ diff --git a/app/src/main/res/font/roboto.ttf b/app/src/main/res/font/roboto.ttf new file mode 100644 index 0000000..3313686 Binary files /dev/null and b/app/src/main/res/font/roboto.ttf differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2d026df..5b0deeb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,13 +1,19 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto" + tools:context=".MainActivity" + android:id="@+id/main"> + android:layout_height="match_parent" + android:orientation="vertical" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:itemCount="10" + tools:listitem="@layout/person_item" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/person_item.xml b/app/src/main/res/layout/person_item.xml new file mode 100644 index 0000000..abcaa97 --- /dev/null +++ b/app/src/main/res/layout/person_item.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 114f376..68f8225 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,6 +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 f8c6127..d76d028 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,6 @@ #FF018786 #FF000000 #FFFFFFFF + #0CE4F8 + #4BF80C \ 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..42d0ed7 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/build.gradle b/build.gradle index 62d0e4c..aa5efea 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.2.0' apply false - id 'com.android.library' version '7.2.0' apply false + id 'com.android.application' version '8.4.0' apply false + id 'com.android.library' version '8.4.0' apply false id 'org.jetbrains.kotlin.android' version '1.6.21' apply false } diff --git a/gradle.properties b/gradle.properties index cd0519b..022338b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,6 @@ 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.defaults.buildfeatures.buildconfig=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f4bda2..e86e59c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Nov 06 22:33:53 MSK 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME