Improve manga list
This commit is contained in:
@@ -6,23 +6,37 @@ import androidx.appcompat.widget.AppCompatImageView
|
||||
|
||||
|
||||
class CoverImageView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val originalWidth = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val calculatedHeight: Int = (originalWidth * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).toInt()
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
||||
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED) {
|
||||
val originalWidth = MeasureSpec.getSize(widthMeasureSpec)
|
||||
super.onMeasure(
|
||||
MeasureSpec.makeMeasureSpec(originalWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(
|
||||
(originalWidth * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).toInt(),
|
||||
MeasureSpec.EXACTLY
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val originalHeight = MeasureSpec.getSize(heightMeasureSpec)
|
||||
super.onMeasure(
|
||||
MeasureSpec.makeMeasureSpec(
|
||||
(originalHeight * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT).toInt(),
|
||||
MeasureSpec.EXACTLY
|
||||
),
|
||||
MeasureSpec.makeMeasureSpec(originalHeight, MeasureSpec.EXACTLY)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
super.onMeasure(
|
||||
MeasureSpec.makeMeasureSpec(originalWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(calculatedHeight, MeasureSpec.EXACTLY)
|
||||
)
|
||||
}
|
||||
private companion object {
|
||||
|
||||
private companion object {
|
||||
|
||||
const val ASPECT_RATIO_HEIGHT = 18f
|
||||
const val ASPECT_RATIO_WIDTH = 13f
|
||||
}
|
||||
const val ASPECT_RATIO_HEIGHT = 18f
|
||||
const val ASPECT_RATIO_WIDTH = 13f
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.ui.common.list.BaseRecyclerAdapter
|
||||
class MangaListAdapter(onItemClickListener: ((Manga) -> Unit)?) :
|
||||
BaseRecyclerAdapter<Manga>(onItemClickListener) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup) = MangaGridHolder(parent)
|
||||
override fun onCreateViewHolder(parent: ViewGroup) = MangaListHolder(parent)
|
||||
|
||||
override fun onGetItemId(item: Manga) = item.id
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package org.koitharu.kotatsu.ui.main.list
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.fragment_list.*
|
||||
import moxy.ktx.moxyPresenter
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -12,6 +14,7 @@ import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.ui.common.BaseFragment
|
||||
import org.koitharu.kotatsu.ui.common.list.PaginationScrollListener
|
||||
import org.koitharu.kotatsu.ui.common.list.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.hasItems
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@@ -29,7 +32,8 @@ class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView,
|
||||
adapter = MangaListAdapter {
|
||||
|
||||
}
|
||||
recyclerView.addItemDecoration(SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)))
|
||||
// recyclerView.addItemDecoration(SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)))
|
||||
recyclerView.addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL))
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.addOnScrollListener(PaginationScrollListener(4, this))
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
@@ -54,6 +58,12 @@ class MangaListFragment : BaseFragment(R.layout.fragment_list), MangaListView,
|
||||
adapter.appendData(list)
|
||||
}
|
||||
|
||||
override fun onError(e: Exception) {
|
||||
if (recyclerView.hasItems) {
|
||||
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadingChanged(isLoading: Boolean) {
|
||||
val hasItems = recyclerView.hasItems
|
||||
progressBar.isVisible = isLoading && !hasItems
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.koitharu.kotatsu.ui.main.list
|
||||
|
||||
import android.view.ViewGroup
|
||||
import coil.api.load
|
||||
import coil.request.RequestDisposable
|
||||
import kotlinx.android.synthetic.main.item_manga_list.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
|
||||
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||
|
||||
class MangaListHolder(parent: ViewGroup) : BaseViewHolder<Manga>(parent, R.layout.item_manga_list) {
|
||||
|
||||
private var coverRequest: RequestDisposable? = null
|
||||
|
||||
override fun onBind(data: Manga) {
|
||||
coverRequest?.dispose()
|
||||
textView_title.text = data.title
|
||||
textView_subtitle.textAndVisible = data.localizedTitle
|
||||
coverRequest = imageView_cover.load(data.coverUrl) {
|
||||
crossfade(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import moxy.InjectViewState
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
||||
import org.koitharu.kotatsu.ui.common.BasePresenter
|
||||
@@ -25,7 +26,10 @@ class MangaListPresenter : BasePresenter<MangaListView>() {
|
||||
viewState.onListAppended(list)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
viewState.onError(e)
|
||||
} finally {
|
||||
viewState.onLoadingChanged(false)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package org.koitharu.kotatsu.ui.main.list
|
||||
|
||||
import moxy.MvpView
|
||||
import moxy.viewstate.strategy.AddToEndSingleStrategy
|
||||
import moxy.viewstate.strategy.AddToEndSingleTagStrategy
|
||||
import moxy.viewstate.strategy.AddToEndStrategy
|
||||
import moxy.viewstate.strategy.StateStrategyType
|
||||
import moxy.viewstate.strategy.*
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
|
||||
interface MangaListView : MvpView {
|
||||
@@ -17,4 +14,7 @@ interface MangaListView : MvpView {
|
||||
|
||||
@StateStrategyType(AddToEndSingleStrategy::class)
|
||||
fun onLoadingChanged(isLoading: Boolean)
|
||||
|
||||
@StateStrategyType(OneExecutionStateStrategy::class)
|
||||
fun onError(e: Exception)
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.content.res.Resources
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import java.io.IOException
|
||||
|
||||
inline fun <T, R> T.safe(action: T.() -> R?) = try {
|
||||
this.action()
|
||||
@@ -9,4 +12,13 @@ inline fun <T, R> T.safe(action: T.() -> R?) = try {
|
||||
e.printStackTrace()
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
fun Throwable.getDisplayMessage(resources: Resources) = when(this) {
|
||||
is IOException -> resources.getString(R.string.network_error)
|
||||
else -> if (BuildConfig.DEBUG) {
|
||||
message ?: resources.getString(R.string.error_occurred)
|
||||
} else {
|
||||
resources.getString(R.string.error_occurred)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
@@ -52,4 +54,11 @@ var TextView.drawableEnd: Drawable?
|
||||
set(value) {
|
||||
val old = compoundDrawablesRelative
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(old[0], old[1], value, old[3])
|
||||
}
|
||||
|
||||
var TextView.textAndVisible: CharSequence?
|
||||
get() = text?.takeIf { visibility == View.VISIBLE }
|
||||
set(value) {
|
||||
text = value
|
||||
isGone = value.isNullOrEmpty()
|
||||
}
|
||||
@@ -16,8 +16,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="3" />
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
|
||||
40
app/src/main/res/layout/item_manga_list.xml
Normal file
40
app/src/main/res/layout/item_manga_list.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/list_item_height"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.koitharu.kotatsu.ui.common.widgets.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="2"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="grid_spacing">5dp</dimen>
|
||||
<dimen name="list_item_height">84dp</dimen>
|
||||
</resources>
|
||||
@@ -5,4 +5,6 @@
|
||||
<string name="local_storage">Local storage</string>
|
||||
<string name="favourites">Favourites</string>
|
||||
<string name="history">History</string>
|
||||
<string name="error_occurred">An error has occurred</string>
|
||||
<string name="network_error">Network connection error</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user