浏览代码

Added sticky headers for books on home tab

Vadik Sirekanyan 7 年之前
父节点
当前提交
a146e21cd9

+ 27 - 29
app/src/main/java/me/vadik/knigopis/BookRepository.kt

@@ -10,7 +10,7 @@ import me.vadik.knigopis.model.*
 
 interface BookRepository {
 
-    fun loadBooks(): Single<List<Book>>
+    fun loadBooks(): Single<List<Pair<Book, BookHeader>>>
 
     fun saveBook(bookId: String?, book: FinishedBookToSend, done: Boolean?): Completable
 
@@ -24,19 +24,21 @@ class BookRepositoryImpl(
     private val resources: ResourceProvider
 ) : BookRepository {
 
-    override fun loadBooks(): Single<List<Book>> =
+    override fun loadBooks(): Single<List<Pair<Book, BookHeader>>> =
         Singles.zip(
             api.getPlannedBooks(auth.getAccessToken())
                 .map { it.sortedByDescending(PlannedBook::priority) },
             api.getFinishedBooks(auth.getAccessToken())
                 .map { it.sortedByDescending(FinishedBook::order) }
-                .map { it.groupFinishedBooks() }
+                .map { groupFinishedBooks(it) }
         ).map { (planned, finished) ->
-            mutableListOf<Book>().apply {
+            mutableListOf<Pair<Book, BookHeader>>().apply {
                 if (planned.isNotEmpty()) {
-                    add(BookHeader(resources.getString(R.string.books_header_todo), planned.size))
+                    val todoHeaderTitle = resources.getString(R.string.books_header_todo)
+                    val todoHeader = BookHeader(todoHeaderTitle, planned.size)
+                    add(todoHeader to todoHeader)
+                    addAll(planned.map { it to todoHeader })
                 }
-                addAll(planned)
                 addAll(finished)
             }
         }
@@ -63,29 +65,25 @@ class BookRepositoryImpl(
             }
         }
 
-    private fun List<FinishedBook>.groupFinishedBooks(): List<Book> {
-        val groupedBooks = mutableListOf<Book>()
-        var previousReadYear = Int.MAX_VALUE.toString()
-        forEachIndexed { index, book ->
-            val readYear = book.readYear
-            if (previousReadYear != readYear) {
-                groupedBooks.add(
-                    BookHeader(
-                        when {
-                            book.readYear.isEmpty() ->
-                                resources.getString(R.string.books_header_done_other)
-                            index == 0 ->
-                                resources.getString(R.string.books_header_done_first, readYear)
-                            else ->
-                                resources.getString(R.string.books_header_done, readYear)
-                        },
-                        count = 0 // todo
-                    )
-                )
+    private fun groupFinishedBooks(books: List<FinishedBook>): List<Pair<Book, BookHeader>> {
+        var first = true
+        return books.groupBy { it.readYear }
+            .toSortedMap(Comparator { year1, year2 ->
+                year2.compareTo(year1)
+            })
+            .flatMap { (year, books) ->
+                val headerTitle = when {
+                    year.isEmpty() -> resources.getString(R.string.books_header_done_other)
+                    first -> {
+                        first = false
+                        resources.getString(R.string.books_header_done_first, year)
+                    }
+                    else -> resources.getString(R.string.books_header_done, year)
+                }
+                val header = BookHeader(headerTitle, books.size)
+                val items = books.map { it to header }
+                listOf(header to header, *items.toTypedArray())
             }
-            groupedBooks.add(book)
-            previousReadYear = book.readYear
-        }
-        return groupedBooks
     }
+
 }

+ 60 - 9
app/src/main/java/me/vadik/knigopis/MainActivity.kt

@@ -27,19 +27,19 @@ import me.vadik.knigopis.adapters.users.UsersAdapter
 import me.vadik.knigopis.api.BookCoverSearch
 import me.vadik.knigopis.api.Endpoint
 import me.vadik.knigopis.auth.KAuth
+import me.vadik.knigopis.common.HeaderItemDecoration
 import me.vadik.knigopis.common.ResourceProvider
+import me.vadik.knigopis.common.StickyHeaderInterface
 import me.vadik.knigopis.data.AvatarCache
 import me.vadik.knigopis.data.AvatarCacheImpl
 import me.vadik.knigopis.dialog.DialogFactory
-import me.vadik.knigopis.model.Book
-import me.vadik.knigopis.model.CurrentTab
+import me.vadik.knigopis.model.*
 import me.vadik.knigopis.model.CurrentTab.*
-import me.vadik.knigopis.model.FinishedBook
-import me.vadik.knigopis.model.PlannedBook
 import me.vadik.knigopis.model.note.Note
 import me.vadik.knigopis.model.subscription.Subscription
 import me.vadik.knigopis.profile.createProfileIntent
 import me.vadik.knigopis.user.createUserIntent
+import me.vadik.knigopis.utils.showNow
 import me.vadik.knigopis.utils.startActivityOrNull
 import me.vadik.knigopis.utils.toast
 import org.koin.android.ext.android.inject
@@ -59,13 +59,24 @@ class MainActivity : AppCompatActivity(), Router {
     private val auth by inject<KAuth>()
     private val dialogs by inject<DialogFactory> { mapOf("activity" to this) }
     private val bookRepository by inject<BookRepository>()
-    private val resources by inject<ResourceProvider>()
+    private val resourceProvider by inject<ResourceProvider>()
     private val allBooks = mutableListOf<Book>()
+    private val allBookHeaders = mutableListOf<BookHeader>()
     private val allUsers = mutableListOf<Subscription>()
     private val allNotes = mutableListOf<Note>()
-    private val booksAdapter by lazy { BooksAdapter(bookCoverSearch, api, auth, this, dialogs) }
-    private val allBooksAdapter by lazy { booksAdapter.build(allBooks) }
-    private val usersAdapter by lazy { UsersAdapter(allUsers, this, dialogs, resources) }
+    private val booksAdapter by lazy {
+        BooksAdapter(
+            bookCoverSearch,
+            api,
+            auth,
+            this,
+            dialogs,
+            allBooks,
+            allBookHeaders
+        )
+    }
+    private val allBooksAdapter by lazy { booksAdapter.build() }
+    private val usersAdapter by lazy { UsersAdapter(allUsers, this, dialogs, resourceProvider) }
     private val avatarCache by lazy { AvatarCacheImpl() as AvatarCache }
     private val notesAdapter by lazy { NotesAdapter(allNotes, avatarCache, this) }
     private var userLoggedIn = false
@@ -81,6 +92,44 @@ class MainActivity : AppCompatActivity(), Router {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
         initRecyclerView(booksRecyclerView)
+        booksRecyclerView.addItemDecoration(
+            HeaderItemDecoration(
+                object : StickyHeaderInterface {
+                    override fun getHeaderPositionForItem(itemPosition: Int): Int {
+                        return itemPosition
+                    }
+
+                    override fun getHeaderLayout(headerPosition: Int): Int {
+                        return R.layout.header
+                    }
+
+                    override fun bindHeaderData(header: View, headerPosition: Int) {
+                        val book = allBookHeaders[headerPosition]
+                        val title = book.title.let {
+                            if (it.isEmpty()) {
+                                getString(R.string.books_header_done_other)
+                            } else {
+                                it
+                            }
+                        }
+                        header.findViewById<TextView>(R.id.book_title).text = title
+                        header.findViewById<TextView>(R.id.books_count).text =
+                                resources.getQuantityString(
+                                    R.plurals.common_header_books,
+                                    book.count,
+                                    book.count
+                                )
+                        header.findViewById<TextView>(R.id.books_count).showNow()
+                        header.findViewById<View>(R.id.header_bottom_divider).showNow()
+                    }
+
+                    override fun isHeader(itemPosition: Int): Boolean {
+                        return allBooks[itemPosition] is BookHeader
+                    }
+                }
+            )
+        )
+
         initRecyclerView(usersRecyclerView)
         initRecyclerView(notesRecyclerView)
         val currentTabId = savedInstanceState?.getInt(CURRENT_TAB_KEY)
@@ -334,7 +383,9 @@ class MainActivity : AppCompatActivity(), Router {
                 booksPlaceholder.show(books.isEmpty())
                 booksErrorPlaceholder.hide()
                 allBooks.clear()
-                allBooks.addAll(books)
+                allBooks.addAll(books.map { it.first })
+                allBookHeaders.clear()
+                allBookHeaders.addAll(books.map { it.second })
                 allBooksAdapter.notifyDataSetChanged()
             }, {
                 logError("cannot load books", it)

+ 14 - 2
app/src/main/java/me/vadik/knigopis/adapters/BooksAdapter.kt

@@ -27,10 +27,12 @@ class BooksAdapter(
     private val api: Endpoint,
     private val auth: KAuth,
     private val router: Router,
-    private val dialogs: DialogFactory
+    private val dialogs: DialogFactory,
+    private val books: MutableList<Book>,
+    private val bookHeaders: MutableList<BookHeader>
 ) {
 
-    fun build(books: MutableList<Book>) = Adapter(books) {
+    fun build() = Adapter(books) {
         if (it is BookHeader) {
             R.layout.header
         } else {
@@ -52,6 +54,7 @@ class BooksAdapter(
                             logError("cannot delete finished book", it)
                         })
                     books.removeAt(index)
+                    bookHeaders.removeAt(index)
                     adapter.notifyItemRemoved(index)
                 }
             }
@@ -123,6 +126,15 @@ class BooksAdapter(
         .bind<View>(R.id.header_divider) {
             visibility = if (it == 0) View.INVISIBLE else View.VISIBLE
         }
+        .bind<TextView>(R.id.books_count) {
+            val count = (books[it] as? BookHeader)?.count ?: 0
+            text = resources.getQuantityString(
+                R.plurals.common_header_books,
+                count,
+                count
+            )
+            showNow(count > 0)
+        }
         .bind<TextView>(R.id.book_author) {
             text = books[it].authorOrDefault
         }

+ 0 - 33
app/src/main/java/me/vadik/knigopis/adapters/books/BookViewHolder.kt

@@ -1,33 +0,0 @@
-package me.vadik.knigopis.adapters.books
-
-import android.support.v7.widget.RecyclerView
-import android.view.View
-import kotlinx.android.synthetic.main.user_book.view.*
-import me.vadik.knigopis.utils.hideNow
-import me.vadik.knigopis.utils.showNow
-
-class BookViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
-
-    var title: String
-        get() = view.bookTitle.text.toString()
-        set(value) {
-            view.bookTitle.text = value
-        }
-
-    var author: String
-        get() = view.bookAuthor.text.toString()
-        set(value) {
-            view.bookAuthor.text = value
-        }
-
-    var notes: String
-        get() = view.bookNotes.text.toString()
-        set(value) {
-            if (value.isEmpty()) {
-                view.bookNotes.hideNow()
-            } else {
-                view.bookNotes.showNow()
-                view.bookNotes.text = value
-            }
-        }
-}

+ 2 - 0
app/src/main/res/layout/books_page.xml

@@ -10,6 +10,8 @@
         android:id="@+id/booksRecyclerView"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:clipToPadding="false"
+        android:paddingTop="0.5dp"
         tools:listitem="@layout/book" />
 
     <TextView