mirror of
https://github.com/mihonapp/mihon.git
synced 2025-10-26 03:50:40 +01:00
chore: merge upstream.
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
@@ -104,15 +103,17 @@ android {
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.addAll(listOf(
|
||||
"META-INF/DEPENDENCIES",
|
||||
"LICENSE.txt",
|
||||
"META-INF/LICENSE",
|
||||
"META-INF/LICENSE.txt",
|
||||
"META-INF/README.md",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/*.kotlin_module",
|
||||
))
|
||||
resources.excludes.addAll(
|
||||
listOf(
|
||||
"META-INF/DEPENDENCIES",
|
||||
"LICENSE.txt",
|
||||
"META-INF/LICENSE",
|
||||
"META-INF/LICENSE.txt",
|
||||
"META-INF/README.md",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/*.kotlin_module",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
@@ -267,7 +268,9 @@ androidComponents {
|
||||
beforeVariants { variantBuilder ->
|
||||
// Disables standardBenchmark
|
||||
if (variantBuilder.buildType == "benchmark") {
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(
|
||||
listOf("default" to "dev"),
|
||||
)
|
||||
}
|
||||
}
|
||||
onVariants(selector().withFlavor("default" to "standard")) {
|
||||
@@ -278,10 +281,6 @@ androidComponents {
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType<LintTask>().configureEach {
|
||||
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
|
||||
}
|
||||
|
||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||
withType<KotlinCompile> {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
@@ -306,12 +305,12 @@ tasks {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||
project.buildDir.absolutePath + "/compose_metrics"
|
||||
project.buildDir.absolutePath + "/compose_metrics",
|
||||
)
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||
project.buildDir.absolutePath + "/compose_metrics"
|
||||
project.buildDir.absolutePath + "/compose_metrics",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,20 +155,6 @@
|
||||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.UpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".data.download.DownloadService"
|
||||
android:exported="false" />
|
||||
|
||||
@@ -50,6 +50,7 @@ import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||
import tachiyomi.domain.history.interactor.RemoveHistory
|
||||
import tachiyomi.domain.history.interactor.UpsertHistory
|
||||
import tachiyomi.domain.history.repository.HistoryRepository
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||
@@ -57,7 +58,6 @@ import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
||||
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||
@@ -102,7 +102,7 @@ class DomainModule : InjektModule {
|
||||
addFactory { GetNextChapters(get(), get(), get()) }
|
||||
addFactory { ResetViewerFlags(get()) }
|
||||
addFactory { SetMangaChapterFlags(get()) }
|
||||
addFactory { SetFetchInterval(get()) }
|
||||
addFactory { FetchInterval(get()) }
|
||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||
addFactory { SetMangaViewerFlags(get()) }
|
||||
addFactory { NetworkToLocalManga(get()) }
|
||||
|
||||
@@ -3,7 +3,7 @@ package eu.kanade.domain.manga.interactor
|
||||
import eu.kanade.domain.manga.model.hasCustomCover
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
@@ -15,7 +15,7 @@ import java.util.Date
|
||||
|
||||
class UpdateManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
private val setFetchInterval: SetFetchInterval,
|
||||
private val fetchInterval: FetchInterval,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
||||
@@ -79,9 +79,9 @@ class UpdateManga(
|
||||
suspend fun awaitUpdateFetchInterval(
|
||||
manga: Manga,
|
||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
window: Pair<Long, Long> = setFetchInterval.getWindow(dateTime),
|
||||
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
||||
): Boolean {
|
||||
return setFetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
|
||||
return fetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
|
||||
?.let { mangaRepository.update(it) }
|
||||
?: false
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ fun CategoryDeleteDialog(
|
||||
TextButton(onClick = {
|
||||
onDelete()
|
||||
onDismissRequest()
|
||||
},) {
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
}
|
||||
},
|
||||
@@ -217,7 +217,7 @@ fun ChangeCategoryDialog(
|
||||
tachiyomi.presentation.core.components.material.TextButton(onClick = {
|
||||
onDismissRequest()
|
||||
onEditCategories()
|
||||
},) {
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_edit))
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
@@ -3,8 +3,6 @@ package eu.kanade.presentation.components
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import tachiyomi.presentation.core.components.ListGroupHeader
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
@@ -15,11 +13,10 @@ fun RelativeDateHeader(
|
||||
date: Date,
|
||||
dateFormat: DateFormat,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
ListGroupHeader(
|
||||
modifier = modifier,
|
||||
text = remember {
|
||||
date.toRelativeString(context, dateFormat)
|
||||
dateFormat.format(date)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ fun HistoryDeleteDialog(
|
||||
TextButton(onClick = {
|
||||
onDelete(removeEverything)
|
||||
onDismissRequest()
|
||||
},) {
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_remove))
|
||||
}
|
||||
},
|
||||
@@ -90,7 +90,7 @@ fun HistoryDeleteAllDialog(
|
||||
TextButton(onClick = {
|
||||
onDelete()
|
||||
onDismissRequest()
|
||||
},) {
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
}
|
||||
},
|
||||
|
||||
@@ -63,7 +63,6 @@ import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||
import eu.kanade.tachiyomi.ui.manga.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.service.missingChaptersCount
|
||||
@@ -740,7 +739,7 @@ private fun LazyListScope.sharedChapterItems(
|
||||
date = chapterItem.chapter.dateUpload
|
||||
.takeIf { it > 0L }
|
||||
?.let {
|
||||
Date(it).toRelativeString(context, dateFormat)
|
||||
dateFormat.format(Date(it))
|
||||
},
|
||||
readProgress = chapterItem.chapter.lastPageRead
|
||||
.takeIf { !chapterItem.chapter.read && it > 0L }
|
||||
|
||||
@@ -143,7 +143,7 @@ fun MangaBottomActionMenu(
|
||||
if (onMarkPreviousAsReadClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_mark_previous_as_read),
|
||||
icon = ImageVector.vectorResource(id = R.drawable.ic_done_prev_24dp),
|
||||
icon = ImageVector.vectorResource(R.drawable.ic_done_prev_24dp),
|
||||
toConfirm = confirm[4],
|
||||
onLongClick = { onLongClickItem(4) },
|
||||
onClick = onMarkPreviousAsReadClicked,
|
||||
|
||||
@@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.domain.manga.interactor.MAX_FETCH_INTERVAL
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||
|
||||
@Composable
|
||||
@@ -67,7 +67,7 @@ fun SetIntervalDialog(
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val size = DpSize(width = maxWidth / 2, height = 128.dp)
|
||||
val items = (0..MAX_FETCH_INTERVAL).map {
|
||||
val items = (0..FetchInterval.MAX_INTERVAL).map {
|
||||
if (it == 0) {
|
||||
stringResource(R.string.label_default)
|
||||
} else {
|
||||
@@ -91,7 +91,7 @@ fun SetIntervalDialog(
|
||||
TextButton(onClick = {
|
||||
onValueChanged(selectedInterval)
|
||||
onDismissRequest()
|
||||
},) {
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
}
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@ fun MoreScreen(
|
||||
WarningBanner(
|
||||
textRes = R.string.fdroid_warning,
|
||||
modifier = Modifier.clickable {
|
||||
uriHandler.openUri("https://tachiyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version")
|
||||
uriHandler.openUri("https://tachiyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds")
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -108,11 +108,11 @@ fun MoreScreen(
|
||||
stringResource(R.string.paused)
|
||||
} else {
|
||||
"${stringResource(R.string.paused)} • ${
|
||||
pluralStringResource(
|
||||
id = R.plurals.download_queue_summary,
|
||||
count = pending,
|
||||
pending,
|
||||
)
|
||||
pluralStringResource(
|
||||
id = R.plurals.download_queue_summary,
|
||||
count = pending,
|
||||
pending,
|
||||
)
|
||||
}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.more.settings.Preference.PreferenceItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import tachiyomi.core.preference.Preference as PreferenceData
|
||||
@@ -211,7 +211,7 @@ object SettingsBackupAndSyncScreen : SearchableSettings {
|
||||
showCreateDialog = false
|
||||
flag = it
|
||||
try {
|
||||
chooseBackupDir.launch(Backup.getBackupFilename())
|
||||
chooseBackupDir.launch(Backup.getFilename())
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
flag = 0
|
||||
context.toast(R.string.file_picker_error)
|
||||
|
||||
@@ -70,7 +70,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
@Composable
|
||||
override fun RowScope.AppBarAction() {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/help/guides/tracking/") }) {
|
||||
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.HelpOutline,
|
||||
contentDescription = stringResource(R.string.tracking_guide),
|
||||
|
||||
@@ -149,7 +149,7 @@ object AboutScreen : Screen() {
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.help_translate),
|
||||
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/help/contribution/#translation") },
|
||||
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/docs/contribute#translation") },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ object AboutScreen : Screen() {
|
||||
item {
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.privacy_policy),
|
||||
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/privacy") },
|
||||
onPreferenceClick = { uriHandler.openUri("https://tachiyomi.org/privacy/") },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -41,9 +41,9 @@ class OpenSourceLicensesScreen : Screen() {
|
||||
),
|
||||
onLibraryClick = {
|
||||
val libraryLicenseScreen = OpenSourceLibraryLicenseScreen(
|
||||
name = it.name,
|
||||
website = it.website,
|
||||
license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
|
||||
name = it.library.name,
|
||||
website = it.library.website,
|
||||
license = it.library.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
|
||||
)
|
||||
navigator.push(libraryLicenseScreen)
|
||||
},
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
package eu.kanade.presentation.reader
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.manga.model.orientationType
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||
import tachiyomi.presentation.core.components.material.IconToggleButton
|
||||
|
||||
private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it }
|
||||
|
||||
@@ -32,22 +32,20 @@ fun OrientationModeSelectDialog(
|
||||
val manga by screenModel.mangaFlow.collectAsState()
|
||||
val orientationType = remember(manga) { OrientationType.fromPreference(manga?.orientationType?.toInt()) }
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
) {
|
||||
SettingsChipRow(R.string.rotation_type) {
|
||||
orientationTypeOptions.map { (stringRes, it) ->
|
||||
FilterChip(
|
||||
selected = it == orientationType,
|
||||
onClick = {
|
||||
screenModel.onChangeOrientation(it)
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
Box(modifier = Modifier.padding(vertical = 16.dp)) {
|
||||
SettingsIconGrid(R.string.rotation_type) {
|
||||
items(orientationTypeOptions) { (stringRes, mode) ->
|
||||
IconToggleButton(
|
||||
checked = mode == orientationType,
|
||||
onCheckedChange = {
|
||||
screenModel.onChangeOrientation(mode)
|
||||
onChange(stringRes)
|
||||
onDismissRequest()
|
||||
},
|
||||
label = { Text(stringResource(stringRes)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
||||
title = stringResource(stringRes),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
package eu.kanade.presentation.reader
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import eu.kanade.domain.manga.model.readingModeType
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||
import tachiyomi.presentation.core.components.material.IconToggleButton
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it }
|
||||
@@ -32,22 +33,20 @@ fun ReadingModeSelectDialog(
|
||||
val manga by screenModel.mangaFlow.collectAsState()
|
||||
val readingMode = remember(manga) { ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) }
|
||||
|
||||
AdaptiveSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
) {
|
||||
SettingsChipRow(R.string.pref_category_reading_mode) {
|
||||
readingModeOptions.map { (stringRes, it) ->
|
||||
FilterChip(
|
||||
selected = it == readingMode,
|
||||
onClick = {
|
||||
screenModel.onChangeReadingMode(it)
|
||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||
Box(modifier = Modifier.padding(vertical = MaterialTheme.padding.medium)) {
|
||||
SettingsIconGrid(R.string.pref_category_reading_mode) {
|
||||
items(readingModeOptions) { (stringRes, mode) ->
|
||||
IconToggleButton(
|
||||
checked = mode == readingMode,
|
||||
onCheckedChange = {
|
||||
screenModel.onChangeReadingMode(mode)
|
||||
onChange(stringRes)
|
||||
onDismissRequest()
|
||||
},
|
||||
label = { Text(stringResource(stringRes)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
imageVector = ImageVector.vectorResource(mode.iconRes),
|
||||
title = stringResource(stringRes),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ private fun SearchResultItem(
|
||||
val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp)
|
||||
.clip(shape)
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
|
||||
@@ -21,7 +21,7 @@ fun UpdatesDeleteConfirmationDialog(
|
||||
TextButton(onClick = {
|
||||
onConfirm()
|
||||
onDismissRequest()
|
||||
},) {
|
||||
}) {
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
}
|
||||
},
|
||||
|
||||
@@ -175,7 +175,7 @@ fun WebViewScreenContent(
|
||||
WarningBanner(
|
||||
textRes = R.string.information_cloudflare_help,
|
||||
modifier = Modifier.clickable {
|
||||
uriHandler.openUri("https://tachiyomi.org/help/guides/troubleshooting/#solving-cloudflare-issues")
|
||||
uriHandler.openUri("https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -93,15 +93,14 @@ class BackupManager(
|
||||
|
||||
// Delete older backups
|
||||
val numberOfBackups = backupPreferences.numberOfBackups().get()
|
||||
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.proto.gz""")
|
||||
dir.listFiles { _, filename -> backupRegex.matches(filename) }
|
||||
dir.listFiles { _, filename -> Backup.filenameRegex.matches(filename) }
|
||||
.orEmpty()
|
||||
.sortedByDescending { it.name }
|
||||
.drop(numberOfBackups - 1)
|
||||
.forEach { it.delete() }
|
||||
|
||||
// Create new file to place backup
|
||||
dir.createFile(Backup.getBackupFilename())
|
||||
dir.createFile(Backup.getFilename())
|
||||
} else {
|
||||
UniFile.fromUri(context, uri)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.isActive
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.track.model.Track
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@@ -31,10 +31,10 @@ class BackupRestorer(
|
||||
) {
|
||||
private val updateManga: UpdateManga = Injekt.get()
|
||||
private val chapterRepository: ChapterRepository = Injekt.get()
|
||||
private val setFetchInterval: SetFetchInterval = Injekt.get()
|
||||
private val fetchInterval: FetchInterval = Injekt.get()
|
||||
|
||||
private var now = ZonedDateTime.now()
|
||||
private var currentFetchWindow = setFetchInterval.getWindow(now)
|
||||
private var currentFetchWindow = fetchInterval.getWindow(now)
|
||||
|
||||
private var backupManager = BackupManager(context)
|
||||
|
||||
@@ -103,7 +103,7 @@ class BackupRestorer(
|
||||
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
||||
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
||||
now = ZonedDateTime.now()
|
||||
currentFetchWindow = setFetchInterval.getWindow(now)
|
||||
currentFetchWindow = fetchInterval.getWindow(now)
|
||||
|
||||
return coroutineScope {
|
||||
// Restore individual manga
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.backup.models
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -16,9 +17,11 @@ data class Backup(
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun getBackupFilename(): String {
|
||||
val filenameRegex = """${BuildConfig.APPLICATION_ID}_\d+-\d+-\d+_\d+-\d+.tachibk""".toRegex()
|
||||
|
||||
fun getFilename(): String {
|
||||
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
|
||||
return "tachiyomi_$date.proto.gz"
|
||||
return "${BuildConfig.APPLICATION_ID}_$date.tachibk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import logcat.LogPriority
|
||||
import okhttp3.Response
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
@@ -97,6 +99,7 @@ class ChapterCache(private val context: Context) {
|
||||
editor.commit()
|
||||
editor.abortUnlessCommitted()
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.WARN, e) { "Failed to put page list to cache" }
|
||||
// Ignore.
|
||||
} finally {
|
||||
editor?.abortUnlessCommitted()
|
||||
@@ -174,7 +177,7 @@ class ChapterCache(private val context: Context) {
|
||||
* @return status of deletion for the file.
|
||||
*/
|
||||
private fun removeFileFromCache(file: String): Boolean {
|
||||
// Make sure we don't delete the journal file (keeps track of cache).
|
||||
// Make sure we don't delete the journal file (keeps track of cache)
|
||||
if (file == "journal" || file.startsWith("journal.")) {
|
||||
return false
|
||||
}
|
||||
@@ -182,9 +185,10 @@ class ChapterCache(private val context: Context) {
|
||||
return try {
|
||||
// Remove the extension from the file to get the key of the cache
|
||||
val key = file.substringBeforeLast(".")
|
||||
// Remove file from cache.
|
||||
// Remove file from cache
|
||||
diskCache.remove(key)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.WARN, e) { "Failed to remove file from cache" }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ import nl.adaptivity.xmlutil.serialization.XML
|
||||
import okhttp3.Response
|
||||
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.util.lang.awaitSingle
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchNow
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
@@ -363,7 +362,7 @@ class Downloader(
|
||||
if (page.imageUrl.isNullOrEmpty()) {
|
||||
page.status = Page.State.LOAD_PAGE
|
||||
try {
|
||||
page.imageUrl = download.source.fetchImageUrl(page).awaitSingle()
|
||||
page.imageUrl = download.source.getImageUrl(page)
|
||||
} catch (e: Throwable) {
|
||||
page.status = Page.State.ERROR
|
||||
}
|
||||
|
||||
@@ -59,9 +59,9 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U
|
||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||
import tachiyomi.domain.source.model.SourceNotInstalledException
|
||||
@@ -90,7 +90,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
private val getCategories: GetCategories = Injekt.get()
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
||||
private val refreshTracks: RefreshTracks = Injekt.get()
|
||||
private val setFetchInterval: SetFetchInterval = Injekt.get()
|
||||
private val fetchInterval: FetchInterval = Injekt.get()
|
||||
|
||||
private val notifier = LibraryUpdateNotifier(context)
|
||||
|
||||
@@ -216,7 +216,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
||||
val hasDownloads = AtomicBoolean(false)
|
||||
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
|
||||
val fetchWindow = setFetchInterval.getWindow(ZonedDateTime.now())
|
||||
val fetchWindow = fetchInterval.getWindow(ZonedDateTime.now())
|
||||
|
||||
coroutineScope {
|
||||
mangaToUpdate.groupBy { it.manga.source }.values
|
||||
@@ -497,7 +497,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
private const val WORK_NAME_AUTO = "LibraryUpdate-auto"
|
||||
private const val WORK_NAME_MANUAL = "LibraryUpdate-manual"
|
||||
|
||||
private const val ERROR_LOG_HELP_URL = "https://tachiyomi.org/help/guides/troubleshooting"
|
||||
private const val ERROR_LOG_HELP_URL = "https://tachiyomi.org/docs/guides/troubleshooting/"
|
||||
|
||||
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
|
||||
|
||||
|
||||
@@ -329,11 +329,11 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val HELP_WARNING_URL = "https://tachiyomi.org/help/faq/#why-does-the-app-warn-about-large-bulk-updates-and-downloads"
|
||||
const val HELP_WARNING_URL = "https://tachiyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads"
|
||||
}
|
||||
}
|
||||
|
||||
private const val NOTIF_MAX_CHAPTERS = 5
|
||||
private const val NOTIF_TITLE_MAX_LEN = 45
|
||||
private const val NOTIF_ICON_SIZE = 192
|
||||
private const val HELP_SKIPPED_URL = "https://tachiyomi.org/help/faq/#why-does-global-update-skip-some-entries"
|
||||
private const val HELP_SKIPPED_URL = "https://tachiyomi.org/docs/faq/library#why-is-global-update-skipping-entries"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.data.saver
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
@@ -28,30 +27,59 @@ class ImageSaver(
|
||||
val context: Context,
|
||||
) {
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun save(image: Image): Uri {
|
||||
val data = image.data
|
||||
|
||||
val type = ImageUtil.findImageType(data) ?: throw Exception("Not an image")
|
||||
val type = ImageUtil.findImageType(data) ?: throw IllegalArgumentException("Not an image")
|
||||
val filename = DiskUtil.buildValidFilename("${image.name}.${type.extension}")
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || image.location !is Location.Pictures) {
|
||||
return save(data(), image.location.directory(context), filename)
|
||||
}
|
||||
|
||||
return saveApi29(image, type, filename, data)
|
||||
}
|
||||
|
||||
private fun save(inputStream: InputStream, directory: File, filename: String): Uri {
|
||||
directory.mkdirs()
|
||||
|
||||
val destFile = File(directory, filename)
|
||||
|
||||
inputStream.use { input ->
|
||||
destFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
DiskUtil.scanMedia(context, destFile.toUri())
|
||||
|
||||
return destFile.getUriCompat(context)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun saveApi29(
|
||||
image: Image,
|
||||
type: ImageUtil.ImageType,
|
||||
filename: String,
|
||||
data: () -> InputStream,
|
||||
): Uri {
|
||||
val pictureDir =
|
||||
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
|
||||
val folderRelativePath = "${Environment.DIRECTORY_PICTURES}/${context.getString(R.string.app_name)}/"
|
||||
val imageLocation = (image.location as Location.Pictures).relativePath
|
||||
val relativePath = listOf(
|
||||
Environment.DIRECTORY_PICTURES,
|
||||
context.getString(R.string.app_name),
|
||||
imageLocation,
|
||||
).joinToString(File.separator)
|
||||
|
||||
val contentValues = contentValuesOf(
|
||||
MediaStore.Images.Media.RELATIVE_PATH to relativePath,
|
||||
MediaStore.Images.Media.DISPLAY_NAME to image.name,
|
||||
MediaStore.Images.Media.MIME_TYPE to type.mime,
|
||||
MediaStore.Images.Media.RELATIVE_PATH to folderRelativePath + imageLocation,
|
||||
)
|
||||
|
||||
val picture = findUriOrDefault(folderRelativePath, "$imageLocation$filename") {
|
||||
val picture = findUriOrDefault(relativePath, filename) {
|
||||
context.contentResolver.insert(
|
||||
pictureDir,
|
||||
contentValues,
|
||||
@@ -74,49 +102,34 @@ class ImageSaver(
|
||||
return picture
|
||||
}
|
||||
|
||||
private fun save(inputStream: InputStream, directory: File, filename: String): Uri {
|
||||
directory.mkdirs()
|
||||
|
||||
val destFile = File(directory, filename)
|
||||
|
||||
inputStream.use { input ->
|
||||
destFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
DiskUtil.scanMedia(context, destFile.toUri())
|
||||
|
||||
return destFile.getUriCompat(context)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun findUriOrDefault(relativePath: String, imagePath: String, default: () -> Uri): Uri {
|
||||
private fun findUriOrDefault(path: String, filename: String, default: () -> Uri): Uri {
|
||||
val projection = arrayOf(
|
||||
MediaStore.MediaColumns._ID,
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.Images.Media.MIME_TYPE,
|
||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||
)
|
||||
|
||||
val selection = "${MediaStore.MediaColumns.RELATIVE_PATH}=? AND ${MediaStore.MediaColumns.DISPLAY_NAME}=?"
|
||||
|
||||
// Need to make sure it ends with the separator
|
||||
val normalizedPath = "${path.removeSuffix(File.separator)}${File.separator}"
|
||||
|
||||
context.contentResolver.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
projection,
|
||||
selection,
|
||||
arrayOf(relativePath, imagePath),
|
||||
arrayOf(normalizedPath, filename),
|
||||
null,
|
||||
).use { cursor ->
|
||||
if (cursor != null && cursor.count >= 1) {
|
||||
cursor.moveToFirst().let {
|
||||
if (cursor.moveToFirst()) {
|
||||
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
|
||||
|
||||
return ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class Kavita(private val context: Context, id: Long) : TrackService(id, "Kavita"
|
||||
.reduce(Long::or) and Long.MAX_VALUE
|
||||
}
|
||||
val preferences: SharedPreferences by lazy {
|
||||
context.getSharedPreferences("source_$sourceSuffixID", 0x0000)
|
||||
context.getSharedPreferences("source_$sourceSuffixID", Context.MODE_PRIVATE)
|
||||
}
|
||||
val prefApiUrl = preferences.getString("APIURL", "")!!
|
||||
if (prefApiUrl.isEmpty()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.track.suwayomi
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
@@ -107,7 +108,7 @@ class TachideskApi {
|
||||
}
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$tachideskExtensionId", 0x0000)
|
||||
Injekt.get<Application>().getSharedPreferences("source_$tachideskExtensionId", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
private fun getPrefBaseUrl(): String = preferences.getString(ADDRESS_TITLE, ADDRESS_DEFAULT)!!
|
||||
|
||||
@@ -143,7 +143,7 @@ internal class AppUpdateNotifier(private val context: Context) {
|
||||
setContentTitle(context.getString(R.string.update_check_notification_update_available))
|
||||
setContentText(context.getString(R.string.update_check_fdroid_migration_info))
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
setContentIntent(NotificationHandler.openUrl(context, "https://tachiyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version"))
|
||||
setContentIntent(NotificationHandler.openUrl(context, "https://tachiyomi.org/docs/faq/general#how-do-i-update-from-the-f-droid-builds"))
|
||||
}
|
||||
notificationBuilder.show(Notifications.ID_APP_UPDATE_PROMPT)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class ExtensionDetailsScreenModel(
|
||||
val extension = state.value.extension ?: return ""
|
||||
|
||||
if (!extension.hasReadme) {
|
||||
return "https://tachiyomi.org/help/faq/#extensions"
|
||||
return "https://tachiyomi.org/docs/faq/browse/extensions"
|
||||
}
|
||||
|
||||
val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
|
||||
|
||||
@@ -31,7 +31,7 @@ fun Screen.migrateSourceTab(): TabContent {
|
||||
title = stringResource(R.string.migration_help_guide),
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = {
|
||||
uriHandler.openUri("https://tachiyomi.org/help/guides/source-migration/")
|
||||
uriHandler.openUri("https://tachiyomi.org/docs/guides/source-migration")
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.filter
|
||||
import androidx.paging.map
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
@@ -30,7 +31,6 @@ import eu.kanade.tachiyomi.util.removeCovers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -113,25 +113,20 @@ class BrowseSourceScreenModel(
|
||||
/**
|
||||
* Flow of Pager flow tied to [State.listing]
|
||||
*/
|
||||
private val hideInLibraryItems = sourcePreferences.hideInLibraryItems().get()
|
||||
val mangaPagerFlowFlow = state.map { it.listing }
|
||||
.distinctUntilChanged()
|
||||
.map { listing ->
|
||||
Pager(
|
||||
PagingConfig(pageSize = 25),
|
||||
) {
|
||||
Pager(PagingConfig(pageSize = 25)) {
|
||||
getRemoteManga.subscribe(sourceId, listing.query ?: "", listing.filters)
|
||||
}.flow.map { pagingData ->
|
||||
pagingData.map {
|
||||
networkToLocalManga.await(it.toDomainManga(sourceId))
|
||||
.let { localManga ->
|
||||
getManga.subscribe(localManga.url, localManga.source)
|
||||
}
|
||||
.let { localManga -> getManga.subscribe(localManga.url, localManga.source) }
|
||||
.filterNotNull()
|
||||
.filter { localManga ->
|
||||
!sourcePreferences.hideInLibraryItems().get() || !localManga.favorite
|
||||
}
|
||||
.stateIn(ioCoroutineScope)
|
||||
}
|
||||
.filter { !hideInLibraryItems || !it.value.favorite }
|
||||
}
|
||||
.cachedIn(ioCoroutineScope)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ fun SourceFilterDialog(
|
||||
Button(onClick = {
|
||||
onFilter()
|
||||
onDismissRequest()
|
||||
},) {
|
||||
}) {
|
||||
Text(stringResource(R.string.action_filter))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import tachiyomi.core.util.lang.awaitSingle
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
@@ -140,7 +139,7 @@ abstract class SearchScreenModel(
|
||||
|
||||
try {
|
||||
val page = withContext(coroutineDispatcher) {
|
||||
source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle()
|
||||
source.getSearchManga(1, query, source.getFilterList())
|
||||
}
|
||||
|
||||
val titles = page.mangas.map {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package eu.kanade.tachiyomi.ui.download
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
@@ -42,7 +42,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
@@ -243,6 +242,7 @@ object DownloadQueueScreen : Screen() {
|
||||
)
|
||||
return@Scaffold
|
||||
}
|
||||
|
||||
val density = LocalDensity.current
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() }
|
||||
@@ -252,13 +252,13 @@ object DownloadQueueScreen : Screen() {
|
||||
|
||||
Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) {
|
||||
AndroidView(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
factory = { context ->
|
||||
screenModel.controllerBinding = DownloadListBinding.inflate(LayoutInflater.from(context))
|
||||
screenModel.adapter = DownloadAdapter(screenModel.listener)
|
||||
screenModel.controllerBinding.recycler.adapter = screenModel.adapter
|
||||
screenModel.controllerBinding.root.adapter = screenModel.adapter
|
||||
screenModel.adapter?.isHandleDragEnabled = true
|
||||
screenModel.adapter?.fastScroller = screenModel.controllerBinding.fastScroller
|
||||
screenModel.controllerBinding.recycler.layoutManager = LinearLayoutManager(context)
|
||||
screenModel.controllerBinding.root.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
ViewCompat.setNestedScrollingEnabled(screenModel.controllerBinding.root, true)
|
||||
|
||||
@@ -274,7 +274,7 @@ object DownloadQueueScreen : Screen() {
|
||||
screenModel.controllerBinding.root
|
||||
},
|
||||
update = {
|
||||
screenModel.controllerBinding.recycler
|
||||
screenModel.controllerBinding.root
|
||||
.updatePadding(
|
||||
left = left,
|
||||
top = top,
|
||||
@@ -282,14 +282,6 @@ object DownloadQueueScreen : Screen() {
|
||||
bottom = bottom,
|
||||
)
|
||||
|
||||
screenModel.controllerBinding.fastScroller
|
||||
.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
leftMargin = left
|
||||
topMargin = top
|
||||
rightMargin = right
|
||||
bottomMargin = bottom
|
||||
}
|
||||
|
||||
screenModel.adapter?.updateDataSet(downloadList)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -84,13 +84,17 @@ class DownloadQueueScreenModel(
|
||||
}
|
||||
reorder(newDownloads)
|
||||
}
|
||||
R.id.move_to_top_series -> {
|
||||
R.id.move_to_top_series, R.id.move_to_bottom_series -> {
|
||||
val (selectedSeries, otherSeries) = adapter?.currentItems
|
||||
?.filterIsInstance<DownloadItem>()
|
||||
?.map(DownloadItem::download)
|
||||
?.partition { item.download.manga.id == it.manga.id }
|
||||
?: Pair(emptyList(), emptyList())
|
||||
reorder(selectedSeries + otherSeries)
|
||||
if (menuItem.itemId == R.id.move_to_top_series) {
|
||||
reorder(selectedSeries + otherSeries)
|
||||
} else {
|
||||
reorder(otherSeries + selectedSeries)
|
||||
}
|
||||
}
|
||||
R.id.cancel_download -> {
|
||||
cancel(listOf(item.download))
|
||||
@@ -258,6 +262,6 @@ class DownloadQueueScreenModel(
|
||||
* @return the holder of the download or null if it's not bound.
|
||||
*/
|
||||
private fun getHolder(download: Download): DownloadHolder? {
|
||||
return controllerBinding.recycler.findViewHolderForItemId(download.chapter.id) as? DownloadHolder
|
||||
return controllerBinding.root.findViewHolderForItemId(download.chapter.id) as? DownloadHolder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ object LibraryTab : Tab {
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.getting_started_guide,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
|
||||
onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -271,7 +271,10 @@ private data class TrackStatusSelectorScreen(
|
||||
selection = state.selection,
|
||||
onSelectionChange = sm::setSelection,
|
||||
selections = remember { sm.getSelections() },
|
||||
onConfirm = { sm.setStatus(); navigator.pop() },
|
||||
onConfirm = {
|
||||
sm.setStatus()
|
||||
navigator.pop()
|
||||
},
|
||||
onDismissRequest = navigator::pop,
|
||||
)
|
||||
}
|
||||
@@ -322,7 +325,10 @@ private data class TrackChapterSelectorScreen(
|
||||
selection = state.selection,
|
||||
onSelectionChange = sm::setSelection,
|
||||
range = remember { sm.getRange() },
|
||||
onConfirm = { sm.setChapter(); navigator.pop() },
|
||||
onConfirm = {
|
||||
sm.setChapter()
|
||||
navigator.pop()
|
||||
},
|
||||
onDismissRequest = navigator::pop,
|
||||
)
|
||||
}
|
||||
@@ -378,7 +384,10 @@ private data class TrackScoreSelectorScreen(
|
||||
selection = state.selection,
|
||||
onSelectionChange = sm::setSelection,
|
||||
selections = remember { sm.getSelections() },
|
||||
onConfirm = { sm.setScore(); navigator.pop() },
|
||||
onConfirm = {
|
||||
sm.setScore()
|
||||
navigator.pop()
|
||||
},
|
||||
onDismissRequest = navigator::pop,
|
||||
)
|
||||
}
|
||||
@@ -495,7 +504,10 @@ private data class TrackDateSelectorScreen(
|
||||
},
|
||||
initialSelectedDateMillis = sm.initialSelection,
|
||||
selectableDates = selectableDates,
|
||||
onConfirm = { sm.setDate(it); navigator.pop() },
|
||||
onConfirm = {
|
||||
sm.setDate(it)
|
||||
navigator.pop()
|
||||
},
|
||||
onRemove = { sm.confirmRemoveDate(navigator) }.takeIf { canRemove },
|
||||
onDismissRequest = navigator::pop,
|
||||
)
|
||||
@@ -584,7 +596,10 @@ private data class TrackDateRemoverScreen(
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
FilledTonalButton(
|
||||
onClick = { sm.removeDate(); navigator.popUntil { it is TrackInfoDialogHomeScreen } },
|
||||
onClick = {
|
||||
sm.removeDate()
|
||||
navigator.popUntil { it is TrackInfoDialogHomeScreen }
|
||||
},
|
||||
colors = ButtonDefaults.filledTonalButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onErrorContainer,
|
||||
@@ -646,7 +661,10 @@ data class TrackServiceSearchScreen(
|
||||
queryResult = state.queryResult,
|
||||
selected = state.selected,
|
||||
onSelectedChange = sm::updateSelection,
|
||||
onConfirmSelection = { sm.registerTracking(state.selected!!); navigator.pop() },
|
||||
onConfirmSelection = {
|
||||
sm.registerTracking(state.selected!!)
|
||||
navigator.pop()
|
||||
},
|
||||
onDismissRequest = navigator::pop,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import tachiyomi.core.util.lang.awaitSingle
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@@ -170,7 +169,7 @@ internal class HttpPageLoader(
|
||||
try {
|
||||
if (page.imageUrl.isNullOrEmpty()) {
|
||||
page.status = Page.State.LOAD_PAGE
|
||||
page.imageUrl = source.fetchImageUrl(page).awaitSingle()
|
||||
page.imageUrl = source.getImageUrl(page)
|
||||
}
|
||||
val imageUrl = page.imageUrl!!
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class UnlockActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
startAuthentication(
|
||||
getString(R.string.unlock_app),
|
||||
getString(R.string.unlock_app_title, getString(R.string.app_name)),
|
||||
confirmationRequired = false,
|
||||
callback = object : AuthenticatorUtil.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(
|
||||
|
||||
@@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
@@ -384,7 +383,7 @@ class UpdatesScreenModel(
|
||||
val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0)
|
||||
when {
|
||||
beforeDate.time != afterDate.time && afterDate.time != 0L -> {
|
||||
val text = afterDate.toRelativeString(context, dateFormat)
|
||||
val text = dateFormat.format(afterDate)
|
||||
UpdatesUiModel.Header(text)
|
||||
}
|
||||
// Return null to avoid adding a separator between two items.
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.os.Build
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
@@ -35,6 +36,7 @@ class CrashLogUtil(private val context: Context) {
|
||||
Device name: ${Build.DEVICE}
|
||||
Device model: ${Build.MODEL}
|
||||
Device product name: ${Build.PRODUCT}
|
||||
WebView user agent: ${WebViewUtil.getInferredUserAgent(context)}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package eu.kanade.tachiyomi.util.lang
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.R
|
||||
import java.text.DateFormat
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
|
||||
fun Date.toDateTimestampString(dateFormatter: DateFormat): String {
|
||||
val date = dateFormatter.format(this)
|
||||
@@ -45,101 +42,3 @@ fun Long.toDateKey(): Date {
|
||||
cal[Calendar.MILLISECOND] = 0
|
||||
return cal.time
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert epoch long to Calendar instance
|
||||
*
|
||||
* @return Calendar instance at supplied epoch time. Null if epoch was 0.
|
||||
*/
|
||||
fun Long.toCalendar(): Calendar? {
|
||||
if (this == 0L) {
|
||||
return null
|
||||
}
|
||||
val cal = Calendar.getInstance()
|
||||
cal.timeInMillis = this
|
||||
return cal
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert local time millisecond value to Calendar instance in UTC
|
||||
*
|
||||
* @return UTC Calendar instance at supplied time. Null if time is 0.
|
||||
*/
|
||||
fun Long.toUtcCalendar(): Calendar? {
|
||||
if (this == 0L) {
|
||||
return null
|
||||
}
|
||||
val rawCalendar = Calendar.getInstance().apply {
|
||||
timeInMillis = this@toUtcCalendar
|
||||
}
|
||||
return Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
|
||||
clear()
|
||||
set(
|
||||
rawCalendar.get(Calendar.YEAR),
|
||||
rawCalendar.get(Calendar.MONTH),
|
||||
rawCalendar.get(Calendar.DAY_OF_MONTH),
|
||||
rawCalendar.get(Calendar.HOUR_OF_DAY),
|
||||
rawCalendar.get(Calendar.MINUTE),
|
||||
rawCalendar.get(Calendar.SECOND),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert UTC time millisecond to Calendar instance in local time zone
|
||||
*
|
||||
* @return local Calendar instance at supplied UTC time. Null if time is 0.
|
||||
*/
|
||||
fun Long.toLocalCalendar(): Calendar? {
|
||||
if (this == 0L) {
|
||||
return null
|
||||
}
|
||||
val rawCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
|
||||
timeInMillis = this@toLocalCalendar
|
||||
}
|
||||
return Calendar.getInstance().apply {
|
||||
clear()
|
||||
set(
|
||||
rawCalendar.get(Calendar.YEAR),
|
||||
rawCalendar.get(Calendar.MONTH),
|
||||
rawCalendar.get(Calendar.DAY_OF_MONTH),
|
||||
rawCalendar.get(Calendar.HOUR_OF_DAY),
|
||||
rawCalendar.get(Calendar.MINUTE),
|
||||
rawCalendar.get(Calendar.SECOND),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val MILLISECONDS_IN_DAY = 86_400_000L
|
||||
|
||||
fun Date.toRelativeString(
|
||||
context: Context,
|
||||
dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT),
|
||||
): String {
|
||||
val now = Date()
|
||||
val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY)
|
||||
val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt()
|
||||
return when {
|
||||
difference < 0 -> dateFormat.format(this)
|
||||
difference < MILLISECONDS_IN_DAY -> context.getString(R.string.relative_time_today)
|
||||
difference < MILLISECONDS_IN_DAY.times(7) -> context.resources.getQuantityString(
|
||||
R.plurals.relative_time,
|
||||
days,
|
||||
days,
|
||||
)
|
||||
else -> dateFormat.format(this)
|
||||
}
|
||||
}
|
||||
|
||||
private val Date.timeWithOffset: Long
|
||||
get() {
|
||||
return Calendar.getInstance().run {
|
||||
time = this@timeWithOffset
|
||||
val dstOffset = get(Calendar.DST_OFFSET)
|
||||
this@timeWithOffset.time + timeZone.rawOffset + dstOffset
|
||||
}
|
||||
}
|
||||
|
||||
fun Long.floorNearest(to: Long): Long {
|
||||
return this.floorDiv(to) * to
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ package eu.kanade.tachiyomi.util.system
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.domain.ui.model.TabletUiMode
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@@ -64,18 +62,6 @@ fun Context.isNightMode(): Boolean {
|
||||
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
|
||||
val Resources.isLTR
|
||||
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
|
||||
|
||||
/**
|
||||
* Converts to px and takes into account LTR/RTL layout.
|
||||
*/
|
||||
val Float.dpToPxEnd: Float
|
||||
get() = (
|
||||
this * Resources.getSystem().displayMetrics.density *
|
||||
if (Resources.getSystem().isLTR) 1 else -1
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.).
|
||||
*
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package eu.kanade.tachiyomi.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import androidx.core.view.ViewCompat
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.davidea.fastscroller.FastScroller
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.dpToPxEnd
|
||||
import eu.kanade.tachiyomi.util.system.isLTR
|
||||
|
||||
class MaterialFastScroll @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
FastScroller(context, attrs) {
|
||||
|
||||
init {
|
||||
setViewsToUse(
|
||||
R.layout.material_fastscroll,
|
||||
R.id.fast_scroller_bubble,
|
||||
R.id.fast_scroller_handle,
|
||||
)
|
||||
autoHideEnabled = true
|
||||
ignoreTouchesOutsideHandle = true
|
||||
|
||||
applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
margin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overridden to handle RTL
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (recyclerView.computeVerticalScrollRange() <= recyclerView.computeVerticalScrollExtent()) {
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
// start: handle RTL differently
|
||||
if (
|
||||
if (context.resources.isLTR) {
|
||||
event.x < handle.x - ViewCompat.getPaddingStart(handle)
|
||||
} else {
|
||||
event.x > handle.width + ViewCompat.getPaddingStart(handle)
|
||||
}
|
||||
) {
|
||||
return false
|
||||
}
|
||||
// end
|
||||
|
||||
if (ignoreTouchesOutsideHandle &&
|
||||
(event.y < handle.y || event.y > handle.y + handle.height)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
handle.isSelected = true
|
||||
notifyScrollStateChange(true)
|
||||
showBubble()
|
||||
showScrollbar()
|
||||
val y = event.y
|
||||
setBubbleAndHandlePosition(y)
|
||||
setRecyclerViewPosition(y)
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val y = event.y
|
||||
setBubbleAndHandlePosition(y)
|
||||
setRecyclerViewPosition(y)
|
||||
return true
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
handle.isSelected = false
|
||||
notifyScrollStateChange(false)
|
||||
hideBubble()
|
||||
if (autoHideEnabled) hideScrollbar()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun setBubbleAndHandlePosition(y: Float) {
|
||||
super.setBubbleAndHandlePosition(y)
|
||||
if (bubbleEnabled) {
|
||||
bubble.y = handle.y - bubble.height / 2f + handle.height / 2f
|
||||
bubble.translationX = (-45f).dpToPxEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 410 KiB |
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="?attr/colorAccent" />
|
||||
<size android:width="6dp" android:height="54dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="@color/fast_scroller_handle_idle" />
|
||||
<size android:width="6dp" android:height="54dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -1,24 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/frame_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/download_item" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialFastScroll
|
||||
android:id="@+id/fast_scroller"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
app:fastScrollerBubbleEnabled="false"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/download_item" />
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<View
|
||||
android:id="@+id/fast_scroller_bar"
|
||||
android:layout_width="7dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:background="@null" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end">
|
||||
|
||||
<!-- No margin, use padding at the handle -->
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/fast_scroller_bubble"
|
||||
style="@style/FloatingTextView"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_toStartOf="@+id/fast_scroller_handle"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
tools:text="A"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Padding is here to have better grab -->
|
||||
<ImageView
|
||||
android:id="@+id/fast_scroller_handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:contentDescription="@null"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:src="@drawable/material_thumb_drawable" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</merge>
|
||||
@@ -13,6 +13,10 @@
|
||||
android:id="@+id/move_to_bottom"
|
||||
android:title="@string/action_move_to_bottom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/move_to_bottom_series"
|
||||
android:title="@string/action_move_to_bottom_all_for_series" />
|
||||
|
||||
<item
|
||||
android:id="@+id/cancel_download"
|
||||
android:title="@string/action_cancel" />
|
||||
|
||||
@@ -56,21 +56,6 @@
|
||||
</style>
|
||||
|
||||
|
||||
<!--============-->
|
||||
<!--FastScroller-->
|
||||
<!--============-->
|
||||
<style name="FloatingTextView" parent="TextAppearance.AppCompat">
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:elevation">5dp</item>
|
||||
<item name="android:paddingStart">12dp</item>
|
||||
<item name="android:paddingEnd">12dp</item>
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingBottom">8dp</item>
|
||||
<item name="android:textColor">?attr/colorOnPrimary</item>
|
||||
<item name="android:textSize">15sp</item>
|
||||
</style>
|
||||
|
||||
<!--===========-->
|
||||
<!--Preferences-->
|
||||
<!--===========-->
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:description="@string/appwidget_updates_description"
|
||||
android:previewImage="@drawable/updates_grid_widget_preview"
|
||||
android:initialLayout="@layout/appwidget_loading"
|
||||
android:minWidth="240dp"
|
||||
android:minHeight="80dp"
|
||||
android:minResizeWidth="80dp"
|
||||
android:minResizeHeight="110dp"
|
||||
android:maxResizeWidth="600dp"
|
||||
android:maxResizeHeight="600dp"
|
||||
android:targetCellWidth="4"
|
||||
android:targetCellHeight="2"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:widgetCategory="home_screen" />
|
||||
Reference in New Issue
Block a user