Store manga sources in database #426
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.preference.Preference
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
|
||||
class RootSettingsFragment : BasePreferenceFragment(0), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
@AndroidEntryPoint
|
||||
class RootSettingsFragment : BasePreferenceFragment(0) {
|
||||
|
||||
private val viewModel: RootSettingsViewModel by viewModels()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_root)
|
||||
@@ -22,23 +27,18 @@ class RootSettingsFragment : BasePreferenceFragment(0), SharedPreferences.OnShar
|
||||
bindPreferenceSummary("tracker", R.string.track_sources, R.string.notifications_settings)
|
||||
bindPreferenceSummary("services", R.string.suggestions, R.string.sync, R.string.tracking)
|
||||
findPreference<Preference>("about")?.summary = getString(R.string.app_version, BuildConfig.VERSION_NAME)
|
||||
bindRemoteSourcesSummary()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
settings.subscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
settings.unsubscribe(this)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
when (key) {
|
||||
AppSettings.KEY_SOURCES_HIDDEN -> {
|
||||
bindRemoteSourcesSummary()
|
||||
findPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.let { pref ->
|
||||
val total = viewModel.totalSourcesCount
|
||||
viewModel.enabledSourcesCount.observe(viewLifecycleOwner) {
|
||||
pref.summary = if (it >= 0) {
|
||||
getString(R.string.enabled_d_of_d, it, total)
|
||||
} else {
|
||||
resources.getQuantityString(R.plurals.items, total, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,11 +46,4 @@ class RootSettingsFragment : BasePreferenceFragment(0), SharedPreferences.OnShar
|
||||
private fun bindPreferenceSummary(key: String, @StringRes vararg items: Int) {
|
||||
findPreference<Preference>(key)?.summary = items.joinToString { getString(it) }
|
||||
}
|
||||
|
||||
private fun bindRemoteSourcesSummary() {
|
||||
findPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.run {
|
||||
val total = settings.remoteMangaSources.size
|
||||
summary = getString(R.string.enabled_d_of_d, total - settings.hiddenSources.size, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class RootSettingsViewModel @Inject constructor(
|
||||
sourcesRepository: MangaSourcesRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val totalSourcesCount = sourcesRepository.allMangaSources.size
|
||||
|
||||
val enabledSourcesCount = sourcesRepository.observeEnabledSources()
|
||||
.map { it.size }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import androidx.fragment.app.viewModels
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
@@ -39,8 +38,7 @@ class NewSourcesDialogFragment :
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.textViewTitle.setText(R.string.new_sources_text)
|
||||
|
||||
viewModel.sources.filterNotNull()
|
||||
.observe(viewLifecycleOwner) { adapter.items = it }
|
||||
viewModel.content.observe(viewLifecycleOwner) { adapter.items = it }
|
||||
}
|
||||
|
||||
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
||||
@@ -51,7 +49,6 @@ class NewSourcesDialogFragment :
|
||||
}
|
||||
|
||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||
viewModel.apply()
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,74 +1,43 @@
|
||||
package org.koitharu.kotatsu.settings.newsources
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.mapToSet
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class NewSourcesViewModel @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val repository: MangaSourcesRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val initialList = settings.newSources
|
||||
val sources = MutableStateFlow<List<SourceConfigItem>?>(null)
|
||||
private var listUpdateJob: Job? = null
|
||||
|
||||
init {
|
||||
listUpdateJob = launchJob(Dispatchers.Default) {
|
||||
sources.value = buildList()
|
||||
}
|
||||
private val newSources = SuspendLazy {
|
||||
repository.assimilateNewSources()
|
||||
}
|
||||
val content: StateFlow<List<SourceConfigItem>> = repository.observeAll()
|
||||
.map { sources ->
|
||||
val new = newSources.get()
|
||||
sources.mapNotNull { (source, enabled) ->
|
||||
if (source in new) {
|
||||
SourceConfigItem.SourceItem(source, enabled, source.getLocaleTitle(), false)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList())
|
||||
|
||||
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
||||
val prevJob = listUpdateJob
|
||||
listUpdateJob = launchJob(Dispatchers.Default) {
|
||||
if (isEnabled) {
|
||||
settings.hiddenSources -= item.source.name
|
||||
} else {
|
||||
settings.hiddenSources += item.source.name
|
||||
}
|
||||
prevJob?.cancelAndJoin()
|
||||
val list = buildList()
|
||||
ensureActive()
|
||||
sources.value = list
|
||||
}
|
||||
}
|
||||
|
||||
fun apply() {
|
||||
settings.markKnownSources(initialList)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun buildList(): List<SourceConfigItem.SourceItem> {
|
||||
val locales = LocaleListCompat.getDefault().mapToSet { it.language }
|
||||
val pendingHidden = HashSet<String>()
|
||||
return initialList.map {
|
||||
val locale = it.locale
|
||||
val isEnabledByLocale = locale == null || locale in locales
|
||||
if (!isEnabledByLocale) {
|
||||
pendingHidden += it.name
|
||||
}
|
||||
SourceConfigItem.SourceItem(
|
||||
source = it,
|
||||
summary = it.getLocaleTitle(),
|
||||
isEnabled = isEnabledByLocale,
|
||||
isDraggable = false,
|
||||
)
|
||||
}.also {
|
||||
if (pendingHidden.isNotEmpty()) {
|
||||
settings.hiddenSources += pendingHidden
|
||||
}
|
||||
launchJob(Dispatchers.Default) {
|
||||
repository.setSourceEnabled(item.source, isEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +66,9 @@ class OnboardDialogFragment :
|
||||
viewModel.setItemChecked(item.key, isChecked)
|
||||
}
|
||||
|
||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> viewModel.apply()
|
||||
DialogInterface.BUTTON_POSITIVE -> dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package org.koitharu.kotatsu.settings.onboard
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.map
|
||||
import org.koitharu.kotatsu.core.util.ext.mapToSet
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
|
||||
import java.util.Locale
|
||||
@@ -17,31 +18,34 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class OnboardViewModel @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val repository: MangaSourcesRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val allSources = settings.remoteMangaSources
|
||||
|
||||
private val allSources = repository.allMangaSources
|
||||
private val locales = allSources.groupBy { it.locale }
|
||||
|
||||
private val selectedLocales = locales.keys.toMutableSet()
|
||||
|
||||
private val selectedLocales = HashSet<String?>()
|
||||
val list = MutableStateFlow<List<SourceLocale>?>(null)
|
||||
private var updateJob: Job
|
||||
|
||||
init {
|
||||
if (settings.isSourcesSelected) {
|
||||
selectedLocales.removeAll(settings.hiddenSources.mapNotNullToSet { x -> MangaSource(x).locale })
|
||||
} else {
|
||||
val deviceLocales = LocaleListCompat.getDefault().mapToSet { x ->
|
||||
x.language
|
||||
updateJob = launchJob(Dispatchers.Default) {
|
||||
if (repository.isSetupRequired()) {
|
||||
val deviceLocales = LocaleListCompat.getDefault().mapToSet { x ->
|
||||
x.language
|
||||
}
|
||||
selectedLocales.addAll(deviceLocales)
|
||||
if (selectedLocales.isEmpty()) {
|
||||
selectedLocales += "en"
|
||||
}
|
||||
selectedLocales += null
|
||||
} else {
|
||||
selectedLocales.addAll(
|
||||
repository.getEnabledSources().mapNotNullToSet { x -> x.locale },
|
||||
)
|
||||
}
|
||||
selectedLocales.retainAll(deviceLocales)
|
||||
if (selectedLocales.isEmpty()) {
|
||||
selectedLocales += "en"
|
||||
}
|
||||
selectedLocales += null
|
||||
rebuildList()
|
||||
repository.assimilateNewSources()
|
||||
}
|
||||
rebuildList()
|
||||
}
|
||||
|
||||
fun setItemChecked(key: String?, isChecked: Boolean) {
|
||||
@@ -51,17 +55,17 @@ class OnboardViewModel @Inject constructor(
|
||||
selectedLocales.remove(key)
|
||||
}
|
||||
if (isModified) {
|
||||
rebuildList()
|
||||
val prevJob = updateJob
|
||||
updateJob = launchJob(Dispatchers.Default) {
|
||||
prevJob.join()
|
||||
val sources = allSources.filter { x -> x.locale == key }
|
||||
repository.setSourcesEnabled(sources, isChecked)
|
||||
rebuildList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun apply() {
|
||||
settings.hiddenSources = allSources.filterNot { x ->
|
||||
x.locale in selectedLocales
|
||||
}.mapToSet { x -> x.name }
|
||||
settings.markKnownSources(settings.newSources)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun rebuildList() {
|
||||
list.value = locales.map { (key, srcs) ->
|
||||
val locale = if (key != null) {
|
||||
|
||||
@@ -3,25 +3,26 @@ package org.koitharu.kotatsu.settings.sources
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
||||
import org.koitharu.kotatsu.core.util.AlphanumComparator
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.map
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.move
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
import java.util.TreeMap
|
||||
import javax.inject.Inject
|
||||
@@ -34,6 +35,7 @@ private const val TIP_REORDER = "src_reorder"
|
||||
@HiltViewModel
|
||||
class SourcesListViewModel @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
private val repository: MangaSourcesRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val items = MutableStateFlow<List<SourceConfigItem>>(emptyList())
|
||||
@@ -51,13 +53,19 @@ class SourcesListViewModel @Inject constructor(
|
||||
|
||||
fun reorderSources(oldPos: Int, newPos: Int): Boolean {
|
||||
val snapshot = items.value.toMutableList()
|
||||
if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
||||
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
||||
val item = (snapshot[oldPos] as? SourceConfigItem.SourceItem) ?: return false
|
||||
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isDraggable != true) return false
|
||||
launchAtomicJob(Dispatchers.Default) {
|
||||
snapshot.move(oldPos, newPos)
|
||||
settings.sourcesOrder = snapshot.mapNotNull {
|
||||
(it as? SourceConfigItem.SourceItem)?.source?.name
|
||||
var targetPosition = 0
|
||||
for ((i, x) in snapshot.withIndex()) {
|
||||
if (i == newPos) {
|
||||
break
|
||||
}
|
||||
if (x is SourceConfigItem.SourceItem) {
|
||||
targetPosition++
|
||||
}
|
||||
}
|
||||
repository.setPosition(item.source, targetPosition)
|
||||
buildList()
|
||||
}
|
||||
return true
|
||||
@@ -71,17 +79,8 @@ class SourcesListViewModel @Inject constructor(
|
||||
|
||||
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
||||
launchAtomicJob(Dispatchers.Default) {
|
||||
settings.hiddenSources = if (isEnabled) {
|
||||
settings.hiddenSources - source.name
|
||||
} else {
|
||||
settings.hiddenSources + source.name
|
||||
}
|
||||
if (isEnabled) {
|
||||
settings.markKnownSources(setOf(source))
|
||||
} else {
|
||||
val rollback = ReversibleHandle {
|
||||
setEnabled(source, true)
|
||||
}
|
||||
val rollback = repository.setSourceEnabled(source, isEnabled)
|
||||
if (!isEnabled) {
|
||||
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
||||
}
|
||||
buildList()
|
||||
@@ -90,9 +89,7 @@ class SourcesListViewModel @Inject constructor(
|
||||
|
||||
fun disableAll() {
|
||||
launchAtomicJob(Dispatchers.Default) {
|
||||
settings.hiddenSources = settings.getMangaSources(includeHidden = true).mapToSet {
|
||||
it.name
|
||||
}
|
||||
repository.disableAllSources()
|
||||
buildList()
|
||||
}
|
||||
}
|
||||
@@ -122,36 +119,37 @@ class SourcesListViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildList() = runInterruptible(Dispatchers.Default) {
|
||||
val sources = settings.getMangaSources(includeHidden = true)
|
||||
val hiddenSources = settings.hiddenSources
|
||||
private suspend fun buildList() = withContext(Dispatchers.Default) {
|
||||
val allSources = repository.allMangaSources
|
||||
val enabledSources = repository.getEnabledSources()
|
||||
val enabledSet = EnumSet.copyOf(enabledSources)
|
||||
val query = searchQuery
|
||||
if (!query.isNullOrEmpty()) {
|
||||
items.value = sources.mapNotNull {
|
||||
items.value = allSources.mapNotNull {
|
||||
if (!it.title.contains(query, ignoreCase = true)) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
SourceConfigItem.SourceItem(
|
||||
source = it,
|
||||
summary = it.getLocaleTitle(),
|
||||
isEnabled = it.name !in hiddenSources,
|
||||
isEnabled = it in enabledSet,
|
||||
isDraggable = false,
|
||||
)
|
||||
}.ifEmpty {
|
||||
listOf(SourceConfigItem.EmptySearchResult)
|
||||
}
|
||||
return@runInterruptible
|
||||
return@withContext
|
||||
}
|
||||
val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
||||
if (it.name !in hiddenSources) {
|
||||
val map = allSources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
||||
if (it in enabledSet) {
|
||||
KEY_ENABLED
|
||||
} else {
|
||||
it.locale
|
||||
}
|
||||
}
|
||||
val result = ArrayList<SourceConfigItem>(sources.size + map.size + 2)
|
||||
val enabledSources = map.remove(KEY_ENABLED)
|
||||
if (!enabledSources.isNullOrEmpty()) {
|
||||
map.remove(KEY_ENABLED)
|
||||
val result = ArrayList<SourceConfigItem>(allSources.size + map.size + 2)
|
||||
if (enabledSources.isNotEmpty()) {
|
||||
result += SourceConfigItem.Header(R.string.enabled_sources)
|
||||
if (settings.isTipEnabled(TIP_REORDER)) {
|
||||
result += SourceConfigItem.Tip(TIP_REORDER, R.drawable.ic_tap_reorder, R.string.sources_reorder_tip)
|
||||
@@ -165,10 +163,11 @@ class SourcesListViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (enabledSources?.size != sources.size) {
|
||||
if (enabledSources.size != allSources.size) {
|
||||
result += SourceConfigItem.Header(R.string.available_sources)
|
||||
val comparator = compareBy<MangaSource, String>(AlphanumComparator()) { it.name }
|
||||
for ((key, list) in map) {
|
||||
list.sortBy { it.ordinal }
|
||||
list.sortWith(comparator)
|
||||
val isExpanded = key in expandedGroups
|
||||
result += SourceConfigItem.LocaleGroup(
|
||||
localeId = key,
|
||||
@@ -195,12 +194,12 @@ class SourcesListViewModel @Inject constructor(
|
||||
return locale.getDisplayLanguage(locale).toTitleCase(locale)
|
||||
}
|
||||
|
||||
private inline fun launchAtomicJob(
|
||||
private fun launchAtomicJob(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
crossinline block: suspend CoroutineScope.() -> Unit
|
||||
) = launchJob(context) {
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
) = launchJob(start = CoroutineStart.ATOMIC) {
|
||||
mutex.withLock {
|
||||
block()
|
||||
withContext(context, block)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user