Got rid of AssistedInject for ViewModels

pull/311/head
Koitharu 3 years ago
parent cc698cc82d
commit c8141c6046
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -3,6 +3,8 @@ package org.koitharu.kotatsu.base.domain
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle 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.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.getParcelableCompat import org.koitharu.kotatsu.utils.ext.getParcelableCompat
@ -20,6 +22,12 @@ class MangaIntent private constructor(
uri = intent?.data, 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( constructor(args: Bundle?) : this(
manga = args?.getParcelableCompat<ParcelableManga>(KEY_MANGA)?.manga, manga = args?.getParcelableCompat<ParcelableManga>(KEY_MANGA)?.manga,
mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE, mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE,

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

@ -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
}

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

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

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

@ -4,43 +4,37 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Filter import android.widget.Filter
import androidx.activity.viewModels
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.material.R as materialR
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity 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.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.titleRes import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding import org.koitharu.kotatsu.databinding.ActivityCategoryEditBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.parsers.model.SortOrder 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.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getSerializableCompat import org.koitharu.kotatsu.utils.ext.getSerializableCompat
import com.google.android.material.R as materialR
@AndroidEntryPoint @AndroidEntryPoint
class FavouritesCategoryEditActivity : class FavouritesCategoryEditActivity :
BaseActivity<ActivityCategoryEditBinding>(), BaseActivity<ActivityCategoryEditBinding>(),
AdapterView.OnItemClickListener, AdapterView.OnItemClickListener,
View.OnClickListener, View.OnClickListener,
TextWatcher { DefaultTextWatcher {
@Inject
lateinit var viewModelFactory: FavouritesCategoryEditViewModel.Factory
private val viewModel by assistedViewModels<FavouritesCategoryEditViewModel> { private val viewModel by viewModels<FavouritesCategoryEditViewModel>()
viewModelFactory.create(intent.getLongExtra(EXTRA_ID, NO_ID))
}
private var selectedSortOrder: SortOrder? = null private var selectedSortOrder: SortOrder? = null
override fun onCreate(savedInstanceState: Bundle?) { 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?) { override fun afterTextChanged(s: Editable?) {
binding.buttonDone.isEnabled = !s.isNullOrBlank() binding.buttonDone.isEnabled = !s.isNullOrBlank()
} }
@ -167,9 +157,9 @@ class FavouritesCategoryEditActivity :
companion object { 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 KEY_SORT_ORDER = "sort"
private const val NO_ID = -1L
fun newIntent(context: Context, id: Long = NO_ID): Intent { fun newIntent(context: Context, id: Long = NO_ID): Intent {
return Intent(context, FavouritesCategoryEditActivity::class.java) return Intent(context, FavouritesCategoryEditActivity::class.java)

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

@ -8,8 +8,8 @@ import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener 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.adapter.MangaCategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
import org.koitharu.kotatsu.parsers.model.Manga 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.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
@ -30,14 +29,7 @@ class FavouriteCategoriesBottomSheet :
View.OnClickListener, View.OnClickListener,
Toolbar.OnMenuItemClickListener { Toolbar.OnMenuItemClickListener {
@Inject private val viewModel: MangaCategoriesViewModel by viewModels()
lateinit var viewModelFactory: MangaCategoriesViewModel.Factory
private val viewModel: MangaCategoriesViewModel by assistedViewModels {
viewModelFactory.create(
requireNotNull(arguments?.getParcelableArrayList<ParcelableManga>(KEY_MANGA_LIST)).map { it.manga },
)
}
private var adapter: MangaCategoriesAdapter? = null private var adapter: MangaCategoriesAdapter? = null
@ -91,7 +83,7 @@ class FavouriteCategoriesBottomSheet :
companion object { companion object {
private const val TAG = "FavouriteCategoriesDialog" 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)) fun show(fm: FragmentManager, manga: Manga) = Companion.show(fm, listOf(manga))

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

@ -6,6 +6,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.ListSelectionController 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.list.ui.MangaListFragment
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener { class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener {
@Inject override val viewModel by viewModels<FavouritesListViewModel>()
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 isSwipeRefreshEnabled = false override val isSwipeRefreshEnabled = false
@ -83,7 +76,7 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
companion object { companion object {
const val NO_ID = 0L 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) { fun newInstance(categoryId: Long) = FavouritesListFragment().withArgs(1) {
putLong(ARG_CATEGORY_ID, categoryId) putLong(ARG_CATEGORY_ID, categoryId)

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

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

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

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

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

@ -4,12 +4,11 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import coil.ImageLoader import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import javax.inject.Provider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener 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.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderActivity 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.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import org.koitharu.kotatsu.utils.ext.getParcelableCompat import org.koitharu.kotatsu.utils.ext.getParcelableCompat
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
import javax.inject.Provider
@AndroidEntryPoint @AndroidEntryPoint
class PagesThumbnailsSheet : class PagesThumbnailsSheet :
@ -117,9 +119,9 @@ class PagesThumbnailsSheet :
(parentFragment as? OnPageSelectListener) (parentFragment as? OnPageSelectListener)
?: (activity as? OnPageSelectListener) ?: (activity as? OnPageSelectListener)
)?.run { )?.run {
onPageSelected(item) onPageSelected(item)
dismiss() dismiss()
} }
} }
override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) { override fun onExpansionStateChanged(headerBar: BottomSheetHeaderBar, isExpanded: Boolean) {
@ -135,7 +137,7 @@ class PagesThumbnailsSheet :
} }
private fun getPageLoader(): PageLoader { 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 } return viewModel?.pageLoader ?: pageLoaderProvider.get().also { pageLoader = it }
} }

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

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

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

@ -1,10 +1,10 @@
package org.koitharu.kotatsu.scrobbling.common.ui.config package org.koitharu.kotatsu.scrobbling.common.ui.config
import android.net.Uri
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel 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.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.onFirst import org.koitharu.kotatsu.utils.ext.onFirst
import org.koitharu.kotatsu.utils.ext.require
import javax.inject.Inject
class ScrobblerConfigViewModel @AssistedInject constructor( @HiltViewModel
@Assisted scrobblerService: ScrobblerService, class ScrobblerConfigViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
scrobblers: Set<@JvmSuppressWildcards Scrobbler>, scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
) : BaseViewModel() { ) : BaseViewModel() {
private val scrobblerService = getScrobblerService(savedStateHandle)
private val scrobbler = scrobblers.first { it.scrobblerService == scrobblerService } private val scrobbler = scrobblers.first { it.scrobblerService == scrobblerService }
val titleResId = scrobbler.scrobblerService.titleResId val titleResId = scrobbler.scrobblerService.titleResId
@ -90,9 +95,19 @@ class ScrobblerConfigViewModel @AssistedInject constructor(
return result return result
} }
@AssistedFactory private fun getScrobblerService(
interface Factory { savedStateHandle: SavedStateHandle,
): ScrobblerService {
fun create(service: ScrobblerService): ScrobblerConfigViewModel 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")
}
} }
} }

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

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

@ -2,29 +2,18 @@ package org.koitharu.kotatsu.search.ui
import android.view.Menu import android.view.Menu
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.ListSelectionController import org.koitharu.kotatsu.base.ui.list.ListSelectionController
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.parsers.model.MangaSource 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 import org.koitharu.kotatsu.utils.ext.withArgs
@AndroidEntryPoint @AndroidEntryPoint
class SearchFragment : MangaListFragment() { class SearchFragment : MangaListFragment() {
@Inject override val viewModel by viewModels<SearchViewModel>()
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 fun onScrolledToEnd() { override fun onScrolledToEnd() {
viewModel.loadNextPage() viewModel.loadNextPage()
@ -37,8 +26,8 @@ class SearchFragment : MangaListFragment() {
companion object { companion object {
private const val ARG_QUERY = "query" const val ARG_QUERY = "query"
private const val ARG_SOURCE = "source" const val ARG_SOURCE = "source"
fun newInstance(source: MangaSource, query: String) = SearchFragment().withArgs(2) { fun newInstance(source: MangaSource, query: String) = SearchFragment().withArgs(2) {
putSerializable(ARG_SOURCE, source) putSerializable(ARG_SOURCE, source)

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

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

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

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

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

@ -7,6 +7,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags
import java.io.Serializable 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\"" "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"
}
}

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

@ -1,41 +1,12 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.createViewModelLazy 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.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras 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 @MainThread
inline fun <reified VM : ViewModel> Fragment.parentFragmentViewModels( inline fun <reified VM : ViewModel> Fragment.parentFragmentViewModels(
noinline extrasProducer: (() -> CreationExtras)? = null, noinline extrasProducer: (() -> CreationExtras)? = null,

Loading…
Cancel
Save