Got rid of AssistedInject for ViewModels

This commit is contained in:
Koitharu
2023-03-11 12:37:00 +02:00
parent cc698cc82d
commit c8141c6046
32 changed files with 264 additions and 375 deletions

View File

@@ -3,6 +3,8 @@ package org.koitharu.kotatsu.base.domain
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.lifecycle.SavedStateHandle
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.getParcelableCompat
@@ -20,6 +22,12 @@ class MangaIntent private constructor(
uri = intent?.data,
)
constructor(savedStateHandle: SavedStateHandle) : this(
manga = savedStateHandle.get<ParcelableManga>(KEY_MANGA)?.manga,
mangaId = savedStateHandle[KEY_ID] ?: ID_NONE,
uri = savedStateHandle[BaseActivity.EXTRA_DATA],
)
constructor(args: Bundle?) : this(
manga = args?.getParcelableCompat<ParcelableManga>(KEY_MANGA)?.manga,
mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE,

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.base.ui
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
@@ -59,6 +60,12 @@ abstract class BaseActivity<B : ViewBinding> :
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
insetsDelegate.handleImeInsets = true
putDataToExtras(intent)
}
override fun onNewIntent(intent: Intent?) {
putDataToExtras(intent)
super.onNewIntent(intent)
}
@Deprecated("Use ViewBinding", level = DeprecationLevel.ERROR)
@@ -144,4 +151,13 @@ abstract class BaseActivity<B : ViewBinding> :
super.onBackPressed()
}
}
private fun putDataToExtras(intent: Intent?) {
intent?.putExtra(EXTRA_DATA, intent.data)
}
companion object {
const val EXTRA_DATA = "data"
}
}

View File

@@ -0,0 +1,13 @@
package org.koitharu.kotatsu.base.ui.util
import android.text.Editable
import android.text.TextWatcher
interface DefaultTextWatcher : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) = Unit
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.core.parser
import androidx.annotation.AnyThread
import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.MangaLoaderContext
@@ -42,6 +43,7 @@ interface MangaRepository {
private val cache = EnumMap<MangaSource, WeakReference<RemoteMangaRepository>>(MangaSource::class.java)
@AnyThread
fun create(source: MangaSource): MangaRepository {
if (source == MangaSource.LOCAL) {
return localMangaRepository

View File

@@ -13,6 +13,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets
import androidx.core.view.isGone
@@ -42,7 +43,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.utils.ViewBadge
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.utils.ext.textAndVisible
@@ -58,17 +58,12 @@ class DetailsActivity :
override val bsHeader: BottomSheetHeaderBar?
get() = binding.headerChapters
@Inject
lateinit var viewModelFactory: DetailsViewModel.Factory
@Inject
lateinit var shortcutsUpdater: ShortcutsUpdater
private lateinit var viewBadge: ViewBadge
private val viewModel: DetailsViewModel by assistedViewModels {
viewModelFactory.create(MangaIntent(intent))
}
private val viewModel: DetailsViewModel by viewModels()
private lateinit var chaptersMenuProvider: ChaptersMenuProvider
private val downloadReceiver = object : BroadcastReceiver() {

View File

@@ -7,12 +7,11 @@ import android.text.style.ForegroundColorSpan
import androidx.core.text.getSpans
import androidx.core.text.parseAsHtml
import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -55,9 +54,11 @@ import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import java.io.IOException
import javax.inject.Inject
class DetailsViewModel @AssistedInject constructor(
@Assisted intent: MangaIntent,
@HiltViewModel
class DetailsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val historyRepository: HistoryRepository,
favouritesRepository: FavouritesRepository,
private val localMangaRepository: LocalMangaRepository,
@@ -71,7 +72,7 @@ class DetailsViewModel @AssistedInject constructor(
) : BaseViewModel() {
private val delegate = MangaDetailsDelegate(
intent = intent,
intent = MangaIntent(savedStateHandle),
mangaDataRepository = mangaDataRepository,
historyRepository = historyRepository,
localMangaRepository = localMangaRepository,
@@ -321,10 +322,4 @@ class DetailsViewModel @AssistedInject constructor(
}
return scrobbler
}
@AssistedFactory
interface Factory {
fun create(intent: MangaIntent): DetailsViewModel
}
}

View File

@@ -4,43 +4,37 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Filter
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 com.google.android.material.R as materialR
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.util.DefaultTextWatcher
import org.koitharu.kotatsu.core.model.FavouriteCategory
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
import org.koitharu.kotatsu.utils.ext.getSerializableCompat
import com.google.android.material.R as materialR
@AndroidEntryPoint
class FavouritesCategoryEditActivity :
BaseActivity<ActivityCategoryEditBinding>(),
AdapterView.OnItemClickListener,
View.OnClickListener,
TextWatcher {
DefaultTextWatcher {
@Inject
lateinit var viewModelFactory: FavouritesCategoryEditViewModel.Factory
private val viewModel by assistedViewModels<FavouritesCategoryEditViewModel> {
viewModelFactory.create(intent.getLongExtra(EXTRA_ID, NO_ID))
}
private val viewModel by viewModels<FavouritesCategoryEditViewModel>()
private var selectedSortOrder: SortOrder? = null
override fun onCreate(savedInstanceState: Bundle?) {
@@ -87,10 +81,6 @@ class FavouritesCategoryEditActivity :
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) {
binding.buttonDone.isEnabled = !s.isNullOrBlank()
}
@@ -167,9 +157,9 @@ class FavouritesCategoryEditActivity :
companion object {
private const val EXTRA_ID = "id"
const val EXTRA_ID = "id"
const val NO_ID = -1L
private const val KEY_SORT_ORDER = "sort"
private const val NO_ID = -1L
fun newIntent(context: Context, id: Long = NO_ID): Intent {
return Intent(context, FavouritesCategoryEditActivity::class.java)

View File

@@ -1,27 +1,30 @@
package org.koitharu.kotatsu.favourites.ui.categories.edit
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity.Companion.EXTRA_ID
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity.Companion.NO_ID
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.SingleLiveEvent
import javax.inject.Inject
private const val NO_ID = -1L
class FavouritesCategoryEditViewModel @AssistedInject constructor(
@Assisted private val categoryId: Long,
@HiltViewModel
class FavouritesCategoryEditViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val repository: FavouritesRepository,
private val settings: AppSettings,
) : BaseViewModel() {
private val categoryId = savedStateHandle[EXTRA_ID] ?: NO_ID
val onSaved = SingleLiveEvent<Unit>()
val category = MutableLiveData<FavouriteCategory?>()
@@ -30,12 +33,14 @@ class FavouritesCategoryEditViewModel @AssistedInject constructor(
}
init {
launchLoadingJob {
category.value = if (categoryId != NO_ID) {
repository.getCategory(categoryId)
} else {
null
}
launchLoadingJob(Dispatchers.Default) {
category.postValue(
if (categoryId != NO_ID) {
repository.getCategory(categoryId)
} else {
null
},
)
}
}
@@ -44,20 +49,14 @@ class FavouritesCategoryEditViewModel @AssistedInject constructor(
sortOrder: SortOrder,
isTrackerEnabled: Boolean,
) {
launchLoadingJob {
launchLoadingJob(Dispatchers.Default) {
check(title.isNotEmpty())
if (categoryId == NO_ID) {
repository.createCategory(title, sortOrder, isTrackerEnabled)
} else {
repository.updateCategory(categoryId, title, sortOrder, isTrackerEnabled)
}
onSaved.call(Unit)
onSaved.postCall(Unit)
}
}
@AssistedFactory
interface Factory {
fun create(categoryId: Long): FavouritesCategoryEditViewModel
}
}

View File

@@ -8,8 +8,8 @@ import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
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.base.ui.list.OnListItemClickListener
@@ -19,7 +19,6 @@ 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
@@ -30,14 +29,7 @@ class FavouriteCategoriesBottomSheet :
View.OnClickListener,
Toolbar.OnMenuItemClickListener {
@Inject
lateinit var viewModelFactory: MangaCategoriesViewModel.Factory
private val viewModel: MangaCategoriesViewModel by assistedViewModels {
viewModelFactory.create(
requireNotNull(arguments?.getParcelableArrayList<ParcelableManga>(KEY_MANGA_LIST)).map { it.manga },
)
}
private val viewModel: MangaCategoriesViewModel by viewModels()
private var adapter: MangaCategoriesAdapter? = null
@@ -91,7 +83,7 @@ class FavouriteCategoriesBottomSheet :
companion object {
private const val TAG = "FavouriteCategoriesDialog"
private const val KEY_MANGA_LIST = "manga_list"
const val KEY_MANGA_LIST = "manga_list"
fun show(fm: FragmentManager, manga: Manga) = Companion.show(fm, listOf(manga))

View File

@@ -1,23 +1,27 @@
package org.koitharu.kotatsu.favourites.ui.categories.select
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.ids
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet.Companion.KEY_MANGA_LIST
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import javax.inject.Inject
class MangaCategoriesViewModel @AssistedInject constructor(
@Assisted private val manga: List<Manga>,
@HiltViewModel
class MangaCategoriesViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val favouritesRepository: FavouritesRepository,
) : BaseViewModel() {
private val manga = requireNotNull(savedStateHandle.get<List<ParcelableManga>>(KEY_MANGA_LIST)).map { it.manga }
val content = combine(
favouritesRepository.observeCategories(),
observeCategoriesIds(),
@@ -61,10 +65,4 @@ class MangaCategoriesViewModel @AssistedInject constructor(
result
}
}
@AssistedFactory
interface Factory {
fun create(manga: List<Manga>): MangaCategoriesViewModel
}
}

View File

@@ -6,6 +6,7 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
@@ -14,20 +15,12 @@ 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.addMenuProvider
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
@AndroidEntryPoint
class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener {
@Inject
lateinit var viewModelFactory: FavouritesListViewModel.Factory
override val viewModel by assistedViewModels { viewModelFactory.create(categoryId) }
private val categoryId: Long
get() = arguments?.getLong(ARG_CATEGORY_ID) ?: NO_ID
override val viewModel by viewModels<FavouritesListViewModel>()
override val isSwipeRefreshEnabled = false
@@ -83,7 +76,7 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
companion object {
const val NO_ID = 0L
private const val ARG_CATEGORY_ID = "category_id"
const val ARG_CATEGORY_ID = "category_id"
fun newInstance(categoryId: Long) = FavouritesListFragment().withArgs(1) {
putLong(ARG_CATEGORY_ID, categoryId)

View File

@@ -2,10 +2,9 @@ package org.koitharu.kotatsu.favourites.ui.list
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
@@ -16,6 +15,7 @@ import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
@@ -30,9 +30,11 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import javax.inject.Inject
class FavouritesListViewModel @AssistedInject constructor(
@Assisted val categoryId: Long,
@HiltViewModel
class FavouritesListViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val repository: FavouritesRepository,
private val trackingRepository: TrackingRepository,
private val historyRepository: HistoryRepository,
@@ -40,6 +42,8 @@ class FavouritesListViewModel @AssistedInject constructor(
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings), ListExtraProvider {
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
var categoryName: String? = null
private set
@@ -133,10 +137,4 @@ class FavouritesListViewModel @AssistedInject constructor(
PROGRESS_NONE
}
}
@AssistedFactory
interface Factory {
fun create(categoryId: Long): FavouritesListViewModel
}
}

View File

@@ -15,6 +15,7 @@ import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.WindowInsetsCompat
@@ -48,14 +49,11 @@ import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.utils.GridTouchHelper
import org.koitharu.kotatsu.utils.IdlingDetector
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.utils.ext.hasGlobalPoint
import org.koitharu.kotatsu.utils.ext.observeWithPrevious
import org.koitharu.kotatsu.utils.ext.postDelayed
import org.koitharu.kotatsu.utils.ext.setValueRounded
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@AndroidEntryPoint
class ReaderActivity :
@@ -68,18 +66,9 @@ class ReaderActivity :
OnApplyWindowInsetsListener,
IdlingDetector.Callback {
@Inject
lateinit var viewModelFactory: ReaderViewModel.Factory
private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this)
val viewModel by assistedViewModels {
viewModelFactory.create(
intent = MangaIntent(intent),
initialState = intent?.getParcelableExtraCompat(EXTRA_STATE),
preselectedBranch = intent?.getStringExtra(EXTRA_BRANCH),
)
}
private val viewModel: ReaderViewModel by viewModels()
override var pageSwitchDelay: Float
get() = pageSwitchTimer.delaySec
@@ -392,8 +381,8 @@ class ReaderActivity :
companion object {
const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA"
private const val EXTRA_STATE = "state"
private const val EXTRA_BRANCH = "branch"
const val EXTRA_STATE = "state"
const val EXTRA_BRANCH = "branch"
private const val TOAST_DURATION = 1500L
fun newIntent(context: Context, manga: Manga): Intent {

View File

@@ -6,10 +6,9 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.AnyThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -56,15 +55,15 @@ import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.requireValue
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import java.util.Date
import javax.inject.Inject
import javax.inject.Provider
private const val BOUNDS_PAGE_OFFSET = 2
private const val PREFETCH_LIMIT = 10
class ReaderViewModel @AssistedInject constructor(
@Assisted private val intent: MangaIntent,
@Assisted initialState: ReaderState?,
@Assisted private val preselectedBranch: String?,
@HiltViewModel
class ReaderViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val mangaRepositoryFactory: MangaRepository.Factory,
private val dataRepository: MangaDataRepository,
private val historyRepository: HistoryRepository,
@@ -74,10 +73,13 @@ class ReaderViewModel @AssistedInject constructor(
pageLoaderFactory: Provider<PageLoader>,
) : BaseViewModel() {
private val intent = MangaIntent(savedStateHandle)
private val preselectedBranch = savedStateHandle.get<String>(ReaderActivity.EXTRA_BRANCH)
private var loadingJob: Job? = null
private var pageSaveJob: Job? = null
private var bookmarkJob: Job? = null
private val currentState = MutableStateFlow(initialState)
private val currentState = MutableStateFlow<ReaderState?>(savedStateHandle[ReaderActivity.EXTRA_STATE])
private val mangaData = MutableStateFlow(intent.manga)
private val chapters: LongSparseArray<MangaChapter>
get() = chaptersLoader.chapters
@@ -393,16 +395,6 @@ class ReaderViewModel @AssistedInject constructor(
val ppc = 1f / chaptersCount
return ppc * chapterIndex + ppc * pagePercent
}
@AssistedFactory
interface Factory {
fun create(
intent: MangaIntent,
initialState: ReaderState?,
preselectedBranch: String?,
): ReaderViewModel
}
}
/**

View File

@@ -6,6 +6,7 @@ import android.content.res.Resources
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
@@ -25,10 +26,8 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.decodeRegion
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.utils.ext.setValueRounded
import javax.inject.Inject
import com.google.android.material.R as materialR
@@ -42,15 +41,7 @@ class ColorFilterConfigActivity :
@Inject
lateinit var coil: ImageLoader
@Inject
lateinit var viewModelFactory: ColorFilterConfigViewModel.Factory
private val viewModel: ColorFilterConfigViewModel by assistedViewModels {
viewModelFactory.create(
manga = checkNotNull(intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga),
page = checkNotNull(intent.getParcelableExtraCompat<ParcelableMangaPages>(EXTRA_PAGES)?.pages?.firstOrNull()),
)
}
private val viewModel: ColorFilterConfigViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -144,8 +135,8 @@ class ColorFilterConfigActivity :
companion object {
private const val EXTRA_PAGES = "pages"
private const val EXTRA_MANGA = "manga_id"
const val EXTRA_PAGES = "pages"
const val EXTRA_MANGA = "manga_id"
fun newIntent(context: Context, manga: Manga, page: MangaPage) =
Intent(context, ColorFilterConfigActivity::class.java)

View File

@@ -1,24 +1,29 @@
package org.koitharu.kotatsu.reader.ui.colorfilter
import androidx.lifecycle.MutableLiveData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPages
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity.Companion.EXTRA_MANGA
import org.koitharu.kotatsu.utils.SingleLiveEvent
import javax.inject.Inject
class ColorFilterConfigViewModel @AssistedInject constructor(
@Assisted private val manga: Manga,
@Assisted page: MangaPage,
@HiltViewModel
class ColorFilterConfigViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val mangaRepositoryFactory: MangaRepository.Factory,
private val mangaDataRepository: MangaDataRepository,
) : BaseViewModel() {
private val manga = checkNotNull(savedStateHandle.get<ParcelableManga>(EXTRA_MANGA)?.manga)
private var initialColorFilter: ReaderColorFilter? = null
val colorFilter = MutableLiveData<ReaderColorFilter?>(null)
val onDismiss = SingleLiveEvent<Unit>()
@@ -28,19 +33,24 @@ class ColorFilterConfigViewModel @AssistedInject constructor(
get() = colorFilter.value != initialColorFilter
init {
val page = checkNotNull(
savedStateHandle.get<ParcelableMangaPages>(ColorFilterConfigActivity.EXTRA_PAGES)?.pages?.firstOrNull(),
)
launchLoadingJob {
initialColorFilter = mangaDataRepository.getColorFilter(manga.id)
colorFilter.value = initialColorFilter
}
launchLoadingJob {
launchLoadingJob(Dispatchers.Default) {
val repository = mangaRepositoryFactory.create(page.source)
val url = repository.getPageUrl(page)
preview.value = MangaPage(
id = page.id,
url = url,
referer = page.referer,
preview = page.preview,
source = page.source,
preview.postValue(
MangaPage(
id = page.id,
url = url,
referer = page.referer,
preview = page.preview,
source = page.source,
),
)
}
}
@@ -60,15 +70,9 @@ class ColorFilterConfigViewModel @AssistedInject constructor(
}
fun save() {
launchLoadingJob {
launchLoadingJob(Dispatchers.Default) {
mangaDataRepository.saveColorFilter(manga, colorFilter.value)
onDismiss.call(Unit)
onDismiss.postCall(Unit)
}
}
@AssistedFactory
interface Factory {
fun create(manga: Manga, page: MangaPage): ColorFilterConfigViewModel
}
}

View File

@@ -4,12 +4,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.GridLayoutManager
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
@@ -23,10 +22,13 @@ 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.getParcelableCompat
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
import javax.inject.Provider
@AndroidEntryPoint
class PagesThumbnailsSheet :
@@ -117,9 +119,9 @@ class PagesThumbnailsSheet :
(parentFragment as? OnPageSelectListener)
?: (activity as? OnPageSelectListener)
)?.run {
onPageSelected(item)
dismiss()
}
onPageSelected(item)
dismiss()
}
}
override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) {
@@ -135,7 +137,7 @@ class PagesThumbnailsSheet :
}
private fun getPageLoader(): PageLoader {
val viewModel = (activity as? ReaderActivity)?.viewModel
val viewModel = (activity as? ReaderActivity)?.viewModels<ReaderViewModel>()?.value
return viewModel?.pageLoader ?: pageLoaderProvider.get().also { pageLoader = it }
}

View File

@@ -8,8 +8,8 @@ import android.view.View
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels
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,21 +19,12 @@ 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() {
@Inject
lateinit var viewModelFactory: RemoteListViewModel.Factory
public override val viewModel by assistedViewModels {
viewModelFactory.create(source)
}
private val source by serializableArgument<MangaSource>(ARG_SOURCE)
public override val viewModel by viewModels<RemoteListViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -74,13 +65,15 @@ class RemoteListFragment : MangaListFragment() {
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_source_settings -> {
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), source))
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), viewModel.source))
true
}
R.id.action_filter -> {
onFilterClick(null)
true
}
else -> false
}
@@ -90,7 +83,7 @@ class RemoteListFragment : MangaListFragment() {
}
val intent = SearchActivity.newIntent(
context = this@RemoteListFragment.context ?: return false,
source = source,
source = viewModel.source,
query = query,
)
startActivity(intent)
@@ -113,7 +106,7 @@ class RemoteListFragment : MangaListFragment() {
companion object {
private const val ARG_SOURCE = "provider"
const val ARG_SOURCE = "provider"
fun newInstance(provider: MangaSource) = RemoteListFragment().withArgs(1) {
putSerializable(ARG_SOURCE, provider)

View File

@@ -1,10 +1,9 @@
package org.koitharu.kotatsu.remotelist.ui
import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -40,12 +39,15 @@ 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 org.koitharu.kotatsu.utils.ext.require
import java.util.LinkedList
import javax.inject.Inject
private const val FILTER_MIN_INTERVAL = 250L
class RemoteListViewModel @AssistedInject constructor(
@Assisted source: MangaSource,
@HiltViewModel
class RemoteListViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
mangaRepositoryFactory: MangaRepository.Factory,
private val searchRepository: MangaSearchRepository,
settings: AppSettings,
@@ -53,6 +55,7 @@ class RemoteListViewModel @AssistedInject constructor(
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings), OnFilterChangedListener {
val source = savedStateHandle.require<MangaSource>(RemoteListFragment.ARG_SOURCE)
private val repository = mangaRepositoryFactory.create(source) as RemoteMangaRepository
private val filter = FilterCoordinator(repository, dataRepository, viewModelScope)
private val mangaList = MutableStateFlow<List<Manga>?>(null)
@@ -218,10 +221,4 @@ class RemoteListViewModel @AssistedInject constructor(
}
return result
}
@AssistedFactory
interface Factory {
fun create(source: MangaSource): RemoteListViewModel
}
}

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
@@ -22,7 +23,6 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.config.adapter.ScrobblingMangaAdapter
import org.koitharu.kotatsu.tracker.ui.feed.adapter.FeedAdapter
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.hideCompat
@@ -34,15 +34,10 @@ import javax.inject.Inject
class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
OnListItemClickListener<ScrobblingInfo>, View.OnClickListener {
@Inject
lateinit var viewModelFactory: ScrobblerConfigViewModel.Factory
@Inject
lateinit var coil: ImageLoader
private val viewModel: ScrobblerConfigViewModel by assistedViewModels {
viewModelFactory.create(requireNotNull(getScrobblerService(intent)))
}
private val viewModel: ScrobblerConfigViewModel by viewModels()
private var paddingVertical = 0
private var paddingHorizontal = 0
@@ -150,30 +145,14 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
companion object {
private const val EXTRA_SERVICE_ID = "service"
const val EXTRA_SERVICE_ID = "service"
private const val HOST_SHIKIMORI_AUTH = "shikimori-auth"
private const val HOST_ANILIST_AUTH = "anilist-auth"
private const val HOST_MAL_AUTH = "mal-auth"
const val HOST_SHIKIMORI_AUTH = "shikimori-auth"
const val HOST_ANILIST_AUTH = "anilist-auth"
const val HOST_MAL_AUTH = "mal-auth"
fun newIntent(context: Context, service: ScrobblerService) =
Intent(context, ScrobblerConfigActivity::class.java)
.putExtra(EXTRA_SERVICE_ID, service.id)
private fun getScrobblerService(
intent: Intent
): ScrobblerService? {
val serviceId = intent.getIntExtra(EXTRA_SERVICE_ID, 0)
if (serviceId != 0) {
return enumValues<ScrobblerService>().first { it.id == serviceId }
}
val uri = intent.data ?: return null
return when (uri.host) {
HOST_SHIKIMORI_AUTH -> ScrobblerService.SHIKIMORI
HOST_ANILIST_AUTH -> ScrobblerService.ANILIST
HOST_MAL_AUTH -> ScrobblerService.MAL
else -> null
}
}
}
}

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.scrobbling.common.ui.config
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -24,12 +25,16 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.onFirst
import org.koitharu.kotatsu.utils.ext.require
import javax.inject.Inject
class ScrobblerConfigViewModel @AssistedInject constructor(
@Assisted scrobblerService: ScrobblerService,
@HiltViewModel
class ScrobblerConfigViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
) : BaseViewModel() {
private val scrobblerService = getScrobblerService(savedStateHandle)
private val scrobbler = scrobblers.first { it.scrobblerService == scrobblerService }
val titleResId = scrobbler.scrobblerService.titleResId
@@ -90,9 +95,19 @@ class ScrobblerConfigViewModel @AssistedInject constructor(
return result
}
@AssistedFactory
interface Factory {
fun create(service: ScrobblerService): ScrobblerConfigViewModel
private fun getScrobblerService(
savedStateHandle: SavedStateHandle,
): ScrobblerService {
val serviceId = savedStateHandle.get<Int>(ScrobblerConfigActivity.EXTRA_SERVICE_ID) ?: 0
if (serviceId != 0) {
return enumValues<ScrobblerService>().first { it.id == serviceId }
}
val uri = savedStateHandle.require<Uri>(BaseActivity.EXTRA_DATA)
return when (uri.host) {
ScrobblerConfigActivity.HOST_SHIKIMORI_AUTH -> ScrobblerService.SHIKIMORI
ScrobblerConfigActivity.HOST_ANILIST_AUTH -> ScrobblerService.ANILIST
ScrobblerConfigActivity.HOST_MAL_AUTH -> ScrobblerService.MAL
else -> error("Wrong scrobbler uri: $uri")
}
}
}

View File

@@ -9,6 +9,7 @@ import android.widget.Toast
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import coil.ImageLoader
import com.google.android.material.tabs.TabLayout
import dagger.hilt.android.AndroidEntryPoint
@@ -26,10 +27,8 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerMangaSelectionDecoration
import org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter.ScrobblerSelectorAdapter
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.firstVisibleItemPosition
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.requireParcelable
import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
@@ -44,19 +43,12 @@ class ScrobblingSelectorBottomSheet :
TabLayout.OnTabSelectedListener,
ListStateHolderListener {
@Inject
lateinit var viewModelFactory: ScrobblingSelectorViewModel.Factory
@Inject
lateinit var coil: ImageLoader
private var collapsibleActionViewCallback: CollapseActionViewCallback? = null
private val viewModel by assistedViewModels {
viewModelFactory.create(
requireArguments().requireParcelable<ParcelableManga>(MangaIntent.KEY_MANGA).manga,
)
}
private val viewModel by viewModels<ScrobblingSelectorViewModel>()
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetScrobblingSelectorBinding {
return SheetScrobblingSelectorBinding.inflate(inflater, container, false)

View File

@@ -2,21 +2,21 @@ package org.koitharu.kotatsu.scrobbling.common.ui.selector
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.recyclerview.widget.RecyclerView.NO_ID
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
@@ -24,13 +24,18 @@ import org.koitharu.kotatsu.scrobbling.common.ui.selector.model.ScrobblerHint
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.ext.require
import org.koitharu.kotatsu.utils.ext.requireValue
import javax.inject.Inject
class ScrobblingSelectorViewModel @AssistedInject constructor(
@Assisted val manga: Manga,
@HiltViewModel
class ScrobblingSelectorViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
) : BaseViewModel() {
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga
val availableScrobblers = scrobblers.filter { it.isAvailable }
val selectedScrobblerIndex = MutableLiveData(0)
@@ -172,10 +177,4 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
textSecondary = 0,
actionStringRes = R.string.try_again,
)
@AssistedFactory
interface Factory {
fun create(manga: Manga): ScrobblingSelectorViewModel
}
}

View File

@@ -2,29 +2,18 @@ package org.koitharu.kotatsu.search.ui
import android.view.Menu
import androidx.appcompat.view.ActionMode
import androidx.fragment.app.viewModels
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
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.serializableArgument
import org.koitharu.kotatsu.utils.ext.stringArgument
import org.koitharu.kotatsu.utils.ext.withArgs
@AndroidEntryPoint
class SearchFragment : MangaListFragment() {
@Inject
lateinit var viewModelFactory: SearchViewModel.Factory
override val viewModel by assistedViewModels {
viewModelFactory.create(source, query.orEmpty())
}
private val query by stringArgument(ARG_QUERY)
private val source by serializableArgument<MangaSource>(ARG_SOURCE)
override val viewModel by viewModels<SearchViewModel>()
override fun onScrolledToEnd() {
viewModel.loadNextPage()
@@ -37,8 +26,8 @@ class SearchFragment : MangaListFragment() {
companion object {
private const val ARG_QUERY = "query"
private const val ARG_SOURCE = "source"
const val ARG_QUERY = "query"
const val ARG_SOURCE = "source"
fun newInstance(source: MangaSource, query: String) = SearchFragment().withArgs(2) {
putSerializable(ARG_SOURCE, source)

View File

@@ -1,9 +1,8 @@
package org.koitharu.kotatsu.search.ui
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -22,18 +21,20 @@ import org.koitharu.kotatsu.list.ui.model.toErrorFooter
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.require
import javax.inject.Inject
class SearchViewModel @AssistedInject constructor(
@Assisted source: MangaSource,
@Assisted private val query: String,
@HiltViewModel
class SearchViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
repositoryFactory: MangaRepository.Factory,
settings: AppSettings,
private val tagHighlighter: MangaTagHighlighter,
) : MangaListViewModel(settings) {
private val repository = repositoryFactory.create(source)
private val query = savedStateHandle.require<String>(SearchFragment.ARG_QUERY)
private val repository = repositoryFactory.create(savedStateHandle.require(SearchFragment.ARG_SOURCE))
private val mangaList = MutableStateFlow<List<Manga>?>(null)
private val hasNextPage = MutableStateFlow(false)
private val listError = MutableStateFlow<Throwable?>(null)
@@ -111,10 +112,4 @@ class SearchViewModel @AssistedInject constructor(
}
}
}
@AssistedFactory
interface Factory {
fun create(source: MangaSource, query: String): SearchViewModel
}
}

View File

@@ -6,6 +6,7 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.view.ActionMode
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
@@ -31,7 +32,6 @@ import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.search.ui.multi.adapter.MultiSearchAdapter
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
import javax.inject.Inject
@@ -40,17 +40,12 @@ import javax.inject.Inject
class MultiSearchActivity :
BaseActivity<ActivitySearchMultiBinding>(),
MangaListListener,
ListSelectionController.Callback {
@Inject
lateinit var viewModelFactory: MultiSearchViewModel.Factory
ListSelectionController.Callback2 {
@Inject
lateinit var coil: ImageLoader
private val viewModel by assistedViewModels<MultiSearchViewModel> {
viewModelFactory.create(intent.getStringExtra(EXTRA_QUERY).orEmpty())
}
private val viewModel by viewModels<MultiSearchViewModel>()
private lateinit var adapter: MultiSearchAdapter
private lateinit var selectionController: ListSelectionController
@@ -139,17 +134,16 @@ class MultiSearchActivity :
override fun onListHeaderClick(item: ListHeader, view: View) = Unit
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
binding.recyclerView.invalidateNestedItemDecorations()
}
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.mode_remote, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.title = selectionController.count.toString()
return true
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_share -> {
ShareHelper(this).shareMangaLinks(collectSelectedItems())
@@ -173,17 +167,13 @@ class MultiSearchActivity :
}
}
override fun onSelectionChanged(count: Int) {
binding.recyclerView.invalidateNestedItemDecorations()
}
private fun collectSelectedItems(): Set<Manga> {
return viewModel.getItems(selectionController.peekCheckedIds())
}
companion object {
private const val EXTRA_QUERY = "query"
const val EXTRA_QUERY = "query"
fun newIntent(context: Context, query: String) =
Intent(context, MultiSearchActivity::class.java)

View File

@@ -2,10 +2,9 @@ package org.koitharu.kotatsu.search.ui.multi
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -31,12 +30,14 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import javax.inject.Inject
private const val MAX_PARALLELISM = 4
private const val MIN_HAS_MORE_ITEMS = 8
class MultiSearchViewModel @AssistedInject constructor(
@Assisted initialQuery: String,
@HiltViewModel
class MultiSearchViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val settings: AppSettings,
private val mangaRepositoryFactory: MangaRepository.Factory,
) : BaseViewModel() {
@@ -46,7 +47,7 @@ class MultiSearchViewModel @AssistedInject constructor(
private val loadingData = MutableStateFlow(false)
private var listError = MutableStateFlow<Throwable?>(null)
val query = MutableLiveData(initialQuery)
val query = MutableLiveData(savedStateHandle.get<String>(MultiSearchActivity.EXTRA_QUERY).orEmpty())
val list: LiveData<List<ListModel>> = combine(
listData,
loadingData,
@@ -72,7 +73,7 @@ class MultiSearchViewModel @AssistedInject constructor(
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
init {
doSearch(initialQuery)
doSearch(query.value.orEmpty())
}
fun getItems(ids: Set<Long>): Set<Manga> {
@@ -145,10 +146,4 @@ class MultiSearchViewModel @AssistedInject constructor(
}
}
}
@AssistedFactory
interface Factory {
fun create(initialQuery: String): MultiSearchViewModel
}
}

View File

@@ -6,28 +6,21 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.backup.CompositeResult
import org.koitharu.kotatsu.databinding.DialogProgressBinding
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.toUriOrNull
import org.koitharu.kotatsu.utils.ext.withArgs
import org.koitharu.kotatsu.utils.progress.Progress
@AndroidEntryPoint
class RestoreDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
@Inject
lateinit var viewModelFactory: RestoreViewModel.Factory
private val viewModel by assistedViewModels {
viewModelFactory.create(arguments?.getString(ARG_FILE)?.toUriOrNull())
}
private val viewModel: RestoreViewModel by viewModels()
override fun onInflateView(
inflater: LayoutInflater,
@@ -74,12 +67,14 @@ class RestoreDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
when {
result.isAllSuccess -> builder.setTitle(R.string.data_restored)
.setMessage(R.string.data_restored_success)
result.isAllFailed -> builder.setTitle(R.string.error)
.setMessage(
result.failures.map {
it.getDisplayMessage(resources)
}.distinct().joinToString("\n"),
)
else -> builder.setTitle(R.string.data_restored)
.setMessage(R.string.data_restored_with_errors)
}

View File

@@ -1,15 +1,10 @@
package org.koitharu.kotatsu.settings.backup
import android.content.Context
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import java.io.FileNotFoundException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import org.koitharu.kotatsu.base.ui.BaseViewModel
@@ -18,10 +13,15 @@ import org.koitharu.kotatsu.core.backup.BackupRepository
import org.koitharu.kotatsu.core.backup.BackupZipInput
import org.koitharu.kotatsu.core.backup.CompositeResult
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.toUriOrNull
import org.koitharu.kotatsu.utils.progress.Progress
import java.io.File
import java.io.FileNotFoundException
import javax.inject.Inject
class RestoreViewModel @AssistedInject constructor(
@Assisted uri: Uri?,
@HiltViewModel
class RestoreViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val repository: BackupRepository,
@ApplicationContext context: Context,
) : BaseViewModel() {
@@ -31,9 +31,8 @@ class RestoreViewModel @AssistedInject constructor(
init {
launchLoadingJob {
if (uri == null) {
throw FileNotFoundException()
}
val uri = savedStateHandle.get<String>(RestoreDialogFragment.ARG_FILE)
?.toUriOrNull() ?: throw FileNotFoundException()
val contentResolver = context.contentResolver
val backup = runInterruptible(Dispatchers.IO) {
@@ -65,10 +64,4 @@ class RestoreViewModel @AssistedInject constructor(
}
}
}
@AssistedFactory
interface Factory {
fun create(uri: Uri?): RestoreViewModel
}
}

View File

@@ -7,6 +7,7 @@ import android.os.Build
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags
import java.io.Serializable
@@ -47,3 +48,9 @@ inline fun <reified T : Parcelable> Bundle.requireParcelable(key: String): T {
"Parcelable of type \"${T::class.java.name}\" not found at \"$key\""
}
}
fun <T> SavedStateHandle.require(key: String): T {
return checkNotNull(get(key)) {
"Value $key not found in SavedStateHandle or has a wrong type"
}
}

View File

@@ -23,6 +23,7 @@ inline fun <T : Fragment> T.withArgs(size: Int, block: Bundle.() -> Unit): T {
val Fragment.viewLifecycleScope
inline get() = viewLifecycleOwner.lifecycle.coroutineScope
@Deprecated("")
fun <T : Serializable> Fragment.serializableArgument(name: String): Lazy<T> {
return lazy(LazyThreadSafetyMode.NONE) {
@Suppress("UNCHECKED_CAST")
@@ -32,6 +33,7 @@ fun <T : Serializable> Fragment.serializableArgument(name: String): Lazy<T> {
}
}
@Deprecated("")
fun Fragment.stringArgument(name: String) = lazy(LazyThreadSafetyMode.NONE) {
arguments?.getString(name)
}

View File

@@ -1,41 +1,12 @@
package org.koitharu.kotatsu.utils.ext
import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.fragment.app.createViewModelLazy
import androidx.fragment.app.viewModels
import androidx.lifecycle.AbstractSavedStateViewModelFactory
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
@Deprecated("Migrate to SavedStateHandle in vm")
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.assistedViewModels(
noinline viewModelProducer: (SavedStateHandle) -> VM,
): Lazy<VM> = viewModels {
object : AbstractSavedStateViewModelFactory(this@assistedViewModels, intent.extras) {
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return requireNotNull(modelClass.cast(viewModelProducer(handle)))
}
}
}
@Deprecated("Migrate to SavedStateHandle in vm")
@MainThread
inline fun <reified VM : ViewModel> Fragment.assistedViewModels(
noinline viewModelProducer: (SavedStateHandle) -> VM,
): Lazy<VM> = viewModels {
object : AbstractSavedStateViewModelFactory(this@assistedViewModels, arguments) {
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return requireNotNull(modelClass.cast(viewModelProducer(handle)))
}
}
}
@MainThread
inline fun <reified VM : ViewModel> Fragment.parentFragmentViewModels(
noinline extrasProducer: (() -> CreationExtras)? = null,