Merge branch 'feature/suggestions' into devel
This commit is contained in:
@@ -27,6 +27,7 @@ import org.koitharu.kotatsu.reader.readerModule
|
||||
import org.koitharu.kotatsu.remotelist.remoteListModule
|
||||
import org.koitharu.kotatsu.search.searchModule
|
||||
import org.koitharu.kotatsu.settings.settingsModule
|
||||
import org.koitharu.kotatsu.suggestions.suggestionsModule
|
||||
import org.koitharu.kotatsu.tracker.trackerModule
|
||||
import org.koitharu.kotatsu.widget.WidgetUpdater
|
||||
import org.koitharu.kotatsu.widget.appWidgetModule
|
||||
@@ -67,6 +68,7 @@ class KotatsuApp : Application() {
|
||||
settingsModule,
|
||||
readerModule,
|
||||
appWidgetModule,
|
||||
suggestionsModule,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import org.koitharu.kotatsu.favourites.data.FavouriteEntity
|
||||
import org.koitharu.kotatsu.favourites.data.FavouritesDao
|
||||
import org.koitharu.kotatsu.history.data.HistoryDao
|
||||
import org.koitharu.kotatsu.history.data.HistoryEntity
|
||||
import org.koitharu.kotatsu.suggestions.data.SuggestionDao
|
||||
import org.koitharu.kotatsu.suggestions.data.SuggestionEntity
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
@@ -35,4 +37,6 @@ abstract class MangaDatabase : RoomDatabase() {
|
||||
abstract val tracksDao: TracksDao
|
||||
|
||||
abstract val trackLogsDao: TrackLogsDao
|
||||
|
||||
abstract val suggestionDao: SuggestionDao
|
||||
}
|
||||
@@ -237,7 +237,6 @@ class AnibelRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
|
||||
when {
|
||||
c == '-' -> {
|
||||
builder.setCharAt(i, ' ')
|
||||
capitalize = true
|
||||
}
|
||||
capitalize -> {
|
||||
builder.setCharAt(i, c.uppercaseChar())
|
||||
|
||||
@@ -61,7 +61,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
|
||||
tags = runCatching {
|
||||
row.selectFirst("div.genre")?.select("a")?.mapToSet {
|
||||
MangaTag(
|
||||
title = it.text(),
|
||||
title = it.text().toTitleCase(),
|
||||
key = it.attr("href").substringAfterLast('/').urlEncoded(),
|
||||
source = source
|
||||
)
|
||||
@@ -136,7 +136,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
|
||||
return root.select("li.sidetag").mapToSet { li ->
|
||||
val a = li.children().last() ?: throw ParseException("a is null")
|
||||
MangaTag(
|
||||
title = a.text().toCamelCase(),
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
|
||||
tags = json.getJSONArray("genres").mapToSet {
|
||||
MangaTag(
|
||||
key = it.getString("text"),
|
||||
title = it.getString("russian"),
|
||||
title = it.getString("russian").toTitleCase(),
|
||||
source = manga.source
|
||||
)
|
||||
},
|
||||
@@ -133,7 +133,7 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
|
||||
MangaTag(
|
||||
source = source,
|
||||
key = it.selectFirst("input")?.attr("data-genre") ?: parseFailed(),
|
||||
title = it.selectFirst("label")?.text() ?: parseFailed()
|
||||
title = it.selectFirst("label")?.text()?.toTitleCase() ?: parseFailed()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class ExHentaiRepository(
|
||||
val tagsDiv = glink.nextElementSibling() ?: parseFailed("tags div not found")
|
||||
val mainTag = td2.selectFirst("div.cn")?.let { div ->
|
||||
MangaTag(
|
||||
title = div.text(),
|
||||
title = div.text().toTitleCase(),
|
||||
key = tagIdByClass(div.classNames()) ?: return@let null,
|
||||
source = source,
|
||||
)
|
||||
@@ -181,7 +181,7 @@ class ExHentaiRepository(
|
||||
val id = div.id().substringAfterLast('_').toIntOrNull()
|
||||
?: return@mapNotNullToSet null
|
||||
MangaTag(
|
||||
title = div.text(),
|
||||
title = div.text().toTitleCase(),
|
||||
key = id.toString(),
|
||||
source = source
|
||||
)
|
||||
|
||||
@@ -89,7 +89,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
|
||||
tileInfo?.select("a.element-link")
|
||||
?.mapToSet {
|
||||
MangaTag(
|
||||
title = it.text(),
|
||||
title = it.text().toTitleCase(),
|
||||
key = it.attr("href").substringAfterLast('/'),
|
||||
source = source
|
||||
)
|
||||
@@ -119,7 +119,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
|
||||
.mapNotNull {
|
||||
val a = it.selectFirst("a.element-link") ?: return@mapNotNull null
|
||||
MangaTag(
|
||||
title = a.text(),
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source
|
||||
)
|
||||
@@ -183,7 +183,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
|
||||
?.selectFirst("table.table") ?: parseFailed("Cannot find root")
|
||||
return root.select("a.element-link").mapToSet { a ->
|
||||
MangaTag(
|
||||
title = a.text().toCamelCase(),
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.koitharu.kotatsu.core.exceptions.ParseException
|
||||
import org.koitharu.kotatsu.core.model.*
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
import org.koitharu.kotatsu.utils.ext.parseHtml
|
||||
import org.koitharu.kotatsu.utils.ext.toTitleCase
|
||||
|
||||
class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) {
|
||||
|
||||
@@ -36,7 +37,7 @@ class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(load
|
||||
tags = root.selectFirst("div.sidetags")?.select("li.sidetag")?.mapToSet {
|
||||
val a = it.children().last() ?: parseFailed("Invalid tag")
|
||||
MangaTag(
|
||||
title = a.text(),
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('/'),
|
||||
source = source
|
||||
)
|
||||
|
||||
@@ -94,7 +94,8 @@ class MangaDexRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
|
||||
MangaTag(
|
||||
title = tag.getJSONObject("attributes")
|
||||
.getJSONObject("name")
|
||||
.firstStringValue(),
|
||||
.firstStringValue()
|
||||
.toTitleCase(),
|
||||
key = tag.getString("id"),
|
||||
source = source,
|
||||
)
|
||||
@@ -194,7 +195,7 @@ class MangaDexRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
|
||||
.getJSONArray("data")
|
||||
return tags.mapToSet { jo ->
|
||||
MangaTag(
|
||||
title = jo.getJSONObject("attributes").getJSONObject("name").firstStringValue(),
|
||||
title = jo.getJSONObject("attributes").getJSONObject("name").firstStringValue().toTitleCase(),
|
||||
key = jo.getString("id"),
|
||||
source = source,
|
||||
)
|
||||
|
||||
@@ -139,7 +139,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
|
||||
tags = info?.selectFirst("div.media-tags")
|
||||
?.select("a.media-tag-item")?.mapToSet { a ->
|
||||
MangaTag(
|
||||
title = a.text().toCamelCase(),
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringAfterLast('='),
|
||||
source = source
|
||||
)
|
||||
@@ -203,7 +203,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
|
||||
result += MangaTag(
|
||||
source = source,
|
||||
key = x.getInt("id").toString(),
|
||||
title = x.getString("name").toCamelCase()
|
||||
title = x.getString("name").toTitleCase(),
|
||||
)
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -91,7 +91,7 @@ class MangaOwlRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
|
||||
.mapNotNull {
|
||||
val a = it.selectFirst("a") ?: return@mapNotNull null
|
||||
MangaTag(
|
||||
title = a.text(),
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href"),
|
||||
source = source
|
||||
)
|
||||
@@ -144,7 +144,7 @@ class MangaOwlRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
|
||||
return root.mapToSet { p ->
|
||||
val a = p.selectFirst("a") ?: parseFailed("a is null")
|
||||
MangaTag(
|
||||
title = a.text().toCamelCase(),
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href"),
|
||||
source = source
|
||||
)
|
||||
|
||||
@@ -80,7 +80,7 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
|
||||
},
|
||||
tags = li.selectFirst("p.keyWord")?.select("a")?.mapNotNullToSet tags@{ x ->
|
||||
MangaTag(
|
||||
title = x.attr("title"),
|
||||
title = x.attr("title").toTitleCase(),
|
||||
key = x.attr("href").parseTagKey() ?: return@tags null,
|
||||
source = MangaSource.MANGATOWN
|
||||
)
|
||||
@@ -104,7 +104,7 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
|
||||
x.selectFirst("b")?.ownText() == "Genre(s):"
|
||||
}?.select("a")?.mapNotNull { a ->
|
||||
MangaTag(
|
||||
title = a.attr("title"),
|
||||
title = a.attr("title").toTitleCase(),
|
||||
key = a.attr("href").parseTagKey() ?: return@mapNotNull null,
|
||||
source = MangaSource.MANGATOWN
|
||||
)
|
||||
@@ -172,7 +172,7 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
|
||||
MangaTag(
|
||||
source = MangaSource.MANGATOWN,
|
||||
key = key,
|
||||
title = a.text()
|
||||
title = a.text().toTitleCase()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class MangareadRepository(
|
||||
tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
|
||||
title = a.text(),
|
||||
title = a.text().toTitleCase(),
|
||||
source = MangaSource.MANGAREAD
|
||||
)
|
||||
}.orEmpty(),
|
||||
@@ -91,7 +91,7 @@ class MangareadRepository(
|
||||
}
|
||||
MangaTag(
|
||||
key = href,
|
||||
title = a.text(),
|
||||
title = a.text().toTitleCase(),
|
||||
source = MangaSource.MANGAREAD
|
||||
)
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class MangareadRepository(
|
||||
?.mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
|
||||
title = a.text(),
|
||||
title = a.text().toTitleCase(),
|
||||
source = MangaSource.MANGAREAD
|
||||
)
|
||||
} ?: manga.tags,
|
||||
|
||||
@@ -94,7 +94,7 @@ abstract class NineMangaRepository(
|
||||
tags = infoRoot.getElementsByAttributeValue("itemprop", "genre").first()
|
||||
?.select("a")?.mapToSet { a ->
|
||||
MangaTag(
|
||||
title = a.text(),
|
||||
title = a.text().toTitleCase(),
|
||||
key = a.attr("href").substringBetween("/", "."),
|
||||
source = source,
|
||||
)
|
||||
|
||||
@@ -73,7 +73,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
|
||||
author = null,
|
||||
tags = jo.optJSONArray("genres")?.mapToSet { g ->
|
||||
MangaTag(
|
||||
title = g.getString("name"),
|
||||
title = g.getString("name").toTitleCase(),
|
||||
key = g.getInt("id").toString(),
|
||||
source = MangaSource.REMANGA
|
||||
)
|
||||
@@ -109,7 +109,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
|
||||
},
|
||||
tags = content.getJSONArray("genres").mapToSet { g ->
|
||||
MangaTag(
|
||||
title = g.getString("name"),
|
||||
title = g.getString("name").toTitleCase(),
|
||||
key = g.getInt("id").toString(),
|
||||
source = MangaSource.REMANGA
|
||||
)
|
||||
@@ -175,7 +175,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
|
||||
.parseJson().getJSONObject("content").getJSONArray("genres")
|
||||
return content.mapToSet { jo ->
|
||||
MangaTag(
|
||||
title = jo.getString("name"),
|
||||
title = jo.getString("name").toTitleCase(),
|
||||
key = jo.getInt("id").toString(),
|
||||
source = source
|
||||
)
|
||||
|
||||
@@ -2,5 +2,5 @@ package org.koitharu.kotatsu.core.prefs
|
||||
|
||||
enum class AppSection {
|
||||
|
||||
LOCAL, FAVOURITES, HISTORY, FEED
|
||||
LOCAL, FAVOURITES, HISTORY, FEED, SUGGESTIONS
|
||||
}
|
||||
@@ -141,6 +141,12 @@ class AppSettings(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
val isSuggestionsEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_SUGGESTIONS, false)
|
||||
|
||||
val isSuggestionsExcludeNsfw: Boolean
|
||||
get() = prefs.getBoolean(KEY_SUGGESTIONS_EXCLUDE_NSFW, false)
|
||||
|
||||
fun getDateFormat(format: String = prefs.getString(KEY_DATE_FORMAT, "").orEmpty()): DateFormat =
|
||||
when (format) {
|
||||
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
|
||||
@@ -231,6 +237,8 @@ class AppSettings(context: Context) {
|
||||
const val KEY_HISTORY_EXCLUDE_NSFW = "history_exclude_nsfw"
|
||||
const val KEY_PAGES_NUMBERS = "pages_numbers"
|
||||
const val KEY_SCREENSHOTS_POLICY = "screenshots_policy"
|
||||
const val KEY_SUGGESTIONS = "suggestions"
|
||||
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"
|
||||
|
||||
// About
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.history.data
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
|
||||
|
||||
@Dao
|
||||
@@ -22,6 +23,9 @@ abstract class HistoryDao {
|
||||
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM history)")
|
||||
abstract suspend fun findAllManga(): List<MangaEntity>
|
||||
|
||||
@Query("SELECT * FROM tags WHERE tag_id IN (SELECT tag_id FROM manga_tags WHERE manga_id IN (SELECT manga_id FROM history))")
|
||||
abstract suspend fun findAllTags(): List<TagEntity>
|
||||
|
||||
@Query("SELECT * FROM history WHERE manga_id = :id")
|
||||
abstract suspend fun find(id: Long): HistoryEntity?
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.data.HistoryEntity
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
@@ -89,4 +90,8 @@ class HistoryRepository(
|
||||
db.historyDao.delete(manga.id)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAllTags(): Set<MangaTag> {
|
||||
return db.historyDao.findAllTags().mapToSet { x -> x.toMangaTag() }
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,7 @@ import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaChapter
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.utils.ext.getBooleanOrDefault
|
||||
import org.koitharu.kotatsu.utils.ext.getLongOrDefault
|
||||
import org.koitharu.kotatsu.utils.ext.getStringOrNull
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
class MangaIndex(source: String?) {
|
||||
|
||||
@@ -61,7 +58,7 @@ class MangaIndex(source: String?) {
|
||||
description = json.getStringOrNull("description"),
|
||||
tags = json.getJSONArray("tags").mapToSet { x ->
|
||||
MangaTag(
|
||||
title = x.getString("title"),
|
||||
title = x.getString("title").toTitleCase(),
|
||||
key = x.getString("key"),
|
||||
source = source
|
||||
)
|
||||
|
||||
@@ -49,6 +49,8 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||
import org.koitharu.kotatsu.settings.AppUpdateChecker
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
|
||||
import org.koitharu.kotatsu.tracker.ui.FeedFragment
|
||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
@@ -122,6 +124,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
viewModel.onError.observe(this, this::onError)
|
||||
viewModel.isLoading.observe(this, this::onLoadingStateChanged)
|
||||
viewModel.remoteSources.observe(this, this::updateSideMenu)
|
||||
viewModel.isSuggestionsEnabled.observe(this, this::setSuggestionsEnabled)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
@@ -187,6 +190,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
viewModel.defaultSection = AppSection.LOCAL
|
||||
setPrimaryFragment(LocalListFragment.newInstance())
|
||||
}
|
||||
R.id.nav_suggestions -> {
|
||||
viewModel.defaultSection = AppSection.SUGGESTIONS
|
||||
setPrimaryFragment(SuggestionsFragment.newInstance())
|
||||
}
|
||||
R.id.nav_feed -> {
|
||||
viewModel.defaultSection = AppSection.FEED
|
||||
setPrimaryFragment(FeedFragment.newInstance())
|
||||
@@ -303,6 +310,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
submenu.setGroupCheckable(R.id.group_remote_sources, true, true)
|
||||
}
|
||||
|
||||
private fun setSuggestionsEnabled(isEnabled: Boolean) {
|
||||
val item = binding.navigationView.menu.findItem(R.id.nav_suggestions) ?: return
|
||||
if (!isEnabled && item.isChecked) {
|
||||
binding.navigationView.setCheckedItem(R.id.nav_history)
|
||||
}
|
||||
item.isVisible = isEnabled
|
||||
}
|
||||
|
||||
private fun openDefaultSection() {
|
||||
when (viewModel.defaultSection) {
|
||||
AppSection.LOCAL -> {
|
||||
@@ -321,6 +336,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
binding.navigationView.setCheckedItem(R.id.nav_feed)
|
||||
setPrimaryFragment(FeedFragment.newInstance())
|
||||
}
|
||||
AppSection.SUGGESTIONS -> {
|
||||
binding.navigationView.setCheckedItem(R.id.nav_suggestions)
|
||||
setPrimaryFragment(SuggestionsFragment.newInstance())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +363,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
private fun onFirstStart() {
|
||||
lifecycleScope.launch(Dispatchers.Default) {
|
||||
TrackWorker.setup(applicationContext)
|
||||
SuggestionsWorker.setup(applicationContext)
|
||||
AppUpdateChecker(this@MainActivity).checkIfNeeded()
|
||||
if (!get<AppSettings>().isSourcesSelected) {
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
@@ -21,6 +21,12 @@ class MainViewModel(
|
||||
val onOpenReader = SingleLiveEvent<Manga>()
|
||||
var defaultSection by settings::defaultSection
|
||||
|
||||
val isSuggestionsEnabled = settings.observe()
|
||||
.filter { it == AppSettings.KEY_SUGGESTIONS }
|
||||
.onStart { emit("") }
|
||||
.map { settings.isSuggestionsEnabled }
|
||||
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
val remoteSources = settings.observe()
|
||||
.filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN }
|
||||
.onStart { emit("") }
|
||||
|
||||
@@ -14,10 +14,7 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.databinding.ActivitySettingsSimpleBinding
|
||||
import org.koitharu.kotatsu.settings.MainSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.NetworkSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.ReaderSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.SourceSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.*
|
||||
|
||||
class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
|
||||
|
||||
@@ -27,9 +24,11 @@ class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportFragmentManager.commit {
|
||||
replace(
|
||||
R.id.container, when (intent?.action) {
|
||||
R.id.container,
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_MANAGE_NETWORK_USAGE -> NetworkSettingsFragment()
|
||||
ACTION_READER -> ReaderSettingsFragment()
|
||||
ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
|
||||
ACTION_SOURCE -> SourceSettingsFragment.newInstance(
|
||||
intent.getParcelableExtra(EXTRA_SOURCE) ?: MangaSource.LOCAL
|
||||
)
|
||||
@@ -55,6 +54,8 @@ class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
|
||||
|
||||
private const val ACTION_READER =
|
||||
"${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
|
||||
private const val ACTION_SUGGESTIONS =
|
||||
"${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS"
|
||||
private const val ACTION_SOURCE =
|
||||
"${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
|
||||
private const val EXTRA_SOURCE = "source"
|
||||
@@ -63,6 +64,10 @@ class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
|
||||
Intent(context, SimpleSettingsActivity::class.java)
|
||||
.setAction(ACTION_READER)
|
||||
|
||||
fun newSuggestionsSettingsIntent(context: Context) =
|
||||
Intent(context, SimpleSettingsActivity::class.java)
|
||||
.setAction(ACTION_SUGGESTIONS)
|
||||
|
||||
fun newSourceSettingsIntent(context: Context, source: MangaSource) =
|
||||
Intent(context, SimpleSettingsActivity::class.java)
|
||||
.setAction(ACTION_SOURCE)
|
||||
|
||||
@@ -11,7 +11,7 @@ import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreference
|
||||
import androidx.preference.TwoStatePreference
|
||||
import kotlinx.coroutines.launch
|
||||
import leakcanary.LeakCanary
|
||||
import org.koin.android.ext.android.inject
|
||||
@@ -56,7 +56,7 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||
entryValues = ListMode.values().names()
|
||||
setDefaultValueCompat(ListMode.GRID.name)
|
||||
}
|
||||
findPreference<SwitchPreference>(AppSettings.KEY_DYNAMIC_THEME)?.isVisible =
|
||||
findPreference<Preference>(AppSettings.KEY_DYNAMIC_THEME)?.isVisible =
|
||||
AppSettings.isDynamicColorAvailable
|
||||
findPreference<ListPreference>(AppSettings.KEY_DATE_FORMAT)?.run {
|
||||
entryValues = arrayOf("", "MM/dd/yy", "dd/MM/yy", "yyyy-MM-dd", "dd MMM yyyy", "MMM dd, yyyy")
|
||||
@@ -72,12 +72,15 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||
setDefaultValueCompat("")
|
||||
summary = "%s"
|
||||
}
|
||||
findPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(
|
||||
if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
findPreference<Preference>(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName()
|
||||
findPreference<SwitchPreference>(AppSettings.KEY_PROTECT_APP)?.isChecked =
|
||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)?.isChecked =
|
||||
!settings.appPassword.isNullOrEmpty()
|
||||
settings.subscribe(this)
|
||||
}
|
||||
@@ -114,15 +117,20 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||
findPreference<Preference>(key)?.setSummary(R.string.restart_required)
|
||||
}
|
||||
AppSettings.KEY_HIDE_TOOLBAR -> {
|
||||
findPreference<SwitchPreference>(key)?.setSummary(R.string.restart_required)
|
||||
findPreference<Preference>(key)?.setSummary(R.string.restart_required)
|
||||
}
|
||||
AppSettings.KEY_LOCAL_STORAGE -> {
|
||||
findPreference<Preference>(key)?.bindStorageName()
|
||||
}
|
||||
AppSettings.KEY_APP_PASSWORD -> {
|
||||
findPreference<SwitchPreference>(AppSettings.KEY_PROTECT_APP)
|
||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
||||
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
||||
}
|
||||
AppSettings.KEY_SUGGESTIONS -> {
|
||||
findPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(
|
||||
if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +156,7 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_PROTECT_APP -> {
|
||||
val pref = (preference as? SwitchPreference ?: return false)
|
||||
val pref = (preference as? TwoStatePreference ?: return false)
|
||||
if (pref.isChecked) {
|
||||
pref.isChecked = false
|
||||
startActivity(Intent(preference.context, ProtectSetupActivity::class.java))
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
|
||||
|
||||
class SuggestionsSettingsFragment : BasePreferenceFragment(R.string.suggestions),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private val repository by inject<SuggestionRepository>(mode = LazyThreadSafetyMode.NONE)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
settings.subscribe(this)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_suggestions)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
settings.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
if (key == AppSettings.KEY_SUGGESTIONS && settings.isSuggestionsEnabled) {
|
||||
onSuggestionsEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSuggestionsEnabled() {
|
||||
lifecycleScope.launch {
|
||||
if (repository.isEmpty()) {
|
||||
SuggestionsWorker.startNow(context ?: return@launch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.koitharu.kotatsu.suggestions
|
||||
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsViewModel
|
||||
|
||||
val suggestionsModule
|
||||
get() = module {
|
||||
|
||||
factory { SuggestionRepository(get()) }
|
||||
|
||||
viewModel { SuggestionsViewModel(get(), get()) }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.koitharu.kotatsu.suggestions.data
|
||||
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
abstract class SuggestionDao {
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM suggestions ORDER BY relevance DESC")
|
||||
abstract fun observeAll(): Flow<List<SuggestionWithManga>>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM suggestions")
|
||||
abstract suspend fun count(): Int
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract suspend fun insert(entity: SuggestionEntity): Long
|
||||
|
||||
@Update
|
||||
abstract suspend fun update(entity: SuggestionEntity): Int
|
||||
|
||||
@Query("DELETE FROM suggestions")
|
||||
abstract suspend fun deleteAll()
|
||||
|
||||
@Transaction
|
||||
open suspend fun upsert(entity: SuggestionEntity) {
|
||||
if (update(entity) == 0) {
|
||||
insert(entity)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.koitharu.kotatsu.core.db.entity
|
||||
package org.koitharu.kotatsu.suggestions.data
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
|
||||
@Entity(
|
||||
tableName = "suggestions",
|
||||
@@ -19,6 +21,7 @@ import androidx.room.PrimaryKey
|
||||
class SuggestionEntity(
|
||||
@PrimaryKey(autoGenerate = false)
|
||||
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
@ColumnInfo(name = "relevance") val relevance: Float,
|
||||
@ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(),
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.koitharu.kotatsu.suggestions.data
|
||||
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Junction
|
||||
import androidx.room.Relation
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
|
||||
data class SuggestionWithManga(
|
||||
@Embedded val suggestion: SuggestionEntity,
|
||||
@Relation(
|
||||
parentColumn = "manga_id",
|
||||
entityColumn = "manga_id"
|
||||
)
|
||||
val manga: MangaEntity,
|
||||
@Relation(
|
||||
parentColumn = "manga_id",
|
||||
entityColumn = "tag_id",
|
||||
associateBy = Junction(MangaTagsEntity::class)
|
||||
)
|
||||
val tags: List<TagEntity>
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.koitharu.kotatsu.suggestions.domain
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
|
||||
data class MangaSuggestion(
|
||||
val manga: Manga,
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
val relevance: Float,
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.koitharu.kotatsu.suggestions.domain
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.suggestions.data.SuggestionEntity
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
|
||||
class SuggestionRepository(
|
||||
private val db: MangaDatabase,
|
||||
) {
|
||||
|
||||
fun observeAll(): Flow<List<Manga>> {
|
||||
return db.suggestionDao.observeAll().mapItems {
|
||||
it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clear() {
|
||||
db.suggestionDao.deleteAll()
|
||||
}
|
||||
|
||||
suspend fun isEmpty(): Boolean {
|
||||
return db.suggestionDao.count() == 0
|
||||
}
|
||||
|
||||
suspend fun replace(suggestions: Iterable<MangaSuggestion>) {
|
||||
db.withTransaction {
|
||||
db.suggestionDao.deleteAll()
|
||||
suggestions.forEach { x ->
|
||||
val tags = x.manga.tags.map(TagEntity.Companion::fromMangaTag)
|
||||
db.tagsDao.upsert(tags)
|
||||
db.mangaDao.upsert(MangaEntity.from(x.manga), tags)
|
||||
db.suggestionDao.upsert(
|
||||
SuggestionEntity(
|
||||
mangaId = x.manga.id,
|
||||
relevance = x.relevance,
|
||||
createdAt = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.koitharu.kotatsu.suggestions.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity
|
||||
|
||||
class SuggestionsFragment : MangaListFragment() {
|
||||
|
||||
override val viewModel by viewModel<SuggestionsViewModel>()
|
||||
override val isSwipeRefreshEnabled = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.opt_suggestions, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_update -> {
|
||||
SuggestionsWorker.startNow(requireContext())
|
||||
Snackbar.make(
|
||||
binding.recyclerView,
|
||||
R.string.feed_will_update_soon,
|
||||
Snackbar.LENGTH_LONG,
|
||||
).show()
|
||||
true
|
||||
}
|
||||
R.id.action_settings -> {
|
||||
startActivity(SimpleSettingsActivity.newSuggestionsSettingsIntent(requireContext()))
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
return context?.getString(R.string.suggestions)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = SuggestionsFragment()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.koitharu.kotatsu.suggestions.ui
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
|
||||
class SuggestionsViewModel(
|
||||
repository: SuggestionRepository,
|
||||
settings: AppSettings,
|
||||
) : MangaListViewModel(settings) {
|
||||
|
||||
private val headerModel = ListHeader(null, R.string.suggestions)
|
||||
|
||||
override val content = combine(
|
||||
repository.observeAll(),
|
||||
createListModeFlow()
|
||||
) { list, mode ->
|
||||
when {
|
||||
list.isEmpty() -> listOf(EmptyState(
|
||||
icon = R.drawable.ic_book_cross,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_suggestion_holder,
|
||||
))
|
||||
else -> buildList<ListModel>(list.size + 1) {
|
||||
add(headerModel)
|
||||
list.toUi(this, mode)
|
||||
}
|
||||
}
|
||||
}.onFirst {
|
||||
isLoading.postValue(false)
|
||||
}.catch {
|
||||
it.toErrorState(canRetry = false)
|
||||
}.asLiveDataDistinct(
|
||||
viewModelScope.coroutineContext + Dispatchers.Default,
|
||||
listOf(LoadingState)
|
||||
)
|
||||
|
||||
override fun onRefresh() = Unit
|
||||
|
||||
override fun onRetry() = Unit
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package org.koitharu.kotatsu.suggestions.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.SortOrder
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion
|
||||
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
|
||||
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.pow
|
||||
|
||||
class SuggestionsWorker(appContext: Context, params: WorkerParameters) :
|
||||
CoroutineWorker(appContext, params), KoinComponent {
|
||||
|
||||
private val suggestionRepository by inject<SuggestionRepository>()
|
||||
private val historyRepository by inject<HistoryRepository>()
|
||||
private val appSettings by inject<AppSettings>()
|
||||
|
||||
override suspend fun doWork(): Result = try {
|
||||
val count = doWorkImpl()
|
||||
Result.success(workDataOf(DATA_COUNT to count))
|
||||
} catch (t: Throwable) {
|
||||
Result.failure()
|
||||
}
|
||||
|
||||
private suspend fun doWorkImpl(): Int {
|
||||
if (!appSettings.isSuggestionsEnabled) {
|
||||
suggestionRepository.clear()
|
||||
return 0
|
||||
}
|
||||
val rawResults = ArrayList<Manga>()
|
||||
val allTags = historyRepository.getAllTags()
|
||||
if (allTags.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
val tagsBySources = allTags.groupBy { x -> x.source }
|
||||
for ((source, tags) in tagsBySources) {
|
||||
val repo = mangaRepositoryOf(source)
|
||||
tags.flatMapTo(rawResults) { tag ->
|
||||
repo.getList2(
|
||||
offset = 0,
|
||||
sortOrder = SortOrder.UPDATED,
|
||||
tags = setOf(tag),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (appSettings.isSuggestionsExcludeNsfw) {
|
||||
rawResults.removeAll { it.isNsfw }
|
||||
}
|
||||
if (rawResults.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
val suggestions = rawResults.distinctBy { manga ->
|
||||
manga.id
|
||||
}.map { manga ->
|
||||
val jointTags = manga.tags intersect allTags
|
||||
MangaSuggestion(
|
||||
manga = manga,
|
||||
relevance = (jointTags.size / manga.tags.size.toDouble()).pow(2.0).toFloat(),
|
||||
)
|
||||
}.sortedBy { it.relevance }.take(LIMIT)
|
||||
suggestionRepository.replace(suggestions)
|
||||
return suggestions.size
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "suggestions"
|
||||
private const val TAG_ONESHOT = "suggestions_oneshot"
|
||||
private const val LIMIT = 140
|
||||
private const val DATA_COUNT = "count"
|
||||
|
||||
fun setup(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
.setRequiresBatteryNotLow(true)
|
||||
.build()
|
||||
val request = PeriodicWorkRequestBuilder<SuggestionsWorker>(6, TimeUnit.HOURS)
|
||||
.setConstraints(constraints)
|
||||
.addTag(TAG)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
|
||||
.build()
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.KEEP, request)
|
||||
}
|
||||
|
||||
fun startNow(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
val request = OneTimeWorkRequestBuilder<SuggestionsWorker>()
|
||||
.setConstraints(constraints)
|
||||
.addTag(TAG_ONESHOT)
|
||||
.build()
|
||||
WorkManager.getInstance(context)
|
||||
.enqueue(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ class FeedFragment : BaseFragment<FragmentFeedBinding>(), PaginationScrollListen
|
||||
Snackbar.make(
|
||||
binding.recyclerView,
|
||||
R.string.feed_will_update_soon,
|
||||
Snackbar.LENGTH_SHORT
|
||||
Snackbar.LENGTH_LONG,
|
||||
).show()
|
||||
true
|
||||
}
|
||||
|
||||
@@ -236,6 +236,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
private const val DATA_PROGRESS = "progress"
|
||||
private const val DATA_TOTAL = "total"
|
||||
private const val TAG = "tracking"
|
||||
private const val TAG_ONESHOT = "tracking_oneshot"
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel(context: Context) {
|
||||
@@ -276,7 +277,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
.build()
|
||||
val request = OneTimeWorkRequestBuilder<TrackWorker>()
|
||||
.setConstraints(constraints)
|
||||
.addTag(TAG)
|
||||
.addTag(TAG_ONESHOT)
|
||||
.build()
|
||||
WorkManager.getInstance(context)
|
||||
.enqueue(request)
|
||||
|
||||
Reference in New Issue
Block a user