Merge branch 'master' into Failed-Updates-feature-added

This commit is contained in:
Programmer-0-0 2023-08-26 13:55:55 +04:00 committed by GitHub
commit c31b8ada58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 240 additions and 92 deletions

View file

@ -23,7 +23,7 @@ android {
defaultConfig {
applicationId = "eu.kanade.tachiyomi"
versionCode = 104
versionCode = 105
versionName = "0.14.6"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")

View file

@ -65,10 +65,10 @@
android:exported="false" />
<activity
android:name=".ui.main.DeepLinkActivity"
android:name=".ui.deeplink.DeepLinkActivity"
android:launchMode="singleTask"
android:theme="@android:style/Theme.NoDisplay"
android:label="@string/action_global_search"
android:label="@string/action_search"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />

View file

@ -1,7 +1,6 @@
package eu.kanade.core.util
import androidx.compose.ui.util.fastForEach
import java.util.concurrent.ConcurrentHashMap
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@ -20,15 +19,6 @@ fun <T : R, R : Any> List<T>.insertSeparators(
return newList
}
/**
* Returns a new map containing only the key entries of [transform] that are not null.
*/
inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): ConcurrentHashMap<R, V> {
val mutableMap = ConcurrentHashMap<R, V>()
forEach { element -> transform(element)?.let { mutableMap[it] = element.value } }
return mutableMap
}
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
if (shouldAdd) {
add(value)

View file

@ -76,7 +76,7 @@ fun ExtensionScreen(
enabled = !state.isLoading,
) {
when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
state.isEmpty -> {
val msg = if (!searchQuery.isNullOrEmpty()) {
R.string.no_results_found

View file

@ -51,7 +51,7 @@ fun MigrateSourceScreen(
) {
val context = LocalContext.current
when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
state.isEmpty -> EmptyScreen(
textResource = R.string.information_empty_library,
modifier = Modifier.padding(contentPadding),

View file

@ -47,7 +47,7 @@ fun SourcesScreen(
onLongClickItem: (Source) -> Unit,
) {
when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
state.isEmpty -> EmptyScreen(
textResource = R.string.source_empty_screen,
modifier = Modifier.padding(contentPadding),

View file

@ -65,7 +65,7 @@ fun HistoryScreen(
) { contentPadding ->
state.list.let {
if (it == null) {
LoadingScreen(modifier = Modifier.padding(contentPadding))
LoadingScreen(Modifier.padding(contentPadding))
} else if (it.isEmpty()) {
val msg = if (!state.searchQuery.isNullOrEmpty()) {
R.string.no_results_found

View file

@ -180,7 +180,7 @@ private val displayModes = listOf(
private fun ColumnScope.DisplayPage(
screenModel: LibrarySettingsScreenModel,
) {
val displayMode by screenModel.libraryPreferences.libraryDisplayMode().collectAsState()
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
SettingsChipRow(R.string.action_display_mode) {
displayModes.map { (titleRes, mode) ->
FilterChip(

View file

@ -31,7 +31,6 @@ import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.ResetCategoryFlags
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_BATTERY_NOT_LOW
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
@ -123,14 +122,14 @@ object SettingsLibraryScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val libraryUpdateIntervalPref = libraryPreferences.libraryUpdateInterval()
val libraryUpdateCategoriesPref = libraryPreferences.libraryUpdateCategories()
val libraryUpdateCategoriesExcludePref = libraryPreferences.libraryUpdateCategoriesExclude()
val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval()
val autoUpdateCategoriesPref = libraryPreferences.updateCategories()
val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude()
val libraryUpdateInterval by libraryUpdateIntervalPref.collectAsState()
val autoUpdateInterval by autoUpdateIntervalPref.collectAsState()
val included by libraryUpdateCategoriesPref.collectAsState()
val excluded by libraryUpdateCategoriesExcludePref.collectAsState()
val included by autoUpdateCategoriesPref.collectAsState()
val excluded by autoUpdateCategoriesExcludePref.collectAsState()
var showCategoriesDialog by rememberSaveable { mutableStateOf(false) }
if (showCategoriesDialog) {
TriStateListDialog(
@ -142,8 +141,8 @@ object SettingsLibraryScreen : SearchableSettings {
itemLabel = { it.visualName },
onDismissRequest = { showCategoriesDialog = false },
onValueChanged = { newIncluded, newExcluded ->
libraryUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
libraryUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
autoUpdateCategoriesPref.set(newIncluded.map { it.id.toString() }.toSet())
autoUpdateCategoriesExcludePref.set(newExcluded.map { it.id.toString() }.toSet())
showCategoriesDialog = false
},
)
@ -153,7 +152,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(R.string.pref_category_library_update),
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = libraryUpdateIntervalPref,
pref = autoUpdateIntervalPref,
title = stringResource(R.string.pref_library_update_interval),
entries = mapOf(
0 to stringResource(R.string.update_never),
@ -169,15 +168,14 @@ object SettingsLibraryScreen : SearchableSettings {
},
),
Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryPreferences.libraryUpdateDeviceRestriction(),
enabled = libraryUpdateInterval > 0,
pref = libraryPreferences.autoUpdateDeviceRestrictions(),
enabled = autoUpdateInterval > 0,
title = stringResource(R.string.pref_library_update_restriction),
subtitle = stringResource(R.string.restrictions),
entries = mapOf(
DEVICE_ONLY_ON_WIFI to stringResource(R.string.connected_to_wifi),
DEVICE_NETWORK_NOT_METERED to stringResource(R.string.network_not_metered),
DEVICE_CHARGING to stringResource(R.string.charging),
DEVICE_BATTERY_NOT_LOW to stringResource(R.string.battery_not_low),
),
onValueChanged = {
// Post to event looper to allow the preference to be updated.
@ -206,7 +204,7 @@ object SettingsLibraryScreen : SearchableSettings {
subtitle = stringResource(R.string.pref_library_update_refresh_trackers_summary),
),
Preference.PreferenceItem.MultiSelectListPreference(
pref = libraryPreferences.libraryUpdateMangaRestriction(),
pref = libraryPreferences.autoUpdateMangaRestrictions(),
title = stringResource(R.string.pref_library_update_manga_restriction),
entries = mapOf(
MANGA_HAS_UNREAD to stringResource(R.string.pref_update_only_completely_read),

View file

@ -87,7 +87,7 @@ fun UpdateScreen(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding ->
when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
state.items.isEmpty() -> EmptyScreen(
textResource = R.string.information_no_recent,
modifier = Modifier.padding(contentPadding),

View file

@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.workManager
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.getAndSet
import tachiyomi.core.preference.getEnum
import tachiyomi.core.preference.minusAssign
import tachiyomi.core.preference.plusAssign
@ -101,11 +102,11 @@ object Migrations {
}
if (oldVersion < 44) {
// Reset sorting preference if using removed sort by source
val oldSortingMode = prefs.getInt(libraryPreferences.librarySortingMode().key(), 0)
val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0)
if (oldSortingMode == 5) { // SOURCE = 5
prefs.edit {
putInt(libraryPreferences.librarySortingMode().key(), 0) // ALPHABETICAL = 0
putInt(libraryPreferences.sortingMode().key(), 0) // ALPHABETICAL = 0
}
}
}
@ -180,14 +181,14 @@ object Migrations {
}
if (oldVersion < 61) {
// Handle removed every 1 or 2 hour library updates
val updateInterval = libraryPreferences.libraryUpdateInterval().get()
val updateInterval = libraryPreferences.autoUpdateInterval().get()
if (updateInterval == 1 || updateInterval == 2) {
libraryPreferences.libraryUpdateInterval().set(3)
libraryPreferences.autoUpdateInterval().set(3)
LibraryUpdateJob.setupTask(context, 3)
}
}
if (oldVersion < 64) {
val oldSortingMode = prefs.getInt(libraryPreferences.librarySortingMode().key(), 0)
val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0)
val oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true)
val newSortingMode = when (oldSortingMode) {
@ -208,12 +209,12 @@ object Migrations {
}
prefs.edit(commit = true) {
remove(libraryPreferences.librarySortingMode().key())
remove(libraryPreferences.sortingMode().key())
remove("library_sorting_ascending")
}
prefs.edit {
putString(libraryPreferences.librarySortingMode().key(), newSortingMode)
putString(libraryPreferences.sortingMode().key(), newSortingMode)
putString("library_sorting_ascending", newSortingDirection)
}
}
@ -224,16 +225,16 @@ object Migrations {
}
if (oldVersion < 71) {
// Handle removed every 3, 4, 6, and 8 hour library updates
val updateInterval = libraryPreferences.libraryUpdateInterval().get()
val updateInterval = libraryPreferences.autoUpdateInterval().get()
if (updateInterval in listOf(3, 4, 6, 8)) {
libraryPreferences.libraryUpdateInterval().set(12)
libraryPreferences.autoUpdateInterval().set(12)
LibraryUpdateJob.setupTask(context, 12)
}
}
if (oldVersion < 72) {
val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
if (!oldUpdateOngoingOnly) {
libraryPreferences.libraryUpdateMangaRestriction() -= MANGA_NON_COMPLETED
libraryPreferences.autoUpdateMangaRestrictions() -= MANGA_NON_COMPLETED
}
}
if (oldVersion < 75) {
@ -258,20 +259,20 @@ object Migrations {
if (oldVersion < 81) {
// Handle renamed enum values
prefs.edit {
val newSortingMode = when (val oldSortingMode = prefs.getString(libraryPreferences.librarySortingMode().key(), "ALPHABETICAL")) {
val newSortingMode = when (val oldSortingMode = prefs.getString(libraryPreferences.sortingMode().key(), "ALPHABETICAL")) {
"LAST_CHECKED" -> "LAST_MANGA_UPDATE"
"UNREAD" -> "UNREAD_COUNT"
"DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
else -> oldSortingMode
}
putString(libraryPreferences.librarySortingMode().key(), newSortingMode)
putString(libraryPreferences.sortingMode().key(), newSortingMode)
}
}
if (oldVersion < 82) {
prefs.edit {
val sort = prefs.getString(libraryPreferences.librarySortingMode().key(), null) ?: return@edit
val sort = prefs.getString(libraryPreferences.sortingMode().key(), null) ?: return@edit
val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!!
putString(libraryPreferences.librarySortingMode().key(), "$sort,$direction")
putString(libraryPreferences.sortingMode().key(), "$sort,$direction")
remove("library_sorting_ascending")
}
}
@ -368,6 +369,12 @@ object Migrations {
readerPreferences.longStripSplitWebtoon().set(false)
}
}
if (oldVersion < 105) {
val pref = libraryPreferences.autoUpdateDeviceRestrictions()
if (pref.isSet() && "battery_not_low" in pref.get()) {
pref.getAndSet { it - "battery_not_low" }
}
}
return true
}

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy
@ -76,6 +77,10 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
val backupPreferences = Injekt.get<BackupPreferences>()
val interval = prefInterval ?: backupPreferences.backupInterval().get()
if (interval > 0) {
val constraints = Constraints(
requiresBatteryNotLow = true,
)
val request = PeriodicWorkRequestBuilder<BackupCreateJob>(
interval.toLong(),
TimeUnit.HOURS,
@ -84,6 +89,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10.minutes.toJavaDuration())
.addTag(TAG_AUTO)
.setConstraints(constraints)
.setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true))
.build()

View file

@ -5,7 +5,6 @@ import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.core.util.mapNotNullKeys
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.Source
import kotlinx.coroutines.CancellationException
@ -327,14 +326,16 @@ class DownloadCache(
}
}
val sourceMap = sources.associate { provider.getSourceDirName(it).lowercase() to it.id }
rootDownloadsDirLock.withLock {
val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty()
.associate { it.name to SourceDirectory(it) }
.mapNotNullKeys { entry ->
sources.find {
provider.getSourceDirName(it).equals(entry.key, ignoreCase = true)
}?.id
.filter { it.isDirectory && !it.name.isNullOrBlank() }
.mapNotNull { dir ->
val sourceId = sourceMap[dir.name!!.lowercase()]
sourceId?.let { it to SourceDirectory(dir) }
}
.toMap()
rootDownloadsDir.sourceDirs = sourceDirs
@ -342,7 +343,7 @@ class DownloadCache(
.map { sourceDir ->
async {
sourceDir.mangaDirs = sourceDir.dir.listFiles().orEmpty()
.filterNot { it.name.isNullOrBlank() }
.filter { it.isDirectory && !it.name.isNullOrBlank() }
.associate { it.name!! to MangaDirectory(it) }
sourceDir.mangaDirs.values.forEach { mangaDir ->

View file

@ -59,7 +59,6 @@ import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.failed.repository.FailedUpdatesRepository
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_BATTERY_NOT_LOW
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
@ -113,7 +112,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
override suspend fun doWork(): Result {
if (tags.contains(WORK_NAME_AUTO)) {
val preferences = Injekt.get<LibraryPreferences>()
val restrictions = preferences.libraryUpdateDeviceRestriction().get()
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
}
@ -134,7 +133,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
// If this is a chapter update, set the last update time to now
if (target == Target.CHAPTERS) {
libraryPreferences.libraryUpdateLastTimestamp().set(Date().time)
libraryPreferences.lastUpdatedTimestamp().set(Date().time)
}
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
@ -181,14 +180,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val listToUpdate = if (categoryId != -1L) {
libraryManga.filter { it.category == categoryId }
} else {
val categoriesToUpdate = libraryPreferences.libraryUpdateCategories().get().map { it.toLong() }
val categoriesToUpdate = libraryPreferences.updateCategories().get().map { it.toLong() }
val includedManga = if (categoriesToUpdate.isNotEmpty()) {
libraryManga.filter { it.category in categoriesToUpdate }
} else {
libraryManga
}
val categoriesToExclude = libraryPreferences.libraryUpdateCategoriesExclude().get().map { it.toLong() }
val categoriesToExclude = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }
val excludedMangaIds = if (categoriesToExclude.isNotEmpty()) {
libraryManga.filter { it.category in categoriesToExclude }.map { it.manga.id }
} else {
@ -532,13 +531,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
prefInterval: Int? = null,
) {
val preferences = Injekt.get<LibraryPreferences>()
val interval = prefInterval ?: preferences.libraryUpdateInterval().get()
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
if (interval > 0) {
val restrictions = preferences.libraryUpdateDeviceRestriction().get()
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
val constraints = Constraints(
requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { NetworkType.UNMETERED } else { NetworkType.CONNECTED },
requiresCharging = DEVICE_CHARGING in restrictions,
requiresBatteryNotLow = DEVICE_BATTERY_NOT_LOW in restrictions,
requiresBatteryNotLow = true,
)
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(

View file

@ -147,7 +147,7 @@ class SourcePreferencesFragment : PreferenceFragmentCompat() {
sourceScreen.forEach { pref ->
pref.isIconSpaceReserved = false
pref.isSingleLineTitle = false
if (pref is DialogPreference) {
if (pref is DialogPreference && pref.dialogTitle.isNullOrEmpty()) {
pref.dialogTitle = pref.title
}

View file

@ -1,8 +1,9 @@
package eu.kanade.tachiyomi.ui.main
package eu.kanade.tachiyomi.ui.deeplink
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import eu.kanade.tachiyomi.ui.main.MainActivity
class DeepLinkActivity : Activity() {

View file

@ -0,0 +1,59 @@
package eu.kanade.tachiyomi.ui.deeplink
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.LoadingScreen
class DeepLinkScreen(
val query: String = "",
) : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel {
DeepLinkScreenModel(query = query)
}
val state by screenModel.state.collectAsState()
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.action_search_hint),
navigateUp = navigator::pop,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
when (state) {
is DeepLinkScreenModel.State.Loading -> {
LoadingScreen(Modifier.padding(contentPadding))
}
is DeepLinkScreenModel.State.NoResults -> {
navigator.replace(GlobalSearchScreen(query))
}
is DeepLinkScreenModel.State.Result -> {
navigator.replace(
MangaScreen(
(state as DeepLinkScreenModel.State.Result).manga.id,
true,
),
)
}
}
}
}
}

View file

@ -0,0 +1,47 @@
package eu.kanade.tachiyomi.ui.deeplink
import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.tachiyomi.source.online.ResolvableSource
import kotlinx.coroutines.flow.update
import tachiyomi.core.util.lang.launchIO
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class DeepLinkScreenModel(
query: String = "",
private val sourceManager: SourceManager = Injekt.get(),
) : StateScreenModel<DeepLinkScreenModel.State>(State.Loading) {
init {
coroutineScope.launchIO {
val manga = sourceManager.getCatalogueSources()
.filterIsInstance<ResolvableSource>()
.filter { it.canResolveUri(query) }
.firstNotNullOfOrNull { it.getManga(query)?.toDomainManga(it.id) }
mutableState.update {
if (manga == null) {
State.NoResults
} else {
State.Result(manga)
}
}
}
}
sealed interface State {
@Immutable
data object Loading : State
@Immutable
data object NoResults : State
@Immutable
data class Result(val manga: Manga) : State
}
}

View file

@ -519,7 +519,7 @@ class LibraryScreenModel(
}
fun getDisplayMode(): PreferenceMutableState<LibraryDisplayMode> {
return libraryPreferences.libraryDisplayMode().asState(coroutineScope)
return libraryPreferences.displayMode().asState(coroutineScope)
}
fun getColumnsPreferenceForCurrentOrientation(isLandscape: Boolean): PreferenceMutableState<Int> {

View file

@ -148,7 +148,7 @@ object LibraryTab : Tab {
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { contentPadding ->
when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
state.searchQuery.isNullOrEmpty() && !state.hasActiveFilters && state.isLibraryEmpty -> {
val handler = LocalUriHandler.current
EmptyScreen(

View file

@ -71,6 +71,7 @@ import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import eu.kanade.tachiyomi.ui.deeplink.DeepLinkScreen
import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
@ -413,7 +414,7 @@ class MainActivity : BaseActivity() {
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
if (!query.isNullOrEmpty()) {
navigator.popUntilRoot()
navigator.push(GlobalSearchScreen(query))
navigator.push(DeepLinkScreen(query))
}
null
}

View file

@ -128,7 +128,7 @@ class MangaScreenModel(
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
private val skipFiltered by readerPreferences.skipFiltered().asState(coroutineScope)
val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.libraryUpdateMangaRestriction().get()
val isUpdateIntervalEnabled = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions().get()
private val selectedPositions: Array<Int> = arrayOf(-1, -1) // first and last selected index in list
private val selectedChapterIds: HashSet<Long> = HashSet()

View file

@ -87,14 +87,14 @@ class StatsScreenModel(
}
private fun getGlobalUpdateItemCount(libraryManga: List<LibraryManga>): Int {
val includedCategories = preferences.libraryUpdateCategories().get().map { it.toLong() }
val includedCategories = preferences.updateCategories().get().map { it.toLong() }
val includedManga = if (includedCategories.isNotEmpty()) {
libraryManga.filter { it.category in includedCategories }
} else {
libraryManga
}
val excludedCategories = preferences.libraryUpdateCategoriesExclude().get().map { it.toLong() }
val excludedCategories = preferences.updateCategoriesExclude().get().map { it.toLong() }
val excludedMangaIds = if (excludedCategories.isNotEmpty()) {
libraryManga.fastMapNotNull { manga ->
manga.id.takeIf { manga.category in excludedCategories }
@ -103,7 +103,7 @@ class StatsScreenModel(
emptyList()
}
val updateRestrictions = preferences.libraryUpdateMangaRestriction().get()
val updateRestrictions = preferences.autoUpdateMangaRestrictions().get()
return includedManga
.fastFilterNot { it.manga.id in excludedMangaIds }
.fastDistinctBy { it.manga.id }

View file

@ -66,7 +66,7 @@ class UpdatesScreenModel(
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
val events: Flow<Event> = _events.receiveAsFlow()
val lastUpdated by libraryPreferences.libraryUpdateLastTimestamp().asState(coroutineScope)
val lastUpdated by libraryPreferences.lastUpdatedTimestamp().asState(coroutineScope)
// First and last selected index in list
private val selectedPositions: Array<Int> = arrayOf(-1, -1)

View file

@ -21,10 +21,25 @@ data class GithubRelease(
@Serializable
data class GitHubAssets(@SerialName("browser_download_url") val downloadLink: String)
/**
* Regular expression that matches a mention to a valid GitHub username, like it's
* done in GitHub Flavored Markdown. It follows these constraints:
*
* - Alphanumeric with single hyphens (no consecutive hyphens)
* - Cannot begin or end with a hyphen
* - Max length of 39 characters
*
* Reference: https://stackoverflow.com/a/30281147
*/
val gitHubUsernameMentionRegex =
"""\B@([a-z0-9](?:-(?=[a-z0-9])|[a-z0-9]){0,38}(?<=[a-z0-9]))""".toRegex(RegexOption.IGNORE_CASE)
val releaseMapper: (GithubRelease) -> Release = {
Release(
it.version,
it.info,
it.info.replace(gitHubUsernameMentionRegex) { mention ->
"[${mention.value}](https://github.com/${mention.value.substring(1)})"
},
it.releaseLink,
it.assets.map(GitHubAssets::downloadLink),
)

View file

@ -14,7 +14,7 @@ class CreateCategoryWithName(
private val initialFlags: Long
get() {
val sort = preferences.librarySortingMode().get()
val sort = preferences.sortingMode().get()
return sort.type.flag or sort.direction.flag
}

View file

@ -10,7 +10,7 @@ class ResetCategoryFlags(
) {
suspend fun await() {
val sort = preferences.librarySortingMode().get()
val sort = preferences.sortingMode().get()
categoryRepository.updateAllFlags(sort.type + sort.direction)
}
}

View file

@ -8,6 +8,6 @@ class SetDisplayMode(
) {
fun await(display: LibraryDisplayMode) {
preferences.libraryDisplayMode().set(display)
preferences.displayMode().set(display)
}
}

View file

@ -23,7 +23,7 @@ class SetSortModeForCategory(
),
)
} else {
preferences.librarySortingMode().set(LibrarySort(type, direction))
preferences.sortingMode().set(LibrarySort(type, direction))
categoryRepository.updateAllFlags(flags)
}
}

View file

@ -11,24 +11,24 @@ class LibraryPreferences(
private val preferenceStore: PreferenceStore,
) {
fun libraryDisplayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
fun displayMode() = preferenceStore.getObject("pref_display_mode_library", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
fun librarySortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
fun sortingMode() = preferenceStore.getObject("library_sorting_mode", LibrarySort.default, LibrarySort.Serializer::serialize, LibrarySort.Serializer::deserialize)
fun portraitColumns() = preferenceStore.getInt("pref_library_columns_portrait_key", 0)
fun landscapeColumns() = preferenceStore.getInt("pref_library_columns_landscape_key", 0)
fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
fun lastUpdatedTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L)
fun autoUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
fun libraryUpdateDeviceRestriction() = preferenceStore.getStringSet(
fun autoUpdateDeviceRestrictions() = preferenceStore.getStringSet(
"library_update_restriction",
setOf(
DEVICE_ONLY_ON_WIFI,
),
)
fun libraryUpdateMangaRestriction() = preferenceStore.getStringSet(
fun autoUpdateMangaRestrictions() = preferenceStore.getStringSet(
"library_update_manga_restriction",
setOf(
MANGA_HAS_UNREAD,
@ -95,9 +95,9 @@ class LibraryPreferences(
fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false)
fun libraryUpdateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet())
fun updateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet())
fun libraryUpdateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
fun updateCategoriesExclude() = preferenceStore.getStringSet("library_update_categories_exclude", emptySet())
// endregion
@ -148,7 +148,6 @@ class LibraryPreferences(
const val DEVICE_ONLY_ON_WIFI = "wifi"
const val DEVICE_NETWORK_NOT_METERED = "network_not_metered"
const val DEVICE_CHARGING = "ac"
const val DEVICE_BATTERY_NOT_LOW = "battery_not_low"
const val MANGA_NON_COMPLETED = "manga_ongoing"
const val MANGA_HAS_UNREAD = "manga_fully_read"

View file

@ -1,12 +1,12 @@
[versions]
agp_version = "8.1.0"
agp_version = "8.1.1"
lifecycle_version = "2.6.1"
paging_version = "3.2.0"
[libraries]
gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" }
annotation = "androidx.annotation:annotation:1.7.0-beta01"
annotation = "androidx.annotation:annotation:1.7.0-rc01"
appcompat = "androidx.appcompat:appcompat:1.6.1"
biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05"
constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
@ -27,7 +27,7 @@ guava = "com.google.guava:guava:32.1.2-android"
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" }
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0-beta03"
benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.0-beta04"
test-ext = "androidx.test.ext:junit-ktx:1.2.0-alpha01"
test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha01"
test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0-alpha04"

View file

@ -1,7 +1,7 @@
[versions]
compiler = "1.5.1"
compiler = "1.5.2"
compose-bom = "2023.09.00-alpha02"
accompanist = "0.33.0-alpha"
accompanist = "0.33.1-alpha"
[libraries]
activity = "androidx.activity:activity-compose:1.7.2"

View file

@ -263,7 +263,6 @@
<string name="connected_to_wifi">Only on Wi-Fi</string>
<string name="network_not_metered">Only on unmetered network</string>
<string name="charging">When charging</string>
<string name="battery_not_low">When battery not low</string>
<string name="restrictions">Restrictions: %s</string>
<string name="pref_library_update_manga_restriction">Skip updating entries</string>

View file

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SManga
/**
* A source that may handle opening an SManga for a given URI.
*
* @since extensions-lib 1.5
*/
interface ResolvableSource : Source {
/**
* Whether this source may potentially handle the given URI.
*
* @since extensions-lib 1.5
*/
fun canResolveUri(uri: String): Boolean
/**
* Called if canHandleUri is true. Returns the corresponding SManga, if possible.
*
* @since extensions-lib 1.5
*/
suspend fun getManga(uri: String): SManga?
}