Migrate to ViewBinding

pull/26/head
Koitharu 5 years ago
parent a215d9ebfc
commit 75b3ea0bc9

@ -1,9 +1,8 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'kotlin-android' id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt' id 'kotlin-kapt'
// TODO id 'kotlin-parcelize' id 'kotlin-parcelize'
} }
def gitCommits = 'git rev-list --count HEAD'.execute([], rootDir).text.trim().toInteger() def gitCommits = 'git rev-list --count HEAD'.execute([], rootDir).text.trim().toInteger()
@ -40,6 +39,9 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
buildFeatures {
viewBinding true
}
lintOptions { lintOptions {
disable 'MissingTranslation' disable 'MissingTranslation'
abortOnError false abortOnError false
@ -49,9 +51,6 @@ android {
unitTests.returnDefaultValues = true unitTests.returnDefaultValues = true
} }
} }
androidExtensions {
experimental = true
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString() jvmTarget = JavaVersion.VERSION_1_8.toString()
@ -73,13 +72,15 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-beta01' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-beta01'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-beta01' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-beta01'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-beta01' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-beta01'
implementation 'androidx.lifecycle:lifecycle-service:2.3.0-beta01'
implementation 'androidx.lifecycle:lifecycle-process:2.3.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha06' implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha06'
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01' implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.work:work-runtime-ktx:2.4.0' implementation 'androidx.work:work-runtime-ktx:2.4.0'
implementation 'com.google.android.material:material:1.3.0-alpha03' implementation 'com.google.android.material:material:1.3.0-alpha04'
//noinspection LifecycleAnnotationProcessorWithJava8 //noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.3.0-beta01' kapt 'androidx.lifecycle:lifecycle-compiler:2.3.0-beta01'
@ -92,7 +93,7 @@ dependencies {
implementation 'org.jsoup:jsoup:1.13.1' implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.0' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.0'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:4.3.0' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.0'
implementation 'org.koin:koin-android:2.2.0' implementation 'org.koin:koin-android:2.2.0'
implementation 'org.koin:koin-android-viewmodel:2.2.0' implementation 'org.koin:koin-android-viewmodel:2.2.0'

@ -53,7 +53,6 @@ object MangaUtils : KoinComponent {
} }
} }
@JvmStatic
private fun getBitmapSize(input: InputStream?): Size { private fun getBitmapSize(input: InputStream?): Size {
val options = BitmapFactory.Options().apply { val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true inJustDecodeBounds = true

@ -2,39 +2,43 @@ package org.koitharu.kotatsu.base.ui
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.viewbinding.ViewBinding
abstract class AlertDialogFragment( abstract class AlertDialogFragment<B : ViewBinding> : DialogFragment() {
@LayoutRes private val layoutResId: Int
) : DialogFragment() {
private var rootView: View? = null private var viewBinding: B? = null
protected val binding: B
get() = checkNotNull(viewBinding)
final override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { final override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = activity?.layoutInflater?.inflate(layoutResId, null) val inflater = activity?.layoutInflater ?: LayoutInflater.from(requireContext())
rootView = view val binding = onInflateView(inflater, null)
if (view != null) { viewBinding = binding
onViewCreated(view, savedInstanceState) onViewCreated(binding.root, savedInstanceState)
}
return AlertDialog.Builder(requireContext(), theme) return AlertDialog.Builder(requireContext(), theme)
.setView(view) .setView(binding.root)
.also(::onBuildDialog) .also(::onBuildDialog)
.create() .create()
} }
@CallSuper @CallSuper
override fun onDestroyView() { override fun onDestroyView() {
rootView = null viewBinding = null
super.onDestroyView() super.onDestroyView()
} }
final override fun getView(): View? { final override fun getView(): View? {
return rootView return viewBinding?.root
} }
open fun onBuildDialog(builder: AlertDialog.Builder) = Unit open fun onBuildDialog(builder: AlertDialog.Builder) = Unit
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
} }

@ -7,12 +7,16 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.viewbinding.ViewBinding
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
abstract class BaseActivity : AppCompatActivity() { abstract class BaseActivity<B : ViewBinding> : AppCompatActivity() {
protected lateinit var binding: B
private set
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
if (get<AppSettings>().isAmoledTheme) { if (get<AppSettings>().isAmoledTheme) {
@ -21,16 +25,24 @@ abstract class BaseActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
} }
@Deprecated("Use ViewBinding", level = DeprecationLevel.ERROR)
override fun setContentView(layoutResID: Int) { override fun setContentView(layoutResID: Int) {
super.setContentView(layoutResID) super.setContentView(layoutResID)
setupToolbar() setupToolbar()
} }
@Deprecated("Use ViewBinding", level = DeprecationLevel.ERROR)
override fun setContentView(view: View?) { override fun setContentView(view: View?) {
super.setContentView(view) super.setContentView(view)
setupToolbar() setupToolbar()
} }
protected fun setContentView(binding: B) {
this.binding = binding
super.setContentView(binding.root)
(binding.root.findViewById<View>(R.id.toolbar) as? Toolbar)?.let(this::setSupportActionBar)
}
private fun setupToolbar() { private fun setupToolbar() {
(findViewById<View>(R.id.toolbar) as? Toolbar)?.let(this::setSupportActionBar) (findViewById<View>(R.id.toolbar) as? Toolbar)?.let(this::setSupportActionBar)
} }

@ -5,23 +5,39 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatDialog import androidx.appcompat.app.AppCompatDialog
import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.koitharu.kotatsu.utils.UiUtils import org.koitharu.kotatsu.utils.UiUtils
abstract class BaseBottomSheet(@LayoutRes private val layoutResId: Int) : abstract class BaseBottomSheet<B : ViewBinding> :
BottomSheetDialogFragment() { BottomSheetDialogFragment() {
private var viewBinding: B? = null
protected val binding: B
get() = checkNotNull(viewBinding)
final override fun onCreateView( final override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? = inflater.inflate(layoutResId, container, false) ): View {
val binding = onInflateView(inflater, container)
viewBinding = binding
return binding.root
}
override fun onDestroyView() {
viewBinding = null
super.onDestroyView()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return if (UiUtils.isTablet(requireContext())) { return if (UiUtils.isTablet(requireContext())) {
AppCompatDialog(context, theme) AppCompatDialog(context, theme)
} else super.onCreateDialog(savedInstanceState) } else super.onCreateDialog(savedInstanceState)
} }
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
} }

@ -1,19 +1,39 @@
package org.koitharu.kotatsu.base.ui package org.koitharu.kotatsu.base.ui
import android.content.Context import android.content.Context
import androidx.annotation.LayoutRes import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import coil.ImageLoader import androidx.viewbinding.ViewBinding
import org.koin.android.ext.android.inject
abstract class BaseFragment( abstract class BaseFragment<B : ViewBinding> : Fragment() {
@LayoutRes contentLayoutId: Int
) : Fragment(contentLayoutId) {
protected val coil by inject<ImageLoader>() private var viewBinding: B? = null
protected val binding: B
get() = checkNotNull(viewBinding)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = onInflateView(inflater, container)
viewBinding = binding
return binding.root
}
override fun onDestroyView() {
viewBinding = null
super.onDestroyView()
}
open fun getTitle(): CharSequence? = null open fun getTitle(): CharSequence? = null
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
getTitle()?.let { getTitle()?.let {

@ -5,9 +5,10 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.viewbinding.ViewBinding
abstract class BaseFullscreenActivity : BaseActivity() { abstract class BaseFullscreenActivity<B : ViewBinding> : BaseActivity<B>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

@ -1,14 +1,12 @@
package org.koitharu.kotatsu.base.ui.dialog package org.koitharu.kotatsu.base.ui.dialog
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.checkbox.MaterialCheckBox import org.koitharu.kotatsu.databinding.DialogCheckboxBinding
import org.koitharu.kotatsu.R
class CheckBoxAlertDialog private constructor(private val delegate: AlertDialog) : class CheckBoxAlertDialog private constructor(private val delegate: AlertDialog) :
DialogInterface by delegate { DialogInterface by delegate {
@ -17,13 +15,10 @@ class CheckBoxAlertDialog private constructor(private val delegate: AlertDialog)
class Builder(context: Context) { class Builder(context: Context) {
@SuppressLint("InflateParams") private val binding = DialogCheckboxBinding.inflate(LayoutInflater.from(context))
private val view = LayoutInflater.from(context)
.inflate(R.layout.dialog_checkbox, null, false)
private val checkBox = view.findViewById<MaterialCheckBox>(android.R.id.checkbox)
private val delegate = AlertDialog.Builder(context) private val delegate = AlertDialog.Builder(context)
.setView(view) .setView(binding.root)
fun setTitle(@StringRes titleResId: Int): Builder { fun setTitle(@StringRes titleResId: Int): Builder {
delegate.setTitle(titleResId) delegate.setTitle(titleResId)
@ -46,12 +41,12 @@ class CheckBoxAlertDialog private constructor(private val delegate: AlertDialog)
} }
fun setCheckBoxText(@StringRes textId: Int): Builder { fun setCheckBoxText(@StringRes textId: Int): Builder {
checkBox.setText(textId) binding.checkbox.setText(textId)
return this return this
} }
fun setCheckBoxChecked(isChecked: Boolean): Builder { fun setCheckBoxChecked(isChecked: Boolean): Builder {
checkBox.isChecked = isChecked binding.checkbox.isChecked = isChecked
return this return this
} }
@ -65,7 +60,7 @@ class CheckBoxAlertDialog private constructor(private val delegate: AlertDialog)
listener: (DialogInterface, Boolean) -> Unit listener: (DialogInterface, Boolean) -> Unit
): Builder { ): Builder {
delegate.setPositiveButton(textId) { dialog, _ -> delegate.setPositiveButton(textId) { dialog, _ ->
listener(dialog, checkBox.isChecked) listener(dialog, binding.checkbox.isChecked)
} }
return this return this
} }

@ -7,8 +7,8 @@ import android.view.ViewGroup
import android.widget.BaseAdapter import android.widget.BaseAdapter
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.item_storage.view.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemStorageBinding
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.utils.ext.getStorageName import org.koitharu.kotatsu.utils.ext.getStorageName
import org.koitharu.kotatsu.utils.ext.inflate import org.koitharu.kotatsu.utils.ext.inflate
@ -64,8 +64,9 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: parent.inflate(R.layout.item_storage) val view = convertView ?: parent.inflate(R.layout.item_storage)
val item = volumes[position] val item = volumes[position]
view.textView_title.text = item.second val binding = ItemStorageBinding.bind(view)
view.textView_subtitle.text = item.first.path binding.textViewTitle.text = item.second
binding.textViewSubtitle.text = item.first.path
return view return view
} }
@ -84,7 +85,6 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
private companion object { private companion object {
@JvmStatic
fun getAvailableVolumes(context: Context): List<Pair<File, String>> { fun getAvailableVolumes(context: Context): List<Pair<File, String>> {
return LocalMangaRepository.getAvailableStorageDirs(context).map { return LocalMangaRepository.getAvailableStorageDirs(context).map {
it to it.getStorageName(context) it to it.getStorageName(context)

@ -1,14 +1,12 @@
package org.koitharu.kotatsu.base.ui.dialog package org.koitharu.kotatsu.base.ui.dialog
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.text.InputFilter import android.text.InputFilter
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.dialog_input.view.* import org.koitharu.kotatsu.databinding.DialogInputBinding
import org.koitharu.kotatsu.R
class TextInputDialog private constructor( class TextInputDialog private constructor(
private val delegate: AlertDialog private val delegate: AlertDialog
@ -18,12 +16,10 @@ class TextInputDialog private constructor(
class Builder(context: Context) { class Builder(context: Context) {
@SuppressLint("InflateParams") private val binding = DialogInputBinding.inflate(LayoutInflater.from(context))
private val view = LayoutInflater.from(context)
.inflate(R.layout.dialog_input, null, false)
private val delegate = AlertDialog.Builder(context) private val delegate = AlertDialog.Builder(context)
.setView(view) .setView(binding.root)
fun setTitle(@StringRes titleResId: Int): Builder { fun setTitle(@StringRes titleResId: Int): Builder {
delegate.setTitle(titleResId) delegate.setTitle(titleResId)
@ -36,29 +32,29 @@ class TextInputDialog private constructor(
} }
fun setHint(@StringRes hintResId: Int): Builder { fun setHint(@StringRes hintResId: Int): Builder {
view.inputLayout.hint = view.context.getString(hintResId) binding.inputLayout.hint = binding.root.context.getString(hintResId)
return this return this
} }
fun setMaxLength(maxLength: Int, strict: Boolean): Builder { fun setMaxLength(maxLength: Int, strict: Boolean): Builder {
with(view.inputLayout) { with(binding.inputLayout) {
counterMaxLength = maxLength counterMaxLength = maxLength
isCounterEnabled = maxLength > 0 isCounterEnabled = maxLength > 0
} }
if (strict && maxLength > 0) { if (strict && maxLength > 0) {
view.inputEdit.filters += InputFilter.LengthFilter(maxLength) binding.inputEdit.filters += InputFilter.LengthFilter(maxLength)
} }
return this return this
} }
fun setInputType(inputType: Int): Builder { fun setInputType(inputType: Int): Builder {
view.inputEdit.inputType = inputType binding.inputEdit.inputType = inputType
return this return this
} }
fun setText(text: String): Builder { fun setText(text: String): Builder {
view.inputEdit.setText(text) binding.inputEdit.setText(text)
view.inputEdit.setSelection(text.length) binding.inputEdit.setSelection(text.length)
return this return this
} }
@ -67,7 +63,7 @@ class TextInputDialog private constructor(
listener: (DialogInterface, String) -> Unit listener: (DialogInterface, String) -> Unit
): Builder { ): Builder {
delegate.setPositiveButton(textId) { dialog, _ -> delegate.setPositiveButton(textId) { dialog, _ ->
listener(dialog, view.inputEdit.text?.toString().orEmpty()) listener(dialog, binding.inputEdit.text.toString().orEmpty())
} }
return this return this
} }

@ -1,20 +1,11 @@
package org.koitharu.kotatsu.base.ui.list package org.koitharu.kotatsu.base.ui.list
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer import androidx.viewbinding.ViewBinding
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koitharu.kotatsu.utils.ext.inflate
abstract class BaseViewHolder<T, E> protected constructor(view: View) : abstract class BaseViewHolder<T, E, B : ViewBinding> protected constructor(val binding: B) :
RecyclerView.ViewHolder(view), LayoutContainer, KoinComponent { RecyclerView.ViewHolder(binding.root), KoinComponent {
constructor(parent: ViewGroup, @LayoutRes resId: Int) : this(parent.inflate(resId))
override val containerView: View?
get() = itemView
var boundData: T? = null var boundData: T? = null
private set private set

@ -1,34 +0,0 @@
package org.koitharu.kotatsu.base.ui.list
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_progress.*
import org.koitharu.kotatsu.R
class ProgressBarHolder(parent: ViewGroup) :
BaseViewHolder<Boolean, Unit>(parent, R.layout.item_progress) {
private var pendingVisibility: Int = View.GONE
private val action = Runnable {
progressBar?.visibility = pendingVisibility
pendingVisibility = View.GONE
}
override fun onBind(data: Boolean, extra: Unit) {
val visibility = if (data) {
View.VISIBLE
} else {
View.INVISIBLE
}
if (visibility != progressBar.visibility && visibility != pendingVisibility) {
progressBar.removeCallbacks(action)
pendingVisibility = visibility
progressBar.postDelayed(action, 400)
}
}
override fun onRecycled() {
progressBar.removeCallbacks(action)
super.onRecycled()
}
}

@ -9,29 +9,29 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.activity_browser.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
class BrowserActivity : BaseActivity(), BrowserCallback { class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_browser) setContentView(ActivityBrowserBinding.inflate(layoutInflater))
supportActionBar?.run { supportActionBar?.run {
setDisplayHomeAsUpEnabled(true) setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(R.drawable.ic_cross) setHomeAsUpIndicator(R.drawable.ic_cross)
} }
with(webView.settings) { with(binding.webView.settings) {
javaScriptEnabled = true javaScriptEnabled = true
} }
webView.webViewClient = BrowserClient(this) binding.webView.webViewClient = BrowserClient(this)
val url = intent?.dataString val url = intent?.dataString
if (url.isNullOrEmpty()) { if (url.isNullOrEmpty()) {
finishAfterTransition() finishAfterTransition()
} else { } else {
webView.loadUrl(url) binding.webView.loadUrl(url)
} }
} }
@ -42,13 +42,13 @@ class BrowserActivity : BaseActivity(), BrowserCallback {
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
webView.stopLoading() binding.webView.stopLoading()
finishAfterTransition() finishAfterTransition()
true true
} }
R.id.action_browser -> { R.id.action_browser -> {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(webView.url) intent.data = Uri.parse(binding.webView.url)
try { try {
startActivity(Intent.createChooser(intent, item.title)) startActivity(Intent.createChooser(intent, item.title))
} catch (_: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
@ -59,25 +59,25 @@ class BrowserActivity : BaseActivity(), BrowserCallback {
} }
override fun onBackPressed() { override fun onBackPressed() {
if (webView.canGoBack()) { if (binding.webView.canGoBack()) {
webView.goBack() binding.webView.goBack()
} else { } else {
super.onBackPressed() super.onBackPressed()
} }
} }
override fun onPause() { override fun onPause() {
webView.onPause() binding.webView.onPause()
super.onPause() super.onPause()
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
webView.onResume() binding.webView.onResume()
} }
override fun onLoadingStateChanged(isLoading: Boolean) { override fun onLoadingStateChanged(isLoading: Boolean) {
progressBar.isVisible = isLoading binding.progressBar.isVisible = isLoading
} }
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) { override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {
@ -87,7 +87,6 @@ class BrowserActivity : BaseActivity(), BrowserCallback {
companion object { companion object {
@JvmStatic
fun newIntent(context: Context, url: String) = Intent(context, BrowserActivity::class.java) fun newIntent(context: Context, url: String) = Intent(context, BrowserActivity::class.java)
.setData(Uri.parse(url)) .setData(Uri.parse(url))
} }

@ -2,44 +2,50 @@ package org.koitharu.kotatsu.browser.cloudflare
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.webkit.CookieManager import android.webkit.CookieManager
import android.webkit.WebSettings import android.webkit.WebSettings
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.android.synthetic.main.fragment_cloudflare.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.network.UserAgentInterceptor import org.koitharu.kotatsu.core.network.UserAgentInterceptor
import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding
import org.koitharu.kotatsu.utils.ext.stringArgument import org.koitharu.kotatsu.utils.ext.stringArgument
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class CloudFlareDialog : AlertDialogFragment(R.layout.fragment_cloudflare), CloudFlareCallback { class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), CloudFlareCallback {
private val url by stringArgument(ARG_URL) private val url by stringArgument(ARG_URL)
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentCloudflareBinding.inflate(inflater, container, false)
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
with(webView.settings) { with(binding.webView.settings) {
javaScriptEnabled = true javaScriptEnabled = true
cacheMode = WebSettings.LOAD_DEFAULT cacheMode = WebSettings.LOAD_DEFAULT
domStorageEnabled = true domStorageEnabled = true
databaseEnabled = true databaseEnabled = true
userAgentString = UserAgentInterceptor.userAgent userAgentString = UserAgentInterceptor.userAgent
} }
webView.webViewClient = CloudFlareClient(this, url.orEmpty()) binding.webView.webViewClient = CloudFlareClient(this, url.orEmpty())
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true) CookieManager.getInstance().setAcceptThirdPartyCookies(binding.webView, true)
if (url.isNullOrEmpty()) { if (url.isNullOrEmpty()) {
dismissAllowingStateLoss() dismissAllowingStateLoss()
} else { } else {
webView.loadUrl(url.orEmpty()) binding.webView.loadUrl(url.orEmpty())
} }
} }
override fun onDestroyView() { override fun onDestroyView() {
webView.stopLoading() binding.webView.stopLoading()
super.onDestroyView() super.onDestroyView()
} }
@ -49,16 +55,16 @@ class CloudFlareDialog : AlertDialogFragment(R.layout.fragment_cloudflare), Clou
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
webView.onResume() binding.webView.onResume()
} }
override fun onPause() { override fun onPause() {
webView.onPause() binding.webView.onPause()
super.onPause() super.onPause()
} }
override fun onPageLoaded() { override fun onPageLoaded() {
progressBar?.isInvisible = true binding.progressBar.isInvisible = true
} }
override fun onCheckPassed() { override fun onCheckPassed() {

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.github package org.koitharu.kotatsu.core.github
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class AppVersion( data class AppVersion(

@ -32,7 +32,6 @@ data class VersionId(
companion object { companion object {
@JvmStatic
private fun variantWeight(variantType: String) = private fun variantWeight(variantType: String) =
when (variantType.toLowerCase(Locale.ROOT)) { when (variantType.toLowerCase(Locale.ROOT)) {
"a", "alpha" -> 1 "a", "alpha" -> 1
@ -42,7 +41,6 @@ data class VersionId(
else -> 0 else -> 0
} }
@JvmStatic
fun parse(versionName: String): VersionId { fun parse(versionName: String): VersionId {
val parts = versionName.substringBeforeLast('-').split('.') val parts = versionName.substringBeforeLast('-').split('.')
val variant = versionName.substringAfterLast('-', "") val variant = versionName.substringAfterLast('-', "")

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import java.util.* import java.util.*
@Parcelize @Parcelize

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class Manga( data class Manga(

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class MangaChapter( data class MangaChapter(

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class MangaFilter( data class MangaFilter(

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import java.util.* import java.util.*
@Parcelize @Parcelize

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class MangaPage( data class MangaPage(

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import org.koin.core.context.GlobalContext import org.koin.core.context.GlobalContext
import org.koin.core.error.NoBeanDefFoundException import org.koin.core.error.NoBeanDefFoundException
import org.koin.core.qualifier.named import org.koin.core.qualifier.named

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class MangaTag( data class MangaTag(

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import java.util.* import java.util.*
@Parcelize @Parcelize

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import java.util.* import java.util.*
@Parcelize @Parcelize

@ -73,7 +73,6 @@ class PersistentCookieJar(
private companion object { private companion object {
@JvmStatic
fun filterPersistentCookies(cookies: List<Cookie>): List<Cookie> { fun filterPersistentCookies(cookies: List<Cookie>): List<Cookie> {
val persistentCookies: MutableList<Cookie> = ArrayList() val persistentCookies: MutableList<Cookie> = ArrayList()
for (cookie in cookies) { for (cookie in cookies) {
@ -84,7 +83,6 @@ class PersistentCookieJar(
return persistentCookies return persistentCookies
} }
@JvmStatic
fun isCookieExpired(cookie: Cookie): Boolean { fun isCookieExpired(cookie: Cookie): Boolean {
return cookie.expiresAt < System.currentTimeMillis() return cookie.expiresAt < System.currentTimeMillis()
} }

@ -47,7 +47,6 @@ internal class IdentifiableCookie(val cookie: Cookie) {
companion object { companion object {
@JvmStatic
fun decorateAll(cookies: Collection<Cookie>): List<IdentifiableCookie> { fun decorateAll(cookies: Collection<Cookie>): List<IdentifiableCookie> {
val identifiableCookies: MutableList<IdentifiableCookie> = ArrayList(cookies.size) val identifiableCookies: MutableList<IdentifiableCookie> = ArrayList(cookies.size)
for (cookie in cookies) { for (cookie in cookies) {

@ -117,7 +117,6 @@ class SerializableCookie : Serializable {
* @param bytes byte array to be converted * @param bytes byte array to be converted
* @return string containing hex values * @return string containing hex values
*/ */
@JvmStatic
private fun byteArrayToHexString(bytes: ByteArray): String { private fun byteArrayToHexString(bytes: ByteArray): String {
val sb = StringBuilder(bytes.size * 2) val sb = StringBuilder(bytes.size * 2)
for (element in bytes) { for (element in bytes) {
@ -136,7 +135,6 @@ class SerializableCookie : Serializable {
* @param hexString string of hex-encoded values * @param hexString string of hex-encoded values
* @return decoded byte array * @return decoded byte array
*/ */
@JvmStatic
private fun hexStringToByteArray(hexString: String): ByteArray { private fun hexStringToByteArray(hexString: String): ByteArray {
val len = hexString.length val len = hexString.length
val data = ByteArray(len / 2) val data = ByteArray(len / 2)

@ -22,7 +22,6 @@ interface SourceSettings {
companion object { companion object {
@JvmStatic
operator fun invoke(context: Context, source: MangaSource): SourceSettings = operator fun invoke(context: Context, source: MangaSource): SourceSettings =
PrefSourceSettings(context, source) PrefSourceSettings(context, source)

@ -8,20 +8,23 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import kotlinx.android.synthetic.main.activity_crash.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ActivityCrashBinding
import org.koitharu.kotatsu.main.ui.MainActivity import org.koitharu.kotatsu.main.ui.MainActivity
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
class CrashActivity : Activity(), View.OnClickListener { class CrashActivity : Activity(), View.OnClickListener {
private lateinit var binding: ActivityCrashBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crash) binding = ActivityCrashBinding.inflate(layoutInflater)
textView.text = intent.getStringExtra(Intent.EXTRA_TEXT) setContentView(binding.root)
button_close.setOnClickListener(this) binding.textView.text = intent.getStringExtra(Intent.EXTRA_TEXT)
button_restart.setOnClickListener(this) binding.buttonClose.setOnClickListener(this)
button_report.setOnClickListener(this) binding.buttonRestart.setOnClickListener(this)
binding.buttonReport.setOnClickListener(this)
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@ -32,7 +35,7 @@ class CrashActivity : Activity(), View.OnClickListener {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_share -> { R.id.action_share -> {
ShareHelper.shareText(this, textView.text?.toString() ?: return false) ShareHelper.shareText(this, binding.textView.text.toString() ?: return false)
} }
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.details package org.koitharu.kotatsu.details
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.details.ui.DetailsViewModel import org.koitharu.kotatsu.details.ui.DetailsViewModel

@ -2,28 +2,26 @@ package org.koitharu.kotatsu.details.ui
import android.app.ActivityOptions import android.app.ActivityOptions
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.*
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_chapters.* import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koin.android.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.download.DownloadService import org.koitharu.kotatsu.download.DownloadService
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), class ChaptersFragment : BaseFragment<FragmentChaptersBinding>(),
OnListItemClickListener<MangaChapter>, ActionMode.Callback { OnListItemClickListener<MangaChapter>, ActionMode.Callback {
private val viewModel by sharedViewModel<DetailsViewModel>() private val viewModel by sharedViewModel<DetailsViewModel>()
@ -32,11 +30,16 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters),
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private var selectionDecoration: ChaptersSelectionDecoration? = null private var selectionDecoration: ChaptersSelectionDecoration? = null
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentChaptersBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
chaptersAdapter = ChaptersAdapter(this) chaptersAdapter = ChaptersAdapter(this)
selectionDecoration = ChaptersSelectionDecoration(view.context) selectionDecoration = ChaptersSelectionDecoration(view.context)
with(recyclerView_chapters) { with(binding.recyclerViewChapters) {
addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL)) addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL))
addItemDecoration(selectionDecoration!!) addItemDecoration(selectionDecoration!!)
setHasFixedSize(true) setHasFixedSize(true)
@ -58,7 +61,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters),
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
progressBar.isVisible = isLoading binding.progressBar.isVisible = isLoading
} }
override fun onItemClick(item: MangaChapter, view: View) { override fun onItemClick(item: MangaChapter, view: View) {
@ -68,7 +71,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters),
actionMode?.finish() actionMode?.finish()
} else { } else {
actionMode?.invalidate() actionMode?.invalidate()
recyclerView_chapters.invalidateItemDecorations() binding.recyclerViewChapters.invalidateItemDecorations()
} }
return return
} }
@ -94,7 +97,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters),
} }
return actionMode?.also { return actionMode?.also {
selectionDecoration?.setItemIsChecked(item.id, true) selectionDecoration?.setItemIsChecked(item.id, true)
recyclerView_chapters.invalidateItemDecorations() binding.recyclerViewChapters.invalidateItemDecorations()
it.invalidate() it.invalidate()
} != null } != null
} }
@ -113,7 +116,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters),
R.id.action_select_all -> { R.id.action_select_all -> {
val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false
selectionDecoration?.checkAll(ids) selectionDecoration?.checkAll(ids)
recyclerView_chapters.invalidateItemDecorations() binding.recyclerViewChapters.invalidateItemDecorations()
mode.invalidate() mode.invalidate()
true true
} }
@ -142,7 +145,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters),
override fun onDestroyActionMode(mode: ActionMode?) { override fun onDestroyActionMode(mode: ActionMode?) {
selectionDecoration?.clearSelection() selectionDecoration?.clearSelection()
recyclerView_chapters.invalidateItemDecorations() binding.recyclerViewChapters.invalidateItemDecorations()
actionMode = null actionMode = null
} }
} }

@ -16,9 +16,8 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.android.synthetic.main.activity_details.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -27,6 +26,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.download.DownloadService import org.koitharu.kotatsu.download.DownloadService
import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
import org.koitharu.kotatsu.utils.MangaShortcut import org.koitharu.kotatsu.utils.MangaShortcut
@ -34,7 +34,8 @@ import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getThemeColor import org.koitharu.kotatsu.utils.ext.getThemeColor
class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrategy { class DetailsActivity : BaseActivity<ActivityDetailsBinding>(),
TabLayoutMediator.TabConfigurationStrategy {
private val viewModel by viewModel<DetailsViewModel> { private val viewModel by viewModel<DetailsViewModel> {
parametersOf(MangaIntent.from(intent)) parametersOf(MangaIntent.from(intent))
@ -42,10 +43,10 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_details) setContentView(ActivityDetailsBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
pager.adapter = MangaDetailsAdapter(this) binding.pager.adapter = MangaDetailsAdapter(this)
TabLayoutMediator(tabs, pager, this).attach() TabLayoutMediator(binding.tabs, binding.pager, this).attach()
viewModel.manga.observe(this, ::onMangaUpdated) viewModel.manga.observe(this, ::onMangaUpdated)
viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged) viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged)
@ -71,12 +72,13 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show() Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
finishAfterTransition() finishAfterTransition()
} else { } else {
Snackbar.make(pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show() Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG)
.show()
} }
} }
private fun onNewChaptersChanged(newChapters: Int) { private fun onNewChaptersChanged(newChapters: Int) {
val tab = tabs.getTabAt(1) ?: return val tab = binding.tabs.getTabAt(1) ?: return
if (newChapters == 0) { if (newChapters == 0) {
tab.removeBadge() tab.removeBadge()
} else { } else {
@ -171,7 +173,7 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate
lifecycleScope.launch { lifecycleScope.launch {
if (!MangaShortcut(it).requestPinShortcut(this@DetailsActivity)) { if (!MangaShortcut(it).requestPinShortcut(this@DetailsActivity)) {
Snackbar.make( Snackbar.make(
pager, binding.pager,
R.string.operation_not_supported, R.string.operation_not_supported,
Snackbar.LENGTH_SHORT Snackbar.LENGTH_SHORT
).show() ).show()
@ -193,13 +195,13 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate
override fun onSupportActionModeStarted(mode: ActionMode) { override fun onSupportActionModeStarted(mode: ActionMode) {
super.onSupportActionModeStarted(mode) super.onSupportActionModeStarted(mode)
pager.isUserInputEnabled = false binding.pager.isUserInputEnabled = false
window?.statusBarColor = ContextCompat.getColor(this, R.color.grey_dark) window?.statusBarColor = ContextCompat.getColor(this, R.color.grey_dark)
} }
override fun onSupportActionModeFinished(mode: ActionMode) { override fun onSupportActionModeFinished(mode: ActionMode) {
super.onSupportActionModeFinished(mode) super.onSupportActionModeFinished(mode)
pager.isUserInputEnabled = true binding.pager.isUserInputEnabled = true
window?.statusBarColor = getThemeColor(androidx.appcompat.R.attr.colorPrimaryDark) window?.statusBarColor = getThemeColor(androidx.appcompat.R.attr.colorPrimaryDark)
} }

@ -2,20 +2,24 @@ package org.koitharu.kotatsu.details.ui
import android.os.Bundle import android.os.Bundle
import android.text.Spanned import android.text.Spanned
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.text.parseAsHtml import androidx.core.text.parseAsHtml
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.ImageLoader
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.fragment_details.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.android.viewmodel.ext.android.sharedViewModel import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.search.ui.MangaSearchSheet import org.koitharu.kotatsu.search.ui.MangaSearchSheet
@ -23,10 +27,16 @@ import org.koitharu.kotatsu.utils.FileSizeUtils
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import kotlin.math.roundToInt import kotlin.math.roundToInt
class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickListener, class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickListener,
View.OnLongClickListener { View.OnLongClickListener {
private val viewModel by sharedViewModel<DetailsViewModel>() private val viewModel by sharedViewModel<DetailsViewModel>()
private val coil by inject<ImageLoader>()
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentDetailsBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -37,63 +47,66 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis
} }
private fun onMangaUpdated(manga: Manga) { private fun onMangaUpdated(manga: Manga) {
imageView_cover.newImageRequest(manga.largeCoverUrl ?: manga.coverUrl) with(binding) {
.fallback(R.drawable.ic_placeholder) imageViewCover.newImageRequest(manga.largeCoverUrl ?: manga.coverUrl)
.crossfade(true) .fallback(R.drawable.ic_placeholder)
.lifecycle(viewLifecycleOwner) .crossfade(true)
.enqueueWith(coil) .lifecycle(viewLifecycleOwner)
textView_title.text = manga.title .enqueueWith(coil)
textView_subtitle.textAndVisible = manga.altTitle textViewTitle.text = manga.title
textView_description.text = manga.description?.parseAsHtml()?.takeUnless(Spanned::isBlank) textViewSubtitle.textAndVisible = manga.altTitle
?: getString(R.string.no_description) textViewDescription.text =
if (manga.rating == Manga.NO_RATING) { manga.description?.parseAsHtml()?.takeUnless(Spanned::isBlank)
ratingBar.isVisible = false ?: getString(R.string.no_description)
} else { if (manga.rating == Manga.NO_RATING) {
ratingBar.progress = (ratingBar.max * manga.rating).roundToInt() ratingBar.isVisible = false
ratingBar.isVisible = true } else {
} ratingBar.progress = (ratingBar.max * manga.rating).roundToInt()
chips_tags.removeAllViews() ratingBar.isVisible = true
manga.author?.let { a ->
chips_tags.addChips(listOf(a)) {
create(
text = it,
iconRes = R.drawable.ic_chip_user,
tag = it,
onClickListener = this@DetailsFragment
)
} }
} chipsTags.removeAllViews()
chips_tags.addChips(manga.tags) { manga.author?.let { a ->
create( chipsTags.addChips(listOf(a)) {
text = it.title,
iconRes = R.drawable.ic_chip_tag,
tag = it,
onClickListener = this@DetailsFragment
)
}
manga.url.toUri().toFileOrNull()?.let { f ->
viewLifecycleScope.launch {
val size = withContext(Dispatchers.IO) {
f.length()
}
chips_tags.addChips(listOf(f)) {
create( create(
text = FileSizeUtils.formatBytes(context, size), text = it,
iconRes = R.drawable.ic_chip_storage, iconRes = R.drawable.ic_chip_user,
tag = it, tag = it,
onClickListener = this@DetailsFragment onClickListener = this@DetailsFragment
) )
} }
} }
chipsTags.addChips(manga.tags) {
create(
text = it.title,
iconRes = R.drawable.ic_chip_tag,
tag = it,
onClickListener = this@DetailsFragment
)
}
manga.url.toUri().toFileOrNull()?.let { f ->
viewLifecycleScope.launch {
val size = withContext(Dispatchers.IO) {
f.length()
}
chipsTags.addChips(listOf(f)) {
create(
text = FileSizeUtils.formatBytes(context, size),
iconRes = R.drawable.ic_chip_storage,
tag = it,
onClickListener = this@DetailsFragment
)
}
}
}
imageViewFavourite.setOnClickListener(this@DetailsFragment)
buttonRead.setOnClickListener(this@DetailsFragment)
buttonRead.setOnLongClickListener(this@DetailsFragment)
buttonRead.isEnabled = !manga.chapters.isNullOrEmpty()
} }
imageView_favourite.setOnClickListener(this)
button_read.setOnClickListener(this)
button_read.setOnLongClickListener(this)
button_read.isEnabled = !manga.chapters.isNullOrEmpty()
} }
private fun onHistoryChanged(history: MangaHistory?) { private fun onHistoryChanged(history: MangaHistory?) {
with(button_read) { with(binding.buttonRead) {
if (history == null) { if (history == null) {
setText(R.string.read) setText(R.string.read)
setIconResource(R.drawable.ic_read) setIconResource(R.drawable.ic_read)
@ -105,7 +118,7 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis
} }
private fun onFavouriteChanged(isFavourite: Boolean) { private fun onFavouriteChanged(isFavourite: Boolean) {
imageView_favourite.setImageResource( binding.imageViewFavourite.setImageResource(
if (isFavourite) { if (isFavourite) {
R.drawable.ic_heart R.drawable.ic_heart
} else { } else {
@ -115,7 +128,7 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
progressBar.isVisible = isLoading binding.progressBar.isVisible = isLoading
} }
override fun onClick(v: View) { override fun onClick(v: View) {

@ -1,17 +1,19 @@
package org.koitharu.kotatsu.details.ui.adapter package org.koitharu.kotatsu.details.ui.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.android.synthetic.main.item_chapter.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.databinding.ItemChapterBinding
import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.history.domain.ChapterExtra
import org.koitharu.kotatsu.utils.ext.getThemeColor import org.koitharu.kotatsu.utils.ext.getThemeColor
fun chapterListItemAD( fun chapterListItemAD(
clickListener: OnListItemClickListener<MangaChapter> clickListener: OnListItemClickListener<MangaChapter>
) = adapterDelegateLayoutContainer<ChapterListItem, ChapterListItem>(R.layout.item_chapter) { ) = adapterDelegateViewBinding<ChapterListItem, ChapterListItem, ItemChapterBinding>(
{ inflater, parent -> ItemChapterBinding.inflate(inflater, parent, false) }
) {
itemView.setOnClickListener { itemView.setOnClickListener {
clickListener.onItemClick(item.chapter, it) clickListener.onItemClick(item.chapter, it)
@ -21,24 +23,24 @@ fun chapterListItemAD(
} }
bind { payload -> bind { payload ->
textView_title.text = item.chapter.name binding.textViewTitle.text = item.chapter.name
textView_number.text = item.chapter.number.toString() binding.textViewNumber.text = item.chapter.number.toString()
when (item.extra) { when (item.extra) {
ChapterExtra.UNREAD -> { ChapterExtra.UNREAD -> {
textView_number.setBackgroundResource(R.drawable.bg_badge_default) binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_default)
textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorSecondaryInverse)) binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorSecondaryInverse))
} }
ChapterExtra.READ -> { ChapterExtra.READ -> {
textView_number.setBackgroundResource(R.drawable.bg_badge_outline) binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_outline)
textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorTertiary)) binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorTertiary))
} }
ChapterExtra.CURRENT -> { ChapterExtra.CURRENT -> {
textView_number.setBackgroundResource(R.drawable.bg_badge_outline_accent) binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_outline_accent)
textView_number.setTextColor(context.getThemeColor(androidx.appcompat.R.attr.colorAccent)) binding.textViewNumber.setTextColor(context.getThemeColor(androidx.appcompat.R.attr.colorAccent))
} }
ChapterExtra.NEW -> { ChapterExtra.NEW -> {
textView_number.setBackgroundResource(R.drawable.bg_badge_accent) binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_accent)
textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse)) binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse))
} }
} }
} }

@ -142,7 +142,6 @@ class DownloadNotification(private val context: Context) {
private const val PROGRESS_STEP = 20 private const val PROGRESS_STEP = 20
@JvmStatic
private fun createIntent(context: Context, manga: Manga) = PendingIntent.getActivity( private fun createIntent(context: Context, manga: Manga) = PendingIntent.getActivity(
context, context,
manga.hashCode(), manga.hashCode(),

@ -55,9 +55,9 @@ class DownloadService : BaseService() {
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading") .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading")
} }
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
when (intent.action) { when (intent?.action) {
ACTION_DOWNLOAD_START -> { ACTION_DOWNLOAD_START -> {
val manga = intent.getParcelableExtra<Manga>(EXTRA_MANGA) val manga = intent.getParcelableExtra<Manga>(EXTRA_MANGA)
val chapters = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toArraySet() val chapters = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toArraySet()

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.favourites package org.koitharu.kotatsu.favourites
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository

@ -1,26 +1,24 @@
package org.koitharu.kotatsu.favourites.ui package org.koitharu.kotatsu.favourites.ui
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.*
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.android.synthetic.main.fragment_favourites.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.FragmentFavouritesBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.showPopupMenu import org.koitharu.kotatsu.utils.ext.showPopupMenu
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites), class FavouritesContainerFragment : BaseFragment<FragmentFavouritesBinding>(),
FavouritesTabLongClickListener, CategoriesEditDelegate.CategoriesEditCallback { FavouritesTabLongClickListener, CategoriesEditDelegate.CategoriesEditCallback {
private val viewModel by viewModel<FavouritesCategoriesViewModel>() private val viewModel by viewModel<FavouritesCategoriesViewModel>()
@ -33,11 +31,16 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentFavouritesBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val adapter = FavouritesPagerAdapter(this, this) val adapter = FavouritesPagerAdapter(this, this)
pager.adapter = adapter binding.pager.adapter = adapter
TabLayoutMediator(tabs, pager, adapter).attach() TabLayoutMediator(binding.tabs, binding.pager, adapter).attach()
viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged) viewModel.categories.observe(viewLifecycleOwner, ::onCategoriesChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, ::onError)
@ -47,7 +50,7 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
val data = ArrayList<FavouriteCategory>(categories.size + 1) val data = ArrayList<FavouriteCategory>(categories.size + 1)
data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date()) data += FavouriteCategory(0L, getString(R.string.all_favourites), -1, Date())
data += categories data += categories
(pager.adapter as? FavouritesPagerAdapter)?.replaceData(data) (binding.pager.adapter as? FavouritesPagerAdapter)?.replaceData(data)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -70,7 +73,7 @@ class FavouritesContainerFragment : BaseFragment(R.layout.fragment_favourites),
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
Snackbar.make(pager, e.message ?: return, Snackbar.LENGTH_LONG).show() Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
} }
override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean { override fun onTabLongClick(tabView: View, category: FavouriteCategory): Boolean {

@ -11,16 +11,17 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_categories.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.showPopupMenu import org.koitharu.kotatsu.utils.ext.showPopupMenu
class CategoriesActivity : BaseActivity(), OnListItemClickListener<FavouriteCategory>, class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<FavouriteCategory>,
View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback { View.OnClickListener, CategoriesEditDelegate.CategoriesEditCallback {
private val viewModel by viewModel<FavouritesCategoriesViewModel>() private val viewModel by viewModel<FavouritesCategoriesViewModel>()
@ -31,16 +32,16 @@ class CategoriesActivity : BaseActivity(), OnListItemClickListener<FavouriteCate
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_categories) setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
fab_add.imageTintList = ColorStateList.valueOf(Color.WHITE) binding.fabAdd.imageTintList = ColorStateList.valueOf(Color.WHITE)
adapter = CategoriesAdapter(this) adapter = CategoriesAdapter(this)
editDelegate = CategoriesEditDelegate(this, this) editDelegate = CategoriesEditDelegate(this, this)
recyclerView.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL)) binding.recyclerView.addItemDecoration(DividerItemDecoration(this, RecyclerView.VERTICAL))
recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
fab_add.setOnClickListener(this) binding.fabAdd.setOnClickListener(this)
reorderHelper = ItemTouchHelper(ReorderHelperCallback()) reorderHelper = ItemTouchHelper(ReorderHelperCallback())
reorderHelper.attachToRecyclerView(recyclerView) reorderHelper.attachToRecyclerView(binding.recyclerView)
viewModel.categories.observe(this, ::onCategoriesChanged) viewModel.categories.observe(this, ::onCategoriesChanged)
viewModel.onError.observe(this, ::onError) viewModel.onError.observe(this, ::onError)
@ -64,18 +65,18 @@ class CategoriesActivity : BaseActivity(), OnListItemClickListener<FavouriteCate
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean { override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
reorderHelper.startDrag( reorderHelper.startDrag(
recyclerView.findContainingViewHolder(view) ?: return false binding.recyclerView.findContainingViewHolder(view) ?: return false
) )
return true return true
} }
private fun onCategoriesChanged(categories: List<FavouriteCategory>) { private fun onCategoriesChanged(categories: List<FavouriteCategory>) {
adapter.items = categories adapter.items = categories
textView_holder.isVisible = categories.isEmpty() binding.textViewHolder.isVisible = categories.isEmpty()
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG) Snackbar.make(binding.recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG)
.show() .show()
} }

@ -1,21 +1,22 @@
package org.koitharu.kotatsu.favourites.ui.categories package org.koitharu.kotatsu.favourites.ui.categories
import android.view.MotionEvent import android.view.MotionEvent
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.android.synthetic.main.item_category.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ItemCategoryBinding
fun categoryAD( fun categoryAD(
clickListener: OnListItemClickListener<FavouriteCategory> clickListener: OnListItemClickListener<FavouriteCategory>
) = adapterDelegateLayoutContainer<FavouriteCategory, FavouriteCategory>(R.layout.item_category) { ) = adapterDelegateViewBinding<FavouriteCategory, FavouriteCategory, ItemCategoryBinding>(
{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) }
) {
imageView_more.setOnClickListener { binding.imageViewMore.setOnClickListener {
clickListener.onItemClick(item, it) clickListener.onItemClick(item, it)
} }
@Suppress("ClickableViewAccessibility") @Suppress("ClickableViewAccessibility")
imageView_handle.setOnTouchListener { v, event -> binding.imageViewHandle.setOnTouchListener { v, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) { if (event.actionMasked == MotionEvent.ACTION_DOWN) {
clickListener.onItemLongClick(item, itemView) clickListener.onItemLongClick(item, itemView)
} else { } else {
@ -24,6 +25,6 @@ fun categoryAD(
} }
bind { bind {
textView_title.text = item.title binding.textViewTitle.text = item.title
} }
} }

@ -1,11 +1,12 @@
package org.koitharu.kotatsu.favourites.ui.categories.select package org.koitharu.kotatsu.favourites.ui.categories.select
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import kotlinx.android.synthetic.main.dialog_favorite_categories.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.domain.MangaIntent
@ -13,13 +14,14 @@ import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.databinding.DialogFavoriteCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categories), class FavouriteCategoriesDialog : BaseBottomSheet<DialogFavoriteCategoriesBinding>(),
OnListItemClickListener<MangaCategoryItem>, CategoriesEditDelegate.CategoriesEditCallback, OnListItemClickListener<MangaCategoryItem>, CategoriesEditDelegate.CategoriesEditCallback,
View.OnClickListener { View.OnClickListener {
@ -32,11 +34,16 @@ class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categ
CategoriesEditDelegate(requireContext(), this@FavouriteCategoriesDialog) CategoriesEditDelegate(requireContext(), this@FavouriteCategoriesDialog)
} }
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = DialogFavoriteCategoriesBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
adapter = MangaCategoriesAdapter(this) adapter = MangaCategoriesAdapter(this)
recyclerView_categories.adapter = adapter binding.recyclerViewCategories.adapter = adapter
textView_add.setOnClickListener(this) binding.textViewAdd.setOnClickListener(this)
viewModel.content.observe(viewLifecycleOwner, this::onContentChanged) viewModel.content.observe(viewLifecycleOwner, this::onContentChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, ::onError)

@ -1,15 +1,14 @@
package org.koitharu.kotatsu.favourites.ui.categories.select.adapter package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.android.synthetic.main.item_category_checkable.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ItemCategoryCheckableBinding
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
fun mangaCategoryAD( fun mangaCategoryAD(
clickListener: OnListItemClickListener<MangaCategoryItem> clickListener: OnListItemClickListener<MangaCategoryItem>
) = adapterDelegateLayoutContainer<MangaCategoryItem, MangaCategoryItem>( ) = adapterDelegateViewBinding<MangaCategoryItem, MangaCategoryItem, ItemCategoryCheckableBinding>(
R.layout.item_category_checkable { inflater, parent -> ItemCategoryCheckableBinding.inflate(inflater, parent, false) }
) { ) {
itemView.setOnClickListener { itemView.setOnClickListener {
@ -17,7 +16,9 @@ fun mangaCategoryAD(
} }
bind { bind {
checkedTextView.text = item.name with(binding.checkedTextView) {
checkedTextView.isChecked = item.isChecked text = item.name
isChecked = item.isChecked
}
} }
} }

@ -3,8 +3,7 @@ package org.koitharu.kotatsu.favourites.ui.list
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import kotlinx.android.synthetic.main.fragment_list.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
@ -25,14 +24,14 @@ class FavouritesListFragment : MangaListFragment() {
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit
override fun setUpEmptyListHolder() { override fun setUpEmptyListHolder() {
textView_holder.setText( binding.textViewHolder.setText(
if (categoryId == 0L) { if (categoryId == 0L) {
R.string.you_have_not_favourites_yet R.string.you_have_not_favourites_yet
} else { } else {
R.string.favourites_category_empty R.string.favourites_category_empty
} }
) )
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) binding.textViewHolder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
} }
override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) { override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) {

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.history package org.koitharu.kotatsu.history
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.ui.HistoryListViewModel import org.koitharu.kotatsu.history.ui.HistoryListViewModel

@ -7,8 +7,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_list.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
@ -36,7 +35,8 @@ class HistoryListFragment : MangaListFragment() {
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu) super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.action_history_grouping)?.isChecked = viewModel.isGroupingEnabled.value == true menu.findItem(R.id.action_history_grouping)?.isChecked =
viewModel.isGroupingEnabled.value == true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -64,8 +64,8 @@ class HistoryListFragment : MangaListFragment() {
} }
override fun setUpEmptyListHolder() { override fun setUpEmptyListHolder() {
textView_holder.setText(R.string.text_history_holder) binding.textViewHolder.setText(R.string.text_history_holder)
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) binding.textViewHolder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
} }
override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) { override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) {
@ -85,7 +85,7 @@ class HistoryListFragment : MangaListFragment() {
private fun onItemRemoved(item: Manga) { private fun onItemRemoved(item: Manga) {
Snackbar.make( Snackbar.make(
recyclerView, getString( binding.recyclerView, getString(
R.string._s_removed_from_history, R.string._s_removed_from_history,
item.title.ellipsize(16) item.title.ellipsize(16)
), Snackbar.LENGTH_SHORT ), Snackbar.LENGTH_SHORT

@ -1,18 +1,20 @@
package org.koitharu.kotatsu.list.ui package org.koitharu.kotatsu.list.ui
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar import android.widget.SeekBar
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import kotlinx.android.synthetic.main.dialog_list_mode.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.databinding.DialogListModeBinding
class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), View.OnClickListener, class ListModeSelectDialog : AlertDialogFragment<DialogListModeBinding>(), View.OnClickListener,
SeekBar.OnSeekBarChangeListener { SeekBar.OnSeekBarChangeListener {
private val settings by inject<AppSettings>() private val settings by inject<AppSettings>()
@ -26,6 +28,11 @@ class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), Vie
pendingGridSize = settings.gridSize pendingGridSize = settings.gridSize
} }
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = DialogListModeBinding.inflate(inflater, container, false)
override fun onBuildDialog(builder: AlertDialog.Builder) { override fun onBuildDialog(builder: AlertDialog.Builder) {
builder.setTitle(R.string.list_mode) builder.setTitle(R.string.list_mode)
.setPositiveButton(R.string.done, null) .setPositiveButton(R.string.done, null)
@ -34,18 +41,18 @@ class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), Vie
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
button_list.isChecked = mode == ListMode.LIST binding.buttonList.isChecked = mode == ListMode.LIST
button_list_detailed.isChecked = mode == ListMode.DETAILED_LIST binding.buttonListDetailed.isChecked = mode == ListMode.DETAILED_LIST
button_grid.isChecked = mode == ListMode.GRID binding.buttonGrid.isChecked = mode == ListMode.GRID
with(seekbar_grid) { with(binding.seekbarGrid) {
progress = pendingGridSize - 50 progress = pendingGridSize - 50
setOnSeekBarChangeListener(this@ListModeSelectDialog) setOnSeekBarChangeListener(this@ListModeSelectDialog)
} }
button_list.setOnClickListener(this) binding.buttonList.setOnClickListener(this)
button_grid.setOnClickListener(this) binding.buttonGrid.setOnClickListener(this)
button_list_detailed.setOnClickListener(this) binding.buttonListDetailed.setOnClickListener(this)
} }
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {

@ -14,7 +14,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_list.*
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
@ -28,6 +27,7 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaFilter import org.koitharu.kotatsu.core.model.MangaFilter
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
import org.koitharu.kotatsu.list.ui.filter.FilterAdapter import org.koitharu.kotatsu.list.ui.filter.FilterAdapter
@ -37,11 +37,11 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.hasItems import org.koitharu.kotatsu.utils.ext.hasItems
import org.koitharu.kotatsu.utils.ext.toggleDrawer import org.koitharu.kotatsu.utils.ext.toggleDrawer
abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
PaginationScrollListener.Callback, OnListItemClickListener<Manga>, OnFilterChangedListener, PaginationScrollListener.Callback, OnListItemClickListener<Manga>, OnFilterChangedListener,
SectionItemDecoration.Callback, SwipeRefreshLayout.OnRefreshListener { SectionItemDecoration.Callback, SwipeRefreshLayout.OnRefreshListener {
private var adapter: MangaListAdapter? = null private var listAdapter: MangaListAdapter? = null
private var paginationListener: PaginationScrollListener? = null private var paginationListener: PaginationScrollListener? = null
private val spanResolver = MangaListSpanResolver() private val spanResolver = MangaListSpanResolver()
private val spanSizeLookup = SpanSizeLookup() private val spanSizeLookup = SpanSizeLookup()
@ -54,19 +54,30 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentListBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) binding.drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
adapter = MangaListAdapter(get(), this) listAdapter = MangaListAdapter(get(), this)
paginationListener = PaginationScrollListener(4, this) paginationListener = PaginationScrollListener(4, this)
recyclerView.setHasFixedSize(true) with(binding.recyclerView) {
recyclerView.adapter = adapter setHasFixedSize(true)
recyclerView.addOnScrollListener(paginationListener!!) adapter = listAdapter
swipeRefreshLayout.setOnRefreshListener(this) addOnScrollListener(paginationListener!!)
swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled }
recyclerView_filter.setHasFixedSize(true) with(binding.swipeRefreshLayout) {
recyclerView_filter.addItemDecoration(ItemTypeDividerDecoration(view.context)) setOnRefreshListener(this@MangaListFragment)
recyclerView_filter.addItemDecoration(SectionItemDecoration(false, this)) isEnabled = isSwipeRefreshEnabled
}
with(binding.recyclerViewFilter) {
setHasFixedSize(true)
addItemDecoration(ItemTypeDividerDecoration(view.context))
addItemDecoration(SectionItemDecoration(false, this@MangaListFragment))
}
viewModel.content.observe(viewLifecycleOwner, ::onListChanged) viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
viewModel.filter.observe(viewLifecycleOwner, ::onInitFilter) viewModel.filter.observe(viewLifecycleOwner, ::onInitFilter)
@ -78,7 +89,7 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
} }
override fun onDestroyView() { override fun onDestroyView() {
adapter = null listAdapter = null
paginationListener = null paginationListener = null
spanSizeLookup.invalidateCache() spanSizeLookup.invalidateCache()
super.onDestroyView() super.onDestroyView()
@ -95,15 +106,15 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
true true
} }
R.id.action_filter -> { R.id.action_filter -> {
drawer?.toggleDrawer(GravityCompat.END) binding.drawer?.toggleDrawer(GravityCompat.END)
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.action_filter).isVisible = drawer != null && menu.findItem(R.id.action_filter).isVisible = binding.drawer != null &&
drawer?.getDrawerLockMode(GravityCompat.END) != DrawerLayout.LOCK_MODE_LOCKED_CLOSED binding.drawer?.getDrawerLockMode(GravityCompat.END) != DrawerLayout.LOCK_MODE_LOCKED_CLOSED
super.onPrepareOptionsMenu(menu) super.onPrepareOptionsMenu(menu)
} }
@ -128,12 +139,12 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
@CallSuper @CallSuper
override fun onRefresh() { override fun onRefresh() {
swipeRefreshLayout.isRefreshing = true binding.swipeRefreshLayout.isRefreshing = true
} }
private fun onListChanged(list: List<Any>) { private fun onListChanged(list: List<Any>) {
spanSizeLookup.invalidateCache() spanSizeLookup.invalidateCache()
adapter?.items = list listAdapter?.items = list
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
@ -141,27 +152,33 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
CloudFlareDialog.newInstance(e.url).show(childFragmentManager, CloudFlareDialog.TAG) CloudFlareDialog.newInstance(e.url).show(childFragmentManager, CloudFlareDialog.TAG)
} }
if (viewModel.isEmptyState.value == true) { if (viewModel.isEmptyState.value == true) {
textView_holder.text = e.getDisplayMessage(resources) binding.textViewHolder.text = e.getDisplayMessage(resources)
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds( binding.textViewHolder.setCompoundDrawablesRelativeWithIntrinsicBounds(
0, 0,
R.drawable.ic_error_large, R.drawable.ic_error_large,
0, 0,
0 0
) )
layout_holder.isVisible = true binding.textViewHolder.isVisible = true
} else { } else {
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT) Snackbar.make(
binding.recyclerView,
e.getDisplayMessage(resources),
Snackbar.LENGTH_SHORT
)
.show() .show()
} }
} }
@CallSuper @CallSuper
protected open fun onLoadingStateChanged(isLoading: Boolean) { protected open fun onLoadingStateChanged(isLoading: Boolean) {
val hasItems = recyclerView.hasItems val hasItems = binding.recyclerView.hasItems
progressBar.isVisible = isLoading && !hasItems && viewModel.isEmptyState.value != true binding.progressBar.isVisible =
swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled && !progressBar.isVisible isLoading && !hasItems && viewModel.isEmptyState.value != true
binding.swipeRefreshLayout.isEnabled =
isSwipeRefreshEnabled && !binding.progressBar.isVisible
if (!isLoading) { if (!isLoading) {
swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
} }
} }
@ -169,47 +186,49 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
if (isEmpty) { if (isEmpty) {
setUpEmptyListHolder() setUpEmptyListHolder()
} }
layout_holder.isVisible = isEmpty binding.layoutHolder.isVisible = isEmpty
} }
protected fun onInitFilter(config: MangaFilterConfig) { protected fun onInitFilter(config: MangaFilterConfig) {
recyclerView_filter.adapter = FilterAdapter( binding.recyclerViewFilter.adapter = FilterAdapter(
sortOrders = config.sortOrders, sortOrders = config.sortOrders,
tags = config.tags, tags = config.tags,
state = config.currentFilter, state = config.currentFilter,
listener = this listener = this
) )
drawer?.setDrawerLockMode( binding.drawer?.setDrawerLockMode(
if (config.sortOrders.isEmpty() && config.tags.isEmpty()) { if (config.sortOrders.isEmpty() && config.tags.isEmpty()) {
DrawerLayout.LOCK_MODE_LOCKED_CLOSED DrawerLayout.LOCK_MODE_LOCKED_CLOSED
} else { } else {
DrawerLayout.LOCK_MODE_UNLOCKED DrawerLayout.LOCK_MODE_UNLOCKED
} }
) ?: divider_filter?.let { ) ?: binding.dividerFilter?.let {
it.isGone = config.sortOrders.isEmpty() && config.tags.isEmpty() it.isGone = config.sortOrders.isEmpty() && config.tags.isEmpty()
recyclerView_filter.isVisible = it.isVisible binding.recyclerViewFilter.isVisible = it.isVisible
} }
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
} }
@CallSuper @CallSuper
override fun onFilterChanged(filter: MangaFilter) { override fun onFilterChanged(filter: MangaFilter) {
drawer?.closeDrawers() binding.drawer?.closeDrawers()
} }
protected open fun setUpEmptyListHolder() { protected open fun setUpEmptyListHolder() {
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null) with(binding.textViewHolder) {
textView_holder.setText(R.string.nothing_found) setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null)
setText(R.string.nothing_found)
}
} }
private fun onGridScaleChanged(scale: Float) { private fun onGridScaleChanged(scale: Float) {
spanSizeLookup.invalidateCache() spanSizeLookup.invalidateCache()
spanResolver.setGridSize(scale, recyclerView) spanResolver.setGridSize(scale, binding.recyclerView)
} }
private fun onListModeChanged(mode: ListMode) { private fun onListModeChanged(mode: ListMode) {
spanSizeLookup.invalidateCache() spanSizeLookup.invalidateCache()
with(recyclerView) { with(binding.recyclerView) {
clearItemDecorations() clearItemDecorations()
removeOnLayoutChangeListener(spanResolver) removeOnLayoutChangeListener(spanResolver)
when (mode) { when (mode) {
@ -246,13 +265,13 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
} }
final override fun isSection(position: Int): Boolean { final override fun isSection(position: Int): Boolean {
return position == 0 || recyclerView_filter.adapter?.run { return position == 0 || binding.recyclerViewFilter.adapter?.run {
getItemViewType(position) != getItemViewType(position - 1) getItemViewType(position) != getItemViewType(position - 1)
} ?: false } ?: false
} }
final override fun getSectionTitle(position: Int): CharSequence? { final override fun getSectionTitle(position: Int): CharSequence? {
return when (recyclerView_filter.adapter?.getItemViewType(position)) { return when (binding.recyclerViewFilter.adapter?.getItemViewType(position)) {
FilterAdapter.VIEW_TYPE_SORT -> getString(R.string.sort_order) FilterAdapter.VIEW_TYPE_SORT -> getString(R.string.sort_order)
FilterAdapter.VIEW_TYPE_TAG -> getString(R.string.genre) FilterAdapter.VIEW_TYPE_TAG -> getString(R.string.genre)
else -> null else -> null
@ -271,8 +290,9 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
} }
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
val total = (recyclerView.layoutManager as? GridLayoutManager)?.spanCount ?: return 1 val total =
return when (adapter?.getItemViewType(position)) { (binding.recyclerView.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
return when (listAdapter?.getItemViewType(position)) {
MangaListAdapter.ITEM_TYPE_DATE, MangaListAdapter.ITEM_TYPE_DATE,
MangaListAdapter.ITEM_TYPE_PROGRESS -> total MangaListAdapter.ITEM_TYPE_PROGRESS -> total
else -> 1 else -> 1

@ -2,8 +2,10 @@ package org.koitharu.kotatsu.list.ui
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
@ -13,7 +15,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.sheet_list.*
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -24,12 +25,13 @@ import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.databinding.SheetListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
import org.koitharu.kotatsu.utils.UiUtils import org.koitharu.kotatsu.utils.UiUtils
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list), abstract class MangaListSheet : BaseBottomSheet<SheetListBinding>(),
PaginationScrollListener.Callback, OnListItemClickListener<Manga>, PaginationScrollListener.Callback, OnListItemClickListener<Manga>,
SharedPreferences.OnSharedPreferenceChangeListener, Toolbar.OnMenuItemClickListener { SharedPreferences.OnSharedPreferenceChangeListener, Toolbar.OnMenuItemClickListener {
@ -39,22 +41,26 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
protected abstract val viewModel: MangaListViewModel protected abstract val viewModel: MangaListViewModel
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetListBinding {
return SheetListBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
adapter = MangaListAdapter(get(), this) adapter = MangaListAdapter(get(), this)
initListMode(settings.listMode) initListMode(settings.listMode)
recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
recyclerView.addOnScrollListener(PaginationScrollListener(4, this)) binding.recyclerView.addOnScrollListener(PaginationScrollListener(4, this))
settings.subscribe(this) settings.subscribe(this)
toolbar.inflateMenu(R.menu.opt_list_sheet) binding.toolbar.inflateMenu(R.menu.opt_list_sheet)
toolbar.setOnMenuItemClickListener(this) binding.toolbar.setOnMenuItemClickListener(this)
toolbar.setNavigationOnClickListener { binding.toolbar.setNavigationOnClickListener {
dismiss() dismiss()
} }
if (dialog !is BottomSheetDialog) { if (dialog !is BottomSheetDialog) {
toolbar.isVisible = true binding.toolbar.isVisible = true
textView_title.isVisible = false binding.textViewTitle.isVisible = false
appbar.elevation = resources.getDimension(R.dimen.elevation_large) binding.appbar.elevation = resources.getDimension(R.dimen.elevation_large)
} }
if (savedInstanceState == null) { if (savedInstanceState == null) {
onScrolledToEnd() onScrolledToEnd()
@ -72,12 +78,12 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
} }
protected fun setTitle(title: CharSequence) { protected fun setTitle(title: CharSequence) {
toolbar.title = title binding.toolbar.title = title
textView_title.text = title binding.textViewTitle.text = title
} }
protected fun setSubtitle(subtitle: CharSequence) { protected fun setSubtitle(subtitle: CharSequence) {
toolbar.subtitle = subtitle binding.toolbar.subtitle = subtitle
} }
override fun onCreateDialog(savedInstanceState: Bundle?) = override fun onCreateDialog(savedInstanceState: Bundle?) =
@ -90,13 +96,13 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) { if (newState == BottomSheetBehavior.STATE_EXPANDED) {
toolbar.isVisible = true binding.toolbar.isVisible = true
textView_title.isVisible = false binding.textViewTitle.isVisible = false
appbar.elevation = elevation binding.appbar.elevation = elevation
} else { } else {
toolbar.isVisible = false binding.toolbar.isVisible = false
textView_title.isVisible = true binding.textViewTitle.isVisible = true
appbar.elevation = 0f binding.appbar.elevation = 0f
} }
} }
}) })
@ -114,7 +120,7 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) { when (key) {
AppSettings.KEY_LIST_MODE -> initListMode(settings.listMode) AppSettings.KEY_LIST_MODE -> initListMode(settings.listMode)
AppSettings.KEY_GRID_SIZE -> UiUtils.SpanCountResolver.update(recyclerView) AppSettings.KEY_GRID_SIZE -> UiUtils.SpanCountResolver.update(binding.recyclerView)
} }
} }
@ -124,28 +130,29 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
private fun onListChanged(list: List<Any>) { private fun onListChanged(list: List<Any>) {
adapter?.items = list adapter?.items = list
textView_holder.isVisible = list.isEmpty() binding.textViewHolder.isVisible = list.isEmpty()
recyclerView.callOnScrollListeners() binding.recyclerView.callOnScrollListeners()
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT)
.show()
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
progressBar.isVisible = isLoading && !recyclerView.hasItems binding.progressBar.isVisible = isLoading && !binding.recyclerView.hasItems
if (isLoading) { if (isLoading) {
textView_holder.isVisible = false binding.textViewHolder.isVisible = false
} }
} }
private fun initListMode(mode: ListMode) { private fun initListMode(mode: ListMode) {
val ctx = context ?: return val ctx = context ?: return
val position = recyclerView.firstItem val position = binding.recyclerView.firstItem
recyclerView.layoutManager = null binding.recyclerView.layoutManager = null
recyclerView.clearItemDecorations() binding.recyclerView.clearItemDecorations()
recyclerView.removeOnLayoutChangeListener(UiUtils.SpanCountResolver) binding.recyclerView.removeOnLayoutChangeListener(UiUtils.SpanCountResolver)
recyclerView.layoutManager = when (mode) { binding.recyclerView.layoutManager = when (mode) {
ListMode.GRID -> { ListMode.GRID -> {
GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx)).apply { GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx)).apply {
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
@ -156,7 +163,7 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
} }
else -> LinearLayoutManager(ctx) else -> LinearLayoutManager(ctx)
} }
recyclerView.addItemDecoration( binding.recyclerView.addItemDecoration(
when (mode) { when (mode) {
ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL) ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL)
ListMode.DETAILED_LIST, ListMode.DETAILED_LIST,
@ -166,9 +173,9 @@ abstract class MangaListSheet : BaseBottomSheet(R.layout.sheet_list),
} }
) )
if (mode == ListMode.GRID) { if (mode == ListMode.GRID) {
recyclerView.addOnLayoutChangeListener(UiUtils.SpanCountResolver) binding.recyclerView.addOnLayoutChangeListener(UiUtils.SpanCountResolver)
} }
adapter?.notifyDataSetChanged() adapter?.notifyDataSetChanged()
recyclerView.firstItem = position binding.recyclerView.firstItem = position
} }
} }

@ -2,11 +2,11 @@ package org.koitharu.kotatsu.list.ui.adapter
import coil.ImageLoader import coil.ImageLoader
import coil.request.Disposable import coil.request.Disposable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.android.synthetic.main.item_manga_list.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
import org.koitharu.kotatsu.list.ui.model.MangaGridModel import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.newImageRequest
@ -14,7 +14,9 @@ import org.koitharu.kotatsu.utils.ext.newImageRequest
fun mangaGridItemAD( fun mangaGridItemAD(
coil: ImageLoader, coil: ImageLoader,
clickListener: OnListItemClickListener<Manga> clickListener: OnListItemClickListener<Manga>
) = adapterDelegateLayoutContainer<MangaGridModel, Any>(R.layout.item_manga_grid) { ) = adapterDelegateViewBinding<MangaGridModel, Any, ItemMangaGridBinding>(
{ inflater, parent -> ItemMangaGridBinding.inflate(inflater, parent, false) }
) {
var imageRequest: Disposable? = null var imageRequest: Disposable? = null
@ -26,9 +28,9 @@ fun mangaGridItemAD(
} }
bind { bind {
textView_title.text = item.title binding.textViewTitle.text = item.title
imageRequest?.dispose() imageRequest?.dispose()
imageRequest = imageView_cover.newImageRequest(item.coverUrl) imageRequest = binding.imageViewCover.newImageRequest(item.coverUrl)
.placeholder(R.drawable.ic_placeholder) .placeholder(R.drawable.ic_placeholder)
.fallback(R.drawable.ic_placeholder) .fallback(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder) .error(R.drawable.ic_placeholder)
@ -37,6 +39,6 @@ fun mangaGridItemAD(
onViewRecycled { onViewRecycled {
imageRequest?.dispose() imageRequest?.dispose()
imageView_cover.setImageDrawable(null) binding.imageViewCover.setImageDrawable(null)
} }
} }

@ -2,11 +2,11 @@ package org.koitharu.kotatsu.list.ui.adapter
import coil.ImageLoader import coil.ImageLoader
import coil.request.Disposable import coil.request.Disposable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.android.synthetic.main.item_manga_list_details.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.newImageRequest
@ -15,7 +15,9 @@ import org.koitharu.kotatsu.utils.ext.textAndVisible
fun mangaListDetailedItemAD( fun mangaListDetailedItemAD(
coil: ImageLoader, coil: ImageLoader,
clickListener: OnListItemClickListener<Manga> clickListener: OnListItemClickListener<Manga>
) = adapterDelegateLayoutContainer<MangaListDetailedModel, Any>(R.layout.item_manga_list_details) { ) = adapterDelegateViewBinding<MangaListDetailedModel, Any, ItemMangaListDetailsBinding>(
{ inflater, parent -> ItemMangaListDetailsBinding.inflate(inflater, parent, false) }
) {
var imageRequest: Disposable? = null var imageRequest: Disposable? = null
@ -28,19 +30,19 @@ fun mangaListDetailedItemAD(
bind { bind {
imageRequest?.dispose() imageRequest?.dispose()
textView_title.text = item.title binding.textViewTitle.text = item.title
textView_subtitle.textAndVisible = item.subtitle binding.textViewSubtitle.textAndVisible = item.subtitle
imageRequest = imageView_cover.newImageRequest(item.coverUrl) imageRequest = binding.imageViewCover.newImageRequest(item.coverUrl)
.placeholder(R.drawable.ic_placeholder) .placeholder(R.drawable.ic_placeholder)
.fallback(R.drawable.ic_placeholder) .fallback(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder) .error(R.drawable.ic_placeholder)
.enqueueWith(coil) .enqueueWith(coil)
textView_rating.textAndVisible = item.rating binding.textViewRating.textAndVisible = item.rating
textView_tags.text = item.tags binding.textViewTags.text = item.tags
} }
onViewRecycled { onViewRecycled {
imageRequest?.dispose() imageRequest?.dispose()
imageView_cover.setImageDrawable(null) binding.imageViewCover.setImageDrawable(null)
} }
} }

@ -2,11 +2,11 @@ package org.koitharu.kotatsu.list.ui.adapter
import coil.ImageLoader import coil.ImageLoader
import coil.request.Disposable import coil.request.Disposable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.android.synthetic.main.item_manga_list.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.databinding.ItemMangaListBinding
import org.koitharu.kotatsu.list.ui.model.MangaListModel import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.newImageRequest
@ -15,7 +15,9 @@ import org.koitharu.kotatsu.utils.ext.textAndVisible
fun mangaListItemAD( fun mangaListItemAD(
coil: ImageLoader, coil: ImageLoader,
clickListener: OnListItemClickListener<Manga> clickListener: OnListItemClickListener<Manga>
) = adapterDelegateLayoutContainer<MangaListModel, Any>(R.layout.item_manga_list) { ) = adapterDelegateViewBinding<MangaListModel, Any, ItemMangaListBinding>(
{ inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) }
) {
var imageRequest: Disposable? = null var imageRequest: Disposable? = null
@ -28,9 +30,9 @@ fun mangaListItemAD(
bind { bind {
imageRequest?.dispose() imageRequest?.dispose()
textView_title.text = item.title binding.textViewTitle.text = item.title
textView_subtitle.textAndVisible = item.subtitle binding.textViewSubtitle.textAndVisible = item.subtitle
imageRequest = imageView_cover.newImageRequest(item.coverUrl) imageRequest = binding.imageViewCover.newImageRequest(item.coverUrl)
.placeholder(R.drawable.ic_placeholder) .placeholder(R.drawable.ic_placeholder)
.fallback(R.drawable.ic_placeholder) .fallback(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder) .error(R.drawable.ic_placeholder)
@ -39,6 +41,6 @@ fun mangaListItemAD(
onViewRecycled { onViewRecycled {
imageRequest?.dispose() imageRequest?.dispose()
imageView_cover.setImageDrawable(null) binding.imageViewCover.setImageDrawable(null)
} }
} }

@ -14,7 +14,7 @@ class FilterAdapter(
tags: List<MangaTag> = emptyList(), tags: List<MangaTag> = emptyList(),
state: MangaFilter?, state: MangaFilter?,
private val listener: OnFilterChangedListener private val listener: OnFilterChangedListener
) : RecyclerView.Adapter<BaseViewHolder<*, Boolean>>() { ) : RecyclerView.Adapter<BaseViewHolder<*, Boolean, *>>() {
private val sortOrders = ArrayList<SortOrder>(sortOrders) private val sortOrders = ArrayList<SortOrder>(sortOrders)
private val tags = ArrayList(Collections.singletonList(null) + tags) private val tags = ArrayList(Collections.singletonList(null) + tags)
@ -37,7 +37,7 @@ class FilterAdapter(
override fun getItemCount() = sortOrders.size + tags.size override fun getItemCount() = sortOrders.size + tags.size
override fun onBindViewHolder(holder: BaseViewHolder<*, Boolean>, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder<*, Boolean, *>, position: Int) {
when (holder) { when (holder) {
is FilterSortHolder -> { is FilterSortHolder -> {
val item = sortOrders[position] val item = sortOrders[position]

@ -1,16 +1,18 @@
package org.koitharu.kotatsu.list.ui.filter package org.koitharu.kotatsu.list.ui.filter
import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_checkable_single.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.databinding.ItemCheckableSingleBinding
class FilterSortHolder(parent: ViewGroup) : class FilterSortHolder(parent: ViewGroup) :
BaseViewHolder<SortOrder, Boolean>(parent, R.layout.item_checkable_single) { BaseViewHolder<SortOrder, Boolean, ItemCheckableSingleBinding>(
ItemCheckableSingleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
) {
override fun onBind(data: SortOrder, extra: Boolean) { override fun onBind(data: SortOrder, extra: Boolean) {
radio.setText(data.titleRes) binding.radio.setText(data.titleRes)
radio.isChecked = extra binding.radio.isChecked = extra
} }
} }

@ -1,16 +1,19 @@
package org.koitharu.kotatsu.list.ui.filter package org.koitharu.kotatsu.list.ui.filter
import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_checkable_single.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.databinding.ItemCheckableSingleBinding
class FilterTagHolder(parent: ViewGroup) : class FilterTagHolder(parent: ViewGroup) :
BaseViewHolder<MangaTag?, Boolean>(parent, R.layout.item_checkable_single) { BaseViewHolder<MangaTag?, Boolean, ItemCheckableSingleBinding>(
ItemCheckableSingleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
) {
override fun onBind(data: MangaTag?, extra: Boolean) { override fun onBind(data: MangaTag?, extra: Boolean) {
radio.text = data?.title ?: context.getString(R.string.all) binding.radio.text = data?.title ?: context.getString(R.string.all)
radio.isChecked = extra binding.radio.isChecked = extra
} }
} }

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.local package org.koitharu.kotatsu.local
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource

@ -11,8 +11,7 @@ import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_list.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
@ -54,7 +53,9 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<Uri> {
e.printStackTrace() e.printStackTrace()
} }
Snackbar.make( Snackbar.make(
recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT binding.recyclerView,
R.string.operation_not_supported,
Snackbar.LENGTH_SHORT
).show() ).show()
} }
true true
@ -64,12 +65,12 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<Uri> {
} }
override fun getTitle(): CharSequence? { override fun getTitle(): CharSequence? {
return getString(R.string.local_storage) return context?.getString(R.string.local_storage)
} }
override fun setUpEmptyListHolder() { override fun setUpEmptyListHolder() {
textView_holder.setText(R.string.text_local_holder) binding.textViewHolder.setText(R.string.text_local_holder)
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) binding.textViewHolder.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
} }
override fun onActivityResult(result: Uri?) { override fun onActivityResult(result: Uri?) {
@ -102,7 +103,7 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<Uri> {
private fun onItemRemoved(item: Manga) { private fun onItemRemoved(item: Manga) {
Snackbar.make( Snackbar.make(
recyclerView, getString( binding.recyclerView, getString(
R.string._s_deleted_from_local_storage, R.string._s_deleted_from_local_storage,
item.title.ellipsize(16) item.title.ellipsize(16)
), Snackbar.LENGTH_SHORT ), Snackbar.LENGTH_SHORT

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.main package org.koitharu.kotatsu.main
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.main.ui.MainViewModel import org.koitharu.kotatsu.main.ui.MainViewModel
import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel

@ -15,12 +15,12 @@ import androidx.fragment.app.Fragment
import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSection import org.koitharu.kotatsu.core.prefs.AppSection
import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment
import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.history.ui.HistoryListFragment
import org.koitharu.kotatsu.local.ui.LocalListFragment import org.koitharu.kotatsu.local.ui.LocalListFragment
@ -37,7 +37,8 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.resolveDp import org.koitharu.kotatsu.utils.ext.resolveDp
import java.io.Closeable import java.io.Closeable
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener, class MainActivity : BaseActivity<ActivityMainBinding>(),
NavigationView.OnNavigationItemSelectedListener,
View.OnClickListener { View.OnClickListener {
private val viewModel by viewModel<MainViewModel>() private val viewModel by viewModel<MainViewModel>()
@ -47,21 +48,27 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(ActivityMainBinding.inflate(layoutInflater))
drawerToggle = drawerToggle =
ActionBarDrawerToggle(this, drawer, toolbar, R.string.open_menu, R.string.close_menu) ActionBarDrawerToggle(
drawer.addDrawerListener(drawerToggle) this,
binding.drawer,
binding.toolbar,
R.string.open_menu,
R.string.close_menu
)
binding.drawer.addDrawerListener(drawerToggle)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
navigationView.setNavigationItemSelectedListener(this) binding.navigationView.setNavigationItemSelectedListener(this)
with(fab) { with(binding.fab) {
imageTintList = ColorStateList.valueOf(Color.WHITE) imageTintList = ColorStateList.valueOf(Color.WHITE)
setOnClickListener(this@MainActivity) setOnClickListener(this@MainActivity)
} }
supportFragmentManager.findFragmentById(R.id.container)?.let { supportFragmentManager.findFragmentById(R.id.container)?.let {
fab.isVisible = it is HistoryListFragment binding.fab.isVisible = it is HistoryListFragment
} ?: run { } ?: run {
openDefaultSection() openDefaultSection()
} }
@ -94,8 +101,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
override fun onBackPressed() { override fun onBackPressed() {
if (drawer.isDrawerOpen(navigationView)) { if (binding.drawer.isDrawerOpen(binding.navigationView)) {
drawer.closeDrawer(navigationView) binding.drawer.closeDrawer(binding.navigationView)
} else { } else {
super.onBackPressed() super.onBackPressed()
} }
@ -148,42 +155,43 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
else -> return false else -> return false
} }
drawer.closeDrawers() binding.drawer.closeDrawers()
return true return true
} }
private fun onOpenReader(state: ReaderState) { private fun onOpenReader(state: ReaderState) {
val options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ActivityOptions.makeClipRevealAnimation( ActivityOptions.makeClipRevealAnimation(
fab, 0, 0, fab.measuredWidth, fab.measuredHeight binding.fab, 0, 0, binding.fab.measuredWidth, binding.fab.measuredHeight
) )
} else { } else {
ActivityOptions.makeScaleUpAnimation( ActivityOptions.makeScaleUpAnimation(
fab, 0, 0, fab.measuredWidth, fab.measuredHeight binding.fab, 0, 0, binding.fab.measuredWidth, binding.fab.measuredHeight
) )
} }
startActivity(ReaderActivity.newIntent(this, state), options?.toBundle()) startActivity(ReaderActivity.newIntent(this, state), options?.toBundle())
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
Snackbar.make(container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() Snackbar.make(binding.container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT)
.show()
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
fab.isEnabled = !isLoading binding.fab.isEnabled = !isLoading
if (isLoading) { if (isLoading) {
fab.setImageDrawable(CircularProgressDrawable(this).also { binding.fab.setImageDrawable(CircularProgressDrawable(this).also {
it.setColorSchemeColors(Color.WHITE) it.setColorSchemeColors(Color.WHITE)
it.strokeWidth = resources.resolveDp(2f) it.strokeWidth = resources.resolveDp(2f)
it.start() it.start()
}) })
} else { } else {
fab.setImageResource(R.drawable.ic_read_fill) binding.fab.setImageResource(R.drawable.ic_read_fill)
} }
} }
private fun updateSideMenu(remoteSources: List<MangaSource>) { private fun updateSideMenu(remoteSources: List<MangaSource>) {
val submenu = navigationView.menu.findItem(R.id.nav_remote_sources).subMenu val submenu = binding.navigationView.menu.findItem(R.id.nav_remote_sources).subMenu
submenu.removeGroup(R.id.group_remote_sources) submenu.removeGroup(R.id.group_remote_sources)
remoteSources.forEachIndexed { index, source -> remoteSources.forEachIndexed { index, source ->
submenu.add(R.id.group_remote_sources, source.ordinal, index, source.title) submenu.add(R.id.group_remote_sources, source.ordinal, index, source.title)
@ -194,19 +202,19 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
private fun openDefaultSection() { private fun openDefaultSection() {
when (viewModel.defaultSection) { when (viewModel.defaultSection) {
AppSection.LOCAL -> { AppSection.LOCAL -> {
navigationView.setCheckedItem(R.id.nav_local_storage) binding.navigationView.setCheckedItem(R.id.nav_local_storage)
setPrimaryFragment(LocalListFragment.newInstance()) setPrimaryFragment(LocalListFragment.newInstance())
} }
AppSection.FAVOURITES -> { AppSection.FAVOURITES -> {
navigationView.setCheckedItem(R.id.nav_favourites) binding.navigationView.setCheckedItem(R.id.nav_favourites)
setPrimaryFragment(FavouritesContainerFragment.newInstance()) setPrimaryFragment(FavouritesContainerFragment.newInstance())
} }
AppSection.HISTORY -> { AppSection.HISTORY -> {
navigationView.setCheckedItem(R.id.nav_history) binding.navigationView.setCheckedItem(R.id.nav_history)
setPrimaryFragment(HistoryListFragment.newInstance()) setPrimaryFragment(HistoryListFragment.newInstance())
} }
AppSection.FEED -> { AppSection.FEED -> {
navigationView.setCheckedItem(R.id.nav_feed) binding.navigationView.setCheckedItem(R.id.nav_feed)
setPrimaryFragment(FeedFragment.newInstance()) setPrimaryFragment(FeedFragment.newInstance())
} }
} }
@ -216,6 +224,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment) .replace(R.id.container, fragment)
.commit() .commit()
fab.isVisible = fragment is HistoryListFragment binding.fab.isVisible = fragment is HistoryListFragment
} }
} }

@ -10,21 +10,22 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import kotlinx.android.synthetic.main.activity_protect.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityProtectBinding
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class ProtectActivity : BaseActivity(), TextView.OnEditorActionListener, TextWatcher { class ProtectActivity : BaseActivity<ActivityProtectBinding>(), TextView.OnEditorActionListener,
TextWatcher {
private val viewModel by viewModel<ProtectViewModel>() private val viewModel by viewModel<ProtectViewModel>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_protect) setContentView(ActivityProtectBinding.inflate(layoutInflater))
edit_password.setOnEditorActionListener(this) binding.editPassword.setOnEditorActionListener(this)
edit_password.addTextChangedListener(this) binding.editPassword.addTextChangedListener(this)
supportActionBar?.run { supportActionBar?.run {
setDisplayHomeAsUpEnabled(true) setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(R.drawable.ic_cross) setHomeAsUpIndicator(R.drawable.ic_cross)
@ -42,7 +43,7 @@ class ProtectActivity : BaseActivity(), TextView.OnEditorActionListener, TextWat
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_done -> { R.id.action_done -> {
viewModel.tryUnlock(edit_password.text?.toString().orEmpty()) viewModel.tryUnlock(binding.editPassword.text.toString().orEmpty())
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
@ -50,7 +51,7 @@ class ProtectActivity : BaseActivity(), TextView.OnEditorActionListener, TextWat
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
return if (actionId == EditorInfo.IME_ACTION_DONE) { return if (actionId == EditorInfo.IME_ACTION_DONE) {
viewModel.tryUnlock(edit_password.text?.toString().orEmpty()) viewModel.tryUnlock(binding.editPassword.text.toString().orEmpty())
true true
} else { } else {
false false
@ -62,7 +63,7 @@ class ProtectActivity : BaseActivity(), TextView.OnEditorActionListener, TextWat
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) { override fun afterTextChanged(s: Editable?) {
layout_password.error = null binding.layoutPassword.error = null
} }
private fun onUnlockSuccess(unit: Unit) { private fun onUnlockSuccess(unit: Unit) {
@ -70,11 +71,11 @@ class ProtectActivity : BaseActivity(), TextView.OnEditorActionListener, TextWat
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
layout_password.error = e.getDisplayMessage(resources) binding.layoutPassword.error = e.getDisplayMessage(resources)
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
layout_password.isEnabled = !isLoading binding.layoutPassword.isEnabled = !isLoading
} }
companion object { companion object {

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.reader package org.koitharu.kotatsu.reader
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.base.domain.MangaDataRepository import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache

@ -1,22 +1,29 @@
package org.koitharu.kotatsu.reader.ui package org.koitharu.kotatsu.reader.ui
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.dialog_chapters.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.databinding.DialogChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class ChaptersDialog : AlertDialogFragment(R.layout.dialog_chapters), class ChaptersDialog : AlertDialogFragment<DialogChaptersBinding>(),
OnListItemClickListener<MangaChapter> { OnListItemClickListener<MangaChapter> {
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = DialogChaptersBinding.inflate(inflater, container, false)
override fun onBuildDialog(builder: AlertDialog.Builder) { override fun onBuildDialog(builder: AlertDialog.Builder) {
builder.setTitle(R.string.chapters) builder.setTitle(R.string.chapters)
.setNegativeButton(R.string.close, null) .setNegativeButton(R.string.close, null)
@ -24,13 +31,13 @@ class ChaptersDialog : AlertDialogFragment(R.layout.dialog_chapters),
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
recyclerView_chapters.addItemDecoration( binding.recyclerViewChapters.addItemDecoration(
DividerItemDecoration( DividerItemDecoration(
requireContext(), requireContext(),
RecyclerView.VERTICAL RecyclerView.VERTICAL
) )
) )
recyclerView_chapters.adapter = ChaptersAdapter(this).apply { binding.recyclerViewChapters.adapter = ChaptersAdapter(this).apply {
// arguments?.getParcelableArrayList<MangaChapter>(ARG_CHAPTERS)?.let(this::setItems) // arguments?.getParcelableArrayList<MangaChapter>(ARG_CHAPTERS)?.let(this::setItems)
// currentChapterId = arguments?.getLong(ARG_CURRENT_ID, 0L)?.takeUnless { it == 0L } // currentChapterId = arguments?.getLong(ARG_CURRENT_ID, 0L)?.takeUnless { it == 0L }
} }

@ -19,14 +19,13 @@ import androidx.core.view.*
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFullscreenActivity import org.koitharu.kotatsu.base.ui.BaseFullscreenActivity
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
@ -35,6 +34,7 @@ import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.reader.ui.base.AbstractReader import org.koitharu.kotatsu.reader.ui.base.AbstractReader
import org.koitharu.kotatsu.reader.ui.reversed.ReversedReaderFragment import org.koitharu.kotatsu.reader.ui.reversed.ReversedReaderFragment
import org.koitharu.kotatsu.reader.ui.standard.PagerReaderFragment import org.koitharu.kotatsu.reader.ui.standard.PagerReaderFragment
@ -48,7 +48,8 @@ import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.anim.Motion import org.koitharu.kotatsu.utils.anim.Motion
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeListener, class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
ChaptersDialog.OnChapterChangeListener,
GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback, GridTouchHelper.OnGridTouchListener, OnPageSelectListener, ReaderConfigDialog.Callback,
ReaderListener, SharedPreferences.OnSharedPreferenceChangeListener, ReaderListener, SharedPreferences.OnSharedPreferenceChangeListener,
ActivityResultCallback<Boolean>, OnApplyWindowInsetsListener { ActivityResultCallback<Boolean>, OnApplyWindowInsetsListener {
@ -65,16 +66,16 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
private var isVolumeKeysSwitchEnabled = false private var isVolumeKeysSwitchEnabled = false
private val reader private val reader
get() = supportFragmentManager.findFragmentById(R.id.container) as? AbstractReader get() = supportFragmentManager.findFragmentById(R.id.container) as? AbstractReader<*>
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_reader) setContentView(ActivityReaderBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
touchHelper = GridTouchHelper(this, this) touchHelper = GridTouchHelper(this, this)
orientationHelper = ScreenOrientationHelper(this) orientationHelper = ScreenOrientationHelper(this)
toolbar_bottom.inflateMenu(R.menu.opt_reader_bottom) binding.toolbarBottom.inflateMenu(R.menu.opt_reader_bottom)
toolbar_bottom.setOnMenuItemClickListener(::onOptionsItemSelected) binding.toolbarBottom.setOnMenuItemClickListener(::onOptionsItemSelected)
@Suppress("RemoveExplicitTypeArguments") @Suppress("RemoveExplicitTypeArguments")
state = savedInstanceState?.getParcelable<ReaderState>(EXTRA_STATE) state = savedInstanceState?.getParcelable<ReaderState>(EXTRA_STATE)
@ -91,13 +92,13 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
getString(R.string.chapter_d_of_d, state.chapter?.number ?: 0, size) getString(R.string.chapter_d_of_d, state.chapter?.number ?: 0, size)
} }
ViewCompat.setOnApplyWindowInsetsListener(rootLayout, this) ViewCompat.setOnApplyWindowInsetsListener(binding.rootLayout, this)
settings.subscribe(this) settings.subscribe(this)
loadSwitchSettings() loadSwitchSettings()
orientationHelper.observeAutoOrientation() orientationHelper.observeAutoOrientation()
.onEach { .onEach {
toolbar_bottom.menu.findItem(R.id.action_screen_rotate).isVisible = !it binding.toolbarBottom.menu.findItem(R.id.action_screen_rotate).isVisible = !it
}.launchIn(lifecycleScope) }.launchIn(lifecycleScope)
if (savedInstanceState == null) { if (savedInstanceState == null) {
@ -133,14 +134,14 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
} }
} }
} }
toolbar_bottom.menu.findItem(R.id.action_reader_mode).setIcon( binding.toolbarBottom.menu.findItem(R.id.action_reader_mode).setIcon(
when (mode) { when (mode) {
ReaderMode.WEBTOON -> R.drawable.ic_script ReaderMode.WEBTOON -> R.drawable.ic_script
ReaderMode.REVERSED -> R.drawable.ic_read_reversed ReaderMode.REVERSED -> R.drawable.ic_read_reversed
ReaderMode.STANDARD -> R.drawable.ic_book_page ReaderMode.STANDARD -> R.drawable.ic_book_page
} }
) )
appbar_top.postDelayed(1000) { binding.appbarTop.postDelayed(1000) {
setUiIsVisible(false) setUiIsVisible(false)
} }
} }
@ -242,8 +243,8 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
override fun onLoadingStateChanged(isLoading: Boolean) { override fun onLoadingStateChanged(isLoading: Boolean) {
val hasPages = reader?.hasItems == true val hasPages = reader?.hasItems == true
layout_loading.isVisible = isLoading && !hasPages binding.layoutLoading.isVisible = isLoading && !hasPages
progressBar_bottom.isVisible = isLoading && hasPages binding.progressBarBottom.isVisible = isLoading && hasPages
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
@ -262,7 +263,7 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
override fun onGridTouch(area: Int) { override fun onGridTouch(area: Int) {
when (area) { when (area) {
GridTouchHelper.AREA_CENTER -> { GridTouchHelper.AREA_CENTER -> {
setUiIsVisible(!appbar_top.isVisible) setUiIsVisible(!binding.appbarTop.isVisible)
} }
GridTouchHelper.AREA_TOP, GridTouchHelper.AREA_TOP,
GridTouchHelper.AREA_LEFT -> if (isTapSwitchEnabled) { GridTouchHelper.AREA_LEFT -> if (isTapSwitchEnabled) {
@ -276,12 +277,12 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
} }
override fun onProcessTouch(rawX: Int, rawY: Int): Boolean { override fun onProcessTouch(rawX: Int, rawY: Int): Boolean {
return if (appbar_top.hasGlobalPoint(rawX, rawY) return if (binding.appbarTop.hasGlobalPoint(rawX, rawY)
|| appbar_bottom.hasGlobalPoint(rawX, rawY) || binding.appbarBottom.hasGlobalPoint(rawX, rawY)
) { ) {
false false
} else { } else {
val targets = rootLayout.hitTest(rawX, rawY) val targets = binding.rootLayout.hitTest(rawX, rawY)
targets.none { it.hasOnClickListeners() } targets.none { it.hasOnClickListeners() }
} }
} }
@ -318,7 +319,7 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
true true
} }
KeyEvent.KEYCODE_DPAD_CENTER -> { KeyEvent.KEYCODE_DPAD_CENTER -> {
setUiIsVisible(!appbar_top.isVisible) setUiIsVisible(!binding.appbarTop.isVisible)
true true
} }
else -> super.onKeyDown(keyCode, event) else -> super.onKeyDown(keyCode, event)
@ -350,14 +351,14 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
private fun onPageSaved(uri: Uri?) { private fun onPageSaved(uri: Uri?) {
if (uri != null) { if (uri != null) {
Snackbar.make(container, R.string.page_saved, Snackbar.LENGTH_LONG) Snackbar.make(binding.container, R.string.page_saved, Snackbar.LENGTH_LONG)
.setAnchorView(appbar_bottom) .setAnchorView(binding.appbarBottom)
.setAction(R.string.share) { .setAction(R.string.share) {
ShareHelper.shareImage(this, uri) ShareHelper.shareImage(this, uri)
}.show() }.show()
} else { } else {
Snackbar.make(container, R.string.error_occurred, Snackbar.LENGTH_SHORT) Snackbar.make(binding.container, R.string.error_occurred, Snackbar.LENGTH_SHORT)
.setAnchorView(appbar_bottom) .setAnchorView(binding.appbarBottom)
.show() .show()
} }
} }
@ -385,14 +386,14 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
} }
private fun setUiIsVisible(isUiVisible: Boolean) { private fun setUiIsVisible(isUiVisible: Boolean) {
if (appbar_top.isVisible != isUiVisible) { if (binding.appbarTop.isVisible != isUiVisible) {
if (isUiVisible) { if (isUiVisible) {
appbar_top.showAnimated(Motion.SlideTop) binding.appbarTop.showAnimated(Motion.SlideTop)
appbar_bottom.showAnimated(Motion.SlideBottom) binding.appbarBottom.showAnimated(Motion.SlideBottom)
showSystemUI() showSystemUI()
} else { } else {
appbar_top.hideAnimated(Motion.SlideTop) binding.appbarTop.hideAnimated(Motion.SlideTop)
appbar_bottom.hideAnimated(Motion.SlideBottom) binding.appbarBottom.hideAnimated(Motion.SlideBottom)
hideSystemUI() hideSystemUI()
} }
} }
@ -400,12 +401,12 @@ class ReaderActivity : BaseFullscreenActivity(), ChaptersDialog.OnChapterChangeL
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
appbar_top.updatePadding( binding.appbarTop.updatePadding(
top = systemBars.top, top = systemBars.top,
right = systemBars.right, right = systemBars.right,
left = systemBars.left left = systemBars.left
) )
appbar_bottom.updatePadding( binding.appbarBottom.updatePadding(
bottom = systemBars.bottom, bottom = systemBars.bottom,
right = systemBars.right, right = systemBars.right,
left = systemBars.left left = systemBars.left

@ -1,20 +1,27 @@
package org.koitharu.kotatsu.reader.ui package org.koitharu.kotatsu.reader.ui
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import kotlinx.android.synthetic.main.dialog_reader_config.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.databinding.DialogReaderConfigBinding
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class ReaderConfigDialog : AlertDialogFragment(R.layout.dialog_reader_config), class ReaderConfigDialog : AlertDialogFragment<DialogReaderConfigBinding>(),
View.OnClickListener { View.OnClickListener {
private lateinit var mode: ReaderMode private lateinit var mode: ReaderMode
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = DialogReaderConfigBinding.inflate(inflater, container, false)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mode = arguments?.getInt(ARG_MODE) mode = arguments?.getInt(ARG_MODE)
@ -29,14 +36,14 @@ class ReaderConfigDialog : AlertDialogFragment(R.layout.dialog_reader_config),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
button_standard.isChecked = mode == ReaderMode.STANDARD binding.buttonStandard.isChecked = mode == ReaderMode.STANDARD
button_reversed.isChecked = mode == ReaderMode.REVERSED binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED
button_webtoon.isChecked = mode == ReaderMode.WEBTOON binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON
button_ok.setOnClickListener(this) binding.buttonOk.setOnClickListener(this)
button_standard.setOnClickListener(this) binding.buttonStandard.setOnClickListener(this)
button_reversed.setOnClickListener(this) binding.buttonReversed.setOnClickListener(this)
button_webtoon.setOnClickListener(this) binding.buttonWebtoon.setOnClickListener(this)
} }
override fun onClick(v: View) { override fun onClick(v: View) {

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.reader.ui
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.IgnoredOnParcel import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter import org.koitharu.kotatsu.core.model.MangaChapter

@ -7,15 +7,16 @@ import androidx.fragment.app.commit
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivitySettingsSimpleBinding
import org.koitharu.kotatsu.settings.MainSettingsFragment import org.koitharu.kotatsu.settings.MainSettingsFragment
import org.koitharu.kotatsu.settings.NetworkSettingsFragment import org.koitharu.kotatsu.settings.NetworkSettingsFragment
import org.koitharu.kotatsu.settings.ReaderSettingsFragment import org.koitharu.kotatsu.settings.ReaderSettingsFragment
class SimpleSettingsActivity : BaseActivity() { class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings_simple) setContentView(ActivitySettingsSimpleBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportFragmentManager.commit { supportFragmentManager.commit {
replace( replace(

@ -6,6 +6,7 @@ import android.view.View
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.core.view.postDelayed import androidx.core.view.postDelayed
import androidx.viewbinding.ViewBinding
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -20,8 +21,7 @@ import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.utils.ext.associateByLong import org.koitharu.kotatsu.utils.ext.associateByLong
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayoutId), abstract class AbstractReader<B : ViewBinding> : BaseFragment<B>(), OnBoundsScrollListener {
OnBoundsScrollListener {
protected lateinit var manga: Manga protected lateinit var manga: Manga
private set private set
@ -30,11 +30,11 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
PageLoader() PageLoader()
} }
protected val pages = ArrayDeque<ReaderPage>() protected val pages = ArrayDeque<ReaderPage>()
protected var adapter: BaseReaderAdapter? = null protected var readerAdapter: BaseReaderAdapter? = null
private set private set
val itemsCount: Int val itemsCount: Int
get() = adapter?.itemCount ?: 0 get() = readerAdapter?.itemCount ?: 0
val hasItems: Boolean val hasItems: Boolean
get() = itemsCount != 0 get() = itemsCount != 0
@ -52,7 +52,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
adapter = onCreateAdapter(pages) readerAdapter = onCreateAdapter(pages)
@Suppress("RemoveExplicitTypeArguments") @Suppress("RemoveExplicitTypeArguments")
val state = savedInstanceState?.getParcelable<ReaderState>(ARG_STATE) val state = savedInstanceState?.getParcelable<ReaderState>(ARG_STATE)
?: requireArguments().getParcelable<ReaderState>(ARG_STATE)!! ?: requireArguments().getParcelable<ReaderState>(ARG_STATE)!!
@ -61,7 +61,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
it.mapIndexedTo(pages) { i, p -> it.mapIndexedTo(pages) { i, p ->
ReaderPage.from(p, i, state.chapterId) ReaderPage.from(p, i, state.chapterId)
} }
adapter?.notifyDataSetChanged() readerAdapter?.notifyDataSetChanged()
setCurrentItem(state.page, false) setCurrentItem(state.page, false)
if (state.scroll != 0) { if (state.scroll != 0) {
restorePageScroll(state.page, state.scroll) restorePageScroll(state.page, state.scroll)
@ -100,7 +100,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
pages.addAll(0, it.mapIndexed { i, p -> pages.addAll(0, it.mapIndexed { i, p ->
ReaderPage.from(p, i, prevChapterId) ReaderPage.from(p, i, prevChapterId)
}) })
adapter?.notifyItemsPrepended(it.size) readerAdapter?.notifyItemsPrepended(it.size)
view?.postDelayed(500) { view?.postDelayed(500) {
trimEnd() trimEnd()
} }
@ -115,7 +115,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
pages.addAll(it.mapIndexed { i, p -> pages.addAll(it.mapIndexed { i, p ->
ReaderPage.from(p, i, nextChapterId) ReaderPage.from(p, i, nextChapterId)
}) })
adapter?.notifyItemsAppended(it.size) readerAdapter?.notifyItemsAppended(it.size)
view?.postDelayed(500) { view?.postDelayed(500) {
trimStart() trimStart()
} }
@ -123,7 +123,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
} }
override fun onDestroyView() { override fun onDestroyView() {
adapter = null readerAdapter = null
super.onDestroyView() super.onDestroyView()
} }
@ -134,7 +134,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
@CallSuper @CallSuper
open fun recreateAdapter() { open fun recreateAdapter() {
adapter = onCreateAdapter(pages) readerAdapter = onCreateAdapter(pages)
} }
fun getPages(): List<MangaPage>? { fun getPages(): List<MangaPage>? {
@ -212,13 +212,13 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
val currentChapterId = pages.getOrNull(getCurrentItem())?.chapterId ?: 0L val currentChapterId = pages.getOrNull(getCurrentItem())?.chapterId ?: 0L
if (chapterId != 0L && chapterId != currentChapterId) { if (chapterId != 0L && chapterId != currentChapterId) {
pages.clear() pages.clear()
adapter?.notifyDataSetChanged() readerAdapter?.notifyDataSetChanged()
loadChapter(chapterId) { loadChapter(chapterId) {
pages.clear() pages.clear()
it.mapIndexedTo(pages) { i, p -> it.mapIndexedTo(pages) { i, p ->
ReaderPage.from(p, i, chapterId) ReaderPage.from(p, i, chapterId)
} }
adapter?.notifyDataSetChanged() readerAdapter?.notifyDataSetChanged()
setCurrentItem( setCurrentItem(
if (pageId == 0L) { if (pageId == 0L) {
0 0

@ -5,14 +5,14 @@ import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
abstract class BaseReaderAdapter(protected val pages: List<ReaderPage>) : abstract class BaseReaderAdapter(protected val pages: List<ReaderPage>) :
RecyclerView.Adapter<BaseViewHolder<ReaderPage, Unit>>() { RecyclerView.Adapter<BaseViewHolder<ReaderPage, Unit, *>>() {
init { init {
@Suppress("LeakingThis") @Suppress("LeakingThis")
setHasStableIds(true) setHasStableIds(true)
} }
override fun onBindViewHolder(holder: BaseViewHolder<ReaderPage, Unit>, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder<ReaderPage, Unit, *>, position: Int) {
val item = pages[position] val item = pages[position]
holder.bind(item, Unit) holder.bind(item, Unit)
} }
@ -42,11 +42,11 @@ abstract class BaseReaderAdapter(protected val pages: List<ReaderPage>) :
final override fun onCreateViewHolder( final override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
): BaseViewHolder<ReaderPage, Unit> { ): BaseViewHolder<ReaderPage, Unit, *> {
return onCreateViewHolder(parent).also(this::onViewHolderCreated) return onCreateViewHolder(parent).also(this::onViewHolderCreated)
} }
protected open fun onViewHolderCreated(holder: BaseViewHolder<ReaderPage, Unit>) = Unit protected open fun onViewHolderCreated(holder: BaseViewHolder<ReaderPage, Unit, *>) = Unit
protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<ReaderPage, Unit> protected abstract fun onCreateViewHolder(parent: ViewGroup): BaseViewHolder<ReaderPage, Unit, *>
} }

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.reader.ui.base package org.koitharu.kotatsu.reader.ui.base
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.parcelize.Parcelize
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource

@ -3,7 +3,6 @@ package org.koitharu.kotatsu.reader.ui.reversed
import android.graphics.PointF import android.graphics.PointF
import android.view.ViewGroup import android.view.ViewGroup
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page.*
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.ui.PageLoader
import org.koitharu.kotatsu.reader.ui.standard.PageHolder import org.koitharu.kotatsu.reader.ui.standard.PageHolder
@ -11,37 +10,39 @@ import org.koitharu.kotatsu.reader.ui.standard.PageHolder
class ReversedPageHolder(parent: ViewGroup, loader: PageLoader) : PageHolder(parent, loader) { class ReversedPageHolder(parent: ViewGroup, loader: PageLoader) : PageHolder(parent, loader) {
override fun onImageShowing(zoom: ZoomMode) { override fun onImageShowing(zoom: ZoomMode) {
ssiv.maxScale = 2f * maxOf( with(binding.ssiv) {
ssiv.width / ssiv.sWidth.toFloat(), maxScale = 2f * maxOf(
ssiv.height / ssiv.sHeight.toFloat() width / sWidth.toFloat(),
) height / sHeight.toFloat()
when (zoom) { )
ZoomMode.FIT_CENTER -> { when (zoom) {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE) ZoomMode.FIT_CENTER -> {
ssiv.resetScaleAndCenter() setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
} resetScaleAndCenter()
ZoomMode.FIT_HEIGHT -> { }
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM) ZoomMode.FIT_HEIGHT -> {
ssiv.minScale = ssiv.height / ssiv.sHeight.toFloat() setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.setScaleAndCenter( minScale = height / sHeight.toFloat()
ssiv.minScale, setScaleAndCenter(
PointF(ssiv.sWidth.toFloat(), ssiv.sHeight / 2f) minScale,
) PointF(sWidth.toFloat(), sHeight / 2f)
} )
ZoomMode.FIT_WIDTH -> { }
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM) ZoomMode.FIT_WIDTH -> {
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat() setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.setScaleAndCenter( minScale = width / sWidth.toFloat()
ssiv.minScale, setScaleAndCenter(
PointF(ssiv.sWidth / 2f, 0f) minScale,
) PointF(sWidth / 2f, 0f)
} )
ZoomMode.KEEP_START -> { }
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE) ZoomMode.KEEP_START -> {
ssiv.setScaleAndCenter( setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
ssiv.maxScale, setScaleAndCenter(
PointF(ssiv.sWidth.toFloat(), 0f) maxScale,
) PointF(sWidth.toFloat(), 0f)
)
}
} }
} }
} }

@ -13,7 +13,7 @@ class ReversedPagesAdapter(
override fun onCreateViewHolder(parent: ViewGroup) = ReversedPageHolder(parent, loader) override fun onCreateViewHolder(parent: ViewGroup) = ReversedPageHolder(parent, loader)
override fun onBindViewHolder(holder: BaseViewHolder<ReaderPage, Unit>, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder<ReaderPage, Unit, *>, position: Int) {
super.onBindViewHolder(holder, reversed(position)) super.onBindViewHolder(holder, reversed(position))
} }

@ -3,11 +3,12 @@ package org.koitharu.kotatsu.reader.ui.reversed
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import kotlinx.android.synthetic.main.fragment_reader_standard.* import android.view.ViewGroup
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.base.AbstractReader import org.koitharu.kotatsu.reader.ui.base.AbstractReader
import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter
@ -18,23 +19,30 @@ import org.koitharu.kotatsu.utils.ext.doOnPageChanged
import org.koitharu.kotatsu.utils.ext.swapAdapter import org.koitharu.kotatsu.utils.ext.swapAdapter
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class ReversedReaderFragment : AbstractReader(R.layout.fragment_reader_standard), class ReversedReaderFragment : AbstractReader<FragmentReaderStandardBinding>(),
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
private var paginationListener: PagerPaginationListener? = null private var paginationListener: PagerPaginationListener? = null
private val settings by inject<AppSettings>() private val settings by inject<AppSettings>()
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentReaderStandardBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
paginationListener = PagerPaginationListener(adapter!!, 2, this) paginationListener = PagerPaginationListener(readerAdapter!!, 2, this)
pager.adapter = adapter with(binding.pager) {
if (settings.readerAnimation) { adapter = readerAdapter
pager.setPageTransformer(ReversedPageAnimTransformer()) if (settings.readerAnimation) {
} setPageTransformer(ReversedPageAnimTransformer())
pager.offscreenPageLimit = 2 }
pager.registerOnPageChangeCallback(paginationListener!!) offscreenPageLimit = 2
pager.doOnPageChanged { registerOnPageChangeCallback(paginationListener!!)
notifyPageChanged(reversed(it)) doOnPageChanged {
notifyPageChanged(reversed(it))
}
} }
} }
@ -59,13 +67,13 @@ class ReversedReaderFragment : AbstractReader(R.layout.fragment_reader_standard)
override fun recreateAdapter() { override fun recreateAdapter() {
super.recreateAdapter() super.recreateAdapter()
pager.swapAdapter(adapter) binding.pager.swapAdapter(readerAdapter)
} }
override fun getCurrentItem() = reversed(pager.currentItem) override fun getCurrentItem() = reversed(binding.pager.currentItem)
override fun setCurrentItem(position: Int, isSmooth: Boolean) { override fun setCurrentItem(position: Int, isSmooth: Boolean) {
pager.setCurrentItem(reversed(position), isSmooth) binding.pager.setCurrentItem(reversed(position), isSmooth)
} }
override fun getCurrentPageScroll() = 0 override fun getCurrentPageScroll() = 0
@ -76,9 +84,9 @@ class ReversedReaderFragment : AbstractReader(R.layout.fragment_reader_standard)
when (key) { when (key) {
AppSettings.KEY_READER_ANIMATION -> { AppSettings.KEY_READER_ANIMATION -> {
if (settings.readerAnimation) { if (settings.readerAnimation) {
pager.setPageTransformer(PageAnimTransformer()) binding.pager.setPageTransformer(PageAnimTransformer())
} else { } else {
pager.setPageTransformer(null) binding.pager.setPageTransformer(null)
} }
} }
} }

@ -2,29 +2,31 @@ package org.koitharu.kotatsu.reader.ui.standard
import android.graphics.PointF import android.graphics.PointF
import android.net.Uri import android.net.Uri
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.ui.PageLoader
import org.koitharu.kotatsu.reader.ui.base.PageHolderDelegate import org.koitharu.kotatsu.reader.ui.base.PageHolderDelegate
import org.koitharu.kotatsu.reader.ui.base.ReaderPage import org.koitharu.kotatsu.reader.ui.base.ReaderPage
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
open class PageHolder(parent: ViewGroup, loader: PageLoader) : open class PageHolder(parent: ViewGroup, loader: PageLoader) :
BaseViewHolder<ReaderPage, Unit>(parent, R.layout.item_page), BaseViewHolder<ReaderPage, Unit, ItemPageBinding>(
PageHolderDelegate.Callback, View.OnClickListener { ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
), PageHolderDelegate.Callback, View.OnClickListener {
private val delegate = PageHolderDelegate(loader, this) private val delegate = PageHolderDelegate(loader, this)
init { init {
ssiv.setOnImageEventListener(delegate) binding.ssiv.setOnImageEventListener(delegate)
button_retry.setOnClickListener(this) binding.buttonRetry.setOnClickListener(this)
} }
override fun onBind(data: ReaderPage, extra: Unit) { override fun onBind(data: ReaderPage, extra: Unit) {
@ -33,49 +35,49 @@ open class PageHolder(parent: ViewGroup, loader: PageLoader) :
override fun onRecycled() { override fun onRecycled() {
delegate.onRecycle() delegate.onRecycle()
ssiv.recycle() binding.ssiv.recycle()
} }
override fun onLoadingStarted() { override fun onLoadingStarted() {
layout_error.isVisible = false binding.layoutError.isVisible = false
progressBar.isVisible = true binding.progressBar.isVisible = true
ssiv.recycle() binding.ssiv.recycle()
} }
override fun onImageReady(uri: Uri) { override fun onImageReady(uri: Uri) {
ssiv.setImage(ImageSource.uri(uri)) binding.ssiv.setImage(ImageSource.uri(uri))
} }
override fun onImageShowing(zoom: ZoomMode) { override fun onImageShowing(zoom: ZoomMode) {
ssiv.maxScale = 2f * maxOf( binding.ssiv.maxScale = 2f * maxOf(
ssiv.width / ssiv.sWidth.toFloat(), binding.ssiv.width / binding.ssiv.sWidth.toFloat(),
ssiv.height / ssiv.sHeight.toFloat() binding.ssiv.height / binding.ssiv.sHeight.toFloat()
) )
when (zoom) { when (zoom) {
ZoomMode.FIT_CENTER -> { ZoomMode.FIT_CENTER -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE) binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
ssiv.resetScaleAndCenter() binding.ssiv.resetScaleAndCenter()
} }
ZoomMode.FIT_HEIGHT -> { ZoomMode.FIT_HEIGHT -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM) binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.height / ssiv.sHeight.toFloat() binding.ssiv.minScale = binding.ssiv.height / binding.ssiv.sHeight.toFloat()
ssiv.setScaleAndCenter( binding.ssiv.setScaleAndCenter(
ssiv.minScale, binding.ssiv.minScale,
PointF(0f, ssiv.sHeight / 2f) PointF(0f, binding.ssiv.sHeight / 2f)
) )
} }
ZoomMode.FIT_WIDTH -> { ZoomMode.FIT_WIDTH -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM) binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat() binding.ssiv.minScale = binding.ssiv.width / binding.ssiv.sWidth.toFloat()
ssiv.setScaleAndCenter( binding.ssiv.setScaleAndCenter(
ssiv.minScale, binding.ssiv.minScale,
PointF(ssiv.sWidth / 2f, 0f) PointF(binding.ssiv.sWidth / 2f, 0f)
) )
} }
ZoomMode.KEEP_START -> { ZoomMode.KEEP_START -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE) binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
ssiv.setScaleAndCenter( binding.ssiv.setScaleAndCenter(
ssiv.maxScale, binding.ssiv.maxScale,
PointF(0f, 0f) PointF(0f, 0f)
) )
} }
@ -83,7 +85,7 @@ open class PageHolder(parent: ViewGroup, loader: PageLoader) :
} }
override fun onImageShown() { override fun onImageShown() {
progressBar.isVisible = false binding.progressBar.isVisible = false
} }
override fun onClick(v: View) { override fun onClick(v: View) {
@ -93,8 +95,8 @@ open class PageHolder(parent: ViewGroup, loader: PageLoader) :
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
textView_error.text = e.getDisplayMessage(context.resources) binding.textViewError.text = e.getDisplayMessage(context.resources)
layout_error.isVisible = true binding.layoutError.isVisible = true
progressBar.isVisible = false binding.progressBar.isVisible = false
} }
} }

@ -3,11 +3,12 @@ package org.koitharu.kotatsu.reader.ui.standard
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import kotlinx.android.synthetic.main.fragment_reader_standard.* import android.view.ViewGroup
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.base.AbstractReader import org.koitharu.kotatsu.reader.ui.base.AbstractReader
import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter
@ -16,22 +17,29 @@ import org.koitharu.kotatsu.utils.ext.doOnPageChanged
import org.koitharu.kotatsu.utils.ext.swapAdapter import org.koitharu.kotatsu.utils.ext.swapAdapter
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard), class PagerReaderFragment : AbstractReader<FragmentReaderStandardBinding>(),
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
private var paginationListener: PagerPaginationListener? = null private var paginationListener: PagerPaginationListener? = null
private val settings by inject<AppSettings>() private val settings by inject<AppSettings>()
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentReaderStandardBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
paginationListener = PagerPaginationListener(adapter!!, 2, this) paginationListener = PagerPaginationListener(readerAdapter!!, 2, this)
pager.adapter = adapter with(binding.pager) {
if (settings.readerAnimation) { adapter = readerAdapter
pager.setPageTransformer(PageAnimTransformer()) if (settings.readerAnimation) {
setPageTransformer(PageAnimTransformer())
}
offscreenPageLimit = 2
registerOnPageChangeCallback(paginationListener!!)
doOnPageChanged(::notifyPageChanged)
} }
pager.offscreenPageLimit = 2
pager.registerOnPageChangeCallback(paginationListener!!)
pager.doOnPageChanged(::notifyPageChanged)
} }
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
@ -55,13 +63,13 @@ class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard),
override fun recreateAdapter() { override fun recreateAdapter() {
super.recreateAdapter() super.recreateAdapter()
pager.swapAdapter(adapter) binding.pager.swapAdapter(readerAdapter)
} }
override fun getCurrentItem() = pager.currentItem override fun getCurrentItem() = binding.pager.currentItem
override fun setCurrentItem(position: Int, isSmooth: Boolean) { override fun setCurrentItem(position: Int, isSmooth: Boolean) {
pager.setCurrentItem(position, isSmooth) binding.pager.setCurrentItem(position, isSmooth)
} }
override fun getCurrentPageScroll() = 0 override fun getCurrentPageScroll() = 0
@ -72,9 +80,9 @@ class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard),
when (key) { when (key) {
AppSettings.KEY_READER_ANIMATION -> { AppSettings.KEY_READER_ANIMATION -> {
if (settings.readerAnimation) { if (settings.readerAnimation) {
pager.setPageTransformer(PageAnimTransformer()) binding.pager.setPageTransformer(PageAnimTransformer())
} else { } else {
pager.setPageTransformer(null) binding.pager.setPageTransformer(null)
} }
} }
} }

@ -1,58 +0,0 @@
package org.koitharu.kotatsu.reader.ui.thumbnails
import android.view.ViewGroup
import androidx.core.net.toUri
import coil.ImageLoader
import coil.request.ImageRequest
import coil.size.PixelSize
import coil.size.Size
import kotlinx.android.synthetic.main.item_page_thumb.*
import kotlinx.coroutines.*
import org.koin.core.component.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.utils.ext.IgnoreErrors
class PageThumbnailHolder(parent: ViewGroup, private val scope: CoroutineScope) :
BaseViewHolder<MangaPage, PagesCache>(parent, R.layout.item_page_thumb) {
private var job: Job? = null
private val thumbSize: Size
private val coil by inject<ImageLoader>()
init {
val width = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width)
thumbSize = PixelSize(
width = width,
height = (width * 13f / 18f).toInt()
)
}
override fun onBind(data: MangaPage, extra: PagesCache) {
imageView_thumb.setImageDrawable(null)
textView_number.text = (bindingAdapterPosition + 1).toString()
job?.cancel()
job = scope.launch(Dispatchers.IO + IgnoreErrors) {
val url = data.preview ?: data.url.let {
val pageUrl = data.source.repository.getPageFullUrl(data)
extra[pageUrl]?.toUri()?.toString() ?: pageUrl
}
val drawable = coil.execute(
ImageRequest.Builder(context)
.data(url)
.size(thumbSize)
.build()
).drawable
withContext(Dispatchers.Main.immediate) {
imageView_thumb.setImageDrawable(drawable)
}
}
}
override fun onRecycled() {
job?.cancel()
imageView_thumb.setImageDrawable(null)
}
}

@ -1,50 +1,57 @@
package org.koitharu.kotatsu.reader.ui.thumbnails package org.koitharu.kotatsu.reader.ui.thumbnails
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.android.synthetic.main.sheet_pages.*
import kotlinx.coroutines.DisposableHandle
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.databinding.SheetPagesBinding
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import org.koitharu.kotatsu.utils.UiUtils import org.koitharu.kotatsu.utils.UiUtils
import org.koitharu.kotatsu.utils.ext.resolveDp import org.koitharu.kotatsu.utils.ext.resolveDp
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class PagesThumbnailsSheet : BaseBottomSheet(R.layout.sheet_pages), class PagesThumbnailsSheet : BaseBottomSheet<SheetPagesBinding>(),
OnListItemClickListener<MangaPage> { OnListItemClickListener<MangaPage> {
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetPagesBinding {
return SheetPagesBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
recyclerView.addItemDecoration(SpacingItemDecoration(view.resources.resolveDp(8))) binding.recyclerView.addItemDecoration(SpacingItemDecoration(view.resources.resolveDp(8)))
val pages = arguments?.getParcelableArrayList<MangaPage>(ARG_PAGES) val pages = arguments?.getParcelableArrayList<MangaPage>(ARG_PAGES)
if (pages == null) { if (pages == null) {
dismissAllowingStateLoss() dismissAllowingStateLoss()
return return
} }
recyclerView.adapter = PageThumbnailAdapter(get(), viewLifecycleScope, get(), this).apply { binding.recyclerView.adapter =
items = pages PageThumbnailAdapter(get(), viewLifecycleScope, get(), this).apply {
} items = pages
}
val title = arguments?.getString(ARG_TITLE) val title = arguments?.getString(ARG_TITLE)
toolbar.title = title binding.toolbar.title = title
toolbar.setNavigationOnClickListener { dismiss() } binding.toolbar.setNavigationOnClickListener { dismiss() }
toolbar.subtitle = resources.getQuantityString(R.plurals.pages, pages.size, pages.size) binding.toolbar.subtitle =
textView_title.text = title resources.getQuantityString(R.plurals.pages, pages.size, pages.size)
binding.textViewTitle.text = title
if (dialog !is BottomSheetDialog) { if (dialog !is BottomSheetDialog) {
toolbar.isVisible = true binding.toolbar.isVisible = true
textView_title.isVisible = false binding.textViewTitle.isVisible = false
appbar.elevation = resources.getDimension(R.dimen.elevation_large) binding.appbar.elevation = resources.getDimension(R.dimen.elevation_large)
} }
recyclerView.addOnLayoutChangeListener(UiUtils.SpanCountResolver) binding.recyclerView.addOnLayoutChangeListener(UiUtils.SpanCountResolver)
} }
override fun onCreateDialog(savedInstanceState: Bundle?) = override fun onCreateDialog(savedInstanceState: Bundle?) =
@ -57,13 +64,13 @@ class PagesThumbnailsSheet : BaseBottomSheet(R.layout.sheet_pages),
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) { if (newState == BottomSheetBehavior.STATE_EXPANDED) {
toolbar.isVisible = true binding.toolbar.isVisible = true
textView_title.isVisible = false binding.textViewTitle.isVisible = false
appbar.elevation = elevation binding.appbar.elevation = elevation
} else { } else {
toolbar.isVisible = false binding.toolbar.isVisible = false
textView_title.isVisible = true binding.textViewTitle.isVisible = true
appbar.elevation = 0f binding.appbar.elevation = 0f
} }
} }
}) })
@ -71,8 +78,7 @@ class PagesThumbnailsSheet : BaseBottomSheet(R.layout.sheet_pages),
} }
override fun onDestroyView() { override fun onDestroyView() {
(recyclerView.adapter as? DisposableHandle)?.dispose() binding.recyclerView.adapter = null
recyclerView.adapter = null
super.onDestroyView() super.onDestroyView()
} }

@ -4,12 +4,12 @@ import androidx.core.net.toUri
import coil.ImageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.PixelSize import coil.size.PixelSize
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.android.synthetic.main.item_page_thumb.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.utils.ext.IgnoreErrors import org.koitharu.kotatsu.utils.ext.IgnoreErrors
@ -18,7 +18,9 @@ fun pageThumbnailAD(
scope: CoroutineScope, scope: CoroutineScope,
cache: PagesCache, cache: PagesCache,
clickListener: OnListItemClickListener<MangaPage> clickListener: OnListItemClickListener<MangaPage>
) = adapterDelegateLayoutContainer<MangaPage, MangaPage>(R.layout.item_page_thumb) { ) = adapterDelegateViewBinding<MangaPage, MangaPage, ItemPageThumbBinding>(
{ inflater, parent -> ItemPageThumbBinding.inflate(inflater, parent, false) }
) {
var job: Job? = null var job: Job? = null
val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width) val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width)
@ -27,14 +29,14 @@ fun pageThumbnailAD(
height = (gridWidth * 13f / 18f).toInt() height = (gridWidth * 13f / 18f).toInt()
) )
handle.setOnClickListener { binding.handle.setOnClickListener {
clickListener.onItemClick(item, itemView) clickListener.onItemClick(item, itemView)
} }
bind { bind {
job?.cancel() job?.cancel()
imageView_thumb.setImageDrawable(null) binding.imageViewThumb.setImageDrawable(null)
textView_number.text = (bindingAdapterPosition + 1).toString() binding.textViewNumber.text = (bindingAdapterPosition + 1).toString()
job = scope.launch(Dispatchers.Default + IgnoreErrors) { job = scope.launch(Dispatchers.Default + IgnoreErrors) {
val url = item.preview ?: item.url.let { val url = item.preview ?: item.url.let {
val pageUrl = item.source.repository.getPageFullUrl(item) val pageUrl = item.source.repository.getPageFullUrl(item)
@ -47,13 +49,13 @@ fun pageThumbnailAD(
.build() .build()
).drawable ).drawable
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
imageView_thumb.setImageDrawable(drawable) binding.imageViewThumb.setImageDrawable(drawable)
} }
} }
} }
onViewRecycled { onViewRecycled {
job?.cancel() job?.cancel()
imageView_thumb.setImageDrawable(null) binding.imageViewThumb.setImageDrawable(null)
} }
} }

@ -1,15 +1,16 @@
package org.koitharu.kotatsu.reader.ui.wetoon package org.koitharu.kotatsu.reader.ui.wetoon
import android.net.Uri import android.net.Uri
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page_webtoon.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.ui.PageLoader
import org.koitharu.kotatsu.reader.ui.base.PageHolderDelegate import org.koitharu.kotatsu.reader.ui.base.PageHolderDelegate
import org.koitharu.kotatsu.reader.ui.base.ReaderPage import org.koitharu.kotatsu.reader.ui.base.ReaderPage
@ -17,15 +18,16 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) : class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
BaseViewHolder<ReaderPage, Unit>(parent, R.layout.item_page_webtoon), BaseViewHolder<ReaderPage, Unit, ItemPageWebtoonBinding>(
PageHolderDelegate.Callback, View.OnClickListener { ItemPageWebtoonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
), PageHolderDelegate.Callback, View.OnClickListener {
private val delegate = PageHolderDelegate(loader, this) private val delegate = PageHolderDelegate(loader, this)
private var scrollToRestore = 0 private var scrollToRestore = 0
init { init {
ssiv.setOnImageEventListener(delegate) binding.ssiv.setOnImageEventListener(delegate)
button_retry.setOnClickListener(this) binding.buttonRetry.setOnClickListener(this)
} }
override fun onBind(data: ReaderPage, extra: Unit) { override fun onBind(data: ReaderPage, extra: Unit) {
@ -34,34 +36,36 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
override fun onRecycled() { override fun onRecycled() {
delegate.onRecycle() delegate.onRecycle()
ssiv.recycle() binding.ssiv.recycle()
} }
override fun onLoadingStarted() { override fun onLoadingStarted() {
layout_error.isVisible = false binding.layoutError.isVisible = false
progressBar.isVisible = true binding.progressBar.isVisible = true
ssiv.recycle() binding.ssiv.recycle()
} }
override fun onImageReady(uri: Uri) { override fun onImageReady(uri: Uri) {
ssiv.setImage(ImageSource.uri(uri)) binding.ssiv.setImage(ImageSource.uri(uri))
} }
override fun onImageShowing(zoom: ZoomMode) { override fun onImageShowing(zoom: ZoomMode) {
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat() with(binding.ssiv) {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM) maxScale = 2f * width / sWidth.toFloat()
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat() setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.scrollTo( minScale = width / sWidth.toFloat()
when { scrollTo(
scrollToRestore != 0 -> scrollToRestore when {
itemView.top < 0 -> ssiv.getScrollRange() scrollToRestore != 0 -> scrollToRestore
else -> 0 itemView.top < 0 -> getScrollRange()
} else -> 0
) }
)
}
} }
override fun onImageShown() { override fun onImageShown() {
progressBar.isVisible = false binding.progressBar.isVisible = false
} }
override fun onClick(v: View) { override fun onClick(v: View) {
@ -71,16 +75,16 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
textView_error.text = e.getDisplayMessage(context.resources) binding.textViewError.text = e.getDisplayMessage(context.resources)
layout_error.isVisible = true binding.layoutError.isVisible = true
progressBar.isVisible = false binding.progressBar.isVisible = false
} }
fun getScrollY() = ssiv.getScroll() fun getScrollY() = binding.ssiv.getScroll()
fun restoreScroll(scroll: Int) { fun restoreScroll(scroll: Int) {
if (ssiv.isReady) { if (binding.ssiv.isReady) {
ssiv.scrollTo(scroll) binding.ssiv.scrollTo(scroll)
} else { } else {
scrollToRestore = scroll scrollToRestore = scroll
} }

@ -1,10 +1,11 @@
package org.koitharu.kotatsu.reader.ui.wetoon package org.koitharu.kotatsu.reader.ui.wetoon
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import kotlinx.android.synthetic.main.fragment_reader_webtoon.* import org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.base.AbstractReader import org.koitharu.kotatsu.reader.ui.base.AbstractReader
import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter import org.koitharu.kotatsu.reader.ui.base.BaseReaderAdapter
@ -14,18 +15,25 @@ import org.koitharu.kotatsu.utils.ext.findCenterViewPosition
import org.koitharu.kotatsu.utils.ext.firstItem import org.koitharu.kotatsu.utils.ext.firstItem
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) { class WebtoonReaderFragment : AbstractReader<FragmentReaderWebtoonBinding>() {
private val scrollInterpolator = AccelerateDecelerateInterpolator() private val scrollInterpolator = AccelerateDecelerateInterpolator()
private var paginationListener: ListPaginationListener? = null private var paginationListener: ListPaginationListener? = null
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentReaderWebtoonBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
paginationListener = ListPaginationListener(2, this) paginationListener = ListPaginationListener(2, this)
recyclerView.setHasFixedSize(true) with(binding.recyclerView) {
recyclerView.adapter = adapter setHasFixedSize(true)
recyclerView.addOnScrollListener(paginationListener!!) adapter = readerAdapter
recyclerView.doOnCurrentItemChanged(::notifyPageChanged) addOnScrollListener(paginationListener!!)
doOnCurrentItemChanged(::notifyPageChanged)
}
} }
override fun onCreateAdapter(dataSet: List<ReaderPage>): BaseReaderAdapter { override fun onCreateAdapter(dataSet: List<ReaderPage>): BaseReaderAdapter {
@ -34,7 +42,7 @@ class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
override fun recreateAdapter() { override fun recreateAdapter() {
super.recreateAdapter() super.recreateAdapter()
recyclerView.swapAdapter(adapter, true) binding.recyclerView.swapAdapter(readerAdapter, true)
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -43,33 +51,33 @@ class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
} }
override fun getCurrentItem(): Int { override fun getCurrentItem(): Int {
return recyclerView.findCenterViewPosition() return binding.recyclerView.findCenterViewPosition()
} }
override fun setCurrentItem(position: Int, isSmooth: Boolean) { override fun setCurrentItem(position: Int, isSmooth: Boolean) {
if (isSmooth) { if (isSmooth) {
recyclerView.smoothScrollToPosition(position) binding.recyclerView.smoothScrollToPosition(position)
} else { } else {
recyclerView.firstItem = position binding.recyclerView.firstItem = position
} }
} }
override fun switchPageBy(delta: Int) { override fun switchPageBy(delta: Int) {
recyclerView.smoothScrollBy( binding.recyclerView.smoothScrollBy(
0, 0,
(recyclerView.height * 0.9).toInt() * delta, (binding.recyclerView.height * 0.9).toInt() * delta,
scrollInterpolator scrollInterpolator
) )
} }
override fun getCurrentPageScroll(): Int { override fun getCurrentPageScroll(): Int {
return (recyclerView.findViewHolderForAdapterPosition(getCurrentItem()) as? WebtoonHolder) return (binding.recyclerView.findViewHolderForAdapterPosition(getCurrentItem()) as? WebtoonHolder)
?.getScrollY() ?: 0 ?.getScrollY() ?: 0
} }
override fun restorePageScroll(position: Int, scroll: Int) { override fun restorePageScroll(position: Int, scroll: Int) {
recyclerView.post { binding.recyclerView.post {
val holder = recyclerView.findViewHolderForAdapterPosition(position) ?: return@post val holder = binding.recyclerView.findViewHolderForAdapterPosition(position) ?: return@post
(holder as WebtoonHolder).restoreScroll(scroll) (holder as WebtoonHolder).restoreScroll(scroll)
} }
} }

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.remotelist package org.koitharu.kotatsu.remotelist
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.remotelist.ui
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaFilter import org.koitharu.kotatsu.core.model.MangaFilter

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.search package org.koitharu.kotatsu.search
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource

@ -3,7 +3,7 @@ package org.koitharu.kotatsu.search.ui
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource

@ -5,40 +5,42 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import kotlinx.android.synthetic.main.activity_search.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.ActivitySearchBinding
import org.koitharu.kotatsu.utils.ext.showKeyboard import org.koitharu.kotatsu.utils.ext.showKeyboard
class SearchActivity : BaseActivity(), SearchView.OnQueryTextListener { class SearchActivity : BaseActivity<ActivitySearchBinding>(), SearchView.OnQueryTextListener {
private lateinit var source: MangaSource private lateinit var source: MangaSource
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search) setContentView(ActivitySearchBinding.inflate(layoutInflater))
source = intent.getParcelableExtra(EXTRA_SOURCE) ?: run { source = intent.getParcelableExtra(EXTRA_SOURCE) ?: run {
finishAfterTransition() finishAfterTransition()
return return
} }
val query = intent.getStringExtra(EXTRA_QUERY) val query = intent.getStringExtra(EXTRA_QUERY)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
searchView.queryHint = getString(R.string.search_on_s, source.title) with(binding.searchView) {
searchView.suggestionsAdapter = MangaSuggestionsProvider.getSuggestionAdapter(this) queryHint = getString(R.string.search_on_s, source.title)
searchView.setOnSuggestionListener(SearchHelper.SuggestionListener(searchView)) suggestionsAdapter = MangaSuggestionsProvider.getSuggestionAdapter(this@SearchActivity)
searchView.setOnQueryTextListener(this) setOnSuggestionListener(SearchHelper.SuggestionListener(this))
setOnQueryTextListener(this@SearchActivity)
if (query.isNullOrBlank()) { if (query.isNullOrBlank()) {
searchView.requestFocus() requestFocus()
searchView.showKeyboard() showKeyboard()
} else { } else {
searchView.setQuery(query, true) setQuery(query, true)
}
} }
} }
override fun onDestroy() { override fun onDestroy() {
searchView.suggestionsAdapter?.changeCursor(null) //close cursor binding.searchView.suggestionsAdapter.changeCursor(null) //close cursor
super.onDestroy() super.onDestroy()
} }
@ -49,7 +51,7 @@ class SearchActivity : BaseActivity(), SearchView.OnQueryTextListener {
.beginTransaction() .beginTransaction()
.replace(R.id.container, SearchFragment.newInstance(source, query)) .replace(R.id.container, SearchFragment.newInstance(source, query))
.commit() .commit()
searchView.clearFocus() binding.searchView.clearFocus()
MangaSuggestionsProvider.saveQueryAsync(applicationContext, query) MangaSuggestionsProvider.saveQueryAsync(applicationContext, query)
true true
} else false } else false

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.search.ui package org.koitharu.kotatsu.search.ui
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment

@ -12,7 +12,6 @@ import java.io.Closeable
object SearchHelper { object SearchHelper {
@JvmStatic
fun setupSearchView(menuItem: MenuItem): Closeable? { fun setupSearchView(menuItem: MenuItem): Closeable? {
val view = menuItem.actionView as? SearchView ?: return null val view = menuItem.actionView as? SearchView ?: return null
val context = view.context val context = view.context

@ -5,12 +5,13 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivitySearchGlobalBinding
class GlobalSearchActivity : BaseActivity() { class GlobalSearchActivity : BaseActivity<ActivitySearchGlobalBinding>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search_global) setContentView(ActivitySearchGlobalBinding.inflate(layoutInflater))
val query = intent.getStringExtra(EXTRA_QUERY) val query = intent.getStringExtra(EXTRA_QUERY)
if (query == null) { if (query == null) {

@ -1,6 +1,6 @@
package org.koitharu.kotatsu.search.ui.global package org.koitharu.kotatsu.search.ui.global
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.utils.ext.stringArgument import org.koitharu.kotatsu.utils.ext.stringArgument

@ -10,13 +10,14 @@ import androidx.preference.PreferenceFragmentCompat
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.ActivitySettingsBinding
class SettingsActivity : BaseActivity(), class SettingsActivity : BaseActivity<ActivitySettingsBinding>(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings) setContentView(ActivitySettingsBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (supportFragmentManager.findFragmentById(R.id.container) == null) { if (supportFragmentManager.findFragmentById(R.id.container) == null) {

@ -2,7 +2,7 @@ package org.koitharu.kotatsu.settings
import android.net.Uri import android.net.Uri
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.backup.BackupRepository import org.koitharu.kotatsu.core.backup.BackupRepository
import org.koitharu.kotatsu.core.backup.RestoreRepository import org.koitharu.kotatsu.core.backup.RestoreRepository

@ -1,26 +1,33 @@
package org.koitharu.kotatsu.settings.backup package org.koitharu.kotatsu.settings.backup
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.dialog_progress.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.databinding.DialogProgressBinding
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.progress.Progress import org.koitharu.kotatsu.utils.progress.Progress
import java.io.File import java.io.File
class BackupDialogFragment : AlertDialogFragment(R.layout.dialog_progress) { class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
private val viewModel by viewModel<BackupViewModel>() private val viewModel by viewModel<BackupViewModel>()
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = DialogProgressBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
textView_title.setText(R.string.create_backup) binding.textViewTitle.setText(R.string.create_backup)
textView_subtitle.setText(R.string.processing_) binding.textViewSubtitle.setText(R.string.processing_)
viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged) viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged)
viewModel.onBackupDone.observe(viewLifecycleOwner, this::onBackupDone) viewModel.onBackupDone.observe(viewLifecycleOwner, this::onBackupDone)
@ -42,7 +49,7 @@ class BackupDialogFragment : AlertDialogFragment(R.layout.dialog_progress) {
} }
private fun onProgressChanged(progress: Progress?) { private fun onProgressChanged(progress: Progress?) {
with(progressBar) { with(binding.progressBar) {
isVisible = true isVisible = true
isIndeterminate = progress == null isIndeterminate = progress == null
if (progress != null) { if (progress != null) {

@ -7,7 +7,6 @@ import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.Preference import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_list.*
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
@ -39,7 +38,7 @@ class BackupSettingsFragment : BasePreferenceFragment(R.string.backup_restore),
e.printStackTrace() e.printStackTrace()
} }
Snackbar.make( Snackbar.make(
recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT
).show() ).show()
} }
true true

@ -2,21 +2,28 @@ package org.koitharu.kotatsu.settings.backup
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.dialog_progress.* import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.android.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.backup.CompositeResult import org.koitharu.kotatsu.core.backup.CompositeResult
import org.koitharu.kotatsu.databinding.DialogProgressBinding
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.toUriOrNull import org.koitharu.kotatsu.utils.ext.toUriOrNull
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
import org.koitharu.kotatsu.utils.progress.Progress import org.koitharu.kotatsu.utils.progress.Progress
class RestoreDialogFragment : AlertDialogFragment(R.layout.dialog_progress) { class RestoreDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = DialogProgressBinding.inflate(inflater, container, false)
private val viewModel by viewModel<RestoreViewModel> { private val viewModel by viewModel<RestoreViewModel> {
parametersOf(arguments?.getString(ARG_FILE)?.toUriOrNull()) parametersOf(arguments?.getString(ARG_FILE)?.toUriOrNull())
@ -24,8 +31,8 @@ class RestoreDialogFragment : AlertDialogFragment(R.layout.dialog_progress) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
textView_title.setText(R.string.restore_backup) binding.textViewTitle.setText(R.string.restore_backup)
textView_subtitle.setText(R.string.preparing_) binding.textViewSubtitle.setText(R.string.preparing_)
viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged) viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged)
viewModel.onRestoreDone.observe(viewLifecycleOwner, this::onRestoreDone) viewModel.onRestoreDone.observe(viewLifecycleOwner, this::onRestoreDone)
@ -46,7 +53,7 @@ class RestoreDialogFragment : AlertDialogFragment(R.layout.dialog_progress) {
} }
private fun onProgressChanged(progress: Progress?) { private fun onProgressChanged(progress: Progress?) {
with(progressBar) { with(binding.progressBar) {
isVisible = true isVisible = true
isIndeterminate = progress == null isIndeterminate = progress == null
if (progress != null) { if (progress != null) {

@ -1,16 +1,18 @@
package org.koitharu.kotatsu.settings.sources package org.koitharu.kotatsu.settings.sources
import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_source_config.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.BaseViewHolder import org.koitharu.kotatsu.base.ui.list.BaseViewHolder
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding
class SourceViewHolder(parent: ViewGroup) : class SourceViewHolder(parent: ViewGroup) :
BaseViewHolder<MangaSource, Boolean>(parent, R.layout.item_source_config) { BaseViewHolder<MangaSource, Boolean, ItemSourceConfigBinding>(
ItemSourceConfigBinding.inflate(LayoutInflater.from(parent.context), parent, false)
) {
override fun onBind(data: MangaSource, extra: Boolean) { override fun onBind(data: MangaSource, extra: Boolean) {
textView_title.text = data.title binding.textViewTitle.text = data.title
imageView_hidden.isChecked = extra binding.imageViewHidden.isChecked = extra
} }
} }

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_source_config.*
import org.koitharu.kotatsu.base.domain.MangaProviderFactory import org.koitharu.kotatsu.base.domain.MangaProviderFactory
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
@ -38,7 +37,7 @@ class SourcesAdapter(
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun onViewHolderCreated(holder: SourceViewHolder) { private fun onViewHolderCreated(holder: SourceViewHolder) {
holder.imageView_hidden.setOnCheckedChangeListener { holder.binding.imageViewHidden.setOnCheckedChangeListener {
if (it) { if (it) {
hiddenItems.remove(holder.requireData()) hiddenItems.remove(holder.requireData())
} else { } else {
@ -46,10 +45,10 @@ class SourcesAdapter(
} }
settings.hiddenSources = hiddenItems.mapToSet { x -> x.name } settings.hiddenSources = hiddenItems.mapToSet { x -> x.name }
} }
holder.imageView_config.setOnClickListener { v -> holder.binding.imageViewConfig.setOnClickListener { v ->
onItemClickListener.onItemClick(holder.requireData(), v) onItemClickListener.onItemClick(holder.requireData(), v)
} }
holder.imageView_handle.setOnTouchListener { v, event -> holder.binding.imageViewHandle.setOnTouchListener { v, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) { if (event.actionMasked == MotionEvent.ACTION_DOWN) {
onItemClickListener.onItemLongClick( onItemClickListener.onItemLongClick(
holder.requireData(), holder.requireData(),

@ -1,19 +1,21 @@
package org.koitharu.kotatsu.settings.sources package org.koitharu.kotatsu.settings.sources
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_settings_sources.*
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.SettingsActivity
class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources), class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
OnListItemClickListener<MangaSource> { OnListItemClickListener<MangaSource> {
private lateinit var reorderHelper: ItemTouchHelper private lateinit var reorderHelper: ItemTouchHelper
@ -23,6 +25,11 @@ class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources)
reorderHelper = ItemTouchHelper(SourcesReorderCallback()) reorderHelper = ItemTouchHelper(SourcesReorderCallback())
} }
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentSettingsSourcesBinding.inflate(inflater, container, false)
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
activity?.setTitle(R.string.remote_sources) activity?.setTitle(R.string.remote_sources)
@ -30,9 +37,11 @@ class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
recyclerView.addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL)) with(binding.recyclerView) {
recyclerView.adapter = SourcesAdapter(get(), this) addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL))
reorderHelper.attachToRecyclerView(recyclerView) adapter = SourcesAdapter(get(), this@SourcesSettingsFragment)
reorderHelper.attachToRecyclerView(this)
}
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -46,7 +55,7 @@ class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources)
override fun onItemLongClick(item: MangaSource, view: View): Boolean { override fun onItemLongClick(item: MangaSource, view: View): Boolean {
reorderHelper.startDrag( reorderHelper.startDrag(
recyclerView.findContainingViewHolder(view) ?: return false binding.recyclerView.findContainingViewHolder(view) ?: return false
) )
return true return true
} }

@ -1,7 +1,7 @@
package org.koitharu.kotatsu.tracker package org.koitharu.kotatsu.tracker
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.ui.FeedViewModel import org.koitharu.kotatsu.tracker.ui.FeedViewModel

@ -1,32 +1,29 @@
package org.koitharu.kotatsu.tracker.ui package org.koitharu.kotatsu.tracker.ui
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.*
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_tracklogs.*
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.databinding.FragmentFeedBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.hasItems import org.koitharu.kotatsu.utils.ext.hasItems
class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScrollListener.Callback, class FeedFragment : BaseFragment<FragmentFeedBinding>(), PaginationScrollListener.Callback,
OnListItemClickListener<Manga> { OnListItemClickListener<Manga> {
private val viewModel by viewModel<FeedViewModel>() private val viewModel by viewModel<FeedViewModel>()
private var adapter: FeedAdapter? = null private var feedAdapter: FeedAdapter? = null
override fun getTitle() = context?.getString(R.string.updates) override fun getTitle() = context?.getString(R.string.updates)
@ -35,15 +32,22 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScroll
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentFeedBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
adapter = FeedAdapter(get(), this) feedAdapter = FeedAdapter(get(), this)
recyclerView.adapter = adapter with(binding.recyclerView) {
recyclerView.addItemDecoration( adapter = feedAdapter
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing)) addItemDecoration(
) SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing))
recyclerView.setHasFixedSize(true) )
recyclerView.addOnScrollListener(PaginationScrollListener(4, this)) setHasFixedSize(true)
addOnScrollListener(PaginationScrollListener(4, this@FeedFragment))
}
viewModel.content.observe(viewLifecycleOwner, this::onListChanged) viewModel.content.observe(viewLifecycleOwner, this::onListChanged)
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
@ -59,47 +63,56 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScroll
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_update -> { R.id.action_update -> {
TrackWorker.startNow(requireContext()) TrackWorker.startNow(requireContext())
Snackbar.make(recyclerView, R.string.feed_will_update_soon, Snackbar.LENGTH_LONG).show() Snackbar.make(
binding.recyclerView,
R.string.feed_will_update_soon,
Snackbar.LENGTH_LONG
).show()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onDestroyView() { override fun onDestroyView() {
adapter = null feedAdapter = null
super.onDestroyView() super.onDestroyView()
} }
private fun onListChanged(list: List<Any>) { private fun onListChanged(list: List<Any>) {
adapter?.items = list feedAdapter?.items = list
} }
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
if (recyclerView.hasItems) { if (binding.recyclerView.hasItems) {
Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT) Snackbar.make(
.show() binding.recyclerView,
e.getDisplayMessage(resources),
Snackbar.LENGTH_SHORT
).show()
} else { } else {
textView_holder.text = e.getDisplayMessage(resources) with(binding.textViewHolder) {
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds( text = e.getDisplayMessage(resources)
0, setCompoundDrawablesRelativeWithIntrinsicBounds(
R.drawable.ic_error_large, 0,
0, R.drawable.ic_error_large,
0 0,
) 0
layout_holder.isVisible = true )
isVisible = true
}
} }
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
val hasItems = recyclerView.hasItems val hasItems = binding.recyclerView.hasItems
progressBar.isVisible = isLoading && !hasItems binding.progressBar.isVisible = isLoading && !hasItems
} }
private fun onEmptyStateChanged(isEmpty: Boolean) { private fun onEmptyStateChanged(isEmpty: Boolean) {
if (isEmpty) { if (isEmpty) {
setUpEmptyListHolder() setUpEmptyListHolder()
} }
layout_holder.isVisible = isEmpty binding.layoutHolder.isVisible = isEmpty
} }
override fun onScrolledToEnd() { override fun onScrolledToEnd() {
@ -111,8 +124,10 @@ class FeedFragment : BaseFragment(R.layout.fragment_tracklogs), PaginationScroll
} }
private fun setUpEmptyListHolder() { private fun setUpEmptyListHolder() {
textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null) with(binding.textViewHolder) {
textView_holder.setText(R.string.text_feed_holder) setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null)
setText(R.string.text_feed_holder)
}
} }
companion object { companion object {

@ -2,11 +2,11 @@ package org.koitharu.kotatsu.tracker.ui.adapter
import coil.ImageLoader import coil.ImageLoader
import coil.request.Disposable import coil.request.Disposable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.android.synthetic.main.item_tracklog.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.databinding.ItemTracklogBinding
import org.koitharu.kotatsu.tracker.ui.model.FeedItem import org.koitharu.kotatsu.tracker.ui.model.FeedItem
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.newImageRequest
@ -14,7 +14,9 @@ import org.koitharu.kotatsu.utils.ext.newImageRequest
fun feedItemAD( fun feedItemAD(
coil: ImageLoader, coil: ImageLoader,
clickListener: OnListItemClickListener<Manga> clickListener: OnListItemClickListener<Manga>
) = adapterDelegateLayoutContainer<FeedItem, Any>(R.layout.item_tracklog) { ) = adapterDelegateViewBinding<FeedItem, Any, ItemTracklogBinding>(
{ inflater, parent -> ItemTracklogBinding.inflate(inflater, parent, false) }
) {
var imageRequest: Disposable? = null var imageRequest: Disposable? = null
@ -24,18 +26,18 @@ fun feedItemAD(
bind { bind {
imageRequest?.dispose() imageRequest?.dispose()
imageRequest = imageView_cover.newImageRequest(item.imageUrl) imageRequest = binding.imageViewCover.newImageRequest(item.imageUrl)
.placeholder(R.drawable.ic_placeholder) .placeholder(R.drawable.ic_placeholder)
.fallback(R.drawable.ic_placeholder) .fallback(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder) .error(R.drawable.ic_placeholder)
.enqueueWith(coil) .enqueueWith(coil)
textView_title.text = item.title binding.textViewTitle.text = item.title
textView_subtitle.text = item.subtitle binding.textViewSubtitle.text = item.subtitle
textView_chapters.text = item.chapters binding.textViewChapters.text = item.chapters
} }
onViewRecycled { onViewRecycled {
imageRequest?.dispose() imageRequest?.dispose()
imageView_cover.setImageDrawable(null) binding.imageViewCover.setImageDrawable(null)
} }
} }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save