Merge branch 'feature/mvvm' into devel
commit
b1be45af8b
@ -1,15 +1,9 @@
|
||||
language: android
|
||||
dist: trusty
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools-30.0.3
|
||||
- build-tools-30.0.2
|
||||
- android-30
|
||||
licenses:
|
||||
- android-sdk-preview-license-.+
|
||||
- android-sdk-license-.+
|
||||
- google-gdk-license-.+
|
||||
- build-tools-30.0.2
|
||||
- platform-tools-30.0.3
|
||||
- tools
|
||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug
|
||||
@ -0,0 +1,33 @@
|
||||
package org.koitharu.kotatsu.base.domain
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
|
||||
data class MangaIntent(
|
||||
val manga: Manga?,
|
||||
val mangaId: Long,
|
||||
val uri: Uri?
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun from(intent: Intent?) = MangaIntent(
|
||||
manga = intent?.getParcelableExtra(KEY_MANGA),
|
||||
mangaId = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE,
|
||||
uri = intent?.data
|
||||
)
|
||||
|
||||
fun from(args: Bundle?) = MangaIntent(
|
||||
manga = args?.getParcelable(KEY_MANGA),
|
||||
mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE,
|
||||
uri = null
|
||||
)
|
||||
|
||||
const val ID_NONE = 0L
|
||||
|
||||
const val KEY_MANGA = "manga"
|
||||
const val KEY_ID = "id"
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.domain
|
||||
package org.koitharu.kotatsu.base.domain
|
||||
|
||||
import okhttp3.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.domain
|
||||
package org.koitharu.kotatsu.base.domain
|
||||
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.domain
|
||||
package org.koitharu.kotatsu.base.domain
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
@ -1,15 +1,15 @@
|
||||
package org.koitharu.kotatsu.ui.base
|
||||
package org.koitharu.kotatsu.base.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import moxy.MvpAppCompatActivity
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
abstract class BaseActivity : MvpAppCompatActivity() {
|
||||
abstract class BaseActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (get<AppSettings>().isAmoledTheme) {
|
||||
@ -1,14 +1,14 @@
|
||||
package org.koitharu.kotatsu.ui.base
|
||||
package org.koitharu.kotatsu.base.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import coil.ImageLoader
|
||||
import moxy.MvpAppCompatFragment
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
abstract class BaseFragment(
|
||||
@LayoutRes contentLayoutId: Int
|
||||
) : MvpAppCompatFragment(contentLayoutId) {
|
||||
) : Fragment(contentLayoutId) {
|
||||
|
||||
protected val coil by inject<ImageLoader>()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base
|
||||
package org.koitharu.kotatsu.base.ui
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base
|
||||
package org.koitharu.kotatsu.base.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
@ -0,0 +1,5 @@
|
||||
package org.koitharu.kotatsu.base.ui
|
||||
|
||||
import androidx.lifecycle.LifecycleService
|
||||
|
||||
abstract class BaseService : LifecycleService()
|
||||
@ -1,9 +1,10 @@
|
||||
package org.koitharu.kotatsu.ui.base.list
|
||||
package org.koitharu.kotatsu.base.ui.list
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import java.util.*
|
||||
|
||||
@Deprecated("")
|
||||
class AdapterUpdater<T>(oldList: List<T>, newList: List<T>, getId: (T) -> Long) {
|
||||
|
||||
private val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base.list
|
||||
package org.koitharu.kotatsu.base.ui.list
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.base.ui.list
|
||||
|
||||
import android.view.View
|
||||
|
||||
interface OnListItemClickListener<I> {
|
||||
|
||||
fun onItemClick(item: I, view: View)
|
||||
|
||||
fun onItemLongClick(item: I, view: View) = false
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package org.koitharu.kotatsu.base.ui.list
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class PaginationScrollListener(offset: Int, private val callback: Callback) :
|
||||
BoundsScrollListener(0, offset) {
|
||||
|
||||
override fun onScrolledToStart(recyclerView: RecyclerView) = Unit
|
||||
|
||||
override fun onScrolledToEnd(recyclerView: RecyclerView) {
|
||||
callback.onScrolledToEnd()
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
||||
fun onScrolledToEnd()
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base.list
|
||||
package org.koitharu.kotatsu.base.ui.list
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base.list.decor
|
||||
package org.koitharu.kotatsu.base.ui.list.decor
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base.list.decor
|
||||
package org.koitharu.kotatsu.base.ui.list.decor
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base.list.decor
|
||||
package org.koitharu.kotatsu.base.ui.list.decor
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base.widgets
|
||||
package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base.widgets
|
||||
package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base.widgets
|
||||
package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.browser
|
||||
package org.koitharu.kotatsu.browser
|
||||
|
||||
interface BrowserCallback {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.browser
|
||||
package org.koitharu.kotatsu.browser
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.webkit.WebResourceRequest
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.utils.cloudflare
|
||||
package org.koitharu.kotatsu.browser.cloudflare
|
||||
|
||||
interface CloudFlareCallback {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.utils.cloudflare
|
||||
package org.koitharu.kotatsu.browser.cloudflare
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.webkit.CookieManager
|
||||
@ -1,3 +1,4 @@
|
||||
package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
class ParseException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||
class ParseException(message: String? = null, cause: Throwable? = null) :
|
||||
RuntimeException(message, cause)
|
||||
@ -1,22 +1,25 @@
|
||||
package org.koitharu.kotatsu.core.parser
|
||||
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.parser.site.*
|
||||
|
||||
val parserModule
|
||||
get() = module {
|
||||
single { LocalMangaRepository(get()) } bind MangaRepository::class
|
||||
|
||||
factory { ReadmangaRepository(get()) } bind MangaRepository::class
|
||||
factory { MintMangaRepository(get()) } bind MangaRepository::class
|
||||
factory { SelfMangaRepository(get()) } bind MangaRepository::class
|
||||
factory { MangaChanRepository(get()) } bind MangaRepository::class
|
||||
factory { DesuMeRepository(get()) } bind MangaRepository::class
|
||||
factory { HenChanRepository(get()) } bind MangaRepository::class
|
||||
factory { YaoiChanRepository(get()) } bind MangaRepository::class
|
||||
factory { MangaTownRepository(get()) } bind MangaRepository::class
|
||||
factory { MangaLibRepository(get()) } bind MangaRepository::class
|
||||
factory { NudeMoonRepository(get()) } bind MangaRepository::class
|
||||
factory { MangareadRepository(get()) } bind MangaRepository::class
|
||||
single { MangaLoaderContext() }
|
||||
|
||||
factory<MangaRepository>(named(MangaSource.READMANGA_RU)) { ReadmangaRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.MINTMANGA)) { MintMangaRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.SELFMANGA)) { SelfMangaRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.MANGACHAN)) { MangaChanRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.DESUME)) { DesuMeRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.HENCHAN)) { HenChanRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.YAOICHAN)) { YaoiChanRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.MANGATOWN)) { MangaTownRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.MANGALIB)) { MangaLibRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.NUDEMOON)) { NudeMoonRepository(get()) }
|
||||
factory<MangaRepository>(named(MangaSource.MANGAREAD)) { MangareadRepository(get()) }
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.utils
|
||||
package org.koitharu.kotatsu.core.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.base
|
||||
package org.koitharu.kotatsu.core.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
@ -1,11 +1,11 @@
|
||||
package org.koitharu.kotatsu.ui.base
|
||||
package org.koitharu.kotatsu.core.ui
|
||||
|
||||
import coil.ComponentRegistry
|
||||
import coil.ImageLoader
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.core.local.CbzFetcher
|
||||
import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||
|
||||
val uiModule
|
||||
get() = module {
|
||||
@ -0,0 +1,14 @@
|
||||
package org.koitharu.kotatsu.details
|
||||
|
||||
import org.koin.android.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.details.ui.DetailsViewModel
|
||||
|
||||
val detailsModule
|
||||
get() = module {
|
||||
|
||||
viewModel { (intent: MangaIntent) ->
|
||||
DetailsViewModel(intent, get(), get(), get(), get(), get())
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,148 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.fragment_chapters.*
|
||||
import org.koin.android.viewmodel.ext.android.sharedViewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.download.DownloadService
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
|
||||
class ChaptersFragment : BaseFragment(R.layout.fragment_chapters),
|
||||
OnListItemClickListener<MangaChapter>, ActionMode.Callback {
|
||||
|
||||
private val viewModel by sharedViewModel<DetailsViewModel>()
|
||||
|
||||
private var chaptersAdapter: ChaptersAdapter? = null
|
||||
private var actionMode: ActionMode? = null
|
||||
private var selectionDecoration: ChaptersSelectionDecoration? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
chaptersAdapter = ChaptersAdapter(this)
|
||||
selectionDecoration = ChaptersSelectionDecoration(view.context)
|
||||
with(recyclerView_chapters) {
|
||||
addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL))
|
||||
addItemDecoration(selectionDecoration!!)
|
||||
setHasFixedSize(true)
|
||||
adapter = chaptersAdapter
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
|
||||
viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
chaptersAdapter = null
|
||||
selectionDecoration = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun onChaptersChanged(list: List<ChapterListItem>) {
|
||||
chaptersAdapter?.items = list
|
||||
}
|
||||
|
||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
progressBar.isVisible = isLoading
|
||||
}
|
||||
|
||||
override fun onItemClick(item: MangaChapter, view: View) {
|
||||
if (selectionDecoration?.checkedItemsCount != 0) {
|
||||
selectionDecoration?.toggleItemChecked(item.id)
|
||||
if (selectionDecoration?.checkedItemsCount == 0) {
|
||||
actionMode?.finish()
|
||||
} else {
|
||||
actionMode?.invalidate()
|
||||
recyclerView_chapters.invalidateItemDecorations()
|
||||
}
|
||||
return
|
||||
}
|
||||
val options = ActivityOptions.makeScaleUpAnimation(
|
||||
view,
|
||||
0,
|
||||
0,
|
||||
view.measuredWidth,
|
||||
view.measuredHeight
|
||||
)
|
||||
startActivity(
|
||||
ReaderActivity.newIntent(
|
||||
context ?: return,
|
||||
viewModel.manga.value ?: return,
|
||||
item.id
|
||||
), options.toBundle()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: MangaChapter, view: View): Boolean {
|
||||
if (actionMode == null) {
|
||||
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
|
||||
}
|
||||
return actionMode?.also {
|
||||
selectionDecoration?.setItemIsChecked(item.id, true)
|
||||
recyclerView_chapters.invalidateItemDecorations()
|
||||
it.invalidate()
|
||||
} != null
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_save -> {
|
||||
DownloadService.start(
|
||||
context ?: return false,
|
||||
viewModel.manga.value ?: return false,
|
||||
selectionDecoration?.checkedItemsIds
|
||||
)
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_select_all -> {
|
||||
val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false
|
||||
selectionDecoration?.checkAll(ids)
|
||||
recyclerView_chapters.invalidateItemDecorations()
|
||||
mode.invalidate()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val manga = viewModel.manga.value
|
||||
mode.menuInflater.inflate(R.menu.mode_chapters, menu)
|
||||
menu.findItem(R.id.action_save).isVisible = manga?.source != MangaSource.LOCAL
|
||||
mode.title = manga?.title
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val count = selectionDecoration?.checkedItemsCount ?: return false
|
||||
mode.subtitle = resources.getQuantityString(
|
||||
R.plurals.chapters_from_x,
|
||||
count,
|
||||
count,
|
||||
chaptersAdapter?.itemCount ?: 0
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
selectionDecoration?.clearSelection()
|
||||
recyclerView_chapters.invalidateItemDecorations()
|
||||
actionMode = null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.history.domain.ChapterExtra
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.safe
|
||||
import java.io.IOException
|
||||
|
||||
class DetailsViewModel(
|
||||
intent: MangaIntent,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val mangaDataRepository: MangaDataRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val mangaData = MutableStateFlow<Manga?>(intent.manga)
|
||||
|
||||
private val history = mangaData.mapNotNull { it?.id }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { mangaId ->
|
||||
historyRepository.observeOne(mangaId)
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
|
||||
private val favourite = mangaData.mapNotNull { it?.id }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { mangaId ->
|
||||
favouritesRepository.observeCategoriesIds(mangaId).map { it.isNotEmpty() }
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, false)
|
||||
|
||||
private val newChapters = mangaData.mapNotNull { it?.id }
|
||||
.distinctUntilChanged()
|
||||
.mapLatest { mangaId ->
|
||||
trackingRepository.getNewChaptersCount(mangaId)
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, 0)
|
||||
|
||||
val manga = mangaData.filterNotNull()
|
||||
.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
val favouriteCategories = favourite
|
||||
.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
val newChaptersCount = newChapters
|
||||
.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
val readingHistory = history
|
||||
.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
val onMangaRemoved = SingleLiveEvent<Manga>()
|
||||
|
||||
val chapters = combine(
|
||||
mangaData.map { it?.chapters.orEmpty() },
|
||||
history.map { it?.chapterId },
|
||||
newChapters
|
||||
) { chapters, currentId, newCount ->
|
||||
val currentIndex = chapters.indexOfFirst { it.id == currentId }
|
||||
val firstNewIndex = chapters.size - newCount
|
||||
chapters.mapIndexed { index, chapter ->
|
||||
chapter.toListItem(
|
||||
when {
|
||||
index >= firstNewIndex -> ChapterExtra.NEW
|
||||
index == currentIndex -> ChapterExtra.CURRENT
|
||||
index < currentIndex -> ChapterExtra.READ
|
||||
else -> ChapterExtra.UNREAD
|
||||
}
|
||||
)
|
||||
}
|
||||
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
init {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
var manga = mangaDataRepository.resolveIntent(intent)
|
||||
?: throw MangaNotFoundException("Cannot find manga")
|
||||
mangaData.value = manga
|
||||
manga = manga.source.repository.getDetails(manga)
|
||||
mangaData.value = manga
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteLocal(manga: Manga) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val original = localMangaRepository.getRemoteManga(manga)
|
||||
localMangaRepository.delete(manga) || throw IOException("Unable to delete file")
|
||||
safe {
|
||||
historyRepository.deleteOrSwap(manga, original)
|
||||
}
|
||||
onMangaRemoved.postCall(manga)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package org.koitharu.kotatsu.details.ui.adapter
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class ChaptersAdapter(
|
||||
onItemClickListener: OnListItemClickListener<MangaChapter>
|
||||
) : AsyncListDifferDelegationAdapter<ChapterListItem>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
delegatesManager.addDelegate(chapterListItemAD(onItemClickListener))
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return items[position].chapter.id
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<ChapterListItem>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: ChapterListItem, newItem: ChapterListItem): Boolean {
|
||||
return oldItem.chapter.id == newItem.chapter.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ChapterListItem, newItem: ChapterListItem): Boolean {
|
||||
return Intrinsics.areEqual(oldItem, newItem)
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: ChapterListItem, newItem: ChapterListItem): Any? {
|
||||
if (oldItem.extra != newItem.extra) {
|
||||
return newItem.extra
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
package org.koitharu.kotatsu.details.ui.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.utils.ext.resolveDp
|
||||
|
||||
class ChaptersSelectionDecoration(context: Context) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private val icon = ContextCompat.getDrawable(context, R.drawable.ic_check)
|
||||
private val padding = context.resources.resolveDp(16)
|
||||
private val bounds = Rect()
|
||||
private val selection = ArraySet<Long>()
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
|
||||
init {
|
||||
paint.color = context.getThemeColor(com.google.android.material.R.attr.scrimBackground)
|
||||
paint.style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
val checkedItemsCount: Int
|
||||
get() = selection.size
|
||||
|
||||
val checkedItemsIds: Set<Long>
|
||||
get() = selection
|
||||
|
||||
fun toggleItemChecked(id: Long) {
|
||||
if (!selection.remove(id)) {
|
||||
selection.add(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun setItemIsChecked(id: Long, isChecked: Boolean) {
|
||||
if (isChecked) {
|
||||
selection.add(id)
|
||||
} else {
|
||||
selection.remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkAll(ids: Collection<Long>) {
|
||||
selection.addAll(ids)
|
||||
}
|
||||
|
||||
fun clearSelection() {
|
||||
selection.clear()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
icon ?: return
|
||||
canvas.save()
|
||||
if (parent.clipToPadding) {
|
||||
canvas.clipRect(
|
||||
parent.paddingLeft, parent.paddingTop, parent.width - parent.paddingRight,
|
||||
parent.height - parent.paddingBottom
|
||||
)
|
||||
}
|
||||
|
||||
for (child in parent.children) {
|
||||
val itemId = parent.getChildItemId(child)
|
||||
if (itemId in selection) {
|
||||
parent.getDecoratedBoundsWithMargins(child, bounds)
|
||||
bounds.offset(child.translationX.toInt(), child.translationY.toInt())
|
||||
canvas.drawRect(bounds, paint)
|
||||
}
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
icon ?: return
|
||||
canvas.save()
|
||||
val left: Int
|
||||
val right: Int
|
||||
if (parent.clipToPadding) {
|
||||
left = parent.paddingLeft
|
||||
right = parent.width - parent.paddingRight
|
||||
canvas.clipRect(
|
||||
left, parent.paddingTop, right,
|
||||
parent.height - parent.paddingBottom
|
||||
)
|
||||
} else {
|
||||
left = 0
|
||||
right = parent.width
|
||||
}
|
||||
|
||||
for (child in parent.children) {
|
||||
val itemId = parent.getChildItemId(child)
|
||||
if (itemId in selection) {
|
||||
parent.getDecoratedBoundsWithMargins(child, bounds)
|
||||
bounds.offset(child.translationX.toInt(), child.translationY.toInt())
|
||||
val hh = (bounds.height() - icon.intrinsicHeight) / 2
|
||||
val top: Int = bounds.top + hh
|
||||
val bottom: Int = bounds.bottom - hh
|
||||
icon.setBounds(right - icon.intrinsicWidth - padding, top, right - padding, bottom)
|
||||
icon.draw(canvas)
|
||||
}
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package org.koitharu.kotatsu.details.ui.model
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
import org.koitharu.kotatsu.history.domain.ChapterExtra
|
||||
|
||||
data class ChapterListItem(
|
||||
val chapter: MangaChapter,
|
||||
val extra: ChapterExtra
|
||||
)
|
||||
@ -0,0 +1,9 @@
|
||||
package org.koitharu.kotatsu.details.ui.model
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
import org.koitharu.kotatsu.history.domain.ChapterExtra
|
||||
|
||||
fun MangaChapter.toListItem(extra: ChapterExtra) = ChapterListItem(
|
||||
chapter = this,
|
||||
extra = extra
|
||||
)
|
||||
@ -1,6 +0,0 @@
|
||||
package org.koitharu.kotatsu.domain.history
|
||||
|
||||
enum class ChapterExtra {
|
||||
|
||||
READ, CURRENT, UNREAD, NEW, CHECKED
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.koitharu.kotatsu.favourites
|
||||
|
||||
import org.koin.android.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.MangaCategoriesViewModel
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListViewModel
|
||||
|
||||
val favouritesModule
|
||||
get() = module {
|
||||
|
||||
single { FavouritesRepository(get()) }
|
||||
|
||||
viewModel { (categoryId: Long) ->
|
||||
FavouritesListViewModel(categoryId, get(), get())
|
||||
}
|
||||
viewModel { FavouritesCategoriesViewModel(get()) }
|
||||
viewModel { (manga: Manga) ->
|
||||
MangaCategoriesViewModel(manga, get())
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.core.db.entity
|
||||
package org.koitharu.kotatsu.favourites.data
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
@ -1,8 +1,9 @@
|
||||
package org.koitharu.kotatsu.core.db.entity
|
||||
package org.koitharu.kotatsu.favourites.data
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
|
||||
@Entity(
|
||||
tableName = "favourites", primaryKeys = ["manga_id", "category_id"], foreignKeys = [
|
||||
@ -1,8 +1,11 @@
|
||||
package org.koitharu.kotatsu.core.db.entity
|
||||
package org.koitharu.kotatsu.favourites.data
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Junction
|
||||
import androidx.room.Relation
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
|
||||
data class FavouriteManga(
|
||||
@Embedded val favourite: FavouriteEntity,
|
||||
@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.domain.favourites
|
||||
package org.koitharu.kotatsu.favourites.domain
|
||||
|
||||
@Deprecated("Use flow")
|
||||
fun interface OnFavouritesChangeListener {
|
||||
|
||||
fun onFavouritesChanged(mangaId: Long)
|
||||
@ -1,12 +1,13 @@
|
||||
package org.koitharu.kotatsu.ui.list.favourites
|
||||
package org.koitharu.kotatsu.favourites.ui
|
||||
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import org.koitharu.kotatsu.base.ui.list.AdapterUpdater
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.ui.base.list.AdapterUpdater
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.utils.ext.replaceWith
|
||||
|
||||
class FavouritesPagerAdapter(
|
||||
@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.ui.list.favourites
|
||||
package org.koitharu.kotatsu.favourites.ui
|
||||
|
||||
import android.view.View
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
@ -0,0 +1,27 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class CategoriesAdapter(
|
||||
onItemClickListener: OnListItemClickListener<FavouriteCategory>
|
||||
) : AsyncListDifferDelegationAdapter<FavouriteCategory>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager.addDelegate(categoryAD(onItemClickListener))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean {
|
||||
return Intrinsics.areEqual(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import android.view.MotionEvent
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_category.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
|
||||
fun categoryAD(
|
||||
clickListener: OnListItemClickListener<FavouriteCategory>
|
||||
) = adapterDelegateLayoutContainer<FavouriteCategory, FavouriteCategory>(R.layout.item_category) {
|
||||
|
||||
imageView_more.setOnClickListener {
|
||||
clickListener.onItemClick(item, it)
|
||||
}
|
||||
@Suppress("ClickableViewAccessibility")
|
||||
imageView_handle.setOnTouchListener { v, event ->
|
||||
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
clickListener.onItemLongClick(item, itemView)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
bind {
|
||||
textView_title.text = item.title
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
|
||||
class FavouritesCategoriesViewModel(
|
||||
private val repository: FavouritesRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
private var reorderJob: Job? = null
|
||||
|
||||
val categories = repository.observeCategories()
|
||||
.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
fun createCategory(name: String) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
repository.addCategory(name)
|
||||
}
|
||||
}
|
||||
|
||||
fun renameCategory(id: Long, name: String) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
repository.renameCategory(id, name)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCategory(id: Long) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
repository.removeCategory(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun reorderCategories(oldPos: Int, newPos: Int) {
|
||||
val prevJob = reorderJob
|
||||
reorderJob = launchJob(Dispatchers.Default) {
|
||||
prevJob?.join()
|
||||
val items = categories.value ?: error("This should not happen")
|
||||
val ids = items.mapTo(ArrayList(items.size)) { it.id }
|
||||
val item = ids.removeAt(oldPos)
|
||||
ids.add(newPos, item)
|
||||
repository.reorderCategories(ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.select
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import kotlinx.android.synthetic.main.dialog_favorite_categories.*
|
||||
import org.koin.android.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesEditDelegate
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class FavouriteCategoriesDialog : BaseBottomSheet(R.layout.dialog_favorite_categories),
|
||||
OnListItemClickListener<MangaCategoryItem>, CategoriesEditDelegate.CategoriesEditCallback,
|
||||
View.OnClickListener {
|
||||
|
||||
private val viewModel by viewModel<MangaCategoriesViewModel> {
|
||||
parametersOf(requireNotNull(arguments?.getParcelable<Manga>(MangaIntent.KEY_MANGA)))
|
||||
}
|
||||
|
||||
private var adapter: MangaCategoriesAdapter? = null
|
||||
private val editDelegate by lazy(LazyThreadSafetyMode.NONE) {
|
||||
CategoriesEditDelegate(requireContext(), this@FavouriteCategoriesDialog)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
adapter = MangaCategoriesAdapter(this)
|
||||
recyclerView_categories.adapter = adapter
|
||||
textView_add.setOnClickListener(this)
|
||||
|
||||
viewModel.content.observe(viewLifecycleOwner, this::onContentChanged)
|
||||
viewModel.onError.observe(viewLifecycleOwner, ::onError)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
adapter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.textView_add -> editDelegate.createCategory()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(item: MangaCategoryItem, view: View) {
|
||||
viewModel.setChecked(item.id, !item.isChecked)
|
||||
}
|
||||
|
||||
override fun onDeleteCategory(category: FavouriteCategory) = Unit
|
||||
|
||||
override fun onRenameCategory(category: FavouriteCategory, newName: String) = Unit
|
||||
|
||||
override fun onCreateCategory(name: String) {
|
||||
viewModel.createCategory(name)
|
||||
}
|
||||
|
||||
private fun onContentChanged(categories: List<MangaCategoryItem>) {
|
||||
adapter?.items = categories
|
||||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
Toast.makeText(context ?: return, e.getDisplayMessage(resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "FavouriteCategoriesDialog"
|
||||
|
||||
fun show(fm: FragmentManager, manga: Manga) = FavouriteCategoriesDialog()
|
||||
.withArgs(1) {
|
||||
putParcelable(MangaIntent.KEY_MANGA, manga)
|
||||
}.show(fm, TAG)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.select
|
||||
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
|
||||
|
||||
class MangaCategoriesViewModel(
|
||||
private val manga: Manga,
|
||||
private val favouritesRepository: FavouritesRepository
|
||||
) : BaseViewModel() {
|
||||
|
||||
val content = combine(
|
||||
favouritesRepository.observeCategories(),
|
||||
favouritesRepository.observeCategoriesIds(manga.id)
|
||||
) { all, checked ->
|
||||
all.map {
|
||||
MangaCategoryItem(
|
||||
id = it.id,
|
||||
name = it.title,
|
||||
isChecked = it.id in checked
|
||||
)
|
||||
}
|
||||
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
fun setChecked(categoryId: Long, isChecked: Boolean) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
if (isChecked) {
|
||||
favouritesRepository.addToCategory(manga, categoryId)
|
||||
} else {
|
||||
favouritesRepository.removeFromCategory(manga, categoryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createCategory(name: String) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
favouritesRepository.addCategory(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_category_checkable.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
|
||||
|
||||
fun mangaCategoryAD(
|
||||
clickListener: OnListItemClickListener<MangaCategoryItem>
|
||||
) = adapterDelegateLayoutContainer<MangaCategoryItem, MangaCategoryItem>(
|
||||
R.layout.item_category_checkable
|
||||
) {
|
||||
|
||||
itemView.setOnClickListener {
|
||||
clickListener.onItemClick(item, itemView)
|
||||
}
|
||||
|
||||
bind {
|
||||
checkedTextView.text = item.name
|
||||
checkedTextView.isChecked = item.isChecked
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
|
||||
|
||||
class MangaCategoriesAdapter(
|
||||
clickListener: OnListItemClickListener<MangaCategoryItem>
|
||||
) : AsyncListDifferDelegationAdapter<MangaCategoryItem>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager.addDelegate(mangaCategoryAD(clickListener))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<MangaCategoryItem>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: MangaCategoryItem,
|
||||
newItem: MangaCategoryItem
|
||||
): Boolean = oldItem.id == newItem.id
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: MangaCategoryItem,
|
||||
newItem: MangaCategoryItem
|
||||
): Boolean = oldItem == newItem
|
||||
|
||||
override fun getChangePayload(
|
||||
oldItem: MangaCategoryItem,
|
||||
newItem: MangaCategoryItem
|
||||
): Any? {
|
||||
if (oldItem.isChecked != newItem.isChecked) {
|
||||
return newItem.isChecked
|
||||
}
|
||||
return super.getChangePayload(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.select.model
|
||||
|
||||
data class MangaCategoryItem(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val isChecked: Boolean
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue