Compare commits

...

19 Commits

Author SHA1 Message Date
2a01a2ac6b Release v0.16.5 2024-04-09 14:17:04 +06:00
9a6559b013 Remove unused imports 2024-04-09 14:17:04 +06:00
a7509b3a3c Fix build time zone in about screen
And slight cleanup
2024-04-09 14:17:04 +06:00
2755d1f35e chore(deps): update dependency gradle to v8.7 (#567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-09 14:17:04 +06:00
6a8a9c6bbf Address detekt issues 2024-04-09 14:17:04 +06:00
w
7862088b94 Update image-decoder, color management (#523)
* Update image-decoder, color management

* move display profile pref

* remove true color pref

* Move Display Profile settings to a new section

* Partially revert "remove true color pref"

This partially reverts commit e1a7581695.

* Tweak label

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2024-04-09 14:12:49 +06:00
35f8eda8c5 Switch to seconds for DATE_MODIFIED of saved pages (#552)
While most Android skins are seemingly able to handle the millisecond
format, the documentation technically specifies seconds. This seems to
be causing issues on Samsung devices using the Samsung Gallery app,
which renders the millisecond timestamps as if they were second ones,
causing the dates to be set at some point in the year 56189.

This change should fix that issue on Samsung devices and have no real
impact on the rest.
2024-04-09 14:04:22 +06:00
fa6fa1f53a Disable SerialVersionUIDInSerializableClass detekt rule 2024-04-09 14:04:21 +06:00
c348fac78f Fix crash in track date selection dialog
Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
2024-04-09 14:04:21 +06:00
ab06720966 Upgrade Compose 2024-04-09 13:23:43 +06:00
42ebf017e4 Fix some issues from 7ff95e2 (#415)
* Fixed extra header introduced in 7ff95e2

* Removed parentheses to make detekt happy

* Updated relative date display for dates in the future

* Small cleanup for header creation logic

* replaced "and" with "&&" for better formatting
2024-04-09 13:23:43 +06:00
3910ffdd9e Fix DelayedTrackingUpdateJob spam on update errors (#411)
* Fix DelayedTrackingUpdateJob spam on update errors

DelayedTrackingUpdateJob would start spamming when it encountered an
error (e.g. a tracker has an issue) and never stop.
This seems to stem from a circular dependency between the Job's
`doWork` and TrackChapter's `await`.

TrackChapter sets up a completely new instance of the
DelayedTrackingUpdateJob if any Exception was thrown during the track
update.

This causes the Job to get replaced (as per the WorkManager's set
ExistingWorkPolicy).

Because of this, the guard clause at the start of doWork would never
trigger, as all instances of the Job would report being the 0th try
(because they were completely new instances).

This simple fix introduces a boolean `isRetry` parameter to
TrackChapter's await method, which is set to `false` by default.
DelayedTrackingUpdateJob however sets this parameter to `true`, which
means TrackChapter won't try to set up the Job again.

* Rename isRetry parameter to setupJobOnFailure

This also inverts the logic, so true & false were swapped.
2024-04-09 13:23:43 +06:00
0bfacf5570 Tweak detekt config 2024-04-09 13:23:42 +06:00
1e28999e13 Revert a mishap in 7ff95e21ba 2024-04-09 13:23:42 +06:00
1ee54d74a4 Ignore detekt [LongParameterList] for composables 2024-04-09 13:23:42 +06:00
a1a52ae81a Refactor use of Java.util.date to Java.time.*, to fix localized date issues. (#402)
* Add support for localdate based relative times

* Update History Screen to use new localdate based relative times

* Update Updates Screen to use new localdate based relative times

* Cleaned up date util classes

* Updated build time display

* Code cleanup

* Fixed crash in settings

* Updated Preferences item

* Worker Info works

* Fixed Tracker date display

* Code changes to pass detekt
2024-04-09 13:23:42 +06:00
a4f5dfab1a Release v0.16.4 2024-02-26 22:19:23 +06:00
085147b15b Fix detekt issue 2024-02-26 22:19:23 +06:00
085ad8d446 Don't add custom User Agent for MAL
Closes #298
2024-02-26 22:08:11 +06:00
39 changed files with 210 additions and 171 deletions

View File

@ -53,7 +53,7 @@ body:
label: Mihon version label: Mihon version
description: You can find your Mihon version in **More → About**. description: You can find your Mihon version in **More → About**.
placeholder: | placeholder: |
Example: "0.16.3" Example: "0.16.5"
validations: validations:
required: true required: true
@ -96,7 +96,7 @@ body:
required: true required: true
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/). - label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
required: true required: true
- label: I have updated the app to version **[0.16.3](https://github.com/mihonapp/mihon/releases/latest)**. - label: I have updated the app to version **[0.16.5](https://github.com/mihonapp/mihon/releases/latest)**.
required: true required: true
- label: I have updated all installed extensions. - label: I have updated all installed extensions.
required: true required: true

View File

@ -31,7 +31,7 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: I have updated the app to version **[0.16.3](https://github.com/mihonapp/mihon/releases/latest)**. - label: I have updated the app to version **[0.16.5](https://github.com/mihonapp/mihon/releases/latest)**.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

View File

@ -22,8 +22,8 @@ android {
defaultConfig { defaultConfig {
applicationId = "app.mihon" applicationId = "app.mihon"
versionCode = 4 versionCode = 6
versionName = "0.16.3" versionName = "0.16.5"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")

View File

@ -28,4 +28,6 @@ class BasePreferences(
SHIZUKU(MR.strings.ext_installer_shizuku, false), SHIZUKU(MR.strings.ext_installer_shizuku, false),
PRIVATE(MR.strings.ext_installer_private, false), PRIVATE(MR.strings.ext_installer_private, false),
} }
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
} }

View File

@ -21,7 +21,7 @@ class TrackChapter(
private val delayedTrackingStore: DelayedTrackingStore, private val delayedTrackingStore: DelayedTrackingStore,
) { ) {
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) { suspend fun await(context: Context, mangaId: Long, chapterNumber: Double, setupJobOnFailure: Boolean = true) {
withNonCancellableContext { withNonCancellableContext {
val tracks = getTracks.await(mangaId) val tracks = getTracks.await(mangaId)
if (tracks.isEmpty()) return@withNonCancellableContext if (tracks.isEmpty()) return@withNonCancellableContext
@ -43,7 +43,9 @@ class TrackChapter(
delayedTrackingStore.remove(track.id) delayedTrackingStore.remove(track.id)
} catch (e: Exception) { } catch (e: Exception) {
delayedTrackingStore.add(track.id, chapterNumber) delayedTrackingStore.add(track.id, chapterNumber)
DelayedTrackingUpdateJob.setupTask(context) if (setupJobOnFailure) {
DelayedTrackingUpdateJob.setupTask(context)
}
throw e throw e
} }
} }

View File

@ -45,7 +45,7 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
logcat(LogPriority.DEBUG) { logcat(LogPriority.DEBUG) {
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}" "Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
} }
trackChapter.await(context, track.mangaId, track.lastChapterRead) trackChapter.await(context, track.mangaId, track.lastChapterRead, setupJobOnFailure = false)
} }
} }

View File

@ -7,8 +7,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum import tachiyomi.core.common.preference.getEnum
import java.text.DateFormat import java.time.format.DateTimeFormatter
import java.text.SimpleDateFormat import java.time.format.FormatStyle
import java.util.Locale import java.util.Locale
class UiPreferences( class UiPreferences(
@ -31,9 +31,9 @@ class UiPreferences(
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC) fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
companion object { companion object {
fun dateFormat(format: String): DateFormat = when (format) { fun dateFormat(format: String): DateTimeFormatter = when (format) {
"" -> DateFormat.getDateInstance(DateFormat.SHORT) "" -> DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
else -> SimpleDateFormat(format, Locale.getDefault()) else -> DateTimeFormatter.ofPattern(format, Locale.getDefault())
} }
} }
} }

View File

@ -9,20 +9,26 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
@Composable @Composable
fun relativeDateText( fun relativeDateText(
dateEpochMillis: Long, dateEpochMillis: Long,
): String { ): String {
return relativeDateText( return relativeDateText(
date = Date(dateEpochMillis).takeIf { dateEpochMillis > 0L }, localDate = LocalDate.ofInstant(
Instant.ofEpochMilli(dateEpochMillis),
ZoneId.systemDefault(),
)
.takeIf { dateEpochMillis > 0L },
) )
} }
@Composable @Composable
fun relativeDateText( fun relativeDateText(
date: Date?, localDate: LocalDate?,
): String { ): String {
val context = LocalContext.current val context = LocalContext.current
@ -30,11 +36,10 @@ fun relativeDateText(
val relativeTime = remember { preferences.relativeTime().get() } val relativeTime = remember { preferences.relativeTime().get() }
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
return date return localDate?.toRelativeString(
?.toRelativeString( context = context,
context = context, relative = relativeTime,
relative = relativeTime, dateFormat = dateFormat,
dateFormat = dateFormat, )
)
?: stringResource(MR.strings.not_applicable) ?: stringResource(MR.strings.not_applicable)
} }

View File

@ -28,7 +28,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
import java.util.Date import java.time.LocalDate
@Composable @Composable
fun HistoryScreen( fun HistoryScreen(
@ -133,7 +133,7 @@ private fun HistoryScreenContent(
} }
sealed interface HistoryUiModel { sealed interface HistoryUiModel {
data class Header(val date: Date) : HistoryUiModel data class Header(val date: LocalDate) : HistoryUiModel
data class Item(val item: HistoryWithRelations) : HistoryUiModel data class Item(val item: HistoryWithRelations) : HistoryUiModel
} }

View File

@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.manga.model.MangaCover
import java.time.Instant import java.time.Instant
import java.time.LocalDate
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.Date import java.util.Date
import kotlin.random.Random import kotlin.random.Random
@ -71,10 +72,10 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
private object HistoryUiModelExamples { private object HistoryUiModelExamples {
val headerToday = header() val headerToday = header()
val headerTomorrow = val headerTomorrow =
HistoryUiModel.Header(Date.from(Instant.now().plus(1, ChronoUnit.DAYS))) HistoryUiModel.Header(LocalDate.now().plusDays(1))
fun header(instantBuilder: (Instant) -> Instant = { it }) = fun header(instantBuilder: (Instant) -> Instant = { it }) =
HistoryUiModel.Header(Date.from(instantBuilder(Instant.now()))) HistoryUiModel.Header(LocalDate.from(instantBuilder(Instant.now())))
fun items() = sequence { fun items() = sequence {
var count = 1 var count = 1

View File

@ -6,6 +6,8 @@ import android.content.Intent
import android.provider.Settings import android.provider.Settings
import android.webkit.WebStorage import android.webkit.WebStorage
import android.webkit.WebView import android.webkit.WebView
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@ -123,6 +125,7 @@ object SettingsAdvancedScreen : SearchableSettings {
getDataGroup(), getDataGroup(),
getNetworkGroup(networkPreferences = networkPreferences), getNetworkGroup(networkPreferences = networkPreferences),
getLibraryGroup(), getLibraryGroup(),
getReaderGroup(basePreferences = basePreferences),
getExtensionsGroup(basePreferences = basePreferences), getExtensionsGroup(basePreferences = basePreferences),
) )
} }
@ -313,6 +316,34 @@ object SettingsAdvancedScreen : SearchableSettings {
) )
} }
@Composable
private fun getReaderGroup(
basePreferences: BasePreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
val chooseColorProfile = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
) { uri ->
uri?.let {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags)
basePreferences.displayProfile().set(uri.toString())
}
}
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_reader),
preferenceItems = persistentListOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_display_profile),
subtitle = basePreferences.displayProfile().get(),
onClick = {
chooseColorProfile.launch(arrayOf("*/*"))
},
),
)
)
}
@Composable @Composable
private fun getExtensionsGroup( private fun getExtensionsGroup(
basePreferences: BasePreferences, basePreferences: BasePreferences,

View File

@ -26,7 +26,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.time.Instant import java.time.LocalDate
object SettingsAppearanceScreen : SearchableSettings { object SettingsAppearanceScreen : SearchableSettings {
@ -101,7 +101,7 @@ object SettingsAppearanceScreen : SearchableSettings {
val context = LocalContext.current val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val now = remember { Instant.now().toEpochMilli() } val now = remember { LocalDate.now() }
val dateFormat by uiPreferences.dateFormat().collectAsState() val dateFormat by uiPreferences.dateFormat().collectAsState()
val formattedNow = remember(dateFormat) { val formattedNow = remember(dateFormat) {

View File

@ -29,6 +29,7 @@ object SettingsReaderScreen : SearchableSettings {
@Composable @Composable
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val readerPref = remember { Injekt.get<ReaderPreferences>() } val readerPref = remember { Injekt.get<ReaderPreferences>() }
return listOf( return listOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = readerPref.defaultReadingMode(), pref = readerPref.defaultReadingMode(),
@ -56,11 +57,6 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_show_navigation_mode), title = stringResource(MR.strings.pref_show_navigation_mode),
subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary), subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary),
), ),
Preference.PreferenceItem.SwitchPreference(
pref = readerPref.trueColor(),
title = stringResource(MR.strings.pref_true_color),
subtitle = stringResource(MR.strings.pref_true_color_summary),
),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
pref = readerPref.pageTransitions(), pref = readerPref.pageTransitions(),
title = stringResource(MR.strings.pref_page_transitions), title = stringResource(MR.strings.pref_page_transitions),

View File

@ -55,10 +55,9 @@ import tachiyomi.presentation.core.icons.Reddit
import tachiyomi.presentation.core.icons.X import tachiyomi.presentation.core.icons.X
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.DateFormat import java.time.Instant
import java.text.SimpleDateFormat import java.time.LocalDateTime
import java.util.Locale import java.time.ZoneId
import java.util.TimeZone
object AboutScreen : Screen() { object AboutScreen : Screen() {
@ -269,18 +268,15 @@ object AboutScreen : Screen() {
internal fun getFormattedBuildTime(): String { internal fun getFormattedBuildTime(): String {
return try { return try {
val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US) LocalDateTime.ofInstant(
inputDf.timeZone = TimeZone.getTimeZone("UTC") Instant.parse(BuildConfig.BUILD_TIME),
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME) ZoneId.systemDefault(),
val outputDf = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.SHORT,
Locale.getDefault(),
) )
outputDf.timeZone = TimeZone.getDefault() .toDateTimestampString(
UiPreferences.dateFormat(
buildTime!!.toDateTimestampString(UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get())) Injekt.get<UiPreferences>().dateFormat().get(),
),
)
} catch (e: Exception) { } catch (e: Exception) {
BuildConfig.BUILD_TIME BuildConfig.BUILD_TIME
} }

View File

@ -42,7 +42,9 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.plus import tachiyomi.presentation.core.util.plus
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
class WorkerInfoScreen : Screen() { class WorkerInfoScreen : Screen() {
@ -149,11 +151,17 @@ class WorkerInfoScreen : Screen() {
appendLine("State: ${workInfo.state}") appendLine("State: ${workInfo.state}")
if (workInfo.state == WorkInfo.State.ENQUEUED) { if (workInfo.state == WorkInfo.State.ENQUEUED) {
appendLine( appendLine(
"Next scheduled run: ${Date(workInfo.nextScheduleTimeMillis).toDateTimestampString( "Next scheduled run: ${
UiPreferences.dateFormat( LocalDateTime.ofInstant(
Injekt.get<UiPreferences>().dateFormat().get(), Instant.ofEpochMilli(workInfo.nextScheduleTimeMillis),
), ZoneId.systemDefault(),
)}", )
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(),
),
)
}",
) )
appendLine("Attempt #${workInfo.runAttemptCount + 1}") appendLine("Attempt #${workInfo.runAttemptCount + 1}")
} }

View File

@ -52,17 +52,18 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.presentation.track.components.TrackLogoIcon import eu.kanade.presentation.track.components.TrackLogoIcon
import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.lang.toLocalDate
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import java.text.DateFormat import java.time.format.DateTimeFormatter
private const val UnsetStatusTextAlpha = 0.5F private const val UnsetStatusTextAlpha = 0.5F
@Composable @Composable
fun TrackInfoDialogHome( fun TrackInfoDialogHome(
trackItems: List<TrackItem>, trackItems: List<TrackItem>,
dateFormat: DateFormat, dateFormat: DateTimeFormatter,
onStatusClick: (TrackItem) -> Unit, onStatusClick: (TrackItem) -> Unit,
onChapterClick: (TrackItem) -> Unit, onChapterClick: (TrackItem) -> Unit,
onScoreClick: (TrackItem) -> Unit, onScoreClick: (TrackItem) -> Unit,
@ -104,11 +105,11 @@ fun TrackInfoDialogHome(
.takeIf { supportsScoring && item.track.score != 0.0 }, .takeIf { supportsScoring && item.track.score != 0.0 },
onScoreClick = { onScoreClick(item) } onScoreClick = { onScoreClick(item) }
.takeIf { supportsScoring }, .takeIf { supportsScoring },
startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) } startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate.toLocalDate()) }
.takeIf { supportsReadingDates && item.track.startDate != 0L }, .takeIf { supportsReadingDates && item.track.startDate != 0L },
onStartDateClick = { onStartDateEdit(item) } // TODO onStartDateClick = { onStartDateEdit(item) } // TODO
.takeIf { supportsReadingDates }, .takeIf { supportsReadingDates },
endDate = dateFormat.format(item.track.finishDate) endDate = dateFormat.format(item.track.finishDate.toLocalDate())
.takeIf { supportsReadingDates && item.track.finishDate != 0L }, .takeIf { supportsReadingDates && item.track.finishDate != 0L },
onEndDateClick = { onEndDateEdit(item) } onEndDateClick = { onEndDateEdit(item) }
.takeIf { supportsReadingDates }, .takeIf { supportsReadingDates },

View File

@ -5,7 +5,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.test.DummyTracker import eu.kanade.test.DummyTracker
import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.model.Track
import java.text.DateFormat import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
internal class TrackInfoDialogHomePreviewProvider : internal class TrackInfoDialogHomePreviewProvider :
PreviewParameterProvider<@Composable () -> Unit> { PreviewParameterProvider<@Composable () -> Unit> {
@ -46,7 +47,7 @@ internal class TrackInfoDialogHomePreviewProvider :
trackItemWithoutTrack, trackItemWithoutTrack,
trackItemWithTrack, trackItemWithTrack,
), ),
dateFormat = DateFormat.getDateInstance(), dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
onStatusClick = {}, onStatusClick = {},
onChapterClick = {}, onChapterClick = {},
onScoreClick = {}, onScoreClick = {},
@ -61,7 +62,7 @@ internal class TrackInfoDialogHomePreviewProvider :
private val noTrackers = @Composable { private val noTrackers = @Composable {
TrackInfoDialogHome( TrackInfoDialogHome(
trackItems = listOf(), trackItems = listOf(),
dateFormat = DateFormat.getDateInstance(), dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM),
onStatusClick = {}, onStatusClick = {},
onChapterClick = {}, onChapterClick = {},
onScoreClick = {}, onScoreClick = {},

View File

@ -36,7 +36,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
import java.util.Date import java.time.LocalDate
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@Composable @Composable
@ -206,6 +206,6 @@ private fun UpdatesBottomBar(
} }
sealed interface UpdatesUiModel { sealed interface UpdatesUiModel {
data class Header(val date: Date) : UpdatesUiModel data class Header(val date: LocalDate) : UpdatesUiModel
data class Item(val item: UpdatesItem) : UpdatesUiModel data class Item(val item: UpdatesItem) : UpdatesUiModel
} }

View File

@ -24,7 +24,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder" } check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder" }
val bitmap = decoder.decode(rgb565 = options.allowRgb565) val bitmap = decoder.decode()
decoder.recycle() decoder.recycle()
check(bitmap != null) { "Failed to decode image" } check(bitmap != null) { "Failed to decode image" }

View File

@ -79,7 +79,7 @@ class ImageSaver(
MediaStore.Images.Media.RELATIVE_PATH to relativePath, MediaStore.Images.Media.RELATIVE_PATH to relativePath,
MediaStore.Images.Media.DISPLAY_NAME to image.name, MediaStore.Images.Media.DISPLAY_NAME to image.name,
MediaStore.Images.Media.MIME_TYPE to type.mime, MediaStore.Images.Media.MIME_TYPE to type.mime,
MediaStore.Images.Media.DATE_MODIFIED to Instant.now().toEpochMilli(), MediaStore.Images.Media.DATE_MODIFIED to Instant.now().epochSecond,
) )
val picture = findUriOrDefault(relativePath, filename) { val picture = findUriOrDefault(relativePath, filename) {

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.myanimelist package eu.kanade.tachiyomi.data.track.myanimelist
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Interceptor import okhttp3.Interceptor
@ -32,7 +31,8 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor
// Add the authorization header to the original request // Add the authorization header to the original request
val authRequest = originalRequest.newBuilder() val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}") .addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})") // TODO(antsy): Add back custom user agent when they stop blocking us for no apparent reason
// .header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
.build() .build()
return chain.proceed(authRequest) return chain.proceed(authRequest)

View File

@ -5,7 +5,7 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.util.insertSeparators import eu.kanade.core.util.insertSeparators
import eu.kanade.presentation.history.HistoryUiModel import eu.kanade.presentation.history.HistoryUiModel
import eu.kanade.tachiyomi.util.lang.toDateKey import eu.kanade.tachiyomi.util.lang.toLocalDate
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -28,7 +28,6 @@ import tachiyomi.domain.history.interactor.RemoveHistory
import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.model.HistoryWithRelations
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date
class HistoryScreenModel( class HistoryScreenModel(
private val getHistory: GetHistory = Injekt.get(), private val getHistory: GetHistory = Injekt.get(),
@ -60,10 +59,10 @@ class HistoryScreenModel(
private fun List<HistoryWithRelations>.toHistoryUiModels(): List<HistoryUiModel> { private fun List<HistoryWithRelations>.toHistoryUiModels(): List<HistoryUiModel> {
return map { HistoryUiModel.Item(it) } return map { HistoryUiModel.Item(it) }
.insertSeparators { before, after -> .insertSeparators { before, after ->
val beforeDate = before?.item?.readAt?.time?.toDateKey() ?: Date(0) val beforeDate = before?.item?.readAt?.time?.toLocalDate()
val afterDate = after?.item?.readAt?.time?.toDateKey() ?: Date(0) val afterDate = after?.item?.readAt?.time?.toLocalDate()
when { when {
beforeDate.time != afterDate.time && afterDate.time != 0L -> HistoryUiModel.Header(afterDate) beforeDate != afterDate && afterDate != null -> HistoryUiModel.Header(afterDate)
// Return null to avoid adding a separator between two items. // Return null to avoid adding a separator between two items.
else -> null else -> null
} }

View File

@ -427,6 +427,7 @@ private data class TrackDateSelectorScreen(
private val start: Boolean, private val start: Boolean,
) : Screen() { ) : Screen() {
@Transient
private val selectableDates = object : SelectableDates { private val selectableDates = object : SelectableDates {
override fun isSelectableDate(utcTimeMillis: Long): Boolean { override fun isSelectableDate(utcTimeMillis: Long): Boolean {
val dateToCheck = Instant.ofEpochMilli(utcTimeMillis) val dateToCheck = Instant.ofEpochMilli(utcTimeMillis)

View File

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.app.assist.AssistContent import android.app.assist.AssistContent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.ColorMatrix import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter import android.graphics.ColorMatrixColorFilter
@ -38,6 +37,7 @@ import androidx.lifecycle.lifecycleScope
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.elevation.SurfaceColors import com.google.android.material.elevation.SurfaceColors
import com.google.android.material.transition.platform.MaterialContainerTransform import com.google.android.material.transition.platform.MaterialContainerTransform
import com.hippo.unifile.UniFile
import dev.chrisbanes.insetter.applyInsetter import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.reader.DisplayRefreshHost import eu.kanade.presentation.reader.DisplayRefreshHost
@ -92,6 +92,7 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.ByteArrayOutputStream
class ReaderActivity : BaseActivity() { class ReaderActivity : BaseActivity() {
@ -795,8 +796,8 @@ class ReaderActivity : BaseActivity() {
} }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
readerPreferences.trueColor().changes() preferences.displayProfile().changes()
.onEach(::setTrueColor) .onEach { setDisplayProfile(it) }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
readerPreferences.cutoutShort().changes() readerPreferences.cutoutShort().changes()
@ -835,13 +836,19 @@ class ReaderActivity : BaseActivity() {
} }
/** /**
* Sets the 32-bit color mode according to [enabled]. * Sets the display profile to [path].
*/ */
private fun setTrueColor(enabled: Boolean) { private fun setDisplayProfile(path: String) {
if (enabled) { val file = UniFile.fromUri(baseContext, path.toUri())
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.ARGB_8888) if (file != null && file.exists()) {
} else { val inputStream = file.openInputStream()
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.RGB_565) val outputStream = ByteArrayOutputStream()
inputStream.use { input ->
outputStream.use { output ->
input.copyTo(output)
}
}
SubsamplingScaleImageView.setDisplayProfile(outputStream.toByteArray())
} }
} }

View File

@ -23,9 +23,6 @@ class ReaderPreferences(
fun showReadingMode() = preferenceStore.getBoolean("pref_show_reading_mode", true) fun showReadingMode() = preferenceStore.getBoolean("pref_show_reading_mode", true)
// TODO: default this to true if reader long strip ever goes stable
fun trueColor() = preferenceStore.getBoolean("pref_true_color_key", false)
fun fullscreen() = preferenceStore.getBoolean("fullscreen", true) fun fullscreen() = preferenceStore.getBoolean("fullscreen", true)
fun cutoutShort() = preferenceStore.getBoolean("cutout_short", true) fun cutoutShort() = preferenceStore.getBoolean("cutout_short", true)

View File

@ -22,7 +22,6 @@ abstract class ViewerConfig(readerPreferences: ReaderPreferences, private val sc
var doubleTapAnimDuration = 500 var doubleTapAnimDuration = 500
var volumeKeysEnabled = false var volumeKeysEnabled = false
var volumeKeysInverted = false var volumeKeysInverted = false
var trueColor = false
var alwaysShowChapterTransition = true var alwaysShowChapterTransition = true
var navigationMode = 0 var navigationMode = 0
protected set protected set
@ -62,9 +61,6 @@ abstract class ViewerConfig(readerPreferences: ReaderPreferences, private val sc
readerPreferences.readWithVolumeKeysInverted() readerPreferences.readWithVolumeKeysInverted()
.register({ volumeKeysInverted = it }) .register({ volumeKeysInverted = it })
readerPreferences.trueColor()
.register({ trueColor = it }, { imagePropertyChangedListener?.invoke() })
readerPreferences.alwaysShowChapterTransition() readerPreferences.alwaysShowChapterTransition()
.register({ alwaysShowChapterTransition = it }) .register({ alwaysShowChapterTransition = it })

View File

@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.util.lang.toDateKey import eu.kanade.tachiyomi.util.lang.toLocalDate
import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -46,7 +46,6 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.Date
class UpdatesScreenModel( class UpdatesScreenModel(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
@ -374,12 +373,10 @@ class UpdatesScreenModel(
return items return items
.map { UpdatesUiModel.Item(it) } .map { UpdatesUiModel.Item(it) }
.insertSeparators { before, after -> .insertSeparators { before, after ->
val beforeDate = before?.item?.update?.dateFetch?.toDateKey() ?: Date(0) val beforeDate = before?.item?.update?.dateFetch?.toLocalDate()
val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0) val afterDate = after?.item?.update?.dateFetch?.toLocalDate()
when { when {
beforeDate.time != afterDate.time && afterDate.time != 0L -> { beforeDate != afterDate && afterDate != null -> UpdatesUiModel.Header(afterDate)
UpdatesUiModel.Header(afterDate)
}
// Return null to avoid adding a separator between two items. // Return null to avoid adding a separator between two items.
else -> null else -> null
} }

View File

@ -6,15 +6,18 @@ import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import java.text.DateFormat import java.text.DateFormat
import java.time.Instant import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.Calendar
import java.util.Date import java.util.Date
import kotlin.math.absoluteValue
fun Date.toDateTimestampString(dateFormatter: DateFormat): String { fun LocalDateTime.toDateTimestampString(dateTimeFormatter: DateTimeFormatter): String {
val date = dateFormatter.format(this) val date = dateTimeFormatter.format(this)
val time = DateFormat.getTimeInstance(DateFormat.SHORT).format(this) val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(this)
return "$date $time" return "$date $time"
} }
@ -32,52 +35,35 @@ fun Long.convertEpochMillisZone(
.toEpochMilli() .toEpochMilli()
} }
/** fun Long.toLocalDate(): LocalDate {
* Get date as time key return LocalDate.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
*
* @param date desired date
* @return date as time key
*/
fun Long.toDateKey(): Date {
val instant = Instant.ofEpochMilli(this)
return Date.from(instant.truncatedTo(ChronoUnit.DAYS))
} }
fun Date.toRelativeString( fun LocalDate.toRelativeString(
context: Context, context: Context,
relative: Boolean = true, relative: Boolean = true,
dateFormat: DateFormat = DateFormat.getDateInstance(DateFormat.SHORT), dateFormat: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT),
): String { ): String {
if (!relative) { if (!relative) {
return dateFormat.format(this) return dateFormat.format(this)
} }
val now = Date() val now = LocalDate.now()
val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - val difference = ChronoUnit.DAYS.between(this, now)
this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY)
val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt()
return when { return when {
difference < 0 -> dateFormat.format(this) difference < -7 -> dateFormat.format(this)
difference < MILLISECONDS_IN_DAY -> context.stringResource(MR.strings.relative_time_today) difference < 0 -> context.pluralStringResource(
difference < MILLISECONDS_IN_DAY.times(7) -> context.pluralStringResource( MR.plurals.upcoming_relative_time,
MR.plurals.relative_time, difference.toInt().absoluteValue,
days, difference.toInt().absoluteValue,
days,
) )
difference < 1 -> context.stringResource(MR.strings.relative_time_today)
difference < 7 -> context.pluralStringResource(
MR.plurals.relative_time,
difference.toInt(),
difference.toInt(),
)
else -> dateFormat.format(this) else -> dateFormat.format(this)
} }
} }
private const val MILLISECONDS_IN_DAY = 86_400_000L
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
}
}
private fun Long.floorNearest(to: Long): Long {
return this.floorDiv(to) * to
}

View File

@ -1,8 +1,8 @@
import org.gradle.api.Project import org.gradle.api.Project
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat import java.time.LocalDateTime
import java.util.TimeZone import java.time.ZoneOffset
import java.util.Date import java.time.format.DateTimeFormatter
// Git is needed in your system PATH for these commands to work. // Git is needed in your system PATH for these commands to work.
// If it's not installed, you can return a random value as a workaround // If it's not installed, you can return a random value as a workaround
@ -16,10 +16,11 @@ fun Project.getGitSha(): String {
// return "1" // return "1"
} }
private val BUILD_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
@Suppress("UnusedReceiverParameter")
fun Project.getBuildTime(): String { fun Project.getBuildTime(): String {
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'") return LocalDateTime.now(ZoneOffset.UTC).format(BUILD_TIME_FORMATTER)
df.timeZone = TimeZone.getTimeZone("UTC")
return df.format(Date())
} }
fun Project.runCommand(command: String): String { fun Project.runCommand(command: String): String {
@ -29,4 +30,4 @@ fun Project.runCommand(command: String): String {
standardOutput = byteOut standardOutput = byteOut
} }
return String(byteOut.toByteArray()).trim() return String(byteOut.toByteArray()).trim()
} }

View File

@ -6,14 +6,19 @@ naming:
constantPattern: '[A-Z][A-Za-z0-9]*' constantPattern: '[A-Z][A-Za-z0-9]*'
complexity: complexity:
LongMethod:
ignoreAnnotated: [ 'Composable' ]
LongParameterList: LongParameterList:
functionThreshold: 6
constructorThreshold: 7
ignoreDefaultParameters: true ignoreDefaultParameters: true
ignoreAnnotated: [ 'Composable' ]
style: style:
MagicNumber: MagicNumber:
ignorePropertyDeclaration: true ignorePropertyDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true ignoreCompanionObjectPropertyDeclaration: true
ReturnCount:
excludeGuardClauses: true
SerialVersionUIDInSerializableClass:
active: false
UnusedPrivateMember: UnusedPrivateMember:
ignoreAnnotated: [ 'Preview' ] ignoreAnnotated: [ 'Preview' ]

View File

@ -1,5 +1,7 @@
package tachiyomi.domain.track.model package tachiyomi.domain.track.model
import java.io.Serializable
data class Track( data class Track(
val id: Long, val id: Long,
val mangaId: Long, val mangaId: Long,
@ -14,4 +16,4 @@ data class Track(
val remoteUrl: String, val remoteUrl: String,
val startDate: Long, val startDate: Long,
val finishDate: Long, val finishDate: Long,
) ) : Serializable

View File

@ -1,7 +1,7 @@
[versions] [versions]
compiler = "1.5.8" compiler = "1.5.10"
compose-bom = "2024.01.00-alpha03" compose-bom = "2024.02.00-alpha02"
accompanist = "0.34.0" accompanist = "0.35.0-alpha"
[libraries] [libraries]
activity = "androidx.activity:activity-compose:1.8.2" activity = "androidx.activity:activity-compose:1.8.2"

View File

@ -47,8 +47,8 @@ coil-core = { module = "io.coil-kt:coil" }
coil-gif = { module = "io.coil-kt:coil-gif" } coil-gif = { module = "io.coil-kt:coil-gif" }
coil-compose = { module = "io.coil-kt:coil-compose" } coil-compose = { module = "io.coil-kt:coil-compose" }
subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:7e57335" subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:aeaa170036"
image-decoder = "com.github.tachiyomiorg:image-decoder:fbd6601290" image-decoder = "com.github.tachiyomiorg:image-decoder:e08e9be535"
natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1" natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

17
gradlew vendored
View File

@ -83,7 +83,8 @@ done
# This is normally unused # This is normally unused
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -201,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command; # Collect all arguments for the java command:
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# shell script including quotes and variable substitutions, so put them in # and any embedded shellness will be escaped.
# double quotes to make sure that they get re-expanded; and # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# * put everything else in single quotes, so that it's not re-expanded. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \

20
gradlew.bat vendored
View File

@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@ -10,6 +10,11 @@
<item quantity="other">%1$d days ago</item> <item quantity="other">%1$d days ago</item>
</plurals> </plurals>
<plurals name="upcoming_relative_time">
<item quantity="one">Tomorrow</item>
<item quantity="other">In %1$d days</item>
</plurals>
<plurals name="num_categories"> <plurals name="num_categories">
<item quantity="one">%d category</item> <item quantity="one">%d category</item>
<item quantity="other">%d categories</item> <item quantity="other">%d categories</item>

View File

@ -364,8 +364,7 @@
<string name="pref_show_page_number">Show page number</string> <string name="pref_show_page_number">Show page number</string>
<string name="pref_show_reading_mode">Show reading mode</string> <string name="pref_show_reading_mode">Show reading mode</string>
<string name="pref_show_reading_mode_summary">Briefly show current mode when reader is opened</string> <string name="pref_show_reading_mode_summary">Briefly show current mode when reader is opened</string>
<string name="pref_true_color">32-bit color</string> <string name="pref_display_profile">Custom display profile</string>
<string name="pref_true_color_summary">Reduces banding, but may impact performance</string>
<string name="pref_crop_borders">Crop borders</string> <string name="pref_crop_borders">Crop borders</string>
<string name="pref_custom_brightness">Custom brightness</string> <string name="pref_custom_brightness">Custom brightness</string>
<string name="pref_grayscale">Grayscale</string> <string name="pref_grayscale">Grayscale</string>