Linting fixes

This commit is contained in:
arkon
2020-04-25 14:24:45 -04:00
parent 4da760d614
commit 3f63b320c4
272 changed files with 4167 additions and 3602 deletions

View File

@@ -59,17 +59,19 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(when (preferences.themeMode().get()) {
Values.THEME_MODE_SYSTEM -> {
if (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) {
darkTheme
} else {
lightTheme
setTheme(
when (preferences.themeMode().get()) {
Values.THEME_MODE_SYSTEM -> {
if (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) {
darkTheme
} else {
lightTheme
}
}
Values.THEME_MODE_DARK -> darkTheme
else -> lightTheme
}
Values.THEME_MODE_DARK -> darkTheme
else -> lightTheme
})
)
super.onCreate(savedInstanceState)

View File

@@ -15,8 +15,9 @@ import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.clearFindViewByIdCache
import timber.log.Timber
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) : RestoreViewOnCreateController(bundle),
LayoutContainer {
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
RestoreViewOnCreateController(bundle),
LayoutContainer {
lateinit var binding: VB

View File

@@ -30,6 +30,6 @@ fun Controller.requestPermissionsSafe(permissions: Array<String>, requestCode: I
fun Controller.withFadeTransaction(): RouterTransaction {
return RouterTransaction.with(this)
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
}

View File

@@ -87,10 +87,12 @@ abstract class DialogController : RestoreViewOnCreateController {
*/
fun showDialog(router: Router, tag: String?) {
dismissed = false
router.pushController(RouterTransaction.with(this)
router.pushController(
RouterTransaction.with(this)
.pushChangeHandler(SimpleSwapChangeHandler(false))
.popChangeHandler(SimpleSwapChangeHandler(false))
.tag(tag))
.tag(tag)
)
}
/**

View File

@@ -44,12 +44,10 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
}
fun <T> Observable<T>.subscribeUntilDetach(): Subscription {
return subscribe().also { untilDetachSubscriptions.add(it) }
}
fun <T> Observable<T>.subscribeUntilDetach(onNext: (T) -> Unit): Subscription {
return subscribe(onNext).also { untilDetachSubscriptions.add(it) }
}
@@ -57,7 +55,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
onNext: (T) -> Unit,
onError: (Throwable) -> Unit
): Subscription {
return subscribe(onNext, onError).also { untilDetachSubscriptions.add(it) }
}
@@ -66,17 +63,14 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
onError: (Throwable) -> Unit,
onCompleted: () -> Unit
): Subscription {
return subscribe(onNext, onError, onCompleted).also { untilDetachSubscriptions.add(it) }
}
fun <T> Observable<T>.subscribeUntilDestroy(): Subscription {
return subscribe().also { untilDestroySubscriptions.add(it) }
}
fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription {
return subscribe(onNext).also { untilDestroySubscriptions.add(it) }
}
@@ -84,7 +78,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
onNext: (T) -> Unit,
onError: (Throwable) -> Unit
): Subscription {
return subscribe(onNext, onError).also { untilDestroySubscriptions.add(it) }
}
@@ -93,7 +86,6 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
onError: (Throwable) -> Unit,
onCompleted: () -> Unit
): Subscription {
return subscribe(onNext, onError, onCompleted).also { untilDestroySubscriptions.add(it) }
}
}

View File

@@ -59,11 +59,11 @@ open class BasePresenter<V> : RxPresenter<V>() {
override fun call(observable: Observable<T>): Observable<Delivery<View, T>> {
return observable
.materialize()
.filter { notification -> !notification.isOnCompleted }
.flatMap { notification ->
view.take(1).filter { it != null }.map { Delivery(it, notification) }
}
.materialize()
.filter { notification -> !notification.isOnCompleted }
.flatMap { notification ->
view.take(1).filter { it != null }.map { Delivery(it, notification) }
}
}
}
}

View File

@@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
* @param controller The containing controller.
*/
class CategoryAdapter(controller: CategoryController) :
FlexibleAdapter<CategoryItem>(null, controller, true) {
FlexibleAdapter<CategoryItem>(null, controller, true) {
/**
* Listener called when an item of the list is released.

View File

@@ -25,14 +25,15 @@ import reactivecircus.flowbinding.android.view.clicks
/**
* Controller to manage the categories for the users' library.
*/
class CategoryController : NucleusController<CategoriesControllerBinding, CategoryPresenter>(),
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
CategoryAdapter.OnItemReleaseListener,
CategoryCreateDialog.Listener,
CategoryRenameDialog.Listener,
UndoHelper.OnActionListener {
class CategoryController :
NucleusController<CategoriesControllerBinding, CategoryPresenter>(),
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
CategoryAdapter.OnItemReleaseListener,
CategoryCreateDialog.Listener,
CategoryRenameDialog.Listener,
UndoHelper.OnActionListener {
/**
* Object used to show ActionMode toolbar.
@@ -176,8 +177,10 @@ class CategoryController : NucleusController<CategoriesControllerBinding, Catego
when (item.itemId) {
R.id.action_delete -> {
undoHelper = UndoHelper(adapter, this)
undoHelper?.start(adapter.selectedPositions, view!!,
R.string.snack_categories_deleted, R.string.action_undo, 3000)
undoHelper?.start(
adapter.selectedPositions, view!!,
R.string.snack_categories_deleted, R.string.action_undo, 3000
)
mode.finish()
}

View File

@@ -31,17 +31,17 @@ class CategoryCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
*/
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_add_category)
.negativeButton(android.R.string.cancel)
.input(
hint = resources?.getString(R.string.name),
prefill = currentName
) { _, input ->
currentName = input.toString()
}
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.createCategory(currentName)
}
.title(R.string.action_add_category)
.negativeButton(android.R.string.cancel)
.input(
hint = resources?.getString(R.string.name),
prefill = currentName
) { _, input ->
currentName = input.toString()
}
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.createCategory(currentName)
}
}
interface Listener {

View File

@@ -49,7 +49,6 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
position: Int,
payloads: List<Any?>?
) {
holder.bind(category)
}

View File

@@ -30,10 +30,10 @@ class CategoryPresenter(
super.onCreate(savedState)
db.getCategories().asRxObservable()
.doOnNext { categories = it }
.map { it.map(::CategoryItem) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(CategoryController::setCategories)
.doOnNext { categories = it }
.map { it.map(::CategoryItem) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(CategoryController::setCategories)
}
/**

View File

@@ -36,15 +36,15 @@ class CategoryRenameDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
*/
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_rename_category)
.negativeButton(android.R.string.cancel)
.input(
hint = resources?.getString(R.string.name),
prefill = currentName
) { _, input ->
currentName = input.toString()
}
.positiveButton(android.R.string.ok) { onPositive() }
.title(R.string.action_rename_category)
.negativeButton(android.R.string.cancel)
.input(
hint = resources?.getString(R.string.name),
prefill = currentName
) { _, input ->
currentName = input.toString()
}
.positiveButton(android.R.string.ok) { onPositive() }
}
/**

View File

@@ -23,7 +23,8 @@ import rx.android.schedulers.AndroidSchedulers
* Controller that shows the currently active downloads.
* Uses R.layout.fragment_download_queue.
*/
class DownloadController : NucleusController<DownloadControllerBinding, DownloadPresenter>(),
class DownloadController :
NucleusController<DownloadControllerBinding, DownloadPresenter>(),
DownloadAdapter.DownloadItemListener {
/**
@@ -75,16 +76,16 @@ class DownloadController : NucleusController<DownloadControllerBinding, Download
// Subscribe to changes
DownloadService.runningRelay
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onQueueStatusChange(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onQueueStatusChange(it) }
presenter.getDownloadStatusObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onStatusChange(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onStatusChange(it) }
presenter.getDownloadProgressObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onUpdateDownloadedPages(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onUpdateDownloadedPages(it) }
}
override fun onDestroyView(view: View) {
@@ -123,8 +124,9 @@ class DownloadController : NucleusController<DownloadControllerBinding, Download
val adapter = adapter ?: return false
val items = adapter.currentItems.sortedBy { it.download.chapter.date_upload }
.toMutableList()
if (item.itemId == R.id.newest)
if (item.itemId == R.id.newest) {
items.reverse()
}
adapter.updateDataSet(items)
val downloads = items.mapNotNull { it.download }
presenter.reorder(downloads)
@@ -161,22 +163,22 @@ class DownloadController : NucleusController<DownloadControllerBinding, Download
*/
private fun observeProgress(download: Download) {
val subscription = Observable.interval(50, TimeUnit.MILLISECONDS)
// Get the sum of percentages for all the pages.
.flatMap {
Observable.from(download.pages)
.map(Page::progress)
.reduce { x, y -> x + y }
}
// Keep only the latest emission to avoid backpressure.
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { progress ->
// Update the view only if the progress has changed.
if (download.totalProgress != progress) {
download.totalProgress = progress
onUpdateProgress(download)
}
// Get the sum of percentages for all the pages.
.flatMap {
Observable.from(download.pages)
.map(Page::progress)
.reduce { x, y -> x + y }
}
// Keep only the latest emission to avoid backpressure.
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { progress ->
// Update the view only if the progress has changed.
if (download.totalProgress != progress) {
download.totalProgress = progress
onUpdateProgress(download)
}
}
// Avoid leaking subscriptions
progressSubscriptions.remove(download)?.unsubscribe()
@@ -279,10 +281,11 @@ class DownloadController : NucleusController<DownloadControllerBinding, Download
val items = adapter?.currentItems?.toMutableList() ?: return
val item = items[position]
items.remove(item)
if (menuItem.itemId == R.id.move_to_top)
if (menuItem.itemId == R.id.move_to_top) {
items.add(0, item)
else
} else {
items.add(item)
}
val adapter = adapter ?: return
adapter.updateDataSet(items)

View File

@@ -80,13 +80,17 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
}
private fun showPopupMenu(view: View) {
view.popupMenu(R.menu.download_single, {
findItem(R.id.move_to_top).isVisible = bindingAdapterPosition != 0
findItem(R.id.move_to_bottom).isVisible =
bindingAdapterPosition != adapter.itemCount - 1
}, {
adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this)
true
})
view.popupMenu(
R.menu.download_single,
{
findItem(R.id.move_to_top).isVisible = bindingAdapterPosition != 0
findItem(R.id.move_to_bottom).isVisible =
bindingAdapterPosition != adapter.itemCount - 1
},
{
adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this)
true
}
)
}
}

View File

@@ -27,21 +27,21 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
super.onCreate(savedState)
downloadQueue.getUpdatedObservable()
.observeOn(AndroidSchedulers.mainThread())
.map { it.map(::DownloadItem) }
.subscribeLatestCache(DownloadController::onNextDownloads) { _, error ->
Timber.e(error)
}
.observeOn(AndroidSchedulers.mainThread())
.map { it.map(::DownloadItem) }
.subscribeLatestCache(DownloadController::onNextDownloads) { _, error ->
Timber.e(error)
}
}
fun getDownloadStatusObservable(): Observable<Download> {
return downloadQueue.getStatusObservable()
.startWith(downloadQueue.getActiveDownloads())
.startWith(downloadQueue.getActiveDownloads())
}
fun getDownloadProgressObservable(): Observable<Download> {
return downloadQueue.getProgressObservable()
.onBackpressureBuffer()
.onBackpressureBuffer()
}
/**

View File

@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
* @param controller instance of [ExtensionController].
*/
class ExtensionAdapter(val controller: ExtensionController) :
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
val cardBackground = controller.activity!!.getResourceColor(R.attr.colorSurface)

View File

@@ -32,11 +32,12 @@ import uy.kohesive.injekt.api.get
/**
* Controller to manage the catalogues available in the app.
*/
open class ExtensionController : NucleusController<ExtensionControllerBinding, ExtensionPresenter>(),
ExtensionAdapter.OnButtonClickListener,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
ExtensionTrustDialog.Listener {
open class ExtensionController :
NucleusController<ExtensionControllerBinding, ExtensionPresenter>(),
ExtensionAdapter.OnButtonClickListener,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
ExtensionTrustDialog.Listener {
private val preferences: PreferencesHelper = Injekt.get()
@@ -92,9 +93,11 @@ open class ExtensionController : NucleusController<ExtensionControllerBinding, E
when (item.itemId) {
R.id.action_search -> expandActionViewFromInteraction = true
R.id.action_settings -> {
router.pushController((RouterTransaction.with(ExtensionFilterController()))
router.pushController(
(RouterTransaction.with(ExtensionFilterController()))
.popChangeHandler(SettingsExtensionsFadeChangeHandler())
.pushChangeHandler(FadeChangeHandler()))
.pushChangeHandler(FadeChangeHandler())
)
}
R.id.action_auto_check -> {
item.isChecked = !item.isChecked
@@ -184,7 +187,7 @@ open class ExtensionController : NucleusController<ExtensionControllerBinding, E
private fun openTrustDialog(extension: Extension.Untrusted) {
ExtensionTrustDialog(this, extension.signatureHash, extension.pkgName)
.showDialog(router)
.showDialog(router)
}
fun setExtensions(extensions: List<ExtensionItem>) {
@@ -196,9 +199,10 @@ open class ExtensionController : NucleusController<ExtensionControllerBinding, E
fun drawExtensions() {
if (!query.isBlank()) {
adapter?.updateDataSet(
extensions.filter {
it.extension.name.contains(query, ignoreCase = true)
})
extensions.filter {
it.extension.name.contains(query, ignoreCase = true)
}
)
} else {
adapter?.updateDataSet(extensions)
}

View File

@@ -38,17 +38,19 @@ import reactivecircus.flowbinding.android.view.clicks
@SuppressLint("RestrictedApi")
class ExtensionDetailsController(bundle: Bundle? = null) :
NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle),
PreferenceManager.OnDisplayPreferenceDialogListener,
DialogPreference.TargetFragment {
NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle),
PreferenceManager.OnDisplayPreferenceDialogListener,
DialogPreference.TargetFragment {
private var lastOpenPreferencePosition: Int? = null
private var preferenceScreen: PreferenceScreen? = null
constructor(pkgName: String) : this(Bundle().apply {
putString(PKGNAME_KEY, pkgName)
})
constructor(pkgName: String) : this(
Bundle().apply {
putString(PKGNAME_KEY, pkgName)
}
)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
@@ -136,8 +138,9 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
val dataStore = SharedPreferencesDataStore(/*if (source is HttpSource) {
source.preferences
} else {*/
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
/*}*/)
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
/*}*/
)
if (source is ConfigurableSource) {
if (multiSource) {
@@ -178,14 +181,19 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
}
val f = when (preference) {
is EditTextPreference -> EditTextPreferenceDialogController
is EditTextPreference ->
EditTextPreferenceDialogController
.newInstance(preference.getKey())
is ListPreference -> ListPreferenceDialogController
is ListPreference ->
ListPreferenceDialogController
.newInstance(preference.getKey())
is MultiSelectListPreference -> MultiSelectListPreferenceDialogController
is MultiSelectListPreference ->
MultiSelectListPreferenceDialogController
.newInstance(preference.getKey())
else -> throw IllegalArgumentException("Tried to display dialog for unknown " +
"preference type. Did you forget to override onDisplayPreferenceDialog()?")
else -> throw IllegalArgumentException(
"Tried to display dialog for unknown " +
"preference type. Did you forget to override onDisplayPreferenceDialog()?"
)
}
f.targetController = this
f.showDialog(router)

View File

@@ -22,14 +22,14 @@ class ExtensionDetailsPresenter(
private fun bindToUninstalledExtension() {
extensionManager.getInstalledExtensionsObservable()
.skip(1)
.filter { extensions -> extensions.none { it.pkgName == pkgName } }
.map { Unit }
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ ->
view.onExtensionUninstalled()
})
.skip(1)
.filter { extensions -> extensions.none { it.pkgName == pkgName } }
.map { Unit }
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ ->
view.onExtensionUninstalled()
})
}
fun uninstallExtension() {

View File

@@ -23,7 +23,8 @@ class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecora
val child = parent.getChildAt(i)
val holder = parent.getChildViewHolder(child)
if (holder is ExtensionHolder &&
parent.getChildViewHolder(parent.getChildAt(i + 1)) is ExtensionHolder) {
parent.getChildViewHolder(parent.getChildAt(i + 1)) is ExtensionHolder
) {
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight

View File

@@ -19,13 +19,13 @@ class ExtensionFilterController : SettingsController() {
val activeLangs = preferences.enabledLanguages().get()
val availableLangs =
Injekt.get<ExtensionManager>().availableExtensions.groupBy {
it.lang
}.keys.minus("all").partition {
it in activeLangs
}.let {
it.first + it.second
}
Injekt.get<ExtensionManager>().availableExtensions.groupBy {
it.lang
}.keys.minus("all").partition {
it in activeLangs
}.let {
it.first + it.second
}
availableLangs.forEach {
switchPreference {

View File

@@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import kotlinx.android.synthetic.main.extension_card_header.title
class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) :
BaseFlexibleViewHolder(view, adapter) {
BaseFlexibleViewHolder(view, adapter) {
@SuppressLint("SetTextI18n")
fun bind(item: ExtensionGroupItem) {

View File

@@ -38,7 +38,6 @@ data class ExtensionGroupItem(val name: String, val size: Int, val showSize: Boo
position: Int,
payloads: List<Any?>?
) {
holder.bind(this)
}

View File

@@ -18,8 +18,8 @@ import kotlinx.android.synthetic.main.extension_card_item.lang
import kotlinx.android.synthetic.main.extension_card_item.version
class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
BaseFlexibleViewHolder(view, adapter),
SlicedHolder {
BaseFlexibleViewHolder(view, adapter),
SlicedHolder {
override val slice = Slice(card).apply {
setColor(adapter.cardBackground)
@@ -50,8 +50,8 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
GlideApp.with(itemView.context).clear(image)
if (extension is Extension.Available) {
GlideApp.with(itemView.context)
.load(extension.iconUrl)
.into(image)
.load(extension.iconUrl)
.into(image)
} else {
extension.getApplicationIcon(itemView.context)?.let { image.setImageDrawable(it) }
}
@@ -69,13 +69,15 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
val installStep = item.installStep
if (installStep != null) {
setText(when (installStep) {
InstallStep.Pending -> R.string.ext_pending
InstallStep.Downloading -> R.string.ext_downloading
InstallStep.Installing -> R.string.ext_installing
InstallStep.Installed -> R.string.ext_installed
InstallStep.Error -> R.string.action_retry
})
setText(
when (installStep) {
InstallStep.Pending -> R.string.ext_pending
InstallStep.Downloading -> R.string.ext_downloading
InstallStep.Installing -> R.string.ext_installing
InstallStep.Installed -> R.string.ext_installed
InstallStep.Error -> R.string.action_retry
}
)
if (installStep != InstallStep.Error) {
isEnabled = false
isClickable = false

View File

@@ -21,7 +21,7 @@ data class ExtensionItem(
val header: ExtensionGroupItem? = null,
val installStep: InstallStep? = null
) :
AbstractSectionableItem<ExtensionHolder, ExtensionGroupItem>(header) {
AbstractSectionableItem<ExtensionHolder, ExtensionGroupItem>(header) {
/**
* Returns the layout resource of this item.
@@ -46,7 +46,6 @@ data class ExtensionItem(
position: Int,
payloads: List<Any?>?
) {
if (payloads == null || payloads.isEmpty()) {
holder.bind(this)
} else {

View File

@@ -17,7 +17,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
private typealias ExtensionTuple =
Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>
Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>
/**
* Presenter of [ExtensionController].
@@ -42,13 +42,13 @@ open class ExtensionPresenter(
val installedObservable = extensionManager.getInstalledExtensionsObservable()
val untrustedObservable = extensionManager.getUntrustedExtensionsObservable()
val availableObservable = extensionManager.getAvailableExtensionsObservable()
.startWith(emptyList<Extension.Available>())
.startWith(emptyList<Extension.Available>())
return Observable.combineLatest(installedObservable, untrustedObservable, availableObservable) { installed, untrusted, available -> Triple(installed, untrusted, available) }
.debounce(100, TimeUnit.MILLISECONDS)
.map(::toItems)
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, _ -> view.setExtensions(extensions) })
.debounce(100, TimeUnit.MILLISECONDS)
.map(::toItems)
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, _ -> view.setExtensions(extensions) })
}
@Synchronized
@@ -64,13 +64,13 @@ open class ExtensionPresenter(
val installedSorted = installed.filter { !it.hasUpdate }.sortedWith(compareBy({ !it.isObsolete }, { it.pkgName }))
val untrustedSorted = untrusted.sortedBy { it.pkgName }
val availableSorted = available
// Filter out already installed extensions and disabled languages
.filter { avail ->
installed.none { it.pkgName == avail.pkgName } &&
untrusted.none { it.pkgName == avail.pkgName } &&
(avail.lang in activeLangs || avail.lang == "all")
}
.sortedBy { it.pkgName }
// Filter out already installed extensions and disabled languages
.filter { avail ->
installed.none { it.pkgName == avail.pkgName } &&
untrusted.none { it.pkgName == avail.pkgName } &&
(avail.lang in activeLangs || avail.lang == "all")
}
.sortedBy { it.pkgName }
if (updatesSorted.isNotEmpty()) {
val header = ExtensionGroupItem(context.getString(R.string.ext_updates_pending), updatesSorted.size, true)
@@ -89,16 +89,16 @@ open class ExtensionPresenter(
}
if (availableSorted.isNotEmpty()) {
val availableGroupedByLang = availableSorted
.groupBy { LocaleHelper.getSourceDisplayName(it.lang, context) }
.toSortedMap()
.groupBy { LocaleHelper.getSourceDisplayName(it.lang, context) }
.toSortedMap()
availableGroupedByLang
.forEach {
val header = ExtensionGroupItem(it.key, it.value.size)
items += it.value.map { extension ->
ExtensionItem(extension, header, currentDownloads[extension.pkgName])
}
.forEach {
val header = ExtensionGroupItem(it.key, it.value.size)
items += it.value.map { extension ->
ExtensionItem(extension, header, currentDownloads[extension.pkgName])
}
}
}
this.extensions = items
@@ -131,13 +131,13 @@ open class ExtensionPresenter(
private fun Observable<InstallStep>.subscribeToInstallUpdate(extension: Extension) {
this.doOnNext { currentDownloads[extension.pkgName] = it }
.doOnUnsubscribe { currentDownloads.remove(extension.pkgName) }
.map { state -> updateInstallStep(extension, state) }
.subscribeWithView({ view, item ->
if (item != null) {
view.downloadUpdate(item)
}
})
.doOnUnsubscribe { currentDownloads.remove(extension.pkgName) }
.map { state -> updateInstallStep(extension, state) }
.subscribeWithView({ view, item ->
if (item != null) {
view.downloadUpdate(item)
}
})
}
fun uninstallExtension(pkgName: String) {

View File

@@ -10,23 +10,25 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
where T : Controller, T : ExtensionTrustDialog.Listener {
constructor(target: T, signatureHash: String, pkgName: String) : this(Bundle().apply {
putString(SIGNATURE_KEY, signatureHash)
putString(PKGNAME_KEY, pkgName)
}) {
constructor(target: T, signatureHash: String, pkgName: String) : this(
Bundle().apply {
putString(SIGNATURE_KEY, signatureHash)
putString(PKGNAME_KEY, pkgName)
}
) {
targetController = target
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.untrusted_extension)
.message(R.string.untrusted_extension_message)
.positiveButton(R.string.ext_trust) {
(targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!)
}
.negativeButton(R.string.ext_uninstall) {
(targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!)
}
.title(R.string.untrusted_extension)
.message(R.string.untrusted_extension_message)
.positiveButton(R.string.ext_trust) {
(targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!)
}
.negativeButton(R.string.ext_uninstall) {
(targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!)
}
}
private companion object {

View File

@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.controller.DialogController
class ChangeMangaCategoriesDialog<T>(bundle: Bundle? = null) :
DialogController(bundle) where T : Controller, T : ChangeMangaCategoriesDialog.Listener {
DialogController(bundle) where T : Controller, T : ChangeMangaCategoriesDialog.Listener {
private var mangas = emptyList<Manga>()
@@ -33,17 +33,17 @@ class ChangeMangaCategoriesDialog<T>(bundle: Bundle? = null) :
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_move_category)
.listItemsMultiChoice(
items = categories.map { it.name },
initialSelection = preselected.toIntArray(),
allowEmptySelection = true
) { _, selections, _ ->
val newCategories = selections.map { categories[it] }
(targetController as? Listener)?.updateCategoriesForMangas(mangas, newCategories)
}
.positiveButton(android.R.string.ok)
.negativeButton(android.R.string.cancel)
.title(R.string.action_move_category)
.listItemsMultiChoice(
items = categories.map { it.name },
initialSelection = preselected.toIntArray(),
allowEmptySelection = true
) { _, selections, _ ->
val newCategories = selections.map { categories[it] }
(targetController as? Listener)?.updateCategoriesForMangas(mangas, newCategories)
}
.positiveButton(android.R.string.ok)
.negativeButton(android.R.string.cancel)
}
interface Listener {

View File

@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.DialogCheckboxView
class DeleteLibraryMangasDialog<T>(bundle: Bundle? = null) :
DialogController(bundle) where T : Controller, T : DeleteLibraryMangasDialog.Listener {
DialogController(bundle) where T : Controller, T : DeleteLibraryMangasDialog.Listener {
private var mangas = emptyList<Manga>()
@@ -27,16 +27,16 @@ class DeleteLibraryMangasDialog<T>(bundle: Bundle? = null) :
}
return MaterialDialog(activity!!)
.title(R.string.action_remove)
.customView(
view = view,
horizontalPadding = true
)
.positiveButton(android.R.string.ok) {
val deleteChapters = view.isChecked()
(targetController as? Listener)?.deleteMangasFromLibrary(mangas, deleteChapters)
}
.negativeButton(android.R.string.cancel)
.title(R.string.action_remove)
.customView(
view = view,
horizontalPadding = true
)
.positiveButton(android.R.string.ok) {
val deleteChapters = view.isChecked()
(targetController as? Listener)?.deleteMangasFromLibrary(mangas, deleteChapters)
}
.negativeButton(android.R.string.cancel)
}
interface Listener {

View File

@@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
* @param view the fragment containing this adapter.
*/
class LibraryCategoryAdapter(view: LibraryCategoryView) :
FlexibleAdapter<LibraryItem>(null, view, true) {
FlexibleAdapter<LibraryItem>(null, view, true) {
/**
* The list of manga in this category.

View File

@@ -33,9 +33,9 @@ import uy.kohesive.injekt.injectLazy
* Fragment containing the library manga for a certain category.
*/
class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener {
FrameLayout(context, attrs),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
@@ -122,24 +122,24 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
}
subscriptions += controller.searchRelay
.doOnNext { adapter.setFilter(it) }
.skip(1)
.subscribe { adapter.performFilter() }
.doOnNext { adapter.setFilter(it) }
.skip(1)
.subscribe { adapter.performFilter() }
subscriptions += controller.libraryMangaRelay
.subscribe { onNextLibraryManga(it) }
.subscribe { onNextLibraryManga(it) }
subscriptions += controller.selectionRelay
.subscribe { onSelectionChanged(it) }
.subscribe { onSelectionChanged(it) }
subscriptions += controller.selectAllRelay
.filter { it == category.id }
.subscribe {
adapter.currentItems.forEach { item ->
controller.setSelection(item.manga, true)
}
controller.invalidateActionMode()
.filter { it == category.id }
.subscribe {
adapter.currentItems.forEach { item ->
controller.setSelection(item.manga, true)
}
controller.invalidateActionMode()
}
subscriptions += controller.selectInverseRelay
.filter { it == category.id }
@@ -255,10 +255,12 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
controller.createActionModeIfNeeded()
when {
lastClickPosition == -1 -> setSelection(position)
lastClickPosition > position -> for (i in position until lastClickPosition)
setSelection(i)
lastClickPosition < position -> for (i in lastClickPosition + 1..position)
setSelection(i)
lastClickPosition > position ->
for (i in position until lastClickPosition)
setSelection(i)
lastClickPosition < position ->
for (i in lastClickPosition + 1..position)
setSelection(i)
else -> setSelection(position)
}
lastClickPosition = position

View File

@@ -51,11 +51,11 @@ class LibraryController(
bundle: Bundle? = null,
private val preferences: PreferencesHelper = Injekt.get()
) : NucleusController<LibraryControllerBinding, LibraryPresenter>(bundle),
RootController,
TabbedController,
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
DeleteLibraryMangasDialog.Listener {
RootController,
TabbedController,
ActionMode.Callback,
ChangeMangaCategoriesDialog.Listener,
DeleteLibraryMangasDialog.Listener {
/**
* Position of the active category.
@@ -155,10 +155,10 @@ class LibraryController(
.launchIn(scope)
getColumnsPreferenceForCurrentOrientation().asObservable()
.doOnNext { mangaPerRow = it }
.skip(1)
// Set again the adapter to recalculate the covers height
.subscribeUntilDestroy { reattachAdapter() }
.doOnNext { mangaPerRow = it }
.skip(1)
// Set again the adapter to recalculate the covers height
.subscribeUntilDestroy { reattachAdapter() }
if (selectedMangas.isNotEmpty()) {
createActionModeIfNeeded()
@@ -230,10 +230,11 @@ class LibraryController(
}
// Get the current active category.
val activeCat = if (adapter.categories.isNotEmpty())
val activeCat = if (adapter.categories.isNotEmpty()) {
binding.libraryPager.currentItem
else
} else {
activeCategory
}
// Set the categories
adapter.categories = categories
@@ -260,10 +261,11 @@ class LibraryController(
* @return the preference.
*/
private fun getColumnsPreferenceForCurrentOrientation(): Preference<Int> {
return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT)
return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
preferences.portraitColumns()
else
} else {
preferences.landscapeColumns()
}
}
/**
@@ -306,8 +308,8 @@ class LibraryController(
if (actionMode == null) {
actionMode = (activity as AppCompatActivity).startSupportActionMode(this)
binding.actionToolbar.show(
actionMode!!,
R.menu.library_selection
actionMode!!,
R.menu.library_selection
) { onActionItemClicked(actionMode!!, it!!) }
}
}
@@ -479,11 +481,11 @@ class LibraryController(
// Get indexes of the common categories to preselect.
val commonCategoriesIndexes = presenter.getCommonCategories(mangas)
.map { categories.indexOf(it) }
.toTypedArray()
.map { categories.indexOf(it) }
.toTypedArray()
ChangeMangaCategoriesDialog(this, mangas, categories, commonCategoriesIndexes)
.showDialog(router)
.showDialog(router)
}
private fun showDeleteMangaDialog() {
@@ -510,8 +512,13 @@ class LibraryController(
if (manga.favorite) {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(Intent.createChooser(intent,
resources?.getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN)
startActivityForResult(
Intent.createChooser(
intent,
resources?.getString(R.string.file_select_cover)
),
REQUEST_IMAGE_OPEN
)
} else {
activity?.toast(R.string.notification_first_add_to_library)
}

View File

@@ -53,9 +53,9 @@ class LibraryGridHolder(
// Update the cover.
GlideApp.with(view.context).clear(thumbnail)
GlideApp.with(view.context)
.load(item.manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(thumbnail)
.load(item.manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(thumbnail)
}
}

View File

@@ -20,17 +20,18 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference<Boolean>) :
AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> {
AbstractFlexibleItem<LibraryHolder>(), IFilterable<String> {
private val sourceManager: SourceManager = Injekt.get()
var downloadCount = -1
override fun getLayoutRes(): Int {
return if (libraryAsList.get())
return if (libraryAsList.get()) {
R.layout.source_list_item
else
} else {
R.layout.source_grid_item
}
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LibraryHolder {
@@ -40,7 +41,8 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
val coverHeight = parent.itemWidth / 3 * 4
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM)
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
)
}
LibraryGridHolder(view, adapter)
} else {
@@ -65,25 +67,26 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference
*/
override fun filter(constraint: String): Boolean {
return manga.title.contains(constraint, true) ||
(manga.author?.contains(constraint, true) ?: false) ||
(manga.artist?.contains(constraint, true) ?: false) ||
sourceManager.getOrStub(manga.source).name.contains(constraint, true) ||
if (constraint.contains(",")) {
constraint.split(",").all { containsGenre(it.trim(), manga.getGenres()) }
} else {
containsGenre(constraint, manga.getGenres())
}
(manga.author?.contains(constraint, true) ?: false) ||
(manga.artist?.contains(constraint, true) ?: false) ||
sourceManager.getOrStub(manga.source).name.contains(constraint, true) ||
if (constraint.contains(",")) {
constraint.split(",").all { containsGenre(it.trim(), manga.getGenres()) }
} else {
containsGenre(constraint, manga.getGenres())
}
}
private fun containsGenre(tag: String, genres: List<String>?): Boolean {
return if (tag.startsWith("-"))
return if (tag.startsWith("-")) {
genres?.find {
it.trim().toLowerCase() == tag.substringAfter("-").toLowerCase()
} == null
else
} else {
genres?.find {
it.trim().toLowerCase() == tag.toLowerCase()
} != null
}
}
override fun equals(other: Any?): Boolean {

View File

@@ -60,11 +60,11 @@ class LibraryListHolder(
// Update the cover.
GlideApp.with(itemView.context).clear(thumbnail)
GlideApp.with(itemView.context)
.load(item.manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.circleCrop()
.dontAnimate()
.into(thumbnail)
.load(item.manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.circleCrop()
.dontAnimate()
.into(thumbnail)
}
}

View File

@@ -88,19 +88,19 @@ class LibraryPresenter(
fun subscribeLibrary() {
if (librarySubscription.isNullOrUnsubscribed()) {
librarySubscription = getLibraryObservable()
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib.apply { setDownloadCount(mangaMap) }
}
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib.copy(mangaMap = applyFilters(lib.mangaMap))
}
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib.copy(mangaMap = applySort(lib.mangaMap))
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, (categories, mangaMap) ->
view.onNextLibraryUpdate(categories, mangaMap)
})
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib.apply { setDownloadCount(mangaMap) }
}
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib.copy(mangaMap = applyFilters(lib.mangaMap))
}
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) { lib, _ ->
lib.copy(mangaMap = applySort(lib.mangaMap))
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, (categories, mangaMap) ->
view.onNextLibraryUpdate(categories, mangaMap)
})
}
}
@@ -205,19 +205,20 @@ class LibraryPresenter(
}
LibrarySort.LATEST_CHAPTER -> {
val manga1latestChapter = latestChapterManga[i1.manga.id!!]
?: latestChapterManga.size
?: latestChapterManga.size
val manga2latestChapter = latestChapterManga[i2.manga.id!!]
?: latestChapterManga.size
?: latestChapterManga.size
manga1latestChapter.compareTo(manga2latestChapter)
}
else -> throw Exception("Unknown sorting mode")
}
}
val comparator = if (preferences.librarySortingAscending().get())
val comparator = if (preferences.librarySortingAscending().get()) {
Comparator(sortFn)
else
} else {
Collections.reverseOrder(sortFn)
}
return map.mapValues { entry -> entry.value.sortedWith(comparator) }
}
@@ -229,10 +230,11 @@ class LibraryPresenter(
*/
private fun getLibraryObservable(): Observable<Library> {
return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga ->
val categories = if (libraryManga.containsKey(0))
val categories = if (libraryManga.containsKey(0)) {
arrayListOf(Category.createDefault()) + dbCategories
else
} else {
dbCategories
}
this.categories = categories
Library(categories, libraryManga)
@@ -257,9 +259,9 @@ class LibraryPresenter(
private fun getLibraryMangasObservable(): Observable<LibraryMap> {
val libraryAsList = preferences.libraryAsList()
return db.getLibraryMangas().asRxObservable()
.map { list ->
list.map { LibraryItem(it, libraryAsList) }.groupBy { it.manga.category }
}
.map { list ->
list.map { LibraryItem(it, libraryAsList) }.groupBy { it.manga.category }
}
}
/**
@@ -299,8 +301,8 @@ class LibraryPresenter(
fun getCommonCategories(mangas: List<Manga>): Collection<Category> {
if (mangas.isEmpty()) return emptyList()
return mangas.toSet()
.map { db.getCategoriesForManga(it).executeAsBlocking() }
.reduce { set1: Iterable<Category>, set2 -> set1.intersect(set2).toMutableList() }
.map { db.getCategoriesForManga(it).executeAsBlocking() }
.reduce { set1: Iterable<Category>, set2 -> set1.intersect(set2).toMutableList() }
}
/**
@@ -315,9 +317,9 @@ class LibraryPresenter(
mangaToDelete.forEach { it.favorite = false }
Observable.fromCallable { db.insertMangas(mangaToDelete).executeAsBlocking() }
.onErrorResumeNext { Observable.empty() }
.subscribeOn(Schedulers.io())
.subscribe()
.onErrorResumeNext { Observable.empty() }
.subscribeOn(Schedulers.io())
.subscribe()
Observable.fromCallable {
mangaToDelete.forEach { manga ->
@@ -330,8 +332,8 @@ class LibraryPresenter(
}
}
}
.subscribeOn(Schedulers.io())
.subscribe()
.subscribeOn(Schedulers.io())
.subscribe()
}
/**

View File

@@ -129,8 +129,11 @@ class LibrarySettingsSheet(
override fun initModels() {
val sorting = preferences.librarySortingMode().get()
val order = if (preferences.librarySortingAscending().get())
Item.MultiSort.SORT_ASC else Item.MultiSort.SORT_DESC
val order = if (preferences.librarySortingAscending().get()) {
Item.MultiSort.SORT_ASC
} else {
Item.MultiSort.SORT_DESC
}
alphabetically.state =
if (sorting == LibrarySort.ALPHA) order else Item.MultiSort.SORT_NONE

View File

@@ -29,7 +29,7 @@ import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.ui.recent.history.HistoryController
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.source.SourceController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.toast

View File

@@ -30,23 +30,23 @@ class ViewHeightAnimator(val view: View) {
init {
view.viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (view.height > 0) {
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (view.height > 0) {
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
// Save the tabs default height.
height = view.height
// Save the tabs default height.
height = view.height
// Now that we know the height, set the initial height.
if (isLastStateShown) {
setHeight(height)
} else {
setHeight(0)
}
// Now that we know the height, set the initial height.
if (isLastStateShown) {
setHeight(height)
} else {
setHeight(0)
}
}
}
}
)
}

View File

@@ -35,10 +35,12 @@ import uy.kohesive.injekt.api.get
class MangaController : RxController<MangaControllerBinding>, TabbedController {
constructor(manga: Manga?, fromSource: Boolean = false) : super(Bundle().apply {
putLong(MANGA_EXTRA, manga?.id ?: 0)
putBoolean(FROM_SOURCE_EXTRA, fromSource)
}) {
constructor(manga: Manga?, fromSource: Boolean = false) : super(
Bundle().apply {
putLong(MANGA_EXTRA, manga?.id ?: 0)
putBoolean(FROM_SOURCE_EXTRA, fromSource)
}
) {
this.manga = manga
if (manga != null) {
source = Injekt.get<SourceManager>().getOrStub(manga.source)
@@ -46,7 +48,8 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
}
constructor(mangaId: Long) : this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking())
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking()
)
@Suppress("unused")
constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA))
@@ -87,8 +90,9 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
binding.mangaPager.offscreenPageLimit = 3
binding.mangaPager.adapter = adapter
if (!fromSource)
if (!fromSource) {
binding.mangaPager.currentItem = CHAPTERS_CONTROLLER
}
}
override fun onDestroyView(view: View) {
@@ -130,9 +134,11 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
private fun setTrackingIconInternal(visible: Boolean) {
val tab = activity?.tabs?.getTabAt(TRACK_CONTROLLER) ?: return
val drawable = if (visible)
val drawable = if (visible) {
VectorDrawableCompat.create(resources!!, R.drawable.ic_done_white_18dp, null)
else null
} else {
null
}
tab.icon = drawable
}
@@ -142,10 +148,11 @@ class MangaController : RxController<MangaControllerBinding>, TabbedController {
private val tabCount = if (Injekt.get<TrackManager>().hasLoggedServices()) 3 else 2
private val tabTitles = listOf(
R.string.manga_detail_tab,
R.string.manga_chapters_tab,
R.string.manga_tracking_tab)
.map { resources!!.getString(it) }
R.string.manga_detail_tab,
R.string.manga_chapters_tab,
R.string.manga_tracking_tab
)
.map { resources!!.getString(it) }
override fun getCount(): Int {
return tabCount

View File

@@ -10,8 +10,9 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem<ChapterHolder>(),
Chapter by chapter {
class ChapterItem(val chapter: Chapter, val manga: Manga) :
AbstractFlexibleItem<ChapterHolder>(),
Chapter by chapter {
private var _status: Int = 0

View File

@@ -26,8 +26,11 @@ class ChaptersAdapter(
val bookmarkedColor = context.getResourceColor(R.attr.colorAccent)
val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols()
.apply { decimalSeparator = '.' })
val decimalFormat = DecimalFormat(
"#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' }
)
val dateFormat: DateFormat = preferences.dateFormat().getOrDefault()

View File

@@ -39,12 +39,13 @@ import reactivecircus.flowbinding.android.view.clicks
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
import timber.log.Timber
class ChaptersController : NucleusController<ChaptersControllerBinding, ChaptersPresenter>(),
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
DownloadCustomChaptersDialog.Listener,
DeleteChaptersDialog.Listener {
class ChaptersController :
NucleusController<ChaptersControllerBinding, ChaptersPresenter>(),
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
DownloadCustomChaptersDialog.Listener,
DeleteChaptersDialog.Listener {
/**
* Adapter containing a list of chapters.
@@ -168,11 +169,13 @@ class ChaptersController : NucleusController<ChaptersControllerBinding, Chapters
menuFilterEmpty.isVisible = filterSet
// Disable unread filter option if read filter is enabled.
if (presenter.onlyRead())
if (presenter.onlyRead()) {
menuFilterUnread.isEnabled = false
}
// Disable read filter option if unread filter is enabled.
if (presenter.onlyUnread())
if (presenter.onlyUnread()) {
menuFilterRead.isEnabled = false
}
// Display mode submenu
if (presenter.manga.displayMode == Manga.DISPLAY_NAME) {
@@ -320,10 +323,12 @@ class ChaptersController : NucleusController<ChaptersControllerBinding, Chapters
createActionModeIfNeeded()
when {
lastClickPosition == -1 -> setSelection(position)
lastClickPosition > position -> for (i in position until lastClickPosition)
setSelection(i)
lastClickPosition < position -> for (i in lastClickPosition + 1..position)
setSelection(i)
lastClickPosition > position ->
for (i in position until lastClickPosition)
setSelection(i)
lastClickPosition < position ->
for (i in lastClickPosition + 1..position)
setSelection(i)
else -> setSelection(position)
}
lastClickPosition = position
@@ -364,8 +369,8 @@ class ChaptersController : NucleusController<ChaptersControllerBinding, Chapters
if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
binding.actionToolbar.show(
actionMode!!,
R.menu.chapter_selection
actionMode!!,
R.menu.chapter_selection
) { onActionItemClicked(actionMode!!, it!!) }
}
}
@@ -526,9 +531,9 @@ class ChaptersController : NucleusController<ChaptersControllerBinding, Chapters
}
private fun getUnreadChaptersSorted() = presenter.chapters
.filter { !it.read && it.status == Download.NOT_DOWNLOADED }
.distinctBy { it.name }
.sortedByDescending { it.source_order }
.filter { !it.read && it.status == Download.NOT_DOWNLOADED }
.distinctBy { it.name }
.sortedByDescending { it.source_order }
private fun downloadChapters(choice: Int) {
val chaptersToDownload = when (choice) {

View File

@@ -43,7 +43,7 @@ class ChaptersPresenter(
* Subject of list of chapters to allow updating the view without going to DB.
*/
val chaptersRelay: PublishRelay<List<ChapterItem>>
by lazy { PublishRelay.create<List<ChapterItem>>() }
by lazy { PublishRelay.create<List<ChapterItem>>() }
/**
* Whether the chapter list has been requested to the source.
@@ -66,12 +66,13 @@ class ChaptersPresenter(
// Prepare the relay.
chaptersRelay.flatMap { applyChapterFilters(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(ChaptersController::onNextChapters) { _, error -> Timber.e(error) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(ChaptersController::onNextChapters) { _, error -> Timber.e(error) }
// Add the subscription that retrieves the chapters from the database, keeps subscribed to
// changes, and sends the list of chapters to the relay.
add(db.getChapters(manga).asRxObservable()
add(
db.getChapters(manga).asRxObservable()
.map { chapters ->
// Convert every chapter to a model.
chapters.map { it.toModel() }
@@ -86,18 +87,19 @@ class ChaptersPresenter(
// Listen for download status changes
observeDownloads()
}
.subscribe { chaptersRelay.call(it) })
.subscribe { chaptersRelay.call(it) }
)
}
private fun observeDownloads() {
observeDownloadsSubscription?.let { remove(it) }
observeDownloadsSubscription = downloadManager.queue.getStatusObservable()
.observeOn(AndroidSchedulers.mainThread())
.filter { download -> download.manga.id == manga.id }
.doOnNext { onDownloadStatusChange(it) }
.subscribeLatestCache(ChaptersController::onChapterStatusChange) { _, error ->
Timber.e(error)
}
.observeOn(AndroidSchedulers.mainThread())
.filter { download -> download.manga.id == manga.id }
.doOnNext { onDownloadStatusChange(it) }
.subscribeLatestCache(ChaptersController::onChapterStatusChange) { _, error ->
Timber.e(error)
}
}
/**
@@ -138,12 +140,15 @@ class ChaptersPresenter(
if (!fetchChaptersSubscription.isNullOrUnsubscribed()) return
fetchChaptersSubscription = Observable.defer { source.fetchChapterList(manga) }
.subscribeOn(Schedulers.io())
.map { syncChaptersWithSource(db, it, manga, source) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ ->
.subscribeOn(Schedulers.io())
.map { syncChaptersWithSource(db, it, manga, source) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, _ ->
view.onFetchChaptersDone()
}, ChaptersController::onFetchChaptersError)
},
ChaptersController::onFetchChaptersError
)
}
/**
@@ -200,8 +205,9 @@ class ChaptersPresenter(
}
// Force UI update if downloaded filter active and download finished.
if (onlyDownloaded() && download.status == Download.DOWNLOADED)
if (onlyDownloaded() && download.status == Download.DOWNLOADED) {
refreshChapters()
}
}
/**
@@ -218,16 +224,16 @@ class ChaptersPresenter(
*/
fun markChaptersRead(selectedChapters: List<ChapterItem>, read: Boolean) {
Observable.from(selectedChapters)
.doOnNext { chapter ->
chapter.read = read
if (!read) {
chapter.last_page_read = 0
}
.doOnNext { chapter ->
chapter.read = read
if (!read) {
chapter.last_page_read = 0
}
.toList()
.flatMap { db.updateChaptersProgress(it).asRxObservable() }
.subscribeOn(Schedulers.io())
.subscribe()
}
.toList()
.flatMap { db.updateChaptersProgress(it).asRxObservable() }
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
@@ -244,13 +250,13 @@ class ChaptersPresenter(
*/
fun bookmarkChapters(selectedChapters: List<ChapterItem>, bookmarked: Boolean) {
Observable.from(selectedChapters)
.doOnNext { chapter ->
chapter.bookmark = bookmarked
}
.toList()
.flatMap { db.updateChaptersProgress(it).asRxObservable() }
.subscribeOn(Schedulers.io())
.subscribe()
.doOnNext { chapter ->
chapter.bookmark = bookmarked
}
.toList()
.flatMap { db.updateChaptersProgress(it).asRxObservable() }
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
@@ -259,13 +265,16 @@ class ChaptersPresenter(
*/
fun deleteChapters(chapters: List<ChapterItem>) {
Observable.just(chapters)
.doOnNext { deleteChaptersInternal(chapters) }
.doOnNext { if (onlyDownloaded()) refreshChapters() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ ->
.doOnNext { deleteChaptersInternal(chapters) }
.doOnNext { if (onlyDownloaded()) refreshChapters() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, _ ->
view.onChaptersDeleted(chapters)
}, ChaptersController::onChaptersDeletedError)
},
ChaptersController::onChaptersDeletedError
)
}
/**

View File

@@ -16,11 +16,11 @@ class DeleteChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.confirm_delete_chapters)
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.deleteChapters()
}
.negativeButton(android.R.string.cancel)
.message(R.string.confirm_delete_chapters)
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.deleteChapters()
}
.negativeButton(android.R.string.cancel)
}
interface Listener {

View File

@@ -24,10 +24,12 @@ class DownloadCustomChaptersDialog<T> : DialogController
* Initialize dialog.
* @param maxChapters maximal number of chapters that user can download.
*/
constructor(target: T, maxChapters: Int) : super(Bundle().apply {
// Add maximum number of chapters to download value to bundle.
putInt(KEY_ITEM_MAX, maxChapters)
}) {
constructor(target: T, maxChapters: Int) : super(
Bundle().apply {
// Add maximum number of chapters to download value to bundle.
putInt(KEY_ITEM_MAX, maxChapters)
}
) {
targetController = target
this.maxChapters = maxChapters
}
@@ -57,12 +59,12 @@ class DownloadCustomChaptersDialog<T> : DialogController
// Build dialog.
// when positive dialog is pressed call custom listener.
return MaterialDialog(activity)
.title(R.string.custom_download)
.customView(view = view, scrollable = true)
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.downloadCustomChapters(view.amount)
}
.negativeButton(android.R.string.cancel)
.title(R.string.custom_download)
.customView(view = view, scrollable = true)
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.downloadCustomChapters(view.amount)
}
.negativeButton(android.R.string.cancel)
}
interface Listener {

View File

@@ -31,7 +31,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.recent.history.HistoryController
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.lang.truncateCenter
import eu.kanade.tachiyomi.util.system.toast
@@ -217,12 +217,14 @@ class MangaInfoController(private val fromSource: Boolean = false) :
}
// Update status TextView.
binding.mangaStatus.setText(when (manga.status) {
SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed
else -> R.string.unknown
})
binding.mangaStatus.setText(
when (manga.status) {
SManga.ONGOING -> R.string.ongoing
SManga.COMPLETED -> R.string.completed
SManga.LICENSED -> R.string.licensed
else -> R.string.unknown
}
)
// Set the favorite drawable to the correct one.
setFavoriteButtonState(manga.favorite)
@@ -233,16 +235,16 @@ class MangaInfoController(private val fromSource: Boolean = false) :
val mangaThumbnail = manga.toMangaThumbnail()
GlideApp.with(view.context)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(binding.mangaCover)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(binding.mangaCover)
GlideApp.with(view.context)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(binding.backdrop)
.load(mangaThumbnail)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(binding.backdrop)
}
// Manga info section
@@ -291,23 +293,26 @@ class MangaInfoController(private val fromSource: Boolean = false) :
val isExpanded = binding.mangaInfoToggle.text == context.getString(R.string.manga_info_collapse)
binding.mangaInfoToggle.text =
if (isExpanded)
if (isExpanded) {
context.getString(R.string.manga_info_expand)
else
} else {
context.getString(R.string.manga_info_collapse)
}
with(binding.mangaSummary) {
maxLines =
if (isExpanded)
if (isExpanded) {
3
else
} else {
Int.MAX_VALUE
}
ellipsize =
if (isExpanded)
if (isExpanded) {
TextUtils.TruncateAt.END
else
} else {
null
}
}
binding.mangaGenresTagsCompact.visibleIf { isExpanded }
@@ -447,7 +452,7 @@ class MangaInfoController(private val fromSource: Boolean = false) :
}.toTypedArray()
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
.showDialog(router)
.showDialog(router)
}
}
}
@@ -463,7 +468,7 @@ class MangaInfoController(private val fromSource: Boolean = false) :
}.toTypedArray()
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
.showDialog(router)
.showDialog(router)
}
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
@@ -492,8 +497,10 @@ class MangaInfoController(private val fromSource: Boolean = false) :
val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
activity.toast(view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)),
Toast.LENGTH_SHORT)
activity.toast(
view.context.getString(R.string.copied_to_clipboard, content.truncateCenter(20)),
Toast.LENGTH_SHORT
)
}
/**

View File

@@ -48,8 +48,8 @@ class MangaInfoPresenter(
// Update favorite status
mangaFavoriteRelay.observeOn(AndroidSchedulers.mainThread())
.subscribe { setFavorite(it) }
.apply { add(this) }
.subscribe { setFavorite(it) }
.apply { add(this) }
}
/**
@@ -58,7 +58,7 @@ class MangaInfoPresenter(
fun sendMangaToView() {
viewMangaSubscription?.let { remove(it) }
viewMangaSubscription = Observable.just(manga)
.subscribeLatestCache({ view, manga -> view.onNextManga(manga, source) })
.subscribeLatestCache({ view, manga -> view.onNextManga(manga, source) })
}
/**
@@ -67,18 +67,21 @@ class MangaInfoPresenter(
fun fetchMangaFromSource() {
if (!fetchMangaSubscription.isNullOrUnsubscribed()) return
fetchMangaSubscription = Observable.defer { source.fetchMangaDetails(manga) }
.map { networkManga ->
manga.copyFrom(networkManga)
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
manga
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { sendMangaToView() }
.subscribeFirst({ view, _ ->
.map { networkManga ->
manga.copyFrom(networkManga)
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
manga
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { sendMangaToView() }
.subscribeFirst(
{ view, _ ->
view.onFetchMangaDone()
}, MangaInfoController::onFetchMangaError)
},
MangaInfoController::onFetchMangaError
)
}
/**

View File

@@ -19,9 +19,11 @@ class SetTrackChaptersDialog<T> : DialogController
private val item: TrackItem
constructor(target: T, item: TrackItem) : super(Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track)
}) {
constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track)
}
) {
targetController = target
this.item = item
}
@@ -37,17 +39,17 @@ class SetTrackChaptersDialog<T> : DialogController
val item = item
val dialog = MaterialDialog(activity!!)
.title(R.string.chapters)
.customView(R.layout.track_chapters_dialog, dialogWrapContent = false)
.positiveButton(android.R.string.ok) { dialog ->
val view = dialog.getCustomView()
// Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.chapters_picker)
np.clearFocus()
.title(R.string.chapters)
.customView(R.layout.track_chapters_dialog, dialogWrapContent = false)
.positiveButton(android.R.string.ok) { dialog ->
val view = dialog.getCustomView()
// Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.chapters_picker)
np.clearFocus()
(targetController as? Listener)?.setChaptersRead(item, np.value)
}
.negativeButton(android.R.string.cancel)
(targetController as? Listener)?.setChaptersRead(item, np.value)
}
.negativeButton(android.R.string.cancel)
val view = dialog.getCustomView()
val np: NumberPicker = view.findViewById(R.id.chapters_picker)

View File

@@ -20,9 +20,11 @@ class SetTrackReadingDatesDialog<T> : DialogController
private val dateToUpdate: ReadingDate
constructor(target: T, dateToUpdate: ReadingDate, item: TrackItem) : super(Bundle().apply {
putSerializable(SetTrackReadingDatesDialog.KEY_ITEM_TRACK, item.track)
}) {
constructor(target: T, dateToUpdate: ReadingDate, item: TrackItem) : super(
Bundle().apply {
putSerializable(SetTrackReadingDatesDialog.KEY_ITEM_TRACK, item.track)
}
) {
targetController = target
this.item = item
this.dateToUpdate = dateToUpdate
@@ -40,16 +42,18 @@ class SetTrackReadingDatesDialog<T> : DialogController
val listener = (targetController as? Listener)
return MaterialDialog(activity!!)
.title(when (dateToUpdate) {
.title(
when (dateToUpdate) {
ReadingDate.Start -> R.string.track_started_reading_date
ReadingDate.Finish -> R.string.track_finished_reading_date
})
.datePicker(currentDate = getCurrentDate()) { _, date ->
listener?.setReadingDate(item, dateToUpdate, date.timeInMillis)
}
.neutralButton(R.string.action_remove) {
listener?.setReadingDate(item, dateToUpdate, 0L)
}
)
.datePicker(currentDate = getCurrentDate()) { _, date ->
listener?.setReadingDate(item, dateToUpdate, date.timeInMillis)
}
.neutralButton(R.string.action_remove) {
listener?.setReadingDate(item, dateToUpdate, 0L)
}
}
private fun getCurrentDate(): Calendar {
@@ -60,8 +64,9 @@ class SetTrackReadingDatesDialog<T> : DialogController
ReadingDate.Start -> it.started_reading_date
ReadingDate.Finish -> it.finished_reading_date
}
if (date != 0L)
if (date != 0L) {
timeInMillis = date
}
}
}
}

View File

@@ -19,9 +19,11 @@ class SetTrackScoreDialog<T> : DialogController
private val item: TrackItem
constructor(target: T, item: TrackItem) : super(Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track)
}) {
constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track)
}
) {
targetController = target
this.item = item
}
@@ -37,17 +39,17 @@ class SetTrackScoreDialog<T> : DialogController
val item = item
val dialog = MaterialDialog(activity!!)
.title(R.string.score)
.customView(R.layout.track_score_dialog, dialogWrapContent = false)
.positiveButton(android.R.string.ok) { dialog ->
val view = dialog.getCustomView()
// Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.score_picker)
np.clearFocus()
.title(R.string.score)
.customView(R.layout.track_score_dialog, dialogWrapContent = false)
.positiveButton(android.R.string.ok) { dialog ->
val view = dialog.getCustomView()
// Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.score_picker)
np.clearFocus()
(targetController as? Listener)?.setScore(item, np.value)
}
.negativeButton(android.R.string.cancel)
(targetController as? Listener)?.setScore(item, np.value)
}
.negativeButton(android.R.string.cancel)
val view = dialog.getCustomView()
val np: NumberPicker = view.findViewById(R.id.score_picker)

View File

@@ -17,9 +17,11 @@ class SetTrackStatusDialog<T> : DialogController
private val item: TrackItem
constructor(target: T, item: TrackItem) : super(Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track)
}) {
constructor(target: T, item: TrackItem) : super(
Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track)
}
) {
targetController = target
this.item = item
}
@@ -38,16 +40,16 @@ class SetTrackStatusDialog<T> : DialogController
val selectedIndex = statusList.indexOf(item.track?.status)
return MaterialDialog(activity!!)
.title(R.string.status)
.negativeButton(android.R.string.cancel)
.listItemsSingleChoice(
items = statusString,
initialSelection = selectedIndex,
waitForPositiveButton = false
) { dialog, position, _ ->
(targetController as? Listener)?.setStatus(item, position)
dialog.dismiss()
}
.title(R.string.status)
.negativeButton(android.R.string.cancel)
.listItemsSingleChoice(
items = statusString,
initialSelection = selectedIndex,
waitForPositiveButton = false
) { dialog, position, _ ->
(targetController as? Listener)?.setStatus(item, position)
dialog.dismiss()
}
}
interface Listener {

View File

@@ -17,12 +17,13 @@ import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
import timber.log.Timber
class TrackController : NucleusController<TrackControllerBinding, TrackPresenter>(),
TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener,
SetTrackChaptersDialog.Listener,
SetTrackScoreDialog.Listener,
SetTrackReadingDatesDialog.Listener {
class TrackController :
NucleusController<TrackControllerBinding, TrackPresenter>(),
TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener,
SetTrackChaptersDialog.Listener,
SetTrackScoreDialog.Listener,
SetTrackReadingDatesDialog.Listener {
private var adapter: TrackAdapter? = null

View File

@@ -56,15 +56,15 @@ class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) {
if (track != null) {
track_title.text = track.title
track_chapters.text = "${track.last_chapter_read}/" +
if (track.total_chapters > 0) track.total_chapters else "-"
if (track.total_chapters > 0) track.total_chapters else "-"
track_status.text = item.service.getStatus(track.status)
track_score.text = if (track.score == 0f) "-" else item.service.displayScore(track)
if (item.service.supportsReadingDates) {
track_start_date.text =
if (track.started_reading_date != 0L) dateFormat.format(track.started_reading_date) else "-"
if (track.started_reading_date != 0L) dateFormat.format(track.started_reading_date) else "-"
track_finish_date.text =
if (track.finished_reading_date != 0L) dateFormat.format(track.finished_reading_date) else "-"
if (track.finished_reading_date != 0L) dateFormat.format(track.finished_reading_date) else "-"
} else {
bottom_divider.gone()
vert_divider_3.gone()

View File

@@ -43,52 +43,60 @@ class TrackPresenter(
fun fetchTrackings() {
trackSubscription?.let { remove(it) }
trackSubscription = db.getTracks(manga)
.asRxObservable()
.map { tracks ->
loggedServices.map { service ->
TrackItem(tracks.find { it.sync_id == service.id }, service)
}
.asRxObservable()
.map { tracks ->
loggedServices.map { service ->
TrackItem(tracks.find { it.sync_id == service.id }, service)
}
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { trackList = it }
.subscribeLatestCache(TrackController::onNextTrackings)
}
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { trackList = it }
.subscribeLatestCache(TrackController::onNextTrackings)
}
fun refresh() {
refreshSubscription?.let { remove(it) }
refreshSubscription = Observable.from(trackList)
.filter { it.track != null }
.concatMap { item ->
item.service.refresh(item.track!!)
.flatMap { db.insertTrack(it).asRxObservable() }
.map { item }
.onErrorReturn { item }
}
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> view.onRefreshDone() },
TrackController::onRefreshError)
.filter { it.track != null }
.concatMap { item ->
item.service.refresh(item.track!!)
.flatMap { db.insertTrack(it).asRxObservable() }
.map { item }
.onErrorReturn { item }
}
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, _ -> view.onRefreshDone() },
TrackController::onRefreshError
)
}
fun search(query: String, service: TrackService) {
searchSubscription?.let { remove(it) }
searchSubscription = service.search(query)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(TrackController::onSearchResults,
TrackController::onSearchResultsError)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(
TrackController::onSearchResults,
TrackController::onSearchResultsError
)
}
fun registerTracking(item: Track?, service: TrackService) {
if (item != null) {
item.manga_id = manga.id!!
add(service.bind(item)
add(
service.bind(item)
.flatMap { db.insertTrack(item).asRxObservable() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ },
{ error -> context.toast(error.message) }))
.subscribe(
{ },
{ error -> context.toast(error.message) }
)
)
} else {
unregisterTracking(service)
}
@@ -100,16 +108,18 @@ class TrackPresenter(
private fun updateRemote(track: Track, service: TrackService) {
service.update(track)
.flatMap { db.insertTrack(track).asRxObservable() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> view.onRefreshDone() },
{ view, error ->
view.onRefreshError(error)
.flatMap { db.insertTrack(track).asRxObservable() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, _ -> view.onRefreshDone() },
{ view, error ->
view.onRefreshError(error)
// Restart on error to set old values
fetchTrackings()
})
// Restart on error to set old values
fetchTrackings()
}
)
}
fun setStatus(item: TrackItem, index: Int) {

View File

@@ -56,10 +56,10 @@ class TrackSearchAdapter(context: Context) :
GlideApp.with(view.context).clear(view.track_search_cover)
if (!track.cover_url.isEmpty()) {
GlideApp.with(view.context)
.load(track.cover_url)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(view.track_search_cover)
.load(track.cover_url)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(view.track_search_cover)
}
if (track.publishing_status.isBlank()) {

View File

@@ -40,9 +40,11 @@ class TrackSearchDialog : DialogController {
private val trackController
get() = targetController as TrackController
constructor(target: TrackController, service: TrackService) : super(Bundle().apply {
putInt(KEY_SERVICE, service.id)
}) {
constructor(target: TrackController, service: TrackService) : super(
Bundle().apply {
putInt(KEY_SERVICE, service.id)
}
) {
targetController = target
this.service = service
}
@@ -54,10 +56,10 @@ class TrackSearchDialog : DialogController {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dialog = MaterialDialog(activity!!)
.customView(R.layout.track_search_dialog)
.positiveButton(android.R.string.ok) { onPositiveButtonClick() }
.negativeButton(android.R.string.cancel)
.neutralButton(R.string.action_remove) { onRemoveButtonClick() }
.customView(R.layout.track_search_dialog)
.positiveButton(android.R.string.ok) { onPositiveButtonClick() }
.negativeButton(android.R.string.cancel)
.neutralButton(R.string.action_remove) { onRemoveButtonClick() }
dialogView = dialog.view
onViewCreated(dialog.view, savedViewState)

View File

@@ -4,7 +4,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
class MangaAdapter(controller: MigrationController) :
FlexibleAdapter<IFlexible<*>>(null, controller) {
FlexibleAdapter<IFlexible<*>>(null, controller) {
private var items: List<IFlexible<*>>? = null

View File

@@ -27,11 +27,11 @@ class MangaHolder(
// Update the cover.
GlideApp.with(itemView.context).clear(thumbnail)
GlideApp.with(itemView.context)
.load(item.manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.circleCrop()
.dontAnimate()
.into(thumbnail)
.load(item.manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.circleCrop()
.dontAnimate()
.into(thumbnail)
}
}

View File

@@ -24,7 +24,6 @@ class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>() {
position: Int,
payloads: List<Any?>?
) {
holder.bind(this)
}

View File

@@ -11,9 +11,10 @@ import eu.kanade.tachiyomi.databinding.MigrationControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
class MigrationController : NucleusController<MigrationControllerBinding, MigrationPresenter>(),
FlexibleAdapter.OnItemClickListener,
SourceAdapter.OnSelectClickListener {
class MigrationController :
NucleusController<MigrationControllerBinding, MigrationPresenter>(),
FlexibleAdapter.OnItemClickListener,
SourceAdapter.OnSelectClickListener {
private var adapter: FlexibleAdapter<IFlexible<*>>? = null

View File

@@ -31,18 +31,19 @@ class MigrationPresenter(
super.onCreate(savedState)
db.getFavoriteMangas()
.asRxObservable()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
.combineLatest(stateRelay.map { it.selectedSource }
.distinctUntilChanged()
) { library, source -> library to source }
.filter { (_, source) -> source != null }
.observeOn(Schedulers.io())
.map { (library, source) -> libraryToMigrationItem(library, source!!.id) }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { state = state.copy(mangaForSource = it) }
.subscribe()
.asRxObservable()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
.combineLatest(
stateRelay.map { it.selectedSource }
.distinctUntilChanged()
) { library, source -> library to source }
.filter { (_, source) -> source != null }
.observeOn(Schedulers.io())
.map { (library, source) -> libraryToMigrationItem(library, source!!.id) }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { state = state.copy(mangaForSource = it) }
.subscribe()
// Render the view when any field changes
stateRelay.subscribeLatestCache(MigrationController::render)
@@ -59,8 +60,8 @@ class MigrationPresenter(
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
val header = SelectionHeader()
return library.map { it.source }.toSet()
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
.map { SourceItem(it, header) }
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
.map { SourceItem(it, header) }
}
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {

View File

@@ -8,8 +8,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchPresenter
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible
import uy.kohesive.injekt.injectLazy
@@ -81,22 +81,22 @@ class SearchController(
val preselected = MigrationFlags.getEnabledFlagsPositions(prefValue)
return MaterialDialog(activity!!)
.message(R.string.migration_dialog_what_to_include)
.listItemsMultiChoice(
items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence },
initialSelection = preselected.toIntArray()
) { _, positions, _ ->
// Save current settings for the next time
val newValue = MigrationFlags.getFlagsFromPositions(positions.toTypedArray())
preferences.migrateFlags().set(newValue)
}
.positiveButton(R.string.migrate) {
(targetController as? SearchController)?.migrateManga()
}
.negativeButton(R.string.copy) {
(targetController as? SearchController)?.copyManga()
}
.neutralButton(android.R.string.cancel)
.message(R.string.migration_dialog_what_to_include)
.listItemsMultiChoice(
items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence },
initialSelection = preselected.toIntArray()
) { _, positions, _ ->
// Save current settings for the next time
val newValue = MigrationFlags.getFlagsFromPositions(positions.toTypedArray())
preferences.migrateFlags().set(newValue)
}
.positiveButton(R.string.migrate) {
(targetController as? SearchController)?.migrateManga()
}
.negativeButton(R.string.copy) {
(targetController as? SearchController)?.copyManga()
}
.neutralButton(android.R.string.cancel)
}
}
}

View File

@@ -8,9 +8,9 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchCardItem
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchItem
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchPresenter
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchCardItem
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchItem
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
@@ -32,7 +32,7 @@ class SearchPresenter(
override fun getEnabledSources(): List<CatalogueSource> {
// Put the source of the selected manga at the top
return super.getEnabledSources()
.sortedByDescending { it.id == manga.source }
.sortedByDescending { it.id == manga.source }
}
override fun createCatalogueSearchItem(source: CatalogueSource, results: List<GlobalSearchCardItem>?): GlobalSearchItem {
@@ -53,13 +53,13 @@ class SearchPresenter(
replacingMangaRelay.call(true)
Observable.defer { source.fetchChapterList(manga) }
.onErrorReturn { emptyList() }
.doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) }
.onErrorReturn { emptyList() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe { replacingMangaRelay.call(false) }
.subscribe()
.onErrorReturn { emptyList() }
.doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) }
.onErrorReturn { emptyList() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe { replacingMangaRelay.call(false) }
.subscribe()
}
private fun migrateMangaInternal(

View File

@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
* @param controller instance of [MigrationController].
*/
class SourceAdapter(val controller: MigrationController) :
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
val cardBackground = controller.activity!!.getResourceColor(R.attr.colorSurface)

View File

@@ -15,8 +15,8 @@ import kotlinx.android.synthetic.main.source_main_controller_card_item.source_la
import kotlinx.android.synthetic.main.source_main_controller_card_item.title
class SourceHolder(view: View, override val adapter: SourceAdapter) :
BaseFlexibleViewHolder(view, adapter),
SlicedHolder {
BaseFlexibleViewHolder(view, adapter),
SlicedHolder {
override val slice = Slice(card).apply {
setColor(adapter.cardBackground)

View File

@@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.source.Source
* @param header The header for this item.
*/
data class SourceItem(val source: Source, val header: SelectionHeader? = null) :
AbstractSectionableItem<SourceHolder, SelectionHeader>(header) {
AbstractSectionableItem<SourceHolder, SelectionHeader>(header) {
/**
* Returns the layout resource of this item.
@@ -40,7 +40,6 @@ data class SourceItem(val source: Source, val header: SelectionHeader? = null) :
position: Int,
payloads: List<Any?>?
) {
holder.bind(this)
}
}

View File

@@ -50,10 +50,11 @@ class AboutController : SettingsController() {
preference {
titleRes = R.string.version
summary = if (BuildConfig.DEBUG)
summary = if (BuildConfig.DEBUG) {
"Preview r${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA})"
else
} else {
"Stable ${BuildConfig.VERSION_NAME}"
}
}
preference {
titleRes = R.string.build_time
@@ -157,24 +158,26 @@ class AboutController : SettingsController() {
class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) {
constructor(body: String, url: String) : this(Bundle().apply {
putString(BODY_KEY, body)
putString(URL_KEY, url)
})
constructor(body: String, url: String) : this(
Bundle().apply {
putString(BODY_KEY, body)
putString(URL_KEY, url)
}
)
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(res = R.string.update_check_notification_update_available)
.message(text = args.getString(BODY_KEY) ?: "")
.positiveButton(R.string.update_check_confirm) {
val appContext = applicationContext
if (appContext != null) {
// Start download
val url = args.getString(URL_KEY) ?: ""
UpdaterService.downloadUpdate(appContext, url)
}
.title(res = R.string.update_check_notification_update_available)
.message(text = args.getString(BODY_KEY) ?: "")
.positiveButton(R.string.update_check_confirm) {
val appContext = applicationContext
if (appContext != null) {
// Start download
val url = args.getString(URL_KEY) ?: ""
UpdaterService.downloadUpdate(appContext, url)
}
.negativeButton(R.string.update_check_ignore)
}
.negativeButton(R.string.update_check_ignore)
}
private companion object {
@@ -190,7 +193,8 @@ class AboutController : SettingsController() {
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
val outputDf = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault())
DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault()
)
outputDf.timeZone = TimeZone.getDefault()
buildTime.toDateTimestampString(dateFormat)

View File

@@ -26,7 +26,8 @@ class ChapterLoadByNumber {
// If there is only one chapter for this number, use it
chaptersForNumber.size == 1 -> chaptersForNumber.first()
// Prefer a chapter of the same scanlator as the selected
else -> chaptersForNumber.find { it.scanlator == selectedChapter.scanlator }
else ->
chaptersForNumber.find { it.scanlator == selectedChapter.scanlator }
?: chaptersForNumber.first()
}
chapters.add(preferredChapter)

View File

@@ -47,8 +47,8 @@ class PageIndicatorTextView(
// A span object with text outlining properties
val spanOutline = OutlineSpan(
strokeColor = strokeColor,
strokeWidth = 4f
strokeColor = strokeColor,
strokeWidth = 4f
)
}
}

View File

@@ -127,10 +127,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* Called when the activity is created. Initializes the presenter and configuration.
*/
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(when (preferences.readerTheme().get()) {
0 -> R.style.Theme_Reader_Light
else -> R.style.Theme_Reader
})
setTheme(
when (preferences.readerTheme().get()) {
0 -> R.style.Theme_Reader_Light
else -> R.style.Theme_Reader
}
)
super.onCreate(savedInstanceState)
binding = ReaderActivityBinding.inflate(layoutInflater)
@@ -275,10 +277,11 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
ViewCompat.setOnApplyWindowInsetsListener(binding.readerMenu) { _, insets ->
if (!window.isDefaultBar()) {
binding.readerMenu.setPadding(
insets.systemWindowInsetLeft,
insets.systemWindowInsetTop,
insets.systemWindowInsetRight,
insets.systemWindowInsetBottom)
insets.systemWindowInsetLeft,
insets.systemWindowInsetTop,
insets.systemWindowInsetRight,
insets.systemWindowInsetBottom
)
}
insets
}
@@ -293,18 +296,20 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
})
binding.leftChapter.setOnClickListener {
if (viewer != null) {
if (viewer is R2LPagerViewer)
if (viewer is R2LPagerViewer) {
loadNextChapter()
else
} else {
loadPreviousChapter()
}
}
}
binding.rightChapter.setOnClickListener {
if (viewer != null) {
if (viewer is R2LPagerViewer)
if (viewer is R2LPagerViewer) {
loadPreviousChapter()
else
} else {
loadNextChapter()
}
}
}
@@ -590,11 +595,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* depending on the [result].
*/
fun onSetAsCoverResult(result: ReaderPresenter.SetAsCoverResult) {
toast(when (result) {
Success -> R.string.cover_updated
AddToLibraryFirst -> R.string.notification_first_add_to_library
Error -> R.string.notification_cover_update_failed
})
toast(
when (result) {
Success -> R.string.cover_updated
AddToLibraryFirst -> R.string.notification_first_add_to_library
Error -> R.string.notification_cover_update_failed
}
)
}
/**
@@ -614,10 +621,10 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
val sharedRotation = preferences.rotation().asObservable().share()
val initialRotation = sharedRotation.take(1)
val rotationUpdates = sharedRotation.skip(1)
.delay(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.delay(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
subscriptions += Observable.merge(initialRotation, rotationUpdates)
.subscribe { setOrientation(it) }
.subscribe { setOrientation(it) }
preferences.readerTheme().asFlow()
.drop(1) // We only care about updates
@@ -700,10 +707,11 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* Sets the 32-bit color mode according to [enabled].
*/
private fun setTrueColor(enabled: Boolean) {
if (enabled)
if (enabled) {
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.ARGB_8888)
else
} else {
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.RGB_565)
}
}
@TargetApi(Build.VERSION_CODES.P)

View File

@@ -214,8 +214,9 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet
brightness_overlay.gone()
}
if (!isDisabled)
if (!isDisabled) {
txt_brightness_seekbar_value.text = value.toString()
}
}
/**

View File

@@ -17,14 +17,16 @@ class ReaderColorFilterView(
fun setFilterColor(color: Int, filterMode: Int) {
colorFilterPaint.color = color
colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) {
1 -> PorterDuff.Mode.MULTIPLY
2 -> PorterDuff.Mode.SCREEN
3 -> PorterDuff.Mode.OVERLAY
4 -> PorterDuff.Mode.LIGHTEN
5 -> PorterDuff.Mode.DARKEN
else -> PorterDuff.Mode.SRC_OVER
})
colorFilterPaint.xfermode = PorterDuffXfermode(
when (filterMode) {
1 -> PorterDuff.Mode.MULTIPLY
2 -> PorterDuff.Mode.SCREEN
3 -> PorterDuff.Mode.OVERLAY
4 -> PorterDuff.Mode.LIGHTEN
5 -> PorterDuff.Mode.DARKEN
else -> PorterDuff.Mode.SRC_OVER
}
)
invalidate()
}

View File

@@ -44,13 +44,13 @@ class ReaderPageSheet(
if (page.status != Page.READY) return
MaterialDialog(activity)
.message(R.string.confirm_set_image_as_cover)
.positiveButton(android.R.string.ok) {
activity.setAsCover(page)
dismiss()
}
.negativeButton(android.R.string.cancel)
.show()
.message(R.string.confirm_set_image_as_cover)
.positiveButton(android.R.string.ok) {
activity.setAsCover(page)
dismiss()
}
.negativeButton(android.R.string.cancel)
.show()
}
/**

View File

@@ -88,38 +88,40 @@ class ReaderPresenter(
val dbChapters = db.getChapters(manga).executeAsBlocking()
val selectedChapter = dbChapters.find { it.id == chapterId }
?: error("Requested chapter of id $chapterId not found in chapter list")
?: error("Requested chapter of id $chapterId not found in chapter list")
val chaptersForReader =
if (preferences.skipRead() || preferences.skipFiltered()) {
val list = dbChapters
.filter {
if (preferences.skipRead() && it.read) {
if (preferences.skipRead() || preferences.skipFiltered()) {
val list = dbChapters
.filter {
if (preferences.skipRead() && it.read) {
return@filter false
} else if (preferences.skipFiltered()) {
if (
(manga.readFilter == Manga.SHOW_READ && !it.read) ||
(manga.readFilter == Manga.SHOW_UNREAD && it.read) ||
(
manga.downloadedFilter == Manga.SHOW_DOWNLOADED &&
!downloadManager.isChapterDownloaded(it, manga)
) ||
(manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED && !it.bookmark)
) {
return@filter false
} else if (preferences.skipFiltered()) {
if (
(manga.readFilter == Manga.SHOW_READ && !it.read) ||
(manga.readFilter == Manga.SHOW_UNREAD && it.read) ||
(manga.downloadedFilter == Manga.SHOW_DOWNLOADED &&
!downloadManager.isChapterDownloaded(it, manga)) ||
(manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED && !it.bookmark)
) {
return@filter false
}
}
true
}
.toMutableList()
val find = list.find { it.id == chapterId }
if (find == null) {
list.add(selectedChapter)
true
}
list
} else {
dbChapters
.toMutableList()
val find = list.find { it.id == chapterId }
if (find == null) {
list.add(selectedChapter)
}
list
} else {
dbChapters
}
when (manga.sorting) {
Manga.SORTING_SOURCE -> ChapterLoadBySource().get(chaptersForReader)
@@ -198,12 +200,15 @@ class ReaderPresenter(
if (!needsInit()) return
db.getManga(mangaId).asRxObservable()
.first()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { init(it, initialChapterId) }
.subscribeFirst({ _, _ ->
.first()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { init(it, initialChapterId) }
.subscribeFirst(
{ _, _ ->
// Ignore onNext event
}, ReaderActivity::setInitialChapterError)
},
ReaderActivity::setInitialChapterError
)
}
/**
@@ -241,13 +246,16 @@ class ReaderPresenter(
// Read chapterList from an io thread because it's retrieved lazily and would block main.
activeChapterSubscription?.unsubscribe()
activeChapterSubscription = Observable
.fromCallable { chapterList.first { chapterId == it.chapter.id } }
.flatMap { getLoadObservable(loader!!, it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst({ _, _ ->
.fromCallable { chapterList.first { chapterId == it.chapter.id } }
.flatMap { getLoadObservable(loader!!, it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ _, _ ->
// Ignore onNext event
}, ReaderActivity::setInitialChapterError)
},
ReaderActivity::setInitialChapterError
)
}
/**
@@ -262,23 +270,27 @@ class ReaderPresenter(
chapter: ReaderChapter
): Observable<ViewerChapters> {
return loader.loadChapter(chapter)
.andThen(Observable.fromCallable {
.andThen(
Observable.fromCallable {
val chapterPos = chapterList.indexOf(chapter)
ViewerChapters(chapter,
chapterList.getOrNull(chapterPos - 1),
chapterList.getOrNull(chapterPos + 1))
})
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { newChapters ->
val oldChapters = viewerChaptersRelay.value
// Add new references first to avoid unnecessary recycling
newChapters.ref()
oldChapters?.unref()
viewerChaptersRelay.call(newChapters)
ViewerChapters(
chapter,
chapterList.getOrNull(chapterPos - 1),
chapterList.getOrNull(chapterPos + 1)
)
}
)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { newChapters ->
val oldChapters = viewerChaptersRelay.value
// Add new references first to avoid unnecessary recycling
newChapters.ref()
oldChapters?.unref()
viewerChaptersRelay.call(newChapters)
}
}
/**
@@ -292,10 +304,10 @@ class ReaderPresenter(
activeChapterSubscription?.unsubscribe()
activeChapterSubscription = getLoadObservable(loader, chapter)
.toCompletable()
.onErrorComplete()
.subscribe()
.also(::add)
.toCompletable()
.onErrorComplete()
.subscribe()
.also(::add)
}
/**
@@ -310,13 +322,16 @@ class ReaderPresenter(
activeChapterSubscription?.unsubscribe()
activeChapterSubscription = getLoadObservable(loader, chapter)
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
.subscribeFirst({ view, _ ->
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
.subscribeFirst(
{ view, _ ->
view.moveToPageIndex(0)
}, { _, _ ->
},
{ _, _ ->
// Ignore onError event, viewers handle that state
})
}
)
}
/**
@@ -333,12 +348,12 @@ class ReaderPresenter(
val loader = loader ?: return
loader.loadChapter(chapter)
.observeOn(AndroidSchedulers.mainThread())
// Update current chapters whenever a chapter is preloaded
.doOnCompleted { viewerChaptersRelay.value?.let(viewerChaptersRelay::call) }
.onErrorComplete()
.subscribe()
.also(::add)
.observeOn(AndroidSchedulers.mainThread())
// Update current chapters whenever a chapter is preloaded
.doOnCompleted { viewerChaptersRelay.value?.let(viewerChaptersRelay::call) }
.onErrorComplete()
.subscribe()
.also(::add)
}
/**
@@ -380,9 +395,9 @@ class ReaderPresenter(
*/
private fun saveChapterProgress(chapter: ReaderChapter) {
db.updateChapterProgress(chapter.chapter).asRxCompletable()
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
@@ -391,9 +406,9 @@ class ReaderPresenter(
private fun saveChapterHistory(chapter: ReaderChapter) {
val history = History.create(chapter.chapter).apply { last_read = Date().time }
db.updateHistoryLastRead(history).asRxCompletable()
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
@@ -456,18 +471,18 @@ class ReaderPresenter(
db.updateMangaViewer(manga).executeAsBlocking()
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ ->
val currChapters = viewerChaptersRelay.value
if (currChapters != null) {
// Save current page
val currChapter = currChapters.currChapter
currChapter.requestedPage = currChapter.chapter.last_page_read
.subscribeFirst({ view, _ ->
val currChapters = viewerChaptersRelay.value
if (currChapters != null) {
// Save current page
val currChapter = currChapters.currChapter
currChapter.requestedPage = currChapter.chapter.last_page_read
// Emit manga and chapters to the new viewer
view.setManga(manga)
view.setChapters(currChapters)
}
})
// Emit manga and chapters to the new viewer
view.setManga(manga)
view.setChapters(currChapters)
}
})
}
/**
@@ -484,7 +499,7 @@ class ReaderPresenter(
// Build destination file.
val filenameSuffix = " - ${page.number}.${type.extension}"
val filename = DiskUtil.buildValidFilename(
"${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
"${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
) + filenameSuffix
val destFile = File(directory, filename)
@@ -509,23 +524,25 @@ class ReaderPresenter(
notifier.onClear()
// Pictures directory.
val destDir = File(Environment.getExternalStorageDirectory().absolutePath +
val destDir = File(
Environment.getExternalStorageDirectory().absolutePath +
File.separator + Environment.DIRECTORY_PICTURES +
File.separator + "Tachiyomi")
File.separator + "Tachiyomi"
)
// Copy file in background.
Observable.fromCallable { saveImage(page, destDir, manga) }
.doOnNext { file ->
DiskUtil.scanMedia(context, file)
notifier.onComplete(file)
}
.doOnError { notifier.onError(it.message) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
{ view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
)
.doOnNext { file ->
DiskUtil.scanMedia(context, file)
notifier.onComplete(file)
}
.doOnError { notifier.onError(it.message) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
{ view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
)
}
/**
@@ -543,13 +560,13 @@ class ReaderPresenter(
val destDir = File(context.cacheDir, "shared_image")
Observable.fromCallable { destDir.deleteRecursively() } // Keep only the last shared file
.map { saveImage(page, destDir, manga) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, file -> view.onShareImageResult(file) },
{ _, _ -> /* Empty */ }
)
.map { saveImage(page, destDir, manga) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, file -> view.onShareImageResult(file) },
{ _, _ -> /* Empty */ }
)
}
/**
@@ -561,28 +578,28 @@ class ReaderPresenter(
val stream = page.stream ?: return
Observable
.fromCallable {
if (manga.source == LocalSource.ID) {
val context = Injekt.get<Application>()
LocalSource.updateCover(context, manga, stream())
R.string.cover_updated
.fromCallable {
if (manga.source == LocalSource.ID) {
val context = Injekt.get<Application>()
LocalSource.updateCover(context, manga, stream())
R.string.cover_updated
SetAsCoverResult.Success
} else {
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
if (manga.favorite) {
coverCache.copyToCache(thumbUrl, stream())
SetAsCoverResult.Success
} else {
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
if (manga.favorite) {
coverCache.copyToCache(thumbUrl, stream())
SetAsCoverResult.Success
} else {
SetAsCoverResult.AddToLibraryFirst
}
SetAsCoverResult.AddToLibraryFirst
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, result -> view.onSetAsCoverResult(result) },
{ view, _ -> view.onSetAsCoverResult(SetAsCoverResult.Error) }
)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, result -> view.onSetAsCoverResult(result) },
{ view, _ -> view.onSetAsCoverResult(SetAsCoverResult.Error) }
)
}
/**
@@ -613,8 +630,9 @@ class ReaderPresenter(
val trackManager = Injekt.get<TrackManager>()
db.getTracks(manga).asRxSingle()
.flatMapCompletable { trackList ->
Completable.concat(trackList.map { track ->
.flatMapCompletable { trackList ->
Completable.concat(
trackList.map { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged && chapterRead > track.last_chapter_read) {
track.last_chapter_read = chapterRead
@@ -622,17 +640,18 @@ class ReaderPresenter(
// We wan't these to execute even if the presenter is destroyed and leaks
// for a while. The view can still be garbage collected.
Observable.defer { service.update(track) }
.map { db.insertTrack(track).executeAsBlocking() }
.toCompletable()
.onErrorComplete()
.map { db.insertTrack(track).executeAsBlocking() }
.toCompletable()
.onErrorComplete()
} else {
Completable.complete()
}
})
}
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
)
}
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
@@ -648,19 +667,19 @@ class ReaderPresenter(
if (removeAfterReadSlots == -1) return
Completable
.fromCallable {
// Position of the read chapter
val position = chapterList.indexOf(chapter)
.fromCallable {
// Position of the read chapter
val position = chapterList.indexOf(chapter)
// Retrieve chapter to delete according to preference
val chapterToDelete = chapterList.getOrNull(position - removeAfterReadSlots)
if (chapterToDelete != null) {
downloadManager.enqueueDeleteChapters(listOf(chapterToDelete.chapter), manga)
}
// Retrieve chapter to delete according to preference
val chapterToDelete = chapterList.getOrNull(position - removeAfterReadSlots)
if (chapterToDelete != null) {
downloadManager.enqueueDeleteChapters(listOf(chapterToDelete.chapter), manga)
}
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
@@ -669,9 +688,9 @@ class ReaderPresenter(
*/
private fun deletePendingChapters() {
Completable.fromCallable { downloadManager.deletePendingChapters() }
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
companion object {

View File

@@ -37,12 +37,12 @@ class SaveImageNotifier(private val context: Context) {
*/
fun onComplete(file: File) {
val bitmap = GlideApp.with(context)
.asBitmap()
.load(file)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.submit(720, 1280)
.get()
.asBitmap()
.load(file)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.submit(720, 1280)
.get()
if (bitmap != null) {
showCompleteNotification(file, bitmap)
@@ -66,13 +66,17 @@ class SaveImageNotifier(private val context: Context) {
setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
// Share action
addAction(R.drawable.ic_share_24dp,
context.getString(R.string.action_share),
NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId))
addAction(
R.drawable.ic_share_24dp,
context.getString(R.string.action_share),
NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId)
)
// Delete action
addAction(R.drawable.ic_delete_24dp,
context.getString(R.string.action_delete),
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId))
addAction(
R.drawable.ic_delete_24dp,
context.getString(R.string.action_delete),
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId)
)
updateNotification()
}

View File

@@ -31,34 +31,34 @@ class ChapterLoader(
}
return Observable.just(chapter)
.doOnNext { chapter.state = ReaderChapter.State.Loading }
.observeOn(Schedulers.io())
.flatMap { readerChapter ->
Timber.d("Loading pages for ${chapter.chapter.name}")
.doOnNext { chapter.state = ReaderChapter.State.Loading }
.observeOn(Schedulers.io())
.flatMap { readerChapter ->
Timber.d("Loading pages for ${chapter.chapter.name}")
val loader = getPageLoader(readerChapter)
chapter.pageLoader = loader
val loader = getPageLoader(readerChapter)
chapter.pageLoader = loader
loader.getPages().take(1).doOnNext { pages ->
pages.forEach { it.chapter = chapter }
}
loader.getPages().take(1).doOnNext { pages ->
pages.forEach { it.chapter = chapter }
}
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { pages ->
if (pages.isEmpty()) {
throw Exception("Page list is empty")
}
chapter.state = ReaderChapter.State.Loaded(pages)
// If the chapter is partially read, set the starting page to the last the user read
// otherwise use the requested page.
if (!chapter.chapter.read) {
chapter.requestedPage = chapter.chapter.last_page_read
}
}
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { pages ->
if (pages.isEmpty()) {
throw Exception("Page list is empty")
}
.toCompletable()
.doOnError { chapter.state = ReaderChapter.State.Error(it) }
chapter.state = ReaderChapter.State.Loaded(pages)
// If the chapter is partially read, set the starting page to the last the user read
// otherwise use the requested page.
if (!chapter.chapter.read) {
chapter.requestedPage = chapter.chapter.last_page_read
}
}
.toCompletable()
.doOnError { chapter.state = ReaderChapter.State.Error(it) }
}
/**

View File

@@ -19,16 +19,16 @@ class DirectoryPageLoader(val file: File) : PageLoader() {
*/
override fun getPages(): Observable<List<ReaderPage>> {
return file.listFiles()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
.sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, file ->
val streamFn = { FileInputStream(file) }
ReaderPage(i).apply {
stream = streamFn
status = Page.READY
}
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
.sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, file ->
val streamFn = { FileInputStream(file) }
ReaderPage(i).apply {
stream = streamFn
status = Page.READY
}
.let { Observable.just(it) }
}
.let { Observable.just(it) }
}
/**

View File

@@ -31,15 +31,15 @@ class DownloadPageLoader(
*/
override fun getPages(): Observable<List<ReaderPage>> {
return downloadManager.buildPageList(source, manga, chapter.chapter)
.map { pages ->
pages.map { page ->
ReaderPage(page.index, page.url, page.imageUrl) {
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
}.apply {
status = Page.READY
}
.map { pages ->
pages.map { page ->
ReaderPage(page.index, page.url, page.imageUrl) {
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
}.apply {
status = Page.READY
}
}
}
}
override fun getPage(page: ReaderPage): Observable<Int> {

View File

@@ -30,24 +30,26 @@ class EpubPageLoader(file: File) : PageLoader() {
*/
override fun getPages(): Observable<List<ReaderPage>> {
return epub.getImagesFromPages()
.mapIndexed { i, path ->
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
ReaderPage(i).apply {
stream = streamFn
status = Page.READY
}
.mapIndexed { i, path ->
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
ReaderPage(i).apply {
stream = streamFn
status = Page.READY
}
.let { Observable.just(it) }
}
.let { Observable.just(it) }
}
/**
* Returns an observable that emits a ready state unless the loader was recycled.
*/
override fun getPage(page: ReaderPage): Observable<Int> {
return Observable.just(if (isRecycled) {
Page.ERROR
} else {
Page.READY
})
return Observable.just(
if (isRecycled) {
Page.ERROR
} else {
Page.READY
}
)
}
}

View File

@@ -42,16 +42,19 @@ class HttpPageLoader(
init {
subscriptions += Observable.defer { Observable.just(queue.take().page) }
.filter { it.status == Page.QUEUE }
.concatMap { source.fetchImageFromCacheThenNet(it) }
.repeat()
.subscribeOn(Schedulers.io())
.subscribe({
}, { error ->
.filter { it.status == Page.QUEUE }
.concatMap { source.fetchImageFromCacheThenNet(it) }
.repeat()
.subscribeOn(Schedulers.io())
.subscribe(
{
},
{ error ->
if (error !is InterruptedException) {
Timber.e(error)
}
})
}
)
}
/**
@@ -66,14 +69,14 @@ class HttpPageLoader(
val pages = chapter.pages
if (pages != null) {
Completable
.fromAction {
// Convert to pages without reader information
val pagesToSave = pages.map { Page(it.index, it.url, it.imageUrl) }
chapterCache.putPageListToCache(chapter.chapter, pagesToSave)
}
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
.fromAction {
// Convert to pages without reader information
val pagesToSave = pages.map { Page(it.index, it.url, it.imageUrl) }
chapterCache.putPageListToCache(chapter.chapter, pagesToSave)
}
.onErrorComplete()
.subscribeOn(Schedulers.io())
.subscribe()
}
}
@@ -83,14 +86,14 @@ class HttpPageLoader(
*/
override fun getPages(): Observable<List<ReaderPage>> {
return chapterCache
.getPageListFromCache(chapter.chapter)
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
.map { pages ->
pages.mapIndexed { index, page ->
// Don't trust sources and use our own indexing
ReaderPage(index, page.url, page.imageUrl)
}
.getPageListFromCache(chapter.chapter)
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
.map { pages ->
pages.mapIndexed { index, page ->
// Don't trust sources and use our own indexing
ReaderPage(index, page.url, page.imageUrl)
}
}
}
/**
@@ -121,16 +124,16 @@ class HttpPageLoader(
queuedPages += preloadNextPages(page, preloadSize)
statusSubject.startWith(page.status)
.doOnUnsubscribe {
queuedPages.forEach {
if (it.page.status == Page.QUEUE) {
queue.remove(it)
}
.doOnUnsubscribe {
queuedPages.forEach {
if (it.page.status == Page.QUEUE) {
queue.remove(it)
}
}
}
}
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
}
/**
@@ -143,12 +146,12 @@ class HttpPageLoader(
if (pageIndex == pages.lastIndex) return emptyList()
return pages
.subList(pageIndex + 1, min(pageIndex + 1 + amount, pages.size))
.mapNotNull {
if (it.status == Page.QUEUE) {
PriorityPage(it, 0).apply { queue.offer(this) }
} else null
}
.subList(pageIndex + 1, min(pageIndex + 1 + amount, pages.size))
.mapNotNull {
if (it.status == Page.QUEUE) {
PriorityPage(it, 0).apply { queue.offer(this) }
} else null
}
}
/**
@@ -186,19 +189,20 @@ class HttpPageLoader(
* @param page the page whose source image has to be downloaded.
*/
private fun HttpSource.fetchImageFromCacheThenNet(page: ReaderPage): Observable<ReaderPage> {
return if (page.imageUrl.isNullOrEmpty())
return if (page.imageUrl.isNullOrEmpty()) {
getImageUrl(page).flatMap { getCachedImage(it) }
else
} else {
getCachedImage(page)
}
}
private fun HttpSource.getImageUrl(page: ReaderPage): Observable<ReaderPage> {
page.status = Page.LOAD_PAGE
return fetchImageUrl(page)
.doOnError { page.status = Page.ERROR }
.onErrorReturn { null }
.doOnNext { page.imageUrl = it }
.map { page }
.doOnError { page.status = Page.ERROR }
.onErrorReturn { null }
.doOnNext { page.imageUrl = it }
.map { page }
}
/**
@@ -211,19 +215,19 @@ class HttpPageLoader(
val imageUrl = page.imageUrl ?: return Observable.just(page)
return Observable.just(page)
.flatMap {
if (!chapterCache.isImageInCache(imageUrl)) {
cacheImage(page)
} else {
Observable.just(page)
}
.flatMap {
if (!chapterCache.isImageInCache(imageUrl)) {
cacheImage(page)
} else {
Observable.just(page)
}
.doOnNext {
page.stream = { chapterCache.getImageFile(imageUrl).inputStream() }
page.status = Page.READY
}
.doOnError { page.status = Page.ERROR }
.onErrorReturn { page }
}
.doOnNext {
page.stream = { chapterCache.getImageFile(imageUrl).inputStream() }
page.status = Page.READY
}
.doOnError { page.status = Page.ERROR }
.onErrorReturn { page }
}
/**
@@ -234,7 +238,7 @@ class HttpPageLoader(
private fun HttpSource.cacheImage(page: ReaderPage): Observable<ReaderPage> {
page.status = Page.DOWNLOAD_IMAGE
return fetchImage(page)
.doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) }
.map { page }
.doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) }
.map { page }
}
}

View File

@@ -43,28 +43,30 @@ class RarPageLoader(file: File) : PageLoader() {
*/
override fun getPages(): Observable<List<ReaderPage>> {
return archive.fileHeaders
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
.sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) })
.mapIndexed { i, header ->
val streamFn = { getStream(header) }
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
.sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) })
.mapIndexed { i, header ->
val streamFn = { getStream(header) }
ReaderPage(i).apply {
stream = streamFn
status = Page.READY
}
ReaderPage(i).apply {
stream = streamFn
status = Page.READY
}
.let { Observable.just(it) }
}
.let { Observable.just(it) }
}
/**
* Returns an observable that emits a ready state unless the loader was recycled.
*/
override fun getPage(page: ReaderPage): Observable<Int> {
return Observable.just(if (isRecycled) {
Page.ERROR
} else {
Page.READY
})
return Observable.just(
if (isRecycled) {
Page.ERROR
} else {
Page.READY
}
)
}
/**

View File

@@ -33,26 +33,28 @@ class ZipPageLoader(file: File) : PageLoader() {
*/
override fun getPages(): Observable<List<ReaderPage>> {
return zip.entries().toList()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, entry ->
val streamFn = { zip.getInputStream(entry) }
ReaderPage(i).apply {
stream = streamFn
status = Page.READY
}
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, entry ->
val streamFn = { zip.getInputStream(entry) }
ReaderPage(i).apply {
stream = streamFn
status = Page.READY
}
.let { Observable.just(it) }
}
.let { Observable.just(it) }
}
/**
* Returns an observable that emits a ready state unless the loader was recycled.
*/
override fun getPage(page: ReaderPage): Observable<Int> {
return Observable.just(if (isRecycled) {
Page.ERROR
} else {
Page.READY
})
return Observable.just(
if (isRecycled) {
Page.ERROR
} else {
Page.READY
}
)
}
}

View File

@@ -8,11 +8,11 @@ import timber.log.Timber
data class ReaderChapter(val chapter: Chapter) {
var state: State =
State.Wait
State.Wait
set(value) {
field = value
stateRelay.call(value)
}
field = value
stateRelay.call(value)
}
private val stateRelay by lazy { BehaviorRelay.create(state) }

View File

@@ -61,9 +61,10 @@ class ReaderProgressBar @JvmOverloads constructor(
* The rotation animation to use while the progress bar is visible.
*/
private val rotationAnimation by lazy {
RotateAnimation(0f, 360f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
RotateAnimation(
0f, 360f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
).apply {
interpolator = LinearInterpolator()
repeatCount = Animation.INFINITE

View File

@@ -126,8 +126,8 @@ class PagerPageHolder(
val loader = page.chapter.pageLoader ?: return
statusSubscription = loader.getPage(page)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { processStatus(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe { processStatus(it) }
}
/**
@@ -137,11 +137,11 @@ class PagerPageHolder(
progressSubscription?.unsubscribe()
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
.map { page.progress }
.distinctUntilChanged()
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> progressBar.setProgress(value) }
.map { page.progress }
.distinctUntilChanged()
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> progressBar.setProgress(value) }
}
/**
@@ -233,25 +233,25 @@ class PagerPageHolder(
var openStream: InputStream? = null
readImageHeaderSubscription = Observable
.fromCallable {
val stream = streamFn().buffered(16)
openStream = stream
.fromCallable {
val stream = streamFn().buffered(16)
openStream = stream
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (!isAnimated) {
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
} else {
initImageView().setImage(openStream!!)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (!isAnimated) {
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
} else {
initImageView().setImage(openStream!!)
}
}
// Keep the Rx stream alive to close the input stream only when unsubscribed
.flatMap { Observable.never<Unit>() }
.doOnUnsubscribe { openStream?.close() }
.subscribe({}, {})
}
// Keep the Rx stream alive to close the input stream only when unsubscribed
.flatMap { Observable.never<Unit>() }
.doOnUnsubscribe { openStream?.close() }
.subscribe({}, {})
}
/**
@@ -283,7 +283,6 @@ class PagerPageHolder(
@SuppressLint("PrivateResource")
private fun createProgressBar(): ReaderProgressBar {
return ReaderProgressBar(context, null).apply {
val size = 48.dpToPx
layoutParams = LayoutParams(size, size).apply {
gravity = Gravity.CENTER
@@ -435,35 +434,35 @@ class PagerPageHolder(
*/
private fun ImageView.setImage(stream: InputStream) {
GlideApp.with(this)
.load(stream)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
onImageDecodeError()
return false
}
.load(stream)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
onImageDecodeError()
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
}
onImageDecoded()
return false
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
}
})
.into(this)
onImageDecoded()
return false
}
})
.into(this)
}
}

View File

@@ -140,17 +140,17 @@ class PagerTransitionHolder(
private fun observeStatus(chapter: ReaderChapter) {
statusSubscription?.unsubscribe()
statusSubscription = chapter.stateObserver
.observeOn(AndroidSchedulers.mainThread())
.subscribe { state ->
pagesContainer.removeAllViews()
when (state) {
is ReaderChapter.State.Wait -> {
}
is ReaderChapter.State.Loading -> setLoading()
is ReaderChapter.State.Error -> setError(state.error)
is ReaderChapter.State.Loaded -> setLoaded()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { state ->
pagesContainer.removeAllViews()
when (state) {
is ReaderChapter.State.Wait -> {
}
is ReaderChapter.State.Loading -> setLoading()
is ReaderChapter.State.Error -> setError(state.error)
is ReaderChapter.State.Loaded -> setLoaded()
}
}
}
/**

View File

@@ -57,12 +57,13 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
// Add next chapter transition and pages.
nextTransition = ChapterTransition.Next(chapters.currChapter, chapters.nextChapter)
.also {
if (forceTransition ||
chapters.nextChapter?.state !is ReaderChapter.State.Loaded) {
newItems.add(it)
}
.also {
if (forceTransition ||
chapters.nextChapter?.state !is ReaderChapter.State.Loaded
) {
newItems.add(it)
}
}
if (chapters.nextChapter != null) {
// Add at most two pages, because this chapter will be selected before the user can

View File

@@ -37,17 +37,18 @@ class WebtoonLayoutManager(activity: ReaderActivity) : LinearLayoutManager(activ
fun findLastEndVisibleItemPosition(): Int {
ensureLayoutState()
@ViewBoundsCheck.ViewBounds val preferredBoundsFlag =
(ViewBoundsCheck.FLAG_CVE_LT_PVE or ViewBoundsCheck.FLAG_CVE_EQ_PVE)
(ViewBoundsCheck.FLAG_CVE_LT_PVE or ViewBoundsCheck.FLAG_CVE_EQ_PVE)
val fromIndex = childCount - 1
val toIndex = -1
val child = if (mOrientation == HORIZONTAL)
val child = if (mOrientation == HORIZONTAL) {
mHorizontalBoundCheck
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
else
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
} else {
mVerticalBoundCheck
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
}
return if (child == null) NO_POSITION else getPosition(child)
}

View File

@@ -162,8 +162,8 @@ class WebtoonPageHolder(
val page = page ?: return
val loader = page.chapter.pageLoader ?: return
statusSubscription = loader.getPage(page)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { processStatus(it) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe { processStatus(it) }
addSubscription(statusSubscription)
}
@@ -177,11 +177,11 @@ class WebtoonPageHolder(
val page = page ?: return
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
.map { page.progress }
.distinctUntilChanged()
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> progressBar.setProgress(value) }
.map { page.progress }
.distinctUntilChanged()
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { value -> progressBar.setProgress(value) }
addSubscription(progressSubscription)
}
@@ -279,29 +279,29 @@ class WebtoonPageHolder(
var openStream: InputStream? = null
readImageHeaderSubscription = Observable
.fromCallable {
val stream = streamFn().buffered(16)
openStream = stream
.fromCallable {
val stream = streamFn().buffered(16)
openStream = stream
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (!isAnimated) {
val subsamplingView = initSubsamplingImageView()
subsamplingView.visible()
subsamplingView.setImage(ImageSource.inputStream(openStream!!))
} else {
val imageView = initImageView()
imageView.visible()
imageView.setImage(openStream!!)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (!isAnimated) {
val subsamplingView = initSubsamplingImageView()
subsamplingView.visible()
subsamplingView.setImage(ImageSource.inputStream(openStream!!))
} else {
val imageView = initImageView()
imageView.visible()
imageView.setImage(openStream!!)
}
}
// Keep the Rx stream alive to close the input stream only when unsubscribed
.flatMap { Observable.never<Unit>() }
.doOnUnsubscribe { openStream?.close() }
.subscribe({}, {})
}
// Keep the Rx stream alive to close the input stream only when unsubscribed
.flatMap { Observable.never<Unit>() }
.doOnUnsubscribe { openStream?.close() }
.subscribe({}, {})
addSubscription(readImageHeaderSubscription)
}
@@ -489,35 +489,35 @@ class WebtoonPageHolder(
*/
private fun ImageView.setImage(stream: InputStream) {
GlideApp.with(this)
.load(stream)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
onImageDecodeError()
return false
}
.load(stream)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
onImageDecodeError()
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
}
onImageDecoded()
return false
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
}
})
.into(this)
onImageDecoded()
return false
}
})
.into(this)
}
}

View File

@@ -59,7 +59,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
super.onScrolled(dx, dy)
val layoutManager = layoutManager
lastVisibleItemPosition =
(layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
(layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
}
@@ -146,13 +146,13 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
}
animate()
.apply {
newX?.let { x(it) }
newY?.let { y(it) }
}
.setInterpolator(DecelerateInterpolator())
.setDuration(400)
.start()
.apply {
newX?.let { x(it) }
newY?.let { y(it) }
}
.setInterpolator(DecelerateInterpolator())
.setDuration(400)
.start()
return true
}
@@ -174,8 +174,9 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
fun onScale(scaleFactor: Float) {
currentScale *= scaleFactor
currentScale = currentScale.coerceIn(
MIN_RATE,
MAX_SCALE_RATE)
MIN_RATE,
MAX_SCALE_RATE
)
setScaleRate(currentScale)

View File

@@ -143,18 +143,18 @@ class WebtoonTransitionHolder(
unsubscribeStatus()
statusSubscription = chapter.stateObserver
.observeOn(AndroidSchedulers.mainThread())
.subscribe { state ->
pagesContainer.removeAllViews()
when (state) {
is ReaderChapter.State.Wait -> {
}
is ReaderChapter.State.Loading -> setLoading()
is ReaderChapter.State.Error -> setError(state.error, transition)
is ReaderChapter.State.Loaded -> setLoaded()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { state ->
pagesContainer.removeAllViews()
when (state) {
is ReaderChapter.State.Wait -> {
}
pagesContainer.visibleIf { pagesContainer.childCount > 0 }
is ReaderChapter.State.Loading -> setLoading()
is ReaderChapter.State.Error -> setError(state.error, transition)
is ReaderChapter.State.Loaded -> setLoaded()
}
pagesContainer.visibleIf { pagesContainer.childCount > 0 }
}
addSubscription(statusSubscription)
}

View File

@@ -291,6 +291,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
val position = layoutManager.findLastEndVisibleItemPosition()
adapter.notifyItemRangeChanged(
max(0, position - 2),
min(position + 2, adapter.itemCount - 1))
min(position + 2, adapter.itemCount - 1)
)
}
}

View File

@@ -26,8 +26,11 @@ class HistoryAdapter(controller: HistoryController) :
/**
* DecimalFormat used to display correct chapter number
*/
val decimalFormat = DecimalFormat("#.###", DecimalFormatSymbols()
.apply { decimalSeparator = '.' })
val decimalFormat = DecimalFormat(
"#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' }
)
init {
setDisplayHeadersAtStartUp(true)

View File

@@ -22,14 +22,15 @@ import eu.kanade.tachiyomi.util.system.toast
* Uses [R.layout.history_controller].
* UI related actions should be called from here.
*/
class HistoryController : NucleusController<HistoryControllerBinding, HistoryPresenter>(),
RootController,
NoToolbarElevationController,
FlexibleAdapter.OnUpdateListener,
HistoryAdapter.OnRemoveClickListener,
HistoryAdapter.OnResumeClickListener,
HistoryAdapter.OnItemClickListener,
RemoveHistoryDialog.Listener {
class HistoryController :
NucleusController<HistoryControllerBinding, HistoryPresenter>(),
RootController,
NoToolbarElevationController,
FlexibleAdapter.OnUpdateListener,
HistoryAdapter.OnRemoveClickListener,
HistoryAdapter.OnResumeClickListener,
HistoryAdapter.OnItemClickListener,
RemoveHistoryDialog.Listener {
/**
* Adapter containing the recent manga.

View File

@@ -59,16 +59,16 @@ class HistoryHolder(
// Set chapter number + timestamp
val formattedNumber = adapter.decimalFormat.format(chapter.chapter_number.toDouble())
manga_subtitle.text = itemView.context.getString(R.string.recent_manga_time)
.format(formattedNumber, Date(history.last_read).toTimestampString())
.format(formattedNumber, Date(history.last_read).toTimestampString())
// Set cover
GlideApp.with(itemView.context).clear(cover)
if (!manga.thumbnail_url.isNullOrEmpty()) {
GlideApp.with(itemView.context)
.load(manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(cover)
.load(manga.toMangaThumbnail())
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(cover)
}
}
}

View File

@@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
import eu.kanade.tachiyomi.ui.recent.DateSectionItem
class HistoryItem(val mch: MangaChapterHistory, header: DateSectionItem) :
AbstractSectionableItem<HistoryHolder, DateSectionItem>(header) {
AbstractSectionableItem<HistoryHolder, DateSectionItem>(header) {
override fun getLayoutRes(): Int {
return R.layout.history_item

View File

@@ -34,7 +34,7 @@ class HistoryPresenter : BasePresenter<HistoryController>() {
// Used to get a list of recently read manga
getRecentMangaObservable()
.subscribeLatestCache(HistoryController::onNextManga)
.subscribeLatestCache(HistoryController::onNextManga)
}
/**
@@ -49,16 +49,16 @@ class HistoryPresenter : BasePresenter<HistoryController>() {
}
return db.getRecentManga(cal.time).asRxObservable()
.map { recents ->
val map = TreeMap<Date, MutableList<MangaChapterHistory>> { d1, d2 -> d2.compareTo(d1) }
val byDay = recents
.groupByTo(map, { it.history.last_read.toDateKey() })
byDay.flatMap { entry ->
val dateItem = DateSectionItem(entry.key)
entry.value.map { HistoryItem(it, dateItem) }
}
.map { recents ->
val map = TreeMap<Date, MutableList<MangaChapterHistory>> { d1, d2 -> d2.compareTo(d1) }
val byDay = recents
.groupByTo(map, { it.history.last_read.toDateKey() })
byDay.flatMap { entry ->
val dateItem = DateSectionItem(entry.key)
entry.value.map { HistoryItem(it, dateItem) }
}
.observeOn(AndroidSchedulers.mainThread())
}
.observeOn(AndroidSchedulers.mainThread())
}
/**
@@ -68,7 +68,7 @@ class HistoryPresenter : BasePresenter<HistoryController>() {
fun removeFromHistory(history: History) {
history.last_read = 0L
db.updateHistoryLastRead(history).asRxObservable()
.subscribe()
.subscribe()
}
/**
@@ -77,11 +77,11 @@ class HistoryPresenter : BasePresenter<HistoryController>() {
*/
fun removeAllFromHistory(mangaId: Long) {
db.getHistoryByMangaId(mangaId).asRxSingle()
.map { list ->
list.forEach { it.last_read = 0L }
db.updateHistoryLastRead(list).executeAsBlocking()
}
.subscribe()
.map { list ->
list.forEach { it.last_read = 0L }
db.updateHistoryLastRead(list).executeAsBlocking()
}
.subscribe()
}
/**
@@ -102,7 +102,7 @@ class HistoryPresenter : BasePresenter<HistoryController>() {
}
val chapters = db.getChapters(manga).executeAsBlocking()
.sortedWith(Comparator { c1, c2 -> sortFunction(c1, c2) })
.sortedWith(Comparator { c1, c2 -> sortFunction(c1, c2) })
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
return when (manga.sorting) {
@@ -111,11 +111,11 @@ class HistoryPresenter : BasePresenter<HistoryController>() {
val chapterNumber = chapter.chapter_number
((currChapterIndex + 1) until chapters.size)
.map { chapters[it] }
.firstOrNull {
it.chapter_number > chapterNumber &&
it.chapter_number <= chapterNumber + 1
}
.map { chapters[it] }
.firstOrNull {
it.chapter_number > chapterNumber &&
it.chapter_number <= chapterNumber + 1
}
}
else -> throw NotImplementedError("Unknown sorting method")
}

View File

@@ -34,10 +34,10 @@ class RemoveHistoryDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
}
return MaterialDialog(activity)
.title(R.string.action_remove)
.customView(view = dialogCheckboxView, horizontalPadding = true)
.positiveButton(R.string.action_remove) { onPositive(dialogCheckboxView.isChecked()) }
.negativeButton(android.R.string.cancel)
.title(R.string.action_remove)
.customView(view = dialogCheckboxView, horizontalPadding = true)
.positiveButton(R.string.action_remove) { onPositive(dialogCheckboxView.isChecked()) }
.negativeButton(android.R.string.cancel)
}
private fun onPositive(checked: Boolean) {

View File

@@ -19,11 +19,11 @@ class ConfirmDeleteChaptersDialog<T>(bundle: Bundle? = null) : DialogController(
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.confirm_delete_chapters)
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.deleteChapters(chaptersToDelete)
}
.negativeButton(android.R.string.cancel)
.message(R.string.confirm_delete_chapters)
.positiveButton(android.R.string.ok) {
(targetController as? Listener)?.deleteChapters(chaptersToDelete)
}
.negativeButton(android.R.string.cancel)
}
interface Listener {

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