mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-15 21:47:28 +01:00
Linting fixes
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -49,7 +49,6 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem<CategoryHolder
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(category)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -38,7 +38,6 @@ data class ExtensionGroupItem(val name: String, val size: Int, val showSize: Boo
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>() {
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -47,8 +47,8 @@ class PageIndicatorTextView(
|
||||
|
||||
// A span object with text outlining properties
|
||||
val spanOutline = OutlineSpan(
|
||||
strokeColor = strokeColor,
|
||||
strokeWidth = 4f
|
||||
strokeColor = strokeColor,
|
||||
strokeWidth = 4f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user