Merge branch 'feature/nextgen_dagger' into feature/nextgen
This commit is contained in:
@@ -5,85 +5,50 @@ import android.content.Context
|
||||
import android.os.StrictMode
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.fragment.app.strictmode.FragmentStrictMode
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.room.InvalidationTracker
|
||||
import androidx.work.Configuration
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import javax.inject.Inject
|
||||
import org.acra.ReportField
|
||||
import org.acra.config.dialog
|
||||
import org.acra.config.mailSender
|
||||
import org.acra.data.StringFormat
|
||||
import org.acra.ktx.initAcra
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.android.ext.android.getKoin
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koitharu.kotatsu.bookmarks.bookmarksModule
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.databaseModule
|
||||
import org.koitharu.kotatsu.core.github.appUpdateModule
|
||||
import org.koitharu.kotatsu.core.network.networkModule
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.uiModule
|
||||
import org.koitharu.kotatsu.details.detailsModule
|
||||
import org.koitharu.kotatsu.explore.exploreModule
|
||||
import org.koitharu.kotatsu.favourites.favouritesModule
|
||||
import org.koitharu.kotatsu.history.historyModule
|
||||
import org.koitharu.kotatsu.library.libraryModule
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.localModule
|
||||
import org.koitharu.kotatsu.main.mainModule
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.reader.readerModule
|
||||
import org.koitharu.kotatsu.remotelist.remoteListModule
|
||||
import org.koitharu.kotatsu.scrobbling.shikimori.shikimoriModule
|
||||
import org.koitharu.kotatsu.search.searchModule
|
||||
import org.koitharu.kotatsu.settings.settingsModule
|
||||
import org.koitharu.kotatsu.suggestions.suggestionsModule
|
||||
import org.koitharu.kotatsu.sync.syncModule
|
||||
import org.koitharu.kotatsu.tracker.trackerModule
|
||||
import org.koitharu.kotatsu.widget.appWidgetModule
|
||||
|
||||
class KotatsuApp : Application() {
|
||||
@HiltAndroidApp
|
||||
class KotatsuApp : Application(), Configuration.Provider {
|
||||
|
||||
@Inject
|
||||
lateinit var databaseObservers: Set<@JvmSuppressWildcards InvalidationTracker.Observer>
|
||||
|
||||
@Inject
|
||||
lateinit var activityLifecycleCallbacks: Set<@JvmSuppressWildcards ActivityLifecycleCallbacks>
|
||||
|
||||
@Inject
|
||||
lateinit var database: MangaDatabase
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
@Inject
|
||||
lateinit var workerFactory: HiltWorkerFactory
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) {
|
||||
enableStrictMode()
|
||||
}
|
||||
initKoin()
|
||||
AppCompatDelegate.setDefaultNightMode(get<AppSettings>().theme)
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
setupActivityLifecycleCallbacks()
|
||||
setupDatabaseObservers()
|
||||
}
|
||||
|
||||
private fun initKoin() {
|
||||
startKoin {
|
||||
androidContext(this@KotatsuApp)
|
||||
modules(
|
||||
networkModule,
|
||||
databaseModule,
|
||||
appUpdateModule,
|
||||
uiModule,
|
||||
mainModule,
|
||||
searchModule,
|
||||
localModule,
|
||||
favouritesModule,
|
||||
historyModule,
|
||||
remoteListModule,
|
||||
detailsModule,
|
||||
trackerModule,
|
||||
settingsModule,
|
||||
readerModule,
|
||||
appWidgetModule,
|
||||
suggestionsModule,
|
||||
syncModule,
|
||||
shikimoriModule,
|
||||
bookmarksModule,
|
||||
libraryModule,
|
||||
exploreModule,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
initAcra {
|
||||
@@ -115,18 +80,21 @@ class KotatsuApp : Application() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration(): Configuration {
|
||||
return Configuration.Builder()
|
||||
.setWorkerFactory(workerFactory)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun setupDatabaseObservers() {
|
||||
val observers = getKoin().getAll<InvalidationTracker.Observer>()
|
||||
val database = get<MangaDatabase>()
|
||||
val tracker = database.invalidationTracker
|
||||
observers.forEach {
|
||||
databaseObservers.forEach {
|
||||
tracker.addObserver(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupActivityLifecycleCallbacks() {
|
||||
val callbacks = getKoin().getAll<ActivityLifecycleCallbacks>()
|
||||
callbacks.forEach {
|
||||
activityLifecycleCallbacks.forEach {
|
||||
registerActivityLifecycleCallbacks(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
package org.koitharu.kotatsu.base.domain
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.util.Size
|
||||
import androidx.room.withTransaction
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.*
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
|
||||
class MangaDataRepository(private val db: MangaDatabase) {
|
||||
private const val MIN_WEBTOON_RATIO = 2
|
||||
|
||||
class MangaDataRepository @Inject constructor(
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val db: MangaDatabase,
|
||||
) {
|
||||
|
||||
suspend fun savePreferences(manga: Manga, mode: ReaderMode) {
|
||||
val tags = manga.tags.toEntities()
|
||||
@@ -18,8 +39,8 @@ class MangaDataRepository(private val db: MangaDatabase) {
|
||||
db.preferencesDao.upsert(
|
||||
MangaPrefsEntity(
|
||||
mangaId = manga.id,
|
||||
mode = mode.id
|
||||
)
|
||||
mode = mode.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -49,4 +70,59 @@ class MangaDataRepository(private val db: MangaDatabase) {
|
||||
suspend fun findTags(source: MangaSource): Set<MangaTag> {
|
||||
return db.tagsDao.findTags(source.name).toMangaTags()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatic determine type of manga by page size
|
||||
* @return ReaderMode.WEBTOON if page is wide
|
||||
*/
|
||||
suspend fun determineMangaIsWebtoon(repository: MangaRepository, pages: List<MangaPage>): Boolean {
|
||||
val pageIndex = (pages.size * 0.3).roundToInt()
|
||||
val page = requireNotNull(pages.getOrNull(pageIndex)) { "No pages" }
|
||||
val url = repository.getPageUrl(page)
|
||||
val uri = Uri.parse(url)
|
||||
val size = if (uri.scheme == "cbz") {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
val zip = ZipFile(uri.schemeSpecificPart)
|
||||
val entry = zip.getEntry(uri.fragment)
|
||||
zip.getInputStream(entry).use {
|
||||
getBitmapSize(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.header(CommonHeaders.REFERER, page.referer)
|
||||
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
|
||||
.build()
|
||||
okHttpClient.newCall(request).await().use {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
getBitmapSize(it.body?.byteStream())
|
||||
}
|
||||
}
|
||||
}
|
||||
return size.width * MIN_WEBTOON_RATIO < size.height
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
suspend fun getImageMimeType(file: File): String? = runInterruptible(Dispatchers.IO) {
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
BitmapFactory.decodeFile(file.path, options)?.recycle()
|
||||
options.outMimeType
|
||||
}
|
||||
|
||||
private fun getBitmapSize(input: InputStream?): Size {
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
BitmapFactory.decodeStream(input, null, options)?.recycle()
|
||||
val imageHeight: Int = options.outHeight
|
||||
val imageWidth: Int = options.outWidth
|
||||
check(imageHeight > 0 && imageWidth > 0)
|
||||
return Size(imageWidth, imageHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.koitharu.kotatsu.base.domain
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.util.Size
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object MangaUtils : KoinComponent {
|
||||
|
||||
private const val MIN_WEBTOON_RATIO = 2
|
||||
|
||||
/**
|
||||
* Automatic determine type of manga by page size
|
||||
* @return ReaderMode.WEBTOON if page is wide
|
||||
*/
|
||||
suspend fun determineMangaIsWebtoon(pages: List<MangaPage>): Boolean {
|
||||
val pageIndex = (pages.size * 0.3).roundToInt()
|
||||
val page = requireNotNull(pages.getOrNull(pageIndex)) { "No pages" }
|
||||
val url = MangaRepository(page.source).getPageUrl(page)
|
||||
val uri = Uri.parse(url)
|
||||
val size = if (uri.scheme == "cbz") {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
val zip = ZipFile(uri.schemeSpecificPart)
|
||||
val entry = zip.getEntry(uri.fragment)
|
||||
zip.getInputStream(entry).use {
|
||||
getBitmapSize(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.header(CommonHeaders.REFERER, page.referer)
|
||||
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
|
||||
.build()
|
||||
get<OkHttpClient>().newCall(request).await().use {
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
getBitmapSize(it.body?.byteStream())
|
||||
}
|
||||
}
|
||||
}
|
||||
return size.width * MIN_WEBTOON_RATIO < size.height
|
||||
}
|
||||
|
||||
suspend fun getImageMimeType(file: File): String? = runInterruptible(Dispatchers.IO) {
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
BitmapFactory.decodeFile(file.path, options)?.recycle()
|
||||
options.outMimeType
|
||||
}
|
||||
|
||||
private fun getBitmapSize(input: InputStream?): Size {
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
BitmapFactory.decodeStream(input, null, options)?.recycle()
|
||||
val imageHeight: Int = options.outHeight
|
||||
val imageWidth: Int = options.outWidth
|
||||
check(imageHeight > 0 && imageWidth > 0)
|
||||
return Size(imageWidth, imageHeight)
|
||||
}
|
||||
}
|
||||
@@ -18,19 +18,24 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import org.koin.android.ext.android.get
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.util.ActionModeDelegate
|
||||
import org.koitharu.kotatsu.base.ui.util.BaseActivityEntryPoint
|
||||
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
|
||||
import org.koitharu.kotatsu.base.ui.util.inject
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
|
||||
abstract class BaseActivity<B : ViewBinding> :
|
||||
AppCompatActivity(),
|
||||
WindowInsetsDelegate.WindowInsetsListener {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
protected lateinit var binding: B
|
||||
private set
|
||||
|
||||
@@ -43,7 +48,7 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
val actionModeDelegate = ActionModeDelegate()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val settings = get<AppSettings>()
|
||||
EntryPointAccessors.fromApplication(this, BaseActivityEntryPoint::class.java).inject(this)
|
||||
val isAmoled = settings.isAmoledTheme
|
||||
val isDynamic = settings.isDynamicTheme
|
||||
// TODO support DialogWhenLarge theme
|
||||
@@ -97,7 +102,7 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
protected fun isDarkAmoledTheme(): Boolean {
|
||||
val uiMode = resources.configuration.uiMode
|
||||
val isNight = uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
return isNight && get<AppSettings>().isAmoledTheme
|
||||
return isNight && settings.isAmoledTheme
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@@ -129,4 +134,4 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.dialog.AppBottomSheetDialog
|
||||
import org.koitharu.kotatsu.utils.ext.displayCompat
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
|
||||
@@ -30,7 +30,7 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
final override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
val binding = onInflateView(inflater, container)
|
||||
viewBinding = binding
|
||||
@@ -83,4 +83,4 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
}
|
||||
b.isDraggable = !isLocked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,21 @@ import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koin.android.ext.android.inject
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.SettingsHeadersFragment
|
||||
|
||||
@AndroidEntryPoint
|
||||
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
||||
PreferenceFragmentCompat(),
|
||||
WindowInsetsDelegate.WindowInsetsListener,
|
||||
RecyclerViewOwner {
|
||||
|
||||
protected val settings by inject<AppSettings>(mode = LazyThreadSafetyMode.NONE)
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
protected val insetsDelegate = WindowInsetsDelegate(this)
|
||||
@@ -48,7 +51,7 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
||||
@CallSuper
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
listView.updatePadding(
|
||||
bottom = insets.bottom
|
||||
bottom = insets.bottom,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,4 +60,4 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
||||
(parentFragment as? SettingsHeadersFragment)?.setTitle(title)
|
||||
?: activity?.setTitle(title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,4 @@ abstract class CoroutineIntentService : BaseService() {
|
||||
}
|
||||
|
||||
protected abstract suspend fun processIntent(intent: Intent?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import android.app.Activity
|
||||
import android.app.Application.ActivityLifecycleCallbacks
|
||||
import android.os.Bundle
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
class ActivityRecreationHandle : ActivityLifecycleCallbacks {
|
||||
@Singleton
|
||||
class ActivityRecreationHandle @Inject constructor() : ActivityLifecycleCallbacks {
|
||||
|
||||
private val activities = WeakHashMap<Activity, Unit>()
|
||||
|
||||
@@ -31,4 +34,4 @@ class ActivityRecreationHandle : ActivityLifecycleCallbacks {
|
||||
val snapshot = activities.keys.toList()
|
||||
snapshot.forEach { it.recreate() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.koitharu.kotatsu.base.ui.util
|
||||
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface BaseActivityEntryPoint {
|
||||
val settings: AppSettings
|
||||
}
|
||||
|
||||
// Hilt cannot inject into parametrized classes
|
||||
fun BaseActivityEntryPoint.inject(activity: BaseActivity<*>) {
|
||||
activity.settings = settings
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.koitharu.kotatsu.bookmarks
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||
import org.koitharu.kotatsu.bookmarks.ui.BookmarksViewModel
|
||||
|
||||
val bookmarksModule
|
||||
get() = module {
|
||||
|
||||
factory { BookmarksRepository(get()) }
|
||||
|
||||
viewModel { BookmarksViewModel(get()) }
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.bookmarks.domain
|
||||
|
||||
import android.database.SQLException
|
||||
import androidx.room.withTransaction
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koitharu.kotatsu.base.domain.ReversibleHandle
|
||||
@@ -17,7 +18,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
|
||||
class BookmarksRepository(
|
||||
class BookmarksRepository @Inject constructor(
|
||||
private val db: MangaDatabase,
|
||||
) {
|
||||
|
||||
@@ -86,4 +87,4 @@ class BookmarksRepository(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import android.os.Bundle
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BookmarksActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -29,7 +31,7 @@ class BookmarksActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
with(binding.toolbar) {
|
||||
updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right
|
||||
right = insets.right,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -38,4 +40,4 @@ class BookmarksActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
|
||||
fun newIntent(context: Context) = Intent(context, BookmarksActivity::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import android.view.*
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.viewModels
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.reverseAsync
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
@@ -30,13 +32,17 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
|
||||
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BookmarksFragment :
|
||||
BaseFragment<FragmentListSimpleBinding>(),
|
||||
ListStateHolderListener,
|
||||
OnListItemClickListener<Bookmark>,
|
||||
SectionedSelectionController.Callback<Manga> {
|
||||
|
||||
private val viewModel by viewModel<BookmarksViewModel>()
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private val viewModel by viewModels<BookmarksViewModel>()
|
||||
private var adapter: BookmarksGroupAdapter? = null
|
||||
private var selectionController: SectionedSelectionController<Manga>? = null
|
||||
|
||||
@@ -53,7 +59,7 @@ class BookmarksFragment :
|
||||
)
|
||||
adapter = BookmarksGroupAdapter(
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
coil = get(),
|
||||
coil = coil,
|
||||
listener = this,
|
||||
selectionController = checkNotNull(selectionController),
|
||||
bookmarkClickListener = this,
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.koitharu.kotatsu.bookmarks.ui
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -18,7 +20,8 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
|
||||
class BookmarksViewModel(
|
||||
@HiltViewModel
|
||||
class BookmarksViewModel @Inject constructor(
|
||||
private val repository: BookmarksRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
@@ -33,7 +36,7 @@ class BookmarksViewModel(
|
||||
textPrimary = R.string.no_bookmarks_yet,
|
||||
textSecondary = R.string.no_bookmarks_summary,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
),
|
||||
)
|
||||
} else list.map { (manga, bookmarks) ->
|
||||
BookmarksGroup(manga, bookmarks)
|
||||
@@ -42,11 +45,10 @@ class BookmarksViewModel(
|
||||
.catch { e -> e.toErrorState(canRetry = false) }
|
||||
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
|
||||
|
||||
fun removeBookmarks(ids: Map<Manga, Set<Long>>) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
val handle = repository.removeBookmarks(ids)
|
||||
onActionDone.postCall(ReversibleAction(R.string.bookmarks_removed, handle))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,27 @@ import android.webkit.WebSettings
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koin.android.ext.android.get
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.core.network.AndroidCookieJar
|
||||
import org.koitharu.kotatsu.core.network.UserAgentInterceptor
|
||||
import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding
|
||||
import org.koitharu.kotatsu.utils.ext.stringArgument
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@AndroidEntryPoint
|
||||
class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), CloudFlareCallback {
|
||||
|
||||
private val url by stringArgument(ARG_URL)
|
||||
private val pendingResult = Bundle(1)
|
||||
|
||||
@Inject
|
||||
lateinit var cookieJar: AndroidCookieJar
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
container: ViewGroup?,
|
||||
) = FragmentCloudflareBinding.inflate(inflater, container, false)
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@@ -38,7 +44,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
||||
databaseEnabled = true
|
||||
userAgentString = UserAgentInterceptor.userAgent
|
||||
}
|
||||
binding.webView.webViewClient = CloudFlareClient(get(), this, url.orEmpty())
|
||||
binding.webView.webViewClient = CloudFlareClient(cookieJar, this, url.orEmpty())
|
||||
CookieManager.getInstance().setAcceptThirdPartyCookies(binding.webView, true)
|
||||
if (url.isNullOrEmpty()) {
|
||||
dismissAllowingStateLoss()
|
||||
@@ -90,4 +96,4 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
|
||||
putString(ARG_URL, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
160
app/src/main/java/org/koitharu/kotatsu/core/AppModule.kt
Normal file
160
app/src/main/java/org/koitharu/kotatsu/core/AppModule.kt
Normal file
@@ -0,0 +1,160 @@
|
||||
package org.koitharu.kotatsu.core
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.provider.SearchRecentSuggestions
|
||||
import android.text.Html
|
||||
import androidx.collection.arraySetOf
|
||||
import androidx.room.InvalidationTracker
|
||||
import coil.ComponentRegistry
|
||||
import coil.ImageLoader
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.disk.DiskCache
|
||||
import coil.util.DebugLogger
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ElementsIntoSet
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.network.*
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
|
||||
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
|
||||
import org.koitharu.kotatsu.settings.backup.BackupObserver
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
|
||||
import org.koitharu.kotatsu.utils.image.CoilImageGetter
|
||||
import org.koitharu.kotatsu.widget.WidgetUpdater
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface AppModule {
|
||||
|
||||
@Binds
|
||||
fun bindCookieJar(androidCookieJar: AndroidCookieJar): CookieJar
|
||||
|
||||
@Binds
|
||||
fun bindMangaLoaderContext(mangaLoaderContextImpl: MangaLoaderContextImpl): MangaLoaderContext
|
||||
|
||||
@Binds
|
||||
fun bindImageGetter(coilImageGetter: CoilImageGetter): Html.ImageGetter
|
||||
|
||||
companion object {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttpClient(
|
||||
localStorageManager: LocalStorageManager,
|
||||
cookieJar: CookieJar,
|
||||
settings: AppSettings,
|
||||
): OkHttpClient {
|
||||
val cache = localStorageManager.createHttpCache()
|
||||
return OkHttpClient.Builder().apply {
|
||||
connectTimeout(20, TimeUnit.SECONDS)
|
||||
readTimeout(60, TimeUnit.SECONDS)
|
||||
writeTimeout(20, TimeUnit.SECONDS)
|
||||
cookieJar(cookieJar)
|
||||
dns(DoHManager(cache, settings))
|
||||
cache(cache)
|
||||
addInterceptor(GZipInterceptor())
|
||||
addInterceptor(UserAgentInterceptor())
|
||||
addInterceptor(CloudFlareInterceptor())
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMangaDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
): MangaDatabase {
|
||||
return MangaDatabase(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCoil(
|
||||
@ApplicationContext context: Context,
|
||||
okHttpClient: OkHttpClient,
|
||||
mangaRepositoryFactory: MangaRepository.Factory,
|
||||
): ImageLoader {
|
||||
val httpClientFactory = {
|
||||
okHttpClient.newBuilder()
|
||||
.cache(null)
|
||||
.build()
|
||||
}
|
||||
val diskCacheFactory = {
|
||||
val rootDir = context.externalCacheDir ?: context.cacheDir
|
||||
DiskCache.Builder()
|
||||
.directory(rootDir.resolve(CacheDir.THUMBS.dir))
|
||||
.build()
|
||||
}
|
||||
return ImageLoader.Builder(context)
|
||||
.okHttpClient(httpClientFactory)
|
||||
.interceptorDispatcher(Dispatchers.Default)
|
||||
.fetcherDispatcher(Dispatchers.IO)
|
||||
.decoderDispatcher(Dispatchers.Default)
|
||||
.transformationDispatcher(Dispatchers.Default)
|
||||
.diskCache(diskCacheFactory)
|
||||
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
|
||||
.allowRgb565(isLowRamDevice(context))
|
||||
.components(
|
||||
ComponentRegistry.Builder()
|
||||
.add(SvgDecoder.Factory())
|
||||
.add(CbzFetcher.Factory())
|
||||
.add(FaviconFetcher.Factory(context, okHttpClient, mangaRepositoryFactory))
|
||||
.build(),
|
||||
).build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideSearchSuggestions(
|
||||
@ApplicationContext context: Context,
|
||||
): SearchRecentSuggestions {
|
||||
return MangaSuggestionsProvider.createSuggestions(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ElementsIntoSet
|
||||
fun provideDatabaseObservers(
|
||||
widgetUpdater: WidgetUpdater,
|
||||
shortcutsUpdater: ShortcutsUpdater,
|
||||
backupObserver: BackupObserver,
|
||||
syncController: SyncController,
|
||||
): Set<@JvmSuppressWildcards InvalidationTracker.Observer> = arraySetOf(
|
||||
widgetUpdater,
|
||||
shortcutsUpdater,
|
||||
backupObserver,
|
||||
syncController,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ElementsIntoSet
|
||||
fun provideActivityLifecycleCallbacks(
|
||||
appProtectHelper: AppProtectHelper,
|
||||
activityRecreationHandle: ActivityRecreationHandle,
|
||||
): Set<@JvmSuppressWildcards Application.ActivityLifecycleCallbacks> = arraySetOf(
|
||||
appProtectHelper,
|
||||
activityRecreationHandle,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.core.backup
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import javax.inject.Inject
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
@@ -10,7 +11,7 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
|
||||
private const val PAGE_SIZE = 10
|
||||
|
||||
class BackupRepository(private val db: MangaDatabase) {
|
||||
class BackupRepository @Inject constructor(private val db: MangaDatabase) {
|
||||
|
||||
suspend fun dumpHistory(): BackupEntry {
|
||||
var offset = 0
|
||||
@@ -125,4 +126,4 @@ class BackupRepository(private val db: MangaDatabase) {
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.koitharu.kotatsu.core.db
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val databaseModule
|
||||
get() = module {
|
||||
single { MangaDatabase(androidContext()) }
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.koitharu.kotatsu.core.github
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val appUpdateModule
|
||||
get() = module {
|
||||
single { AppUpdateRepository(androidContext(), get()) }
|
||||
}
|
||||
@@ -3,11 +3,14 @@ package org.koitharu.kotatsu.core.github
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -22,8 +25,9 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
|
||||
private const val CERT_SHA1 = "2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE"
|
||||
|
||||
class AppUpdateRepository(
|
||||
private val context: Context,
|
||||
@Singleton
|
||||
class AppUpdateRepository @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val okHttp: OkHttpClient,
|
||||
) {
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package org.koitharu.kotatsu.core.network
|
||||
|
||||
import android.webkit.CookieManager
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class AndroidCookieJar : CookieJar {
|
||||
@Singleton
|
||||
class AndroidCookieJar @Inject constructor() : CookieJar {
|
||||
|
||||
private val cookieManager = CookieManager.getInstance()
|
||||
|
||||
@@ -31,4 +34,4 @@ class AndroidCookieJar : CookieJar {
|
||||
suspend fun clear() = suspendCoroutine<Boolean> { continuation ->
|
||||
cookieManager.removeAllCookies(continuation::resume)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.koitharu.kotatsu.core.network
|
||||
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
val networkModule
|
||||
get() = module {
|
||||
single { AndroidCookieJar() } bind CookieJar::class
|
||||
single {
|
||||
val cache = get<LocalStorageManager>().createHttpCache()
|
||||
OkHttpClient.Builder().apply {
|
||||
connectTimeout(20, TimeUnit.SECONDS)
|
||||
readTimeout(60, TimeUnit.SECONDS)
|
||||
writeTimeout(20, TimeUnit.SECONDS)
|
||||
cookieJar(get())
|
||||
dns(DoHManager(cache, get()))
|
||||
cache(cache)
|
||||
addInterceptor(GZipInterceptor())
|
||||
addInterceptor(UserAgentInterceptor())
|
||||
addInterceptor(CloudFlareInterceptor())
|
||||
}.build()
|
||||
}
|
||||
single<MangaLoaderContext> { MangaLoaderContextImpl(get(), get(), get()) }
|
||||
}
|
||||
@@ -13,6 +13,9 @@ import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.room.InvalidationTracker
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -26,8 +29,9 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.requireBitmap
|
||||
|
||||
class ShortcutsUpdater(
|
||||
private val context: Context,
|
||||
@Singleton
|
||||
class ShortcutsUpdater @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val coil: ImageLoader,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val mangaRepository: MangaDataRepository,
|
||||
@@ -37,6 +41,9 @@ class ShortcutsUpdater(
|
||||
private var shortcutsUpdateJob: Job? = null
|
||||
|
||||
override fun onInvalidated(tables: MutableSet<String>) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||
return
|
||||
}
|
||||
val prevJob = shortcutsUpdateJob
|
||||
shortcutsUpdateJob = processLifecycleScope.launch(Dispatchers.Default) {
|
||||
prevJob?.join()
|
||||
@@ -48,7 +55,7 @@ class ShortcutsUpdater(
|
||||
return ShortcutManagerCompat.requestPinShortcut(
|
||||
context,
|
||||
buildShortcutInfo(manga).build(),
|
||||
null
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,12 +80,12 @@ class ShortcutsUpdater(
|
||||
ImageRequest.Builder(context)
|
||||
.data(manga.coverUrl)
|
||||
.size(iconSize.width, iconSize.height)
|
||||
.build()
|
||||
.build(),
|
||||
).requireBitmap()
|
||||
ThumbnailUtils.extractThumbnail(bmp, iconSize.width, iconSize.height, 0)
|
||||
}.fold(
|
||||
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
|
||||
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) }
|
||||
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
|
||||
)
|
||||
mangaRepository.storeManga(manga)
|
||||
return ShortcutInfoCompat.Builder(context, manga.id.toString())
|
||||
@@ -87,7 +94,7 @@ class ShortcutsUpdater(
|
||||
.setIcon(icon)
|
||||
.setIntent(
|
||||
ReaderActivity.newIntent(context, manga.id)
|
||||
.setAction(ReaderActivity.ACTION_MANGA_READ)
|
||||
.setAction(ReaderActivity.ACTION_MANGA_READ),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -102,4 +109,4 @@ class ShortcutsUpdater(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@ import android.content.Context
|
||||
import android.util.Base64
|
||||
import android.webkit.WebView
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -14,14 +20,12 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.ext.toList
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class MangaLoaderContextImpl(
|
||||
@Singleton
|
||||
class MangaLoaderContextImpl @Inject constructor(
|
||||
override val httpClient: OkHttpClient,
|
||||
override val cookieJar: AndroidCookieJar,
|
||||
private val androidContext: Context,
|
||||
@ApplicationContext private val androidContext: Context,
|
||||
) : MangaLoaderContext() {
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@@ -50,4 +54,4 @@ class MangaLoaderContextImpl(
|
||||
override fun getPreferredLocales(): List<Locale> {
|
||||
return LocaleListCompat.getAdjustedDefault().toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.koitharu.kotatsu.core.parser
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.set
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
|
||||
interface MangaRepository {
|
||||
@@ -25,21 +27,25 @@ interface MangaRepository {
|
||||
|
||||
suspend fun getTags(): Set<MangaTag>
|
||||
|
||||
companion object : KoinComponent {
|
||||
@Singleton
|
||||
class Factory @Inject constructor(
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val loaderContext: MangaLoaderContext,
|
||||
) {
|
||||
|
||||
private val cache = EnumMap<MangaSource, WeakReference<RemoteMangaRepository>>(MangaSource::class.java)
|
||||
|
||||
operator fun invoke(source: MangaSource): MangaRepository {
|
||||
fun create(source: MangaSource): MangaRepository {
|
||||
if (source == MangaSource.LOCAL) {
|
||||
return get<LocalMangaRepository>()
|
||||
return localMangaRepository
|
||||
}
|
||||
cache[source]?.get()?.let { return it }
|
||||
return synchronized(cache) {
|
||||
cache[source]?.get()?.let { return it }
|
||||
val repository = RemoteMangaRepository(MangaParser(source, get()))
|
||||
val repository = RemoteMangaRepository(MangaParser(source, loaderContext))
|
||||
cache[source] = WeakReference(repository)
|
||||
repository
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import coil.network.HttpException
|
||||
import coil.request.Options
|
||||
import coil.size.Size
|
||||
import coil.size.pxOrElse
|
||||
import java.net.HttpURLConnection
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -25,7 +26,6 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import java.net.HttpURLConnection
|
||||
|
||||
private const val FALLBACK_SIZE = 9999 // largest icon
|
||||
|
||||
@@ -34,6 +34,7 @@ class FaviconFetcher(
|
||||
private val diskCache: Lazy<DiskCache?>,
|
||||
private val mangaSource: MangaSource,
|
||||
private val options: Options,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) : Fetcher {
|
||||
|
||||
private val diskCacheKey
|
||||
@@ -44,7 +45,7 @@ class FaviconFetcher(
|
||||
|
||||
override suspend fun fetch(): FetchResult {
|
||||
getCached(options)?.let { return it }
|
||||
val repo = MangaRepository(mangaSource) as RemoteMangaRepository
|
||||
val repo = mangaRepositoryFactory.create(mangaSource) as RemoteMangaRepository
|
||||
val favicons = repo.getFavicons()
|
||||
val sizePx = maxOf(
|
||||
options.size.width.pxOrElse { FALLBACK_SIZE },
|
||||
@@ -136,6 +137,7 @@ class FaviconFetcher(
|
||||
class Factory(
|
||||
context: Context,
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) : Fetcher.Factory<Uri> {
|
||||
|
||||
private val diskCache = lazy {
|
||||
@@ -148,7 +150,7 @@ class FaviconFetcher(
|
||||
override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher? {
|
||||
return if (data.scheme == URI_SCHEME_FAVICON) {
|
||||
val mangaSource = MangaSource.valueOf(data.schemeSpecificPart)
|
||||
FaviconFetcher(okHttpClient, diskCache, mangaSource, options)
|
||||
FaviconFetcher(okHttpClient, diskCache, mangaSource, options, mangaRepositoryFactory)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -156,4 +158,4 @@ class FaviconFetcher(
|
||||
}
|
||||
|
||||
class FaviconMetadata(val source: MangaSource) : ImageSource.Metadata()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ import androidx.collection.arraySetOf
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
import org.koitharu.kotatsu.core.network.DoHProvider
|
||||
@@ -18,12 +25,9 @@ import org.koitharu.kotatsu.utils.ext.getEnumValue
|
||||
import org.koitharu.kotatsu.utils.ext.observe
|
||||
import org.koitharu.kotatsu.utils.ext.putEnumValue
|
||||
import org.koitharu.kotatsu.utils.ext.toUriOrNull
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class AppSettings(context: Context) {
|
||||
@Singleton
|
||||
class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
|
||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package org.koitharu.kotatsu.core.ui
|
||||
|
||||
import android.text.Html
|
||||
import coil.ComponentRegistry
|
||||
import coil.ImageLoader
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.disk.DiskCache
|
||||
import coil.util.DebugLogger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
|
||||
import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
|
||||
import org.koitharu.kotatsu.utils.image.CoilImageGetter
|
||||
|
||||
val uiModule
|
||||
get() = module {
|
||||
single {
|
||||
val httpClientFactory = {
|
||||
get<OkHttpClient>().newBuilder()
|
||||
.cache(null)
|
||||
.build()
|
||||
}
|
||||
val diskCacheFactory = {
|
||||
val context = androidContext()
|
||||
val rootDir = context.externalCacheDir ?: context.cacheDir
|
||||
DiskCache.Builder()
|
||||
.directory(rootDir.resolve(CacheDir.THUMBS.dir))
|
||||
.build()
|
||||
}
|
||||
ImageLoader.Builder(androidContext())
|
||||
.okHttpClient(httpClientFactory)
|
||||
.interceptorDispatcher(Dispatchers.Default)
|
||||
.fetcherDispatcher(Dispatchers.IO)
|
||||
.decoderDispatcher(Dispatchers.Default)
|
||||
.transformationDispatcher(Dispatchers.Default)
|
||||
.diskCache(diskCacheFactory)
|
||||
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
|
||||
.allowRgb565(isLowRamDevice(androidContext()))
|
||||
.components(
|
||||
ComponentRegistry.Builder()
|
||||
.add(SvgDecoder.Factory())
|
||||
.add(CbzFetcher.Factory())
|
||||
.add(FaviconFetcher.Factory(androidContext(), get()))
|
||||
.build()
|
||||
).build()
|
||||
}
|
||||
factory<Html.ImageGetter> { CoilImageGetter(androidContext(), get()) }
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.koitharu.kotatsu.details
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.details.ui.DetailsViewModel
|
||||
|
||||
val detailsModule
|
||||
get() = module {
|
||||
|
||||
viewModel { intent ->
|
||||
DetailsViewModel(intent.get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,9 @@ import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import kotlin.math.roundToInt
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||
@@ -30,7 +31,6 @@ import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ChaptersFragment :
|
||||
BaseFragment<FragmentChaptersBinding>(),
|
||||
@@ -40,14 +40,14 @@ class ChaptersFragment :
|
||||
SearchView.OnQueryTextListener,
|
||||
ListSelectionController.Callback {
|
||||
|
||||
private val viewModel by sharedViewModel<DetailsViewModel>()
|
||||
private val viewModel by activityViewModels<DetailsViewModel>()
|
||||
|
||||
private var chaptersAdapter: ChaptersAdapter? = null
|
||||
private var selectionController: ListSelectionController? = null
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
container: ViewGroup?,
|
||||
) = FragmentChaptersBinding.inflate(inflater, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -98,7 +98,7 @@ class ChaptersFragment :
|
||||
manga = viewModel.manga.value ?: return,
|
||||
state = ReaderState(item.chapter.id, 0, 0),
|
||||
),
|
||||
scaleUpActivityOptionsOf(view).toBundle()
|
||||
scaleUpActivityOptionsOf(view).toBundle(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ class ChaptersFragment :
|
||||
Snackbar.make(
|
||||
binding.recyclerViewChapters,
|
||||
R.string.chapters_will_removed_background,
|
||||
Snackbar.LENGTH_LONG
|
||||
Snackbar.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
@@ -286,4 +286,4 @@ class ChaptersFragment :
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Spinner
|
||||
import android.widget.Toast
|
||||
@@ -16,7 +15,6 @@ import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -24,10 +22,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.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.BaseActivity
|
||||
@@ -45,17 +42,25 @@ import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.scrobbling.ui.selector.ScrobblingSelectorBottomSheet
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.isReportable
|
||||
import org.koitharu.kotatsu.utils.ext.report
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DetailsActivity :
|
||||
BaseActivity<ActivityDetailsBinding>(),
|
||||
TabLayoutMediator.TabConfigurationStrategy,
|
||||
AdapterView.OnItemSelectedListener {
|
||||
|
||||
private val viewModel by viewModel<DetailsViewModel> {
|
||||
parametersOf(MangaIntent(intent))
|
||||
@Inject
|
||||
lateinit var viewModelFactory: DetailsViewModel.Factory
|
||||
|
||||
@Inject
|
||||
lateinit var shortcutsUpdater: ShortcutsUpdater
|
||||
|
||||
private val viewModel by assistedViewModels<DetailsViewModel> {
|
||||
viewModelFactory.create(MangaIntent(intent))
|
||||
}
|
||||
|
||||
private val downloadReceiver = object : BroadcastReceiver() {
|
||||
@@ -103,8 +108,9 @@ class DetailsActivity :
|
||||
|
||||
private fun onMangaRemoved(manga: Manga) {
|
||||
Toast.makeText(
|
||||
this, getString(R.string._s_deleted_from_local_storage, manga.title),
|
||||
Toast.LENGTH_SHORT
|
||||
this,
|
||||
getString(R.string._s_deleted_from_local_storage, manga.title),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
finishAfterTransition()
|
||||
}
|
||||
@@ -130,7 +136,7 @@ class DetailsActivity :
|
||||
onActionClick = {
|
||||
e.report("DetailsActivity::onError")
|
||||
dismiss()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
@@ -141,11 +147,11 @@ class DetailsActivity :
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.snackbar.updatePadding(
|
||||
bottom = insets.bottom
|
||||
bottom = insets.bottom,
|
||||
)
|
||||
binding.root.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right
|
||||
right = insets.right,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -222,7 +228,7 @@ class DetailsActivity :
|
||||
R.id.action_shortcut -> {
|
||||
viewModel.manga.value?.let {
|
||||
lifecycleScope.launch {
|
||||
if (!get<ShortcutsUpdater>().requestPinShortcut(it)) {
|
||||
if (!shortcutsUpdater.requestPinShortcut(it)) {
|
||||
binding.snackbar.show(getString(R.string.operation_not_supported))
|
||||
}
|
||||
}
|
||||
@@ -272,8 +278,8 @@ class DetailsActivity :
|
||||
ReaderActivity.newIntent(
|
||||
context = this@DetailsActivity,
|
||||
manga = remoteManga,
|
||||
state = ReaderState(chapterId, 0, 0)
|
||||
)
|
||||
state = ReaderState(chapterId, 0, 0),
|
||||
),
|
||||
)
|
||||
}
|
||||
setNeutralButton(R.string.download) { _, _ ->
|
||||
@@ -347,8 +353,8 @@ class DetailsActivity :
|
||||
dialogBuilder.setMessage(
|
||||
getString(
|
||||
R.string.large_manga_save_confirm,
|
||||
resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount)
|
||||
)
|
||||
resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount),
|
||||
),
|
||||
).setPositiveButton(R.string.save) { _, _ ->
|
||||
DownloadService.start(this, manga)
|
||||
}
|
||||
|
||||
@@ -12,13 +12,14 @@ import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.util.CoilUtils
|
||||
import com.google.android.material.chip.Chip
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
@@ -45,6 +46,7 @@ import org.koitharu.kotatsu.utils.FileSize
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DetailsFragment :
|
||||
BaseFragment<FragmentDetailsBinding>(),
|
||||
View.OnClickListener,
|
||||
@@ -52,8 +54,10 @@ class DetailsFragment :
|
||||
ChipsView.OnChipClickListener,
|
||||
OnListItemClickListener<Bookmark> {
|
||||
|
||||
private val viewModel by sharedViewModel<DetailsViewModel>()
|
||||
private val coil by inject<ImageLoader>(mode = LazyThreadSafetyMode.NONE)
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private val viewModel by activityViewModels<DetailsViewModel>()
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -263,7 +267,7 @@ class DetailsFragment :
|
||||
context = context ?: return,
|
||||
manga = manga,
|
||||
branch = viewModel.selectedBranchValue,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -273,13 +277,13 @@ class DetailsFragment :
|
||||
context = v.context,
|
||||
source = manga.source,
|
||||
query = manga.author ?: return,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
R.id.imageView_cover -> {
|
||||
startActivity(
|
||||
ImageActivity.newIntent(v.context, manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }),
|
||||
scaleUpActivityOptionsOf(v).toBundle()
|
||||
scaleUpActivityOptionsOf(v).toBundle(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -305,8 +309,8 @@ class DetailsFragment :
|
||||
c.chapter.branch == branch
|
||||
}?.let { c ->
|
||||
ReaderState(c.chapter.id, 0, 0)
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
true
|
||||
}
|
||||
@@ -329,7 +333,7 @@ class DetailsFragment :
|
||||
binding.root.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
bottom = insets.bottom
|
||||
bottom = insets.bottom,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -343,7 +347,7 @@ class DetailsFragment :
|
||||
isCheckable = false,
|
||||
isChecked = false,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -386,4 +390,4 @@ class DetailsFragment :
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
@@ -17,6 +21,7 @@ import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.details.domain.BranchComparator
|
||||
@@ -33,10 +38,9 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import java.io.IOException
|
||||
|
||||
class DetailsViewModel(
|
||||
intent: MangaIntent,
|
||||
class DetailsViewModel @AssistedInject constructor(
|
||||
@Assisted intent: MangaIntent,
|
||||
private val historyRepository: HistoryRepository,
|
||||
favouritesRepository: FavouritesRepository,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
@@ -44,16 +48,20 @@ class DetailsViewModel(
|
||||
mangaDataRepository: MangaDataRepository,
|
||||
private val bookmarksRepository: BookmarksRepository,
|
||||
private val settings: AppSettings,
|
||||
private val scrobbler: Scrobbler,
|
||||
scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
||||
private val imageGetter: Html.ImageGetter,
|
||||
mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val scrobbler = scrobblers.first() // TODO support multiple scrobblers
|
||||
|
||||
private val delegate = MangaDetailsDelegate(
|
||||
intent = intent,
|
||||
settings = settings,
|
||||
mangaDataRepository = mangaDataRepository,
|
||||
historyRepository = historyRepository,
|
||||
localMangaRepository = localMangaRepository,
|
||||
mangaRepositoryFactory = mangaRepositoryFactory,
|
||||
)
|
||||
|
||||
private var loadingJob: Job
|
||||
@@ -110,7 +118,7 @@ class DetailsViewModel(
|
||||
|
||||
val selectedBranchIndex = combine(
|
||||
branches.asFlow(),
|
||||
delegate.selectedBranch
|
||||
delegate.selectedBranch,
|
||||
) { branches, selected ->
|
||||
branches.indexOf(selected)
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, -1)
|
||||
@@ -225,7 +233,7 @@ class DetailsViewModel(
|
||||
fun unregisterScrobbling() {
|
||||
launchJob(Dispatchers.Default) {
|
||||
scrobbler.unregisterScrobbling(
|
||||
mangaId = delegate.mangaId
|
||||
mangaId = delegate.mangaId,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -242,4 +250,10 @@ class DetailsViewModel(
|
||||
it.chapter.name.contains(query, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(intent: MangaIntent): DetailsViewModel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ class MangaDetailsDelegate(
|
||||
private val mangaDataRepository: MangaDataRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) {
|
||||
|
||||
private val mangaData = MutableStateFlow(intent.manga)
|
||||
@@ -42,7 +43,7 @@ class MangaDetailsDelegate(
|
||||
suspend fun doLoad() {
|
||||
var manga = mangaDataRepository.resolveIntent(intent) ?: throw NotFoundException("Cannot find manga", "")
|
||||
mangaData.value = manga
|
||||
manga = MangaRepository(manga.source).getDetails(manga)
|
||||
manga = mangaRepositoryFactory.create(manga.source).getDetails(manga)
|
||||
// find default branch
|
||||
val hist = historyRepository.getOne(manga)
|
||||
selectedBranch.value = if (hist != null) {
|
||||
@@ -55,7 +56,7 @@ class MangaDetailsDelegate(
|
||||
relatedManga.value = runCatching {
|
||||
if (manga.source == MangaSource.LOCAL) {
|
||||
val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatching null
|
||||
MangaRepository(m.source).getDetails(m)
|
||||
mangaRepositoryFactory.create(m.source).getDetails(m)
|
||||
} else {
|
||||
localMangaRepository.findSavedManga(manga)
|
||||
}
|
||||
@@ -181,4 +182,4 @@ class MangaDetailsDelegate(
|
||||
}
|
||||
return groups.maxByOrNull { it.value.size }?.key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,11 @@ import android.widget.Toast
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.databinding.SheetScrobblingBinding
|
||||
@@ -30,6 +31,7 @@ import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ScrobblingInfoBottomSheet :
|
||||
BaseBottomSheet<SheetScrobblingBinding>(),
|
||||
AdapterView.OnItemSelectedListener,
|
||||
@@ -37,8 +39,10 @@ class ScrobblingInfoBottomSheet :
|
||||
View.OnClickListener,
|
||||
PopupMenu.OnMenuItemClickListener {
|
||||
|
||||
private val viewModel by sharedViewModel<DetailsViewModel>()
|
||||
private val coil by inject<ImageLoader>(mode = LazyThreadSafetyMode.NONE)
|
||||
private val viewModel by activityViewModels<DetailsViewModel>()
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
private var menu: PopupMenu? = null
|
||||
|
||||
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetScrobblingBinding {
|
||||
@@ -131,7 +135,7 @@ class ScrobblingInfoBottomSheet :
|
||||
val url = viewModel.scrobblingInfo.value?.externalUrl ?: return false
|
||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
startActivity(
|
||||
Intent.createChooser(intent, getString(R.string.open_in_browser))
|
||||
Intent.createChooser(intent, getString(R.string.open_in_browser)),
|
||||
)
|
||||
}
|
||||
R.id.action_unregister -> {
|
||||
@@ -146,4 +150,4 @@ class ScrobblingInfoBottomSheet :
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ import android.net.ConnectivityManager
|
||||
import android.webkit.MimeTypeMap
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
@@ -26,30 +31,30 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
import org.koitharu.kotatsu.utils.ext.waitForNetwork
|
||||
import org.koitharu.kotatsu.utils.progress.ProgressJob
|
||||
import java.io.File
|
||||
|
||||
private const val MAX_DOWNLOAD_ATTEMPTS = 3
|
||||
private const val DOWNLOAD_ERROR_DELAY = 500L
|
||||
private const val SLOWDOWN_DELAY = 200L
|
||||
|
||||
class DownloadManager(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val context: Context,
|
||||
class DownloadManager @AssistedInject constructor(
|
||||
@Assisted private val coroutineScope: CoroutineScope,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val okHttp: OkHttpClient,
|
||||
private val cache: PagesCache,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val settings: AppSettings,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) {
|
||||
|
||||
private val connectivityManager = context.getSystemService(
|
||||
Context.CONNECTIVITY_SERVICE
|
||||
Context.CONNECTIVITY_SERVICE,
|
||||
) as ConnectivityManager
|
||||
private val coverWidth = context.resources.getDimensionPixelSize(
|
||||
androidx.core.R.dimen.compat_notification_large_icon_max_width
|
||||
androidx.core.R.dimen.compat_notification_large_icon_max_width,
|
||||
)
|
||||
private val coverHeight = context.resources.getDimensionPixelSize(
|
||||
androidx.core.R.dimen.compat_notification_large_icon_max_height
|
||||
androidx.core.R.dimen.compat_notification_large_icon_max_height,
|
||||
)
|
||||
private val semaphore = Semaphore(settings.downloadsParallelism)
|
||||
|
||||
@@ -59,7 +64,7 @@ class DownloadManager(
|
||||
startId: Int,
|
||||
): ProgressJob<DownloadState> {
|
||||
val stateFlow = MutableStateFlow<DownloadState>(
|
||||
DownloadState.Queued(startId = startId, manga = manga, cover = null)
|
||||
DownloadState.Queued(startId = startId, manga = manga, cover = null),
|
||||
)
|
||||
val job = downloadMangaImpl(manga, chaptersIds?.takeUnless { it.isEmpty() }, stateFlow, startId)
|
||||
return ProgressJob(job, stateFlow)
|
||||
@@ -71,7 +76,8 @@ class DownloadManager(
|
||||
outState: MutableStateFlow<DownloadState>,
|
||||
startId: Int,
|
||||
): Job = coroutineScope.launch(Dispatchers.Default + errorStateHandler(outState)) {
|
||||
@Suppress("NAME_SHADOWING") var manga = manga
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var manga = manga
|
||||
val chaptersIdsSet = chaptersIds?.toMutableSet()
|
||||
val cover = loadCover(manga)
|
||||
outState.value = DownloadState.Queued(startId, manga, cover)
|
||||
@@ -87,7 +93,7 @@ class DownloadManager(
|
||||
if (manga.source == MangaSource.LOCAL) {
|
||||
manga = localMangaRepository.getRemoteManga(manga) ?: error("Cannot obtain remote manga instance")
|
||||
}
|
||||
val repo = MangaRepository(manga.source)
|
||||
val repo = mangaRepositoryFactory.create(manga.source)
|
||||
outState.value = DownloadState.Preparing(startId, manga, cover)
|
||||
val data = if (manga.chapters.isNullOrEmpty()) repo.getDetails(manga) else manga
|
||||
output = CbzMangaOutput.get(destination, data)
|
||||
@@ -100,7 +106,7 @@ class DownloadManager(
|
||||
data.chapters
|
||||
} else {
|
||||
data.chapters?.filter { x -> chaptersIdsSet.remove(x.id) }
|
||||
}
|
||||
},
|
||||
) { "Chapters list must not be null" }
|
||||
check(chapters.isNotEmpty()) { "Chapters list must not be empty" }
|
||||
check(chaptersIdsSet.isNullOrEmpty()) {
|
||||
@@ -134,7 +140,9 @@ class DownloadManager(
|
||||
}
|
||||
|
||||
outState.value = DownloadState.Progress(
|
||||
startId, data, cover,
|
||||
startId,
|
||||
data,
|
||||
cover,
|
||||
totalChapters = chapters.size,
|
||||
currentChapter = chapterIndex,
|
||||
totalPages = pages.size,
|
||||
@@ -203,27 +211,13 @@ class DownloadManager(
|
||||
.data(manga.coverUrl)
|
||||
.referer(manga.publicUrl)
|
||||
.size(coverWidth, coverHeight)
|
||||
.build()
|
||||
.build(),
|
||||
).drawable
|
||||
}.getOrNull()
|
||||
|
||||
class Factory(
|
||||
private val context: Context,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val okHttp: OkHttpClient,
|
||||
private val cache: PagesCache,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val settings: AppSettings,
|
||||
) {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(coroutineScope: CoroutineScope) = DownloadManager(
|
||||
coroutineScope = coroutineScope,
|
||||
context = context,
|
||||
imageLoader = imageLoader,
|
||||
okHttp = okHttp,
|
||||
cache = cache,
|
||||
localMangaRepository = localMangaRepository,
|
||||
settings = settings,
|
||||
)
|
||||
fun create(coroutineScope: CoroutineScope): DownloadManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,23 +7,29 @@ import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import coil.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.utils.bindServiceWithLifecycle
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityDownloadsBinding.inflate(layoutInflater))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
val adapter = DownloadsAdapter(lifecycleScope, get())
|
||||
val adapter = DownloadsAdapter(lifecycleScope, coil)
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.adapter = adapter
|
||||
bindServiceWithLifecycle(
|
||||
@@ -42,11 +48,11 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
|
||||
binding.recyclerView.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
bottom = insets.bottom
|
||||
bottom = insets.bottom,
|
||||
)
|
||||
binding.toolbar.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right
|
||||
right = insets.right,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,4 +60,4 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
|
||||
|
||||
fun newIntent(context: Context) = Intent(context, DownloadsActivity::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,32 +11,34 @@ import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.set
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseService
|
||||
import org.koitharu.kotatsu.base.ui.dialog.CheckBoxAlertDialog
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.download.domain.DownloadManager
|
||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||
import org.koitharu.kotatsu.download.domain.WakeLockNode
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.connectivityManager
|
||||
import org.koitharu.kotatsu.utils.ext.throttle
|
||||
import org.koitharu.kotatsu.utils.progress.ProgressJob
|
||||
import org.koitharu.kotatsu.utils.progress.TimeLeftEstimator
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DownloadService : BaseService() {
|
||||
|
||||
private lateinit var downloadManager: DownloadManager
|
||||
private lateinit var notificationSwitcher: ForegroundNotificationSwitcher
|
||||
|
||||
@Inject
|
||||
lateinit var downloadManagerFactory: DownloadManager.Factory
|
||||
|
||||
private val jobs = LinkedHashMap<Int, ProgressJob<DownloadState>>()
|
||||
private val jobCount = MutableStateFlow(0)
|
||||
private val controlReceiver = ControlReceiver()
|
||||
@@ -48,7 +50,7 @@ class DownloadService : BaseService() {
|
||||
notificationSwitcher = ForegroundNotificationSwitcher(this)
|
||||
val wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager)
|
||||
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading")
|
||||
downloadManager = get<DownloadManager.Factory>().create(
|
||||
downloadManager = downloadManagerFactory.create(
|
||||
coroutineScope = lifecycleScope + WakeLockNode(wakeLock, TimeUnit.HOURS.toMillis(1)),
|
||||
)
|
||||
DownloadNotification.createChannel(this)
|
||||
@@ -122,7 +124,7 @@ class DownloadService : BaseService() {
|
||||
(job.progressValue as? DownloadState.Done)?.let {
|
||||
sendBroadcast(
|
||||
Intent(ACTION_DOWNLOAD_COMPLETE)
|
||||
.putExtra(EXTRA_MANGA, ParcelableManga(it.localManga, withChapters = false))
|
||||
.putExtra(EXTRA_MANGA, ParcelableManga(it.localManga, withChapters = false)),
|
||||
)
|
||||
}
|
||||
notificationSwitcher.detach(
|
||||
@@ -131,7 +133,7 @@ class DownloadService : BaseService() {
|
||||
null
|
||||
} else {
|
||||
notification.create(job.progressValue, -1L)
|
||||
}
|
||||
},
|
||||
)
|
||||
stopSelf(startId)
|
||||
}
|
||||
@@ -182,27 +184,23 @@ class DownloadService : BaseService() {
|
||||
if (chaptersIds?.isEmpty() == true) {
|
||||
return
|
||||
}
|
||||
confirmDataTransfer(context) {
|
||||
val intent = Intent(context, DownloadService::class.java)
|
||||
intent.putExtra(EXTRA_MANGA, ParcelableManga(manga, withChapters = false))
|
||||
if (chaptersIds != null) {
|
||||
intent.putExtra(EXTRA_CHAPTERS_IDS, chaptersIds.toLongArray())
|
||||
}
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
Toast.makeText(context, R.string.manga_downloading_, Toast.LENGTH_SHORT).show()
|
||||
val intent = Intent(context, DownloadService::class.java)
|
||||
intent.putExtra(EXTRA_MANGA, ParcelableManga(manga, withChapters = false))
|
||||
if (chaptersIds != null) {
|
||||
intent.putExtra(EXTRA_CHAPTERS_IDS, chaptersIds.toLongArray())
|
||||
}
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
Toast.makeText(context, R.string.manga_downloading_, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun start(context: Context, manga: Collection<Manga>) {
|
||||
if (manga.isEmpty()) {
|
||||
return
|
||||
}
|
||||
confirmDataTransfer(context) {
|
||||
for (item in manga) {
|
||||
val intent = Intent(context, DownloadService::class.java)
|
||||
intent.putExtra(EXTRA_MANGA, ParcelableManga(item, withChapters = false))
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
for (item in manga) {
|
||||
val intent = Intent(context, DownloadService::class.java)
|
||||
intent.putExtra(EXTRA_MANGA, ParcelableManga(item, withChapters = false))
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,24 +223,5 @@ class DownloadService : BaseService() {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun confirmDataTransfer(context: Context, callback: () -> Unit) {
|
||||
val settings = GlobalContext.get().get<AppSettings>()
|
||||
if (context.connectivityManager.isActiveNetworkMetered && settings.isTrafficWarningEnabled) {
|
||||
CheckBoxAlertDialog.Builder(context)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(R.string.network_consumption_warning)
|
||||
.setCheckBoxText(R.string.dont_ask_again)
|
||||
.setCheckBoxChecked(false)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string._continue) { _, doNotAsk ->
|
||||
settings.isTrafficWarningEnabled = !doNotAsk
|
||||
callback()
|
||||
}.create()
|
||||
.show()
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.koitharu.kotatsu.explore
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.explore.domain.ExploreRepository
|
||||
import org.koitharu.kotatsu.explore.ui.ExploreViewModel
|
||||
|
||||
val exploreModule
|
||||
get() = module {
|
||||
|
||||
factory { ExploreRepository(get(), get()) }
|
||||
|
||||
viewModel { ExploreViewModel(get(), get()) }
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
package org.koitharu.kotatsu.explore.domain
|
||||
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
|
||||
class ExploreRepository(
|
||||
class ExploreRepository @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) {
|
||||
|
||||
suspend fun findRandomManga(tagsLimit: Int): Manga {
|
||||
@@ -20,7 +22,7 @@ class ExploreRepository(
|
||||
val source = checkNotNull(tag?.source ?: settings.getMangaSources(includeHidden = false).randomOrNull()) {
|
||||
"No sources found"
|
||||
}
|
||||
val repo = MangaRepository(source)
|
||||
val repo = mangaRepositoryFactory.create(source)
|
||||
val list = repo.getList(
|
||||
offset = 0,
|
||||
sortOrder = if (SortOrder.UPDATED in repo.sortOrders) SortOrder.UPDATED else null,
|
||||
@@ -37,4 +39,4 @@ class ExploreRepository(
|
||||
}
|
||||
return list.random()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
@@ -30,12 +32,17 @@ import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
|
||||
@AndroidEntryPoint
|
||||
class ExploreFragment :
|
||||
BaseFragment<FragmentExploreBinding>(),
|
||||
RecyclerViewOwner,
|
||||
ExploreListEventListener,
|
||||
OnListItemClickListener<ExploreItem.Source> {
|
||||
|
||||
private val viewModel by viewModel<ExploreViewModel>()
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private val viewModel by viewModels<ExploreViewModel>()
|
||||
private var exploreAdapter: ExploreAdapter? = null
|
||||
private var paddingHorizontal = 0
|
||||
|
||||
@@ -48,7 +55,7 @@ class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
exploreAdapter = ExploreAdapter(get(), viewLifecycleOwner, this, this)
|
||||
exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this)
|
||||
with(binding.recyclerView) {
|
||||
adapter = exploreAdapter
|
||||
setHasFixedSize(true)
|
||||
@@ -112,7 +119,7 @@ class ExploreFragment : BaseFragment<FragmentExploreBinding>(),
|
||||
val snackbar = Snackbar.make(
|
||||
binding.recyclerView,
|
||||
e.getDisplayMessage(resources),
|
||||
Snackbar.LENGTH_SHORT
|
||||
Snackbar.LENGTH_SHORT,
|
||||
)
|
||||
snackbar.anchorView = (activity as? BottomNavOwner)?.bottomNav
|
||||
snackbar.show()
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.koitharu.kotatsu.explore.ui
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -15,7 +17,8 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
|
||||
class ExploreViewModel(
|
||||
@HiltViewModel
|
||||
class ExploreViewModel @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val exploreRepository: ExploreRepository,
|
||||
) : BaseViewModel() {
|
||||
@@ -66,4 +69,4 @@ class ExploreViewModel(
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.koitharu.kotatsu.favourites
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouritesCategoriesViewModel
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditViewModel
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.MangaCategoriesViewModel
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListViewModel
|
||||
|
||||
val favouritesModule
|
||||
get() = module {
|
||||
|
||||
single { FavouritesRepository(get(), get()) }
|
||||
|
||||
viewModel { categoryId ->
|
||||
FavouritesListViewModel(categoryId.get(), get(), get(), get(), get())
|
||||
}
|
||||
viewModel { FavouritesCategoriesViewModel(get(), get()) }
|
||||
viewModel { manga ->
|
||||
MangaCategoriesViewModel(manga.get(), get())
|
||||
}
|
||||
viewModel { params -> FavouritesCategoryEditViewModel(params[0], get(), get()) }
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.favourites.domain
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koitharu.kotatsu.base.domain.ReversibleHandle
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
@@ -14,7 +16,8 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
|
||||
class FavouritesRepository(
|
||||
@Singleton
|
||||
class FavouritesRepository @Inject constructor(
|
||||
private val db: MangaDatabase,
|
||||
private val channels: TrackerNotificationChannels,
|
||||
) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Bundle
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
@@ -13,6 +14,7 @@ import org.koitharu.kotatsu.databinding.ActivityContainerBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -50,4 +52,4 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
.putExtra(EXTRA_CATEGORY_ID, category.id)
|
||||
.putExtra(EXTRA_TITLE, category.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,17 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||
@@ -33,13 +35,17 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FavouriteCategoriesActivity :
|
||||
BaseActivity<ActivityCategoriesBinding>(),
|
||||
FavouriteCategoriesListListener,
|
||||
View.OnClickListener,
|
||||
ListStateHolderListener {
|
||||
|
||||
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private val viewModel by viewModels<FavouritesCategoriesViewModel>()
|
||||
|
||||
private lateinit var adapter: CategoriesAdapter
|
||||
private lateinit var selectionController: ListSelectionController
|
||||
@@ -49,7 +55,7 @@ class FavouriteCategoriesActivity :
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
adapter = CategoriesAdapter(get(), this, this, this)
|
||||
adapter = CategoriesAdapter(coil, this, this, this)
|
||||
selectionController = ListSelectionController(
|
||||
activity = this,
|
||||
decoration = CategoriesSelectionDecoration(this),
|
||||
@@ -169,7 +175,8 @@ class FavouriteCategoriesActivity :
|
||||
}
|
||||
|
||||
private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(
|
||||
ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0
|
||||
ItemTouchHelper.DOWN or ItemTouchHelper.UP,
|
||||
0,
|
||||
) {
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
|
||||
@@ -2,6 +2,9 @@ package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -16,9 +19,9 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
import org.koitharu.kotatsu.utils.ext.requireValue
|
||||
import java.util.*
|
||||
|
||||
class FavouritesCategoriesViewModel(
|
||||
@HiltViewModel
|
||||
class FavouritesCategoriesViewModel @Inject constructor(
|
||||
private val repository: FavouritesRepository,
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel() {
|
||||
@@ -56,7 +59,7 @@ class FavouritesCategoriesViewModel(
|
||||
textPrimary = R.string.text_empty_holder_primary,
|
||||
textSecondary = R.string.empty_favourite_categories,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
|
||||
@@ -14,8 +14,8 @@ import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
@@ -23,13 +23,21 @@ import org.koitharu.kotatsu.core.ui.titleRes
|
||||
import org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>(), AdapterView.OnItemClickListener,
|
||||
View.OnClickListener, TextWatcher {
|
||||
@AndroidEntryPoint
|
||||
class FavouritesCategoryEditActivity :
|
||||
BaseActivity<ActivityCategoryEditBinding>(),
|
||||
AdapterView.OnItemClickListener,
|
||||
View.OnClickListener,
|
||||
TextWatcher {
|
||||
|
||||
private val viewModel by viewModel<FavouritesCategoryEditViewModel> {
|
||||
parametersOf(intent.getLongExtra(EXTRA_ID, NO_ID))
|
||||
@Inject
|
||||
lateinit var viewModelFactory: FavouritesCategoryEditViewModel.Factory
|
||||
|
||||
private val viewModel by assistedViewModels<FavouritesCategoryEditViewModel> {
|
||||
viewModelFactory.create(intent.getLongExtra(EXTRA_ID, NO_ID))
|
||||
}
|
||||
private var selectedSortOrder: SortOrder? = null
|
||||
|
||||
@@ -164,4 +172,4 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
|
||||
.putExtra(EXTRA_ID, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package org.koitharu.kotatsu.favourites.ui.categories.edit
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
@@ -13,8 +16,8 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
|
||||
private const val NO_ID = -1L
|
||||
|
||||
class FavouritesCategoryEditViewModel(
|
||||
private val categoryId: Long,
|
||||
class FavouritesCategoryEditViewModel @AssistedInject constructor(
|
||||
@Assisted private val categoryId: Long,
|
||||
private val repository: FavouritesRepository,
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel() {
|
||||
@@ -51,4 +54,10 @@ class FavouritesCategoryEditViewModel(
|
||||
onSaved.call(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(categoryId: Long): FavouritesCategoryEditViewModel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
@@ -19,6 +18,7 @@ import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEdit
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@@ -28,8 +28,13 @@ class FavouriteCategoriesBottomSheet :
|
||||
View.OnClickListener,
|
||||
Toolbar.OnMenuItemClickListener {
|
||||
|
||||
private val viewModel by viewModel<MangaCategoriesViewModel> {
|
||||
parametersOf(requireNotNull(arguments?.getParcelableArrayList<ParcelableManga>(KEY_MANGA_LIST)).map { it.manga })
|
||||
@Inject
|
||||
lateinit var viewModelFactory: MangaCategoriesViewModel.Factory
|
||||
|
||||
private val viewModel by assistedViewModels {
|
||||
viewModelFactory.create(
|
||||
requireNotNull(arguments?.getParcelableArrayList<ParcelableManga>(KEY_MANGA_LIST)).map { it.manga },
|
||||
)
|
||||
}
|
||||
|
||||
private var adapter: MangaCategoriesAdapter? = null
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.select
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
@@ -10,9 +13,9 @@ import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryI
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
|
||||
class MangaCategoriesViewModel(
|
||||
private val manga: List<Manga>,
|
||||
private val favouritesRepository: FavouritesRepository
|
||||
class MangaCategoriesViewModel @AssistedInject constructor(
|
||||
@Assisted private val manga: List<Manga>,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val content = combine(
|
||||
@@ -23,7 +26,7 @@ class MangaCategoriesViewModel(
|
||||
MangaCategoryItem(
|
||||
id = it.id,
|
||||
name = it.title,
|
||||
isChecked = it.id in checked
|
||||
isChecked = it.id in checked,
|
||||
)
|
||||
}
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
@@ -43,7 +46,7 @@ class MangaCategoriesViewModel(
|
||||
favouritesRepository.observeCategoriesIds(manga[0].id)
|
||||
} else {
|
||||
combine(
|
||||
manga.map { favouritesRepository.observeCategoriesIds(it.id) }
|
||||
manga.map { favouritesRepository.observeCategoriesIds(it.id) },
|
||||
) { array ->
|
||||
val result = HashSet<Long>()
|
||||
var isFirst = true
|
||||
@@ -58,4 +61,10 @@ class MangaCategoriesViewModel(
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(manga: List<Manga>): MangaCategoriesViewModel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,25 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.core.ui.titleRes
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener {
|
||||
|
||||
override val viewModel by viewModel<FavouritesListViewModel> {
|
||||
parametersOf(categoryId)
|
||||
@Inject
|
||||
lateinit var viewModelFactory: FavouritesListViewModel.Factory
|
||||
|
||||
override val viewModel by assistedViewModels<FavouritesListViewModel> {
|
||||
viewModelFactory.create(categoryId)
|
||||
}
|
||||
|
||||
private val categoryId: Long
|
||||
|
||||
@@ -3,6 +3,9 @@ package org.koitharu.kotatsu.favourites.ui.list
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -25,8 +28,8 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
|
||||
class FavouritesListViewModel(
|
||||
private val categoryId: Long,
|
||||
class FavouritesListViewModel @AssistedInject constructor(
|
||||
@Assisted private val categoryId: Long,
|
||||
private val repository: FavouritesRepository,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
@@ -121,4 +124,10 @@ class FavouritesListViewModel(
|
||||
PROGRESS_NONE
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(categoryId: Long): FavouritesListViewModel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.koitharu.kotatsu.history
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.ui.HistoryListViewModel
|
||||
|
||||
val historyModule
|
||||
get() = module {
|
||||
|
||||
single { HistoryRepository(get(), get(), get(), getAll()) }
|
||||
|
||||
viewModel { HistoryListViewModel(get(), get(), get()) }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.history.domain
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -20,11 +21,11 @@ import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
|
||||
const val PROGRESS_NONE = -1f
|
||||
|
||||
class HistoryRepository(
|
||||
class HistoryRepository @Inject constructor(
|
||||
private val db: MangaDatabase,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val settings: AppSettings,
|
||||
private val scrobblers: List<Scrobbler>,
|
||||
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
||||
) {
|
||||
|
||||
suspend fun getList(offset: Int, limit: Int = 20): List<Manga> {
|
||||
@@ -82,7 +83,7 @@ class HistoryRepository(
|
||||
scroll = scroll.toFloat(), // we migrate to int, but decide to not update database
|
||||
percent = percent,
|
||||
deletedAt = 0L,
|
||||
)
|
||||
),
|
||||
)
|
||||
trackingRepository.syncWithHistory(manga, chapterId)
|
||||
val chapter = manga.chapters?.find { x -> x.id == chapterId }
|
||||
|
||||
@@ -9,11 +9,13 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HistoryActivity :
|
||||
BaseActivity<ActivityContainerBinding>(),
|
||||
AppBarOwner {
|
||||
|
||||
@@ -5,17 +5,18 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import androidx.fragment.app.viewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HistoryListFragment : MangaListFragment() {
|
||||
|
||||
override val viewModel by viewModel<HistoryListViewModel>()
|
||||
override val viewModel by viewModels<HistoryListViewModel>()
|
||||
override val isSwipeRefreshEnabled = false
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -51,7 +52,7 @@ class HistoryListFragment : MangaListFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateAdapter() = HistoryListAdapter(get(), viewLifecycleOwner, this)
|
||||
override fun onCreateAdapter() = HistoryListAdapter(coil, viewLifecycleOwner, this)
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ package org.koitharu.kotatsu.history.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -22,10 +26,9 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class HistoryListViewModel(
|
||||
@HiltViewModel
|
||||
class HistoryListViewModel @Inject constructor(
|
||||
private val repository: HistoryRepository,
|
||||
private val settings: AppSettings,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
@@ -39,7 +42,7 @@ class HistoryListViewModel(
|
||||
override val content = combine(
|
||||
repository.observeAllWithHistory(),
|
||||
historyGrouping,
|
||||
createListModeFlow()
|
||||
createListModeFlow(),
|
||||
) { list, grouped, mode ->
|
||||
when {
|
||||
list.isEmpty() -> listOf(
|
||||
@@ -48,7 +51,7 @@ class HistoryListViewModel(
|
||||
textPrimary = R.string.text_history_holder_primary,
|
||||
textSecondary = R.string.text_history_holder_secondary,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
),
|
||||
)
|
||||
else -> mapList(list, grouped, mode)
|
||||
}
|
||||
@@ -87,7 +90,7 @@ class HistoryListViewModel(
|
||||
private suspend fun mapList(
|
||||
list: List<MangaWithHistory>,
|
||||
grouped: Boolean,
|
||||
mode: ListMode
|
||||
mode: ListMode,
|
||||
): List<ListModel> {
|
||||
val result = ArrayList<ListModel>(if (grouped) (list.size * 1.4).toInt() else list.size + 1)
|
||||
val showPercent = settings.isReadingIndicatorsEnabled
|
||||
|
||||
@@ -16,15 +16,18 @@ import coil.request.ImageRequest
|
||||
import coil.target.ViewTarget
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import org.koin.android.ext.android.inject
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivityImageBinding
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.indicator
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ImageActivity : BaseActivity<ActivityImageBinding>() {
|
||||
|
||||
private val coil: ImageLoader by inject()
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -40,7 +43,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>() {
|
||||
with(binding.toolbar) {
|
||||
updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right
|
||||
right = insets.right,
|
||||
)
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top
|
||||
@@ -90,4 +93,4 @@ class ImageActivity : BaseActivity<ActivityImageBinding>() {
|
||||
.setData(Uri.parse(url))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.koitharu.kotatsu.library
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.library.domain.LibraryRepository
|
||||
import org.koitharu.kotatsu.library.ui.LibraryViewModel
|
||||
import org.koitharu.kotatsu.library.ui.config.categories.LibraryCategoriesConfigViewModel
|
||||
|
||||
val libraryModule
|
||||
get() = module {
|
||||
|
||||
factory { LibraryRepository(get()) }
|
||||
|
||||
viewModel { LibraryViewModel(get(), get(), get(), get(), get()) }
|
||||
viewModel { LibraryCategoriesConfigViewModel(get()) }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.library.domain
|
||||
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
@@ -9,7 +10,7 @@ import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
|
||||
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
class LibraryRepository(
|
||||
class LibraryRepository @Inject constructor(
|
||||
private val db: MangaDatabase,
|
||||
) {
|
||||
|
||||
|
||||
@@ -6,14 +6,17 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.viewModels
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.reverseAsync
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
|
||||
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.FragmentLibraryBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
|
||||
@@ -28,11 +31,18 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LibraryFragment :
|
||||
BaseFragment<FragmentLibraryBinding>(),
|
||||
LibraryListEventListener {
|
||||
|
||||
private val viewModel by viewModel<LibraryViewModel>()
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private val viewModel by viewModels<LibraryViewModel>()
|
||||
private var adapter: LibraryAdapter? = null
|
||||
private var selectionController: SectionedSelectionController<LibrarySectionModel>? = null
|
||||
|
||||
@@ -42,7 +52,7 @@ class LibraryFragment :
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val sizeResolver = ItemSizeResolver(resources, get())
|
||||
val sizeResolver = ItemSizeResolver(resources, settings)
|
||||
selectionController = SectionedSelectionController(
|
||||
activity = requireActivity(),
|
||||
owner = this,
|
||||
@@ -50,7 +60,7 @@ class LibraryFragment :
|
||||
)
|
||||
adapter = LibraryAdapter(
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
coil = get(),
|
||||
coil = coil,
|
||||
listener = this,
|
||||
sizeResolver = sizeResolver,
|
||||
selectionController = checkNotNull(selectionController),
|
||||
|
||||
@@ -3,7 +3,9 @@ package org.koitharu.kotatsu.library.ui
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -30,7 +32,8 @@ import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||
|
||||
private const val HISTORY_MAX_SEGMENTS = 2
|
||||
|
||||
class LibraryViewModel(
|
||||
@HiltViewModel
|
||||
class LibraryViewModel @Inject constructor(
|
||||
repository: LibraryRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
|
||||
@@ -6,19 +6,21 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import androidx.fragment.app.viewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
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.databinding.SheetBaseBinding
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LibraryCategoriesConfigSheet :
|
||||
BaseBottomSheet<SheetBaseBinding>(),
|
||||
OnListItemClickListener<FavouriteCategory>,
|
||||
View.OnClickListener {
|
||||
|
||||
private val viewModel by viewModel<LibraryCategoriesConfigViewModel>()
|
||||
private val viewModel by viewModels<LibraryCategoriesConfigViewModel>()
|
||||
|
||||
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetBaseBinding {
|
||||
return SheetBaseBinding.inflate(inflater, container, false)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.library.ui.config.categories
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
@@ -8,7 +10,8 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
|
||||
class LibraryCategoriesConfigViewModel(
|
||||
@HiltViewModel
|
||||
class LibraryCategoriesConfigViewModel @Inject constructor(
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.slider.LabelFormatter
|
||||
import com.google.android.material.slider.Slider
|
||||
import org.koin.android.ext.android.inject
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
@@ -15,12 +16,14 @@ import org.koitharu.kotatsu.databinding.SheetLibrarySizeBinding
|
||||
import org.koitharu.kotatsu.utils.ext.setValueRounded
|
||||
import org.koitharu.kotatsu.utils.progress.IntPercentLabelFormatter
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LibrarySizeBottomSheet :
|
||||
BaseBottomSheet<SheetLibrarySizeBinding>(),
|
||||
Slider.OnChangeListener,
|
||||
View.OnClickListener {
|
||||
|
||||
private val settings by inject<AppSettings>(mode = LazyThreadSafetyMode.NONE)
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
private var labelFormatter: LabelFormatter? = null
|
||||
|
||||
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetLibrarySizeBinding {
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import org.koin.android.ext.android.inject
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
|
||||
@@ -17,13 +17,16 @@ import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.databinding.DialogListModeBinding
|
||||
import org.koitharu.kotatsu.utils.ext.setValueRounded
|
||||
import org.koitharu.kotatsu.utils.progress.IntPercentLabelFormatter
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ListModeSelectDialog :
|
||||
AlertDialogFragment<DialogListModeBinding>(),
|
||||
CheckableButtonGroup.OnCheckedChangeListener,
|
||||
Slider.OnChangeListener {
|
||||
|
||||
private val settings by inject<AppSettings>(mode = LazyThreadSafetyMode.NONE)
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
|
||||
@@ -12,9 +12,10 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.reverseAsync
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
@@ -56,6 +57,9 @@ abstract class MangaListFragment :
|
||||
ListSelectionController.Callback2,
|
||||
FastScroller.FastScrollListener {
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private var listAdapter: MangaListAdapter? = null
|
||||
private var paginationListener: PaginationScrollListener? = null
|
||||
private var selectionController: ListSelectionController? = null
|
||||
@@ -188,7 +192,7 @@ abstract class MangaListFragment :
|
||||
|
||||
protected open fun onCreateAdapter(): MangaListAdapter {
|
||||
return MangaListAdapter(
|
||||
coil = get(),
|
||||
coil = coil,
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
listener = this,
|
||||
)
|
||||
|
||||
@@ -6,11 +6,12 @@ import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.databinding.SheetFilterBinding
|
||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
|
||||
import org.koitharu.kotatsu.utils.ext.parentFragmentViewModels
|
||||
|
||||
class FilterBottomSheet :
|
||||
BaseBottomSheet<SheetFilterBinding>(),
|
||||
@@ -18,9 +19,7 @@ class FilterBottomSheet :
|
||||
SearchView.OnQueryTextListener,
|
||||
DialogInterface.OnKeyListener {
|
||||
|
||||
private val viewModel by sharedViewModel<RemoteListViewModel>(
|
||||
owner = { requireParentFragment() },
|
||||
)
|
||||
private val viewModel by parentFragmentViewModels<RemoteListViewModel>()
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return super.onCreateDialog(savedInstanceState).also {
|
||||
|
||||
@@ -1,20 +1 @@
|
||||
package org.koitharu.kotatsu.local
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.download.domain.DownloadManager
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.ui.LocalListViewModel
|
||||
|
||||
val localModule
|
||||
get() = module {
|
||||
|
||||
factory { LocalStorageManager(androidContext(), get()) }
|
||||
single { LocalMangaRepository(get()) }
|
||||
|
||||
factory { DownloadManager.Factory(androidContext(), get(), get(), get(), get(), get()) }
|
||||
|
||||
viewModel { LocalListViewModel(get(), get(), get()) }
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.os.StatFs
|
||||
import androidx.annotation.WorkerThread
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -12,15 +16,15 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.utils.ext.computeSize
|
||||
import org.koitharu.kotatsu.utils.ext.getStorageName
|
||||
import java.io.File
|
||||
|
||||
private const val DIR_NAME = "manga"
|
||||
private const val CACHE_DISK_PERCENTAGE = 0.02
|
||||
private const val CACHE_SIZE_MIN: Long = 10 * 1024 * 1024 // 10MB
|
||||
private const val CACHE_SIZE_MAX: Long = 250 * 1024 * 1024 // 250MB
|
||||
|
||||
class LocalStorageManager(
|
||||
private val context: Context,
|
||||
@Singleton
|
||||
class LocalStorageManager @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val settings: AppSettings,
|
||||
) {
|
||||
|
||||
@@ -131,4 +135,4 @@ class LocalStorageManager(
|
||||
private fun File.isWriteable() = runCatching {
|
||||
canWrite()
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,19 @@ package org.koitharu.kotatsu.local.data
|
||||
|
||||
import android.content.Context
|
||||
import com.tomclaw.cache.DiskLruCache
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.koitharu.kotatsu.utils.FileSize
|
||||
import org.koitharu.kotatsu.utils.ext.longHashCode
|
||||
import org.koitharu.kotatsu.utils.ext.subdir
|
||||
import org.koitharu.kotatsu.utils.ext.takeIfReadable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
class PagesCache(context: Context) {
|
||||
@Singleton
|
||||
class PagesCache @Inject constructor(@ApplicationContext context: Context) {
|
||||
|
||||
private val cacheDir = context.externalCacheDir ?: context.cacheDir
|
||||
private val lruCache = createDiskLruCacheSafe(
|
||||
@@ -70,4 +74,4 @@ private fun createDiskLruCacheSafe(dir: File, size: Long): DiskLruCache {
|
||||
dir.mkdir()
|
||||
DiskLruCache.create(dir, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.*
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||
@@ -31,7 +33,8 @@ import org.koitharu.kotatsu.utils.ext.resolveName
|
||||
|
||||
private const val MAX_PARALLELISM = 4
|
||||
|
||||
class LocalMangaRepository(private val storageManager: LocalStorageManager) : MangaRepository {
|
||||
@Singleton
|
||||
class LocalMangaRepository @Inject constructor(private val storageManager: LocalStorageManager) : MangaRepository {
|
||||
|
||||
override val source = MangaSource.LOCAL
|
||||
private val filenameFilter = CbzFilter()
|
||||
@@ -86,7 +89,7 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
|
||||
entries.filter { x ->
|
||||
!x.isDirectory && x.name.substringBeforeLast(
|
||||
File.separatorChar,
|
||||
""
|
||||
"",
|
||||
) == parent
|
||||
}
|
||||
}
|
||||
@@ -138,11 +141,11 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
|
||||
url = fileUri,
|
||||
coverUrl = zipUri(
|
||||
file,
|
||||
entryName = index.getCoverEntry() ?: findFirstImageEntry(zip.entries())?.name.orEmpty()
|
||||
entryName = index.getCoverEntry() ?: findFirstImageEntry(zip.entries())?.name.orEmpty(),
|
||||
),
|
||||
chapters = info.chapters?.map { c ->
|
||||
c.copy(url = fileUri, source = MangaSource.LOCAL)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
// fallback
|
||||
@@ -211,7 +214,7 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
|
||||
return@runInterruptible info.copy2(
|
||||
source = MangaSource.LOCAL,
|
||||
url = fileUri,
|
||||
chapters = info.chapters?.map { c -> c.copy(url = fileUri) }
|
||||
chapters = info.chapters?.map { c -> c.copy(url = fileUri) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -342,4 +345,4 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
|
||||
branch = branch,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.koin.android.ext.android.inject
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.CoroutineIntentService
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
@@ -16,9 +17,11 @@ import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
|
||||
private val localMangaRepository by inject<LocalMangaRepository>()
|
||||
@Inject
|
||||
lateinit var localMangaRepository: LocalMangaRepository
|
||||
|
||||
override suspend fun processIntent(intent: Intent?) {
|
||||
val manga = intent?.getParcelableExtra<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
|
||||
@@ -28,7 +31,7 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)
|
||||
sendBroadcast(
|
||||
Intent(DownloadService.ACTION_DOWNLOAD_COMPLETE)
|
||||
.putExtra(EXTRA_MANGA, ParcelableManga(manga, withChapters = false))
|
||||
.putExtra(EXTRA_MANGA, ParcelableManga(manga, withChapters = false)),
|
||||
)
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
@@ -77,4 +80,4 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
@@ -25,7 +25,7 @@ import org.koitharu.kotatsu.utils.progress.Progress
|
||||
|
||||
class LocalListFragment : MangaListFragment(), ActivityResultCallback<List<@JvmSuppressWildcards Uri>> {
|
||||
|
||||
override val viewModel by viewModel<LocalListViewModel>()
|
||||
override val viewModel by viewModels<LocalListViewModel>()
|
||||
private val importCall = registerForActivityResult(
|
||||
ActivityResultContracts.OpenMultipleDocuments(),
|
||||
this,
|
||||
|
||||
@@ -3,6 +3,9 @@ package org.koitharu.kotatsu.local.ui
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -25,9 +28,9 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.progress.Progress
|
||||
import java.io.IOException
|
||||
|
||||
class LocalListViewModel(
|
||||
@HiltViewModel
|
||||
class LocalListViewModel @Inject constructor(
|
||||
private val repository: LocalMangaRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
settings: AppSettings,
|
||||
@@ -42,7 +45,7 @@ class LocalListViewModel(
|
||||
override val content = combine(
|
||||
mangaList,
|
||||
createListModeFlow(),
|
||||
listError
|
||||
listError,
|
||||
) { list, mode, error ->
|
||||
when {
|
||||
error != null -> listOf(error.toErrorState(canRetry = true))
|
||||
@@ -53,7 +56,7 @@ class LocalListViewModel(
|
||||
textPrimary = R.string.text_local_holder_primary,
|
||||
textSecondary = R.string.text_local_holder_secondary,
|
||||
actionStringRes = R.string._import,
|
||||
)
|
||||
),
|
||||
)
|
||||
else -> list.toUi(mode)
|
||||
}
|
||||
@@ -125,4 +128,4 @@ class LocalListViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.koitharu.kotatsu.main
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import androidx.room.InvalidationTracker
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsUpdater
|
||||
import org.koitharu.kotatsu.main.ui.MainViewModel
|
||||
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
|
||||
import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel
|
||||
|
||||
val mainModule
|
||||
get() = module {
|
||||
single { AppProtectHelper(get()) } bind Application.ActivityLifecycleCallbacks::class
|
||||
single { ActivityRecreationHandle() } bind Application.ActivityLifecycleCallbacks::class
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
single { ShortcutsUpdater(androidContext(), get(), get(), get()) } bind InvalidationTracker.Observer::class
|
||||
} else {
|
||||
factory { ShortcutsUpdater(androidContext(), get(), get(), get()) }
|
||||
}
|
||||
|
||||
viewModel { MainViewModel(get(), get(), get(), get()) }
|
||||
viewModel { ProtectViewModel(get(), get()) }
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -12,13 +11,13 @@ import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
|
||||
class ExitCallback(
|
||||
private val activity: ComponentActivity,
|
||||
private val activity: BaseActivity<*>,
|
||||
private val snackbarHost: View,
|
||||
) : OnBackPressedCallback(false) {
|
||||
|
||||
@@ -46,7 +45,7 @@ class ExitCallback(
|
||||
}
|
||||
|
||||
private fun observeSettings() {
|
||||
activity.get<AppSettings>()
|
||||
activity.settings
|
||||
.observeAsFlow(AppSettings.KEY_EXIT_CONFIRM) { isExitConfirmationEnabled }
|
||||
.flowOn(Dispatchers.Default)
|
||||
.onEach { isEnabled = it }
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
@@ -17,20 +18,19 @@ import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.yield
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.base.ui.widgets.KotatsuBottomNavigationView
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.ActivityMainBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.explore.ui.ExploreFragment
|
||||
@@ -48,16 +48,15 @@ import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
||||
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
|
||||
import org.koitharu.kotatsu.settings.tools.ToolsFragment
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.tracker.ui.FeedFragment
|
||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||
import org.koitharu.kotatsu.utils.VoiceInputContract
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
private const val TAG_PRIMARY = "primary"
|
||||
private const val TAG_SEARCH = "search"
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity :
|
||||
BaseActivity<ActivityMainBinding>(),
|
||||
AppBarOwner,
|
||||
@@ -68,8 +67,8 @@ class MainActivity :
|
||||
NavigationBarView.OnItemSelectedListener,
|
||||
NavigationBarView.OnItemReselectedListener {
|
||||
|
||||
private val viewModel by viewModel<MainViewModel>()
|
||||
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>()
|
||||
private val viewModel by viewModels<MainViewModel>()
|
||||
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
|
||||
private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback())
|
||||
private lateinit var navBar: NavigationBarView
|
||||
|
||||
@@ -284,7 +283,8 @@ class MainActivity :
|
||||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
Snackbar.make(binding.container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).setAnchorView(bottomNav).show()
|
||||
Snackbar.make(binding.container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).setAnchorView(bottomNav)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onCountersChanged(counters: SparseIntArray) {
|
||||
@@ -366,13 +366,12 @@ class MainActivity :
|
||||
TrackWorker.setup(applicationContext)
|
||||
SuggestionsWorker.setup(applicationContext)
|
||||
}
|
||||
val settings = get<AppSettings>()
|
||||
when {
|
||||
!settings.isSourcesSelected -> OnboardDialogFragment.showWelcome(supportFragmentManager)
|
||||
settings.newSources.isNotEmpty() -> NewSourcesDialogFragment.show(supportFragmentManager)
|
||||
}
|
||||
yield()
|
||||
get<SyncController>().requestFullSyncAndGc(get())
|
||||
// TODO get<SyncController>().requestFullSyncAndGc(get())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.main.ui
|
||||
import android.util.SparseIntArray
|
||||
import androidx.core.util.set
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -15,8 +16,10 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainViewModel(
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val settings: AppSettings,
|
||||
private val appUpdateRepository: AppUpdateRepository,
|
||||
|
||||
@@ -4,9 +4,12 @@ import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
class AppProtectHelper(private val settings: AppSettings) : Application.ActivityLifecycleCallbacks {
|
||||
@Singleton
|
||||
class AppProtectHelper @Inject constructor(private val settings: AppSettings) : Application.ActivityLifecycleCallbacks {
|
||||
|
||||
private var isUnlocked = settings.appPassword.isNullOrEmpty()
|
||||
|
||||
@@ -46,4 +49,4 @@ class AppProtectHelper(private val settings: AppSettings) : Application.Activity
|
||||
private fun restoreLock() {
|
||||
isUnlocked = settings.appPassword.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,25 +10,27 @@ import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.biometric.BiometricPrompt.AuthenticationCallback
|
||||
import androidx.core.graphics.Insets
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivityProtectBinding
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ProtectActivity :
|
||||
BaseActivity<ActivityProtectBinding>(),
|
||||
TextView.OnEditorActionListener,
|
||||
TextWatcher,
|
||||
View.OnClickListener {
|
||||
|
||||
private val viewModel by viewModel<ProtectViewModel>()
|
||||
private val viewModel by viewModels<ProtectViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -58,7 +60,7 @@ class ProtectActivity :
|
||||
basePadding + insets.left,
|
||||
basePadding + insets.top,
|
||||
basePadding + insets.right,
|
||||
basePadding + insets.bottom
|
||||
basePadding + insets.bottom,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -129,4 +131,4 @@ class ProtectActivity :
|
||||
.putExtra(EXTRA_INTENT, sourceIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.koitharu.kotatsu.main.ui.protect
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
@@ -10,7 +12,8 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
|
||||
private const val PASSWORD_COMPARE_DELAY = 1_000L
|
||||
|
||||
class ProtectViewModel(
|
||||
@HiltViewModel
|
||||
class ProtectViewModel @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val protectHelper: AppProtectHelper,
|
||||
) : BaseViewModel() {
|
||||
@@ -42,4 +45,4 @@ class ProtectViewModel(
|
||||
protectHelper.unlock()
|
||||
onUnlockSuccess.call(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.koitharu.kotatsu.reader
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.reader.ui.PageSaveHelper
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
||||
|
||||
val readerModule
|
||||
get() = module {
|
||||
|
||||
factory { MangaDataRepository(get()) }
|
||||
single { PagesCache(get()) }
|
||||
|
||||
factory { PageSaveHelper(get(), androidContext()) }
|
||||
|
||||
viewModel { params ->
|
||||
ReaderViewModel(
|
||||
intent = params[0],
|
||||
initialState = params[1],
|
||||
preselectedBranch = params[2],
|
||||
dataRepository = get(),
|
||||
historyRepository = get(),
|
||||
settings = get(),
|
||||
pageSaveHelper = get(),
|
||||
bookmarksRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
|
||||
private const val PAGES_TRIM_THRESHOLD = 120
|
||||
|
||||
class ChaptersLoader {
|
||||
class ChaptersLoader(
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) {
|
||||
|
||||
val chapters = LongSparseArray<MangaChapter>()
|
||||
private val chapterPages = ChapterPages()
|
||||
@@ -62,7 +64,7 @@ class ChaptersLoader {
|
||||
|
||||
private suspend fun loadChapter(manga: Manga, chapterId: Long): List<ReaderPage> {
|
||||
val chapter = checkNotNull(chapters[chapterId]) { "Requested chapter not found" }
|
||||
val repo = MangaRepository(manga.source)
|
||||
val repo = mangaRepositoryFactory.create(manga.source)
|
||||
return repo.getPages(chapter).mapIndexed { index, page ->
|
||||
ReaderPage(page, index, chapterId)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import androidx.collection.LongSparseArray
|
||||
import androidx.collection.set
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -14,8 +20,6 @@ import kotlinx.coroutines.sync.withLock
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okio.Closeable
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
@@ -27,26 +31,25 @@ import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import org.koitharu.kotatsu.utils.ext.connectivityManager
|
||||
import org.koitharu.kotatsu.utils.progress.ProgressDeferred
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
private const val PROGRESS_UNDEFINED = -1f
|
||||
private const val PREFETCH_LIMIT_DEFAULT = 10
|
||||
|
||||
class PageLoader : KoinComponent, Closeable {
|
||||
class PageLoader @Inject constructor(
|
||||
private val okHttp: OkHttpClient,
|
||||
private val cache: PagesCache,
|
||||
private val settings: AppSettings,
|
||||
@ApplicationContext context: Context,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) : Closeable {
|
||||
|
||||
val loaderScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
|
||||
private val okHttp = get<OkHttpClient>()
|
||||
private val cache = get<PagesCache>()
|
||||
private val settings = get<AppSettings>()
|
||||
private val connectivityManager = get<Context>().connectivityManager
|
||||
private val connectivityManager = context.connectivityManager
|
||||
private val tasks = LongSparseArray<ProgressDeferred<File, Float>>()
|
||||
private val convertLock = Mutex()
|
||||
private var repository: MangaRepository? = null
|
||||
private var prefetchQueue = LinkedList<MangaPage>()
|
||||
private val prefetchQueue = LinkedList<MangaPage>()
|
||||
private val counter = AtomicInteger(0)
|
||||
private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive
|
||||
private val emptyProgressFlow: StateFlow<Float> = MutableStateFlow(-1f)
|
||||
@@ -150,7 +153,7 @@ class PageLoader : KoinComponent, Closeable {
|
||||
return if (result != null && result.source == source) {
|
||||
result
|
||||
} else {
|
||||
MangaRepository(source).also { repository = it }
|
||||
mangaRepositoryFactory.create(source).also { repository = it }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,4 +197,4 @@ class PageLoader : KoinComponent, Closeable {
|
||||
val deferred = CompletableDeferred(file)
|
||||
return ProgressDeferred(deferred, emptyProgressFlow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import kotlin.math.roundToInt
|
||||
import org.koin.android.ext.android.get
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
@@ -19,9 +18,15 @@ import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemClickListener<ChapterListItem> {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetChaptersBinding {
|
||||
return SheetChaptersBinding.inflate(inflater, container, false)
|
||||
}
|
||||
@@ -35,7 +40,7 @@ class ChaptersBottomSheet : BaseBottomSheet<SheetChaptersBinding>(), OnListItemC
|
||||
}
|
||||
val currentId = requireArguments().getLong(ARG_CURRENT_ID, 0L)
|
||||
val currentPosition = chapters.indexOfFirst { it.id == currentId }
|
||||
val dateFormat = get<AppSettings>().getDateFormat()
|
||||
val dateFormat = settings.getDateFormat()
|
||||
val items = chapters.mapIndexed { index, chapter ->
|
||||
chapter.toListItem(
|
||||
isCurrent = index == currentPosition,
|
||||
|
||||
@@ -4,27 +4,27 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.base.domain.MangaUtils
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.toFileNameSafe
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import java.io.File
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
private const val MAX_FILENAME_LENGTH = 10
|
||||
private const val EXTENSION_FALLBACK = "png"
|
||||
|
||||
class PageSaveHelper(
|
||||
private val cache: PagesCache,
|
||||
context: Context,
|
||||
class PageSaveHelper @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
) {
|
||||
|
||||
private var continuation: Continuation<Uri>? = null
|
||||
@@ -65,7 +65,7 @@ class PageSaveHelper(
|
||||
var extension = name.substringAfterLast('.', "")
|
||||
name = name.substringBeforeLast('.')
|
||||
if (extension.length !in 2..4) {
|
||||
val mimeType = MangaUtils.getImageMimeType(file)
|
||||
val mimeType = MangaDataRepository.getImageMimeType(file)
|
||||
extension = if (mimeType != null) {
|
||||
MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: EXTENSION_FALLBACK
|
||||
} else {
|
||||
@@ -74,4 +74,4 @@ class PageSaveHelper(
|
||||
}
|
||||
return name.toFileNameSafe().take(MAX_FILENAME_LENGTH) + "." + extension
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.OnApplyWindowInsetsListener
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
@@ -18,12 +17,12 @@ import androidx.transition.TransitionManager
|
||||
import androidx.transition.TransitionSet
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
@@ -44,8 +43,8 @@ import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.utils.GridTouchHelper
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ReaderActivity :
|
||||
BaseFullscreenActivity<ActivityReaderBinding>(),
|
||||
ChaptersBottomSheet.OnChapterChangeListener,
|
||||
@@ -55,11 +54,14 @@ class ReaderActivity :
|
||||
ReaderControlDelegate.OnInteractionListener,
|
||||
OnApplyWindowInsetsListener {
|
||||
|
||||
private val viewModel by viewModel<ReaderViewModel> {
|
||||
parametersOf(
|
||||
MangaIntent(intent),
|
||||
intent?.getParcelableExtra<ReaderState>(EXTRA_STATE),
|
||||
intent?.getStringExtra(EXTRA_BRANCH),
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ReaderViewModel.Factory
|
||||
|
||||
val viewModel by assistedViewModels {
|
||||
viewModelFactory.create(
|
||||
intent = MangaIntent(intent),
|
||||
initialState = intent?.getParcelableExtra(EXTRA_STATE),
|
||||
preselectedBranch = intent?.getStringExtra(EXTRA_BRANCH),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -75,7 +77,7 @@ class ReaderActivity :
|
||||
readerManager = ReaderManager(supportFragmentManager, R.id.container)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
touchHelper = GridTouchHelper(this, this)
|
||||
controlDelegate = ReaderControlDelegate(lifecycleScope, get(), this)
|
||||
controlDelegate = ReaderControlDelegate(lifecycleScope, settings, this)
|
||||
binding.toolbarBottom.setOnMenuItemClickListener(::onOptionsItemSelected)
|
||||
binding.slider.setLabelFormatter(PageLabelFormatter())
|
||||
ReaderSliderListener(this, viewModel).attachToSlider(binding.slider)
|
||||
@@ -121,7 +123,7 @@ class ReaderActivity :
|
||||
ChaptersBottomSheet.show(
|
||||
supportFragmentManager,
|
||||
viewModel.manga?.chapters.orEmpty(),
|
||||
viewModel.getCurrentState()?.chapterId ?: 0L
|
||||
viewModel.getCurrentState()?.chapterId ?: 0L,
|
||||
)
|
||||
}
|
||||
R.id.action_pages_thumbs -> {
|
||||
@@ -284,12 +286,12 @@ class ReaderActivity :
|
||||
binding.appbarTop.updatePadding(
|
||||
top = systemBars.top,
|
||||
right = systemBars.right,
|
||||
left = systemBars.left
|
||||
left = systemBars.left,
|
||||
)
|
||||
binding.appbarBottom?.updatePadding(
|
||||
bottom = systemBars.bottom,
|
||||
right = systemBars.right,
|
||||
left = systemBars.left
|
||||
left = systemBars.left,
|
||||
)
|
||||
return WindowInsetsCompat.Builder(insets)
|
||||
.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)
|
||||
|
||||
@@ -7,12 +7,16 @@ import androidx.annotation.AnyThread
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import java.util.*
|
||||
import javax.inject.Provider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.base.domain.MangaUtils
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||
@@ -33,20 +37,21 @@ import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.requireValue
|
||||
import java.util.*
|
||||
|
||||
private const val BOUNDS_PAGE_OFFSET = 2
|
||||
private const val PREFETCH_LIMIT = 10
|
||||
|
||||
class ReaderViewModel(
|
||||
private val intent: MangaIntent,
|
||||
initialState: ReaderState?,
|
||||
private val preselectedBranch: String?,
|
||||
class ReaderViewModel @AssistedInject constructor(
|
||||
@Assisted private val intent: MangaIntent,
|
||||
@Assisted initialState: ReaderState?,
|
||||
@Assisted private val preselectedBranch: String?,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
private val dataRepository: MangaDataRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val bookmarksRepository: BookmarksRepository,
|
||||
private val settings: AppSettings,
|
||||
private val pageSaveHelper: PageSaveHelper,
|
||||
pageLoaderFactory: Provider<PageLoader>,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private var loadingJob: Job? = null
|
||||
@@ -57,8 +62,8 @@ class ReaderViewModel(
|
||||
private val chapters: LongSparseArray<MangaChapter>
|
||||
get() = chaptersLoader.chapters
|
||||
|
||||
val pageLoader = PageLoader()
|
||||
private val chaptersLoader = ChaptersLoader()
|
||||
val pageLoader = pageLoaderFactory.get()
|
||||
private val chaptersLoader = ChaptersLoader(mangaRepositoryFactory)
|
||||
|
||||
val readerMode = MutableLiveData<ReaderMode>()
|
||||
val onPageSaved = SingleLiveEvent<Uri?>()
|
||||
@@ -72,7 +77,7 @@ class ReaderViewModel(
|
||||
val readerAnimation = settings.observeAsLiveData(
|
||||
context = viewModelScope.coroutineContext + Dispatchers.Default,
|
||||
key = AppSettings.KEY_READER_ANIMATION,
|
||||
valueProducer = { readerAnimation }
|
||||
valueProducer = { readerAnimation },
|
||||
)
|
||||
|
||||
val isScreenshotsBlockEnabled = combine(
|
||||
@@ -115,12 +120,12 @@ class ReaderViewModel(
|
||||
val manga = checkNotNull(mangaData.value)
|
||||
dataRepository.savePreferences(
|
||||
manga = manga,
|
||||
mode = newMode
|
||||
mode = newMode,
|
||||
)
|
||||
readerMode.value = newMode
|
||||
content.value?.run {
|
||||
content.value = copy(
|
||||
state = getCurrentState()
|
||||
state = getCurrentState(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -253,7 +258,7 @@ class ReaderViewModel(
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
var manga = dataRepository.resolveIntent(intent) ?: throw NotFoundException("Cannot find manga", "")
|
||||
mangaData.value = manga
|
||||
val repo = MangaRepository(manga.source)
|
||||
val repo = mangaRepositoryFactory.create(manga.source)
|
||||
manga = repo.getDetails(manga)
|
||||
manga.chapters?.forEach {
|
||||
chapters.put(it.id, it)
|
||||
@@ -317,7 +322,7 @@ class ReaderViewModel(
|
||||
?: error("There are no chapters in this manga")
|
||||
val pages = repo.getPages(chapter)
|
||||
return runCatching {
|
||||
val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages)
|
||||
val isWebtoon = dataRepository.determineMangaIsWebtoon(repo, pages)
|
||||
if (isWebtoon) ReaderMode.WEBTOON else defaultMode
|
||||
}.onSuccess {
|
||||
dataRepository.savePreferences(manga, it)
|
||||
@@ -353,6 +358,16 @@ class ReaderViewModel(
|
||||
val ppc = 1f / chaptersCount
|
||||
return ppc * chapterIndex + ppc * pagePercent
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
intent: MangaIntent,
|
||||
initialState: ReaderState?,
|
||||
preselectedBranch: String?,
|
||||
): ReaderViewModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,10 +8,10 @@ import android.view.ViewGroup
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.core.view.isGone
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
|
||||
@@ -30,7 +30,7 @@ class ReaderConfigBottomSheet :
|
||||
ActivityResultCallback<Uri?>,
|
||||
View.OnClickListener {
|
||||
|
||||
private val viewModel by sharedViewModel<ReaderViewModel>()
|
||||
private val viewModel by activityViewModels<ReaderViewModel>()
|
||||
private val savePageRequest = registerForActivityResult(PageSaveContract(), this)
|
||||
private var orientationHelper: ScreenOrientationHelper? = null
|
||||
private lateinit var mode: ReaderMode
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.koitharu.kotatsu.reader.ui.pager
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
||||
@@ -13,7 +13,7 @@ private const val KEY_STATE = "state"
|
||||
|
||||
abstract class BaseReader<B : ViewBinding> : BaseFragment<B>() {
|
||||
|
||||
protected val viewModel by sharedViewModel<ReaderViewModel>()
|
||||
protected val viewModel by activityViewModels<ReaderViewModel>()
|
||||
private var stateToSave: ReaderState? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -53,4 +53,4 @@ abstract class BaseReader<B : ViewBinding> : BaseFragment<B>() {
|
||||
abstract fun getCurrentState(): ReaderState?
|
||||
|
||||
protected abstract fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlinx.coroutines.async
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||
@@ -20,19 +22,23 @@ import org.koitharu.kotatsu.utils.ext.recyclerView
|
||||
import org.koitharu.kotatsu.utils.ext.resetTransformations
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private var pagerAdapter: ReversedPagesAdapter? = null
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
container: ViewGroup?,
|
||||
) = FragmentReaderStandardBinding.inflate(inflater, container, false)
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
pagerAdapter = ReversedPagesAdapter(viewModel.pageLoader, get(), exceptionResolver)
|
||||
pagerAdapter = ReversedPagesAdapter(viewModel.pageLoader, settings, exceptionResolver)
|
||||
with(binding.pager) {
|
||||
adapter = pagerAdapter
|
||||
offscreenPageLimit = 2
|
||||
@@ -67,7 +73,7 @@ class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
override fun switchPageTo(position: Int, smooth: Boolean) {
|
||||
binding.pager.setCurrentItem(
|
||||
reversed(position),
|
||||
smooth && (binding.pager.currentItem - position).absoluteValue < PagerReaderFragment.SMOOTH_SCROLL_LIMIT
|
||||
smooth && (binding.pager.currentItem - position).absoluteValue < PagerReaderFragment.SMOOTH_SCROLL_LIMIT,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,7 +104,7 @@ class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
ReaderState(
|
||||
chapterId = page.chapterId,
|
||||
page = page.index,
|
||||
scroll = 0
|
||||
scroll = 0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -109,4 +115,4 @@ class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
private fun reversed(position: Int): Int {
|
||||
return ((pagerAdapter?.itemCount ?: 0) - position - 1).coerceAtLeast(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlinx.coroutines.async
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||
@@ -19,19 +21,23 @@ import org.koitharu.kotatsu.utils.ext.recyclerView
|
||||
import org.koitharu.kotatsu.utils.ext.resetTransformations
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private var pagesAdapter: PagesAdapter? = null
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
container: ViewGroup?,
|
||||
) = FragmentReaderStandardBinding.inflate(inflater, container, false)
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
pagesAdapter = PagesAdapter(viewModel.pageLoader, get(), exceptionResolver)
|
||||
pagesAdapter = PagesAdapter(viewModel.pageLoader, settings, exceptionResolver)
|
||||
with(binding.pager) {
|
||||
adapter = pagesAdapter
|
||||
offscreenPageLimit = 2
|
||||
@@ -86,7 +92,7 @@ class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
override fun switchPageTo(position: Int, smooth: Boolean) {
|
||||
binding.pager.setCurrentItem(
|
||||
position,
|
||||
smooth && (binding.pager.currentItem - position).absoluteValue < SMOOTH_SCROLL_LIMIT
|
||||
smooth && (binding.pager.currentItem - position).absoluteValue < SMOOTH_SCROLL_LIMIT,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -96,7 +102,7 @@ class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
ReaderState(
|
||||
chapterId = page.chapterId,
|
||||
page = page.index,
|
||||
scroll = 0
|
||||
scroll = 0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -108,4 +114,4 @@ class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
|
||||
const val SMOOTH_SCROLL_LIMIT = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.async
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||
@@ -16,19 +18,23 @@ import org.koitharu.kotatsu.utils.ext.findCenterViewPosition
|
||||
import org.koitharu.kotatsu.utils.ext.firstVisibleItemPosition
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private val scrollInterpolator = AccelerateDecelerateInterpolator()
|
||||
private var webtoonAdapter: WebtoonAdapter? = null
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
container: ViewGroup?,
|
||||
) = FragmentReaderWebtoonBinding.inflate(inflater, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
webtoonAdapter = WebtoonAdapter(viewModel.pageLoader, get(), exceptionResolver)
|
||||
webtoonAdapter = WebtoonAdapter(viewModel.pageLoader, settings, exceptionResolver)
|
||||
with(binding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
adapter = webtoonAdapter
|
||||
@@ -73,7 +79,7 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
chapterId = page.chapterId,
|
||||
page = page.index,
|
||||
scroll = (recyclerView.findViewHolderForAdapterPosition(currentItem) as? WebtoonHolder)
|
||||
?.getScrollY() ?: 0
|
||||
?.getScrollY() ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,7 +91,7 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
binding.recyclerView.smoothScrollBy(
|
||||
0,
|
||||
(binding.recyclerView.height * 0.9).toInt() * delta,
|
||||
scrollInterpolator
|
||||
scrollInterpolator,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,4 +106,4 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
notifyPageChanged(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.getViewModel
|
||||
import coil.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
@@ -21,16 +23,28 @@ import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PagesThumbnailsSheet :
|
||||
BaseBottomSheet<SheetPagesBinding>(),
|
||||
OnListItemClickListener<MangaPage>,
|
||||
BottomSheetHeaderBar.OnExpansionChangeListener {
|
||||
|
||||
@Inject
|
||||
lateinit var mangaRepositoryFactory: MangaRepository.Factory
|
||||
|
||||
@Inject
|
||||
lateinit var pageLoaderProvider: Provider<PageLoader>
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private lateinit var thumbnails: List<PageThumbnail>
|
||||
private var spanResolver: MangaListSpanResolver? = null
|
||||
private var currentPageIndex = -1
|
||||
@@ -44,7 +58,7 @@ class PagesThumbnailsSheet :
|
||||
return
|
||||
}
|
||||
currentPageIndex = requireArguments().getInt(ARG_CURRENT, currentPageIndex)
|
||||
val repository = MangaRepository(pages.first().source)
|
||||
val repository = mangaRepositoryFactory.create(pages.first().source)
|
||||
thumbnails = pages.mapIndexed { i, x ->
|
||||
PageThumbnail(
|
||||
number = i + 1,
|
||||
@@ -75,13 +89,13 @@ class PagesThumbnailsSheet :
|
||||
)
|
||||
adapter = PageThumbnailAdapter(
|
||||
dataSet = thumbnails,
|
||||
coil = get(),
|
||||
coil = coil,
|
||||
scope = viewLifecycleScope,
|
||||
loader = getPageLoader(),
|
||||
clickListener = this@PagesThumbnailsSheet,
|
||||
)
|
||||
addOnLayoutChangeListener(spanResolver)
|
||||
spanResolver?.setGridSize(get<AppSettings>().gridSize / 100f, this)
|
||||
spanResolver?.setGridSize(settings.gridSize / 100f, this)
|
||||
if (currentPageIndex > 0) {
|
||||
val offset = resources.getDimensionPixelOffset(R.dimen.preferred_grid_width)
|
||||
(layoutManager as GridLayoutManager).scrollToPositionWithOffset(currentPageIndex, offset)
|
||||
@@ -119,8 +133,8 @@ class PagesThumbnailsSheet :
|
||||
}
|
||||
|
||||
private fun getPageLoader(): PageLoader {
|
||||
val viewModel = (activity as? ReaderActivity)?.getViewModel<ReaderViewModel>()
|
||||
return viewModel?.pageLoader ?: PageLoader().also { pageLoader = it }
|
||||
val viewModel = (activity as? ReaderActivity)?.viewModel
|
||||
return viewModel?.pageLoader ?: pageLoaderProvider.get().also { pageLoader = it }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.koitharu.kotatsu.remotelist
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
|
||||
|
||||
val remoteListModule
|
||||
get() = module {
|
||||
|
||||
viewModel { params ->
|
||||
RemoteListViewModel(
|
||||
repository = MangaRepository(params[0]) as RemoteMangaRepository,
|
||||
settings = get(),
|
||||
dataRepository = get(),
|
||||
searchRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import android.view.View
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.MenuProvider
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
@@ -19,13 +19,18 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.search.ui.SearchActivity
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.serializableArgument
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RemoteListFragment : MangaListFragment() {
|
||||
|
||||
override val viewModel by viewModel<RemoteListViewModel> {
|
||||
parametersOf(source)
|
||||
@Inject
|
||||
lateinit var viewModelFactory: RemoteListViewModel.Factory
|
||||
|
||||
public override val viewModel by assistedViewModels {
|
||||
viewModelFactory.create(source)
|
||||
}
|
||||
|
||||
private val source by serializableArgument<MangaSource>(ARG_SOURCE)
|
||||
|
||||
@@ -2,6 +2,10 @@ package org.koitharu.kotatsu.remotelist.ui
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
@@ -9,6 +13,7 @@ import kotlinx.coroutines.flow.*
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
@@ -18,21 +23,23 @@ import org.koitharu.kotatsu.list.ui.filter.FilterState
|
||||
import org.koitharu.kotatsu.list.ui.filter.OnFilterChangedListener
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import java.util.*
|
||||
|
||||
private const val FILTER_MIN_INTERVAL = 250L
|
||||
|
||||
class RemoteListViewModel(
|
||||
private val repository: RemoteMangaRepository,
|
||||
class RemoteListViewModel @AssistedInject constructor(
|
||||
@Assisted source: MangaSource,
|
||||
mangaRepositoryFactory: MangaRepository.Factory,
|
||||
private val searchRepository: MangaSearchRepository,
|
||||
settings: AppSettings,
|
||||
dataRepository: MangaDataRepository,
|
||||
) : MangaListViewModel(settings), OnFilterChangedListener {
|
||||
|
||||
private val repository = mangaRepositoryFactory.create(source) as RemoteMangaRepository
|
||||
private val filter = FilterCoordinator(repository, dataRepository, viewModelScope)
|
||||
private val mangaList = MutableStateFlow<List<Manga>?>(null)
|
||||
private val hasNextPage = MutableStateFlow(false)
|
||||
@@ -158,7 +165,7 @@ class RemoteListViewModel(
|
||||
|
||||
private suspend fun createChipsList(
|
||||
filterState: FilterState,
|
||||
availableTags: Set<MangaTag>
|
||||
availableTags: Set<MangaTag>,
|
||||
): List<ChipsView.ChipModel> {
|
||||
val selectedTags = filterState.tags.toMutableSet()
|
||||
var tags = searchRepository.getTagsSuggestion("", 6, repository.source)
|
||||
@@ -195,4 +202,10 @@ class RemoteListViewModel(
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(source: MangaSource): RemoteListViewModel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.koitharu.kotatsu.scrobbling
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ElementsIntoSet
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.scrobbling.domain.Scrobbler
|
||||
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriAuthenticator
|
||||
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriInterceptor
|
||||
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
|
||||
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriStorage
|
||||
import org.koitharu.kotatsu.scrobbling.shikimori.domain.ShikimoriScrobbler
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object ScrobblingModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShikimoriRepository(
|
||||
storage: ShikimoriStorage,
|
||||
database: MangaDatabase,
|
||||
authenticator: ShikimoriAuthenticator,
|
||||
): ShikimoriRepository {
|
||||
val okHttp = OkHttpClient.Builder().apply {
|
||||
authenticator(authenticator)
|
||||
addInterceptor(ShikimoriInterceptor(storage))
|
||||
}.build()
|
||||
return ShikimoriRepository(okHttp, storage, database)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ElementsIntoSet
|
||||
fun provideScrobblers(
|
||||
shikimoriScrobbler: ShikimoriScrobbler,
|
||||
): Set<@JvmSuppressWildcards Scrobbler> = setOf(shikimoriScrobbler)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user