Compare commits

...

17 Commits

Author SHA1 Message Date
36a74f32df Show hint if navigation section is unavailable #751 2024-02-10 16:52:49 +02:00
0e4ef32642 Show battery percentage in reader bar 2024-02-10 16:33:18 +02:00
3125cac4c8 Use constant textSize for reader info bar #748 2024-02-10 15:16:08 +02:00
5d9016d1bc Do not query suggestions by blacklisted tags #749 2024-02-10 14:59:25 +02:00
c5eeb89d10 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (589 of 589 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (589 of 589 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Davi Silveira
4f8f43cab1 Translated using Weblate (Portuguese)
Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Davi Silveira <davilego10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Diegofmar
4cbff308ce Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Diegofmar <diego2771@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Макар Разин
d786ab7deb Translated using Weblate (Polish)
Currently translated at 83.7% (491 of 586 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Mirkó Attila
c823d402ff Translated using Weblate (Hungarian)
Currently translated at 11.2% (66 of 586 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Hungarian)

Co-authored-by: Mirkó Attila <kisatti007hun@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/hu/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-10 14:43:16 +02:00
gekka
12e68db41f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Madaraki
96717321d2 Translated using Weblate (Ukrainian)
Currently translated at 99.8% (585 of 586 strings)

Co-authored-by: Madaraki <115705267+Madaraki-chan@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Oğuz Ersen
044b5590ef Translated using Weblate (Turkish)
Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Eduardo Malaspina
00112ebb44 Translated using Weblate (Spanish)
Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Eduardo Malaspina <vaio0@swismail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
00dde80fdf Update parsers 2024-02-10 14:42:12 +02:00
f1dfc4ebd6 Fix strings 2024-02-10 14:33:57 +02:00
5426edd83a Option to disable reading time estimator 2024-02-10 14:10:38 +02:00
2a500eb2cb Improve background works notifications 2024-02-10 13:54:36 +02:00
29 changed files with 455 additions and 130 deletions

View File

@@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
versionCode = 621
versionName = '6.7-b1'
versionCode = 622
versionName = '6.7'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
@@ -82,7 +82,7 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:4a8c7fa36a') {
implementation('com.github.KotatsuApp:kotatsu-parsers:014ea5ef49') {
exclude group: 'org.json', module: 'json'
}
@@ -121,7 +121,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0'
implementation 'com.squareup.okio:okio:3.7.0'
implementation 'com.squareup.okio:okio:3.8.0'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'

View File

@@ -403,6 +403,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getString(KEY_BACKUP_PERIODICAL_OUTPUT, null)?.toUriOrNull()
set(value) = prefs.edit { putString(KEY_BACKUP_PERIODICAL_OUTPUT, value?.toString()) }
val isReadingTimeEstimationEnabled: Boolean
get() = prefs.getBoolean(KEY_READING_TIME, true)
fun isTipEnabled(tip: String): Boolean {
return prefs.getStringSet(KEY_TIPS_CLOSED, emptySet())?.contains(tip) != true
}
@@ -579,6 +582,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_CF_GRAYSCALE = "cf_grayscale"
const val KEY_IGNORE_DOZE = "ignore_dose"
const val KEY_DETAILS_TAB = "details_tab"
const val KEY_READING_TIME = "reading_time"
// About
const val KEY_APP_UPDATE = "app_update"

View File

@@ -4,13 +4,12 @@ import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.list.ui.model.ListModel
enum class NavItem(
@IdRes val id: Int,
@StringRes val title: Int,
@DrawableRes val icon: Int,
) : ListModel {
) {
HISTORY(R.id.nav_history, R.string.history, R.drawable.ic_history_selector),
FAVORITES(R.id.nav_favorites, R.string.favourites, R.drawable.ic_favourites_selector),
@@ -21,10 +20,6 @@ enum class NavItem(
BOOKMARKS(R.id.nav_bookmarks, R.string.bookmarks, R.drawable.ic_bookmark_selector),
;
override fun areItemsTheSame(other: ListModel): Boolean {
return other is NavItem && ordinal == other.ordinal
}
fun isAvailable(settings: AppSettings): Boolean = when (this) {
SUGGESTIONS -> settings.isSuggestionsEnabled
FEED -> settings.isTrackerEnabled

View File

@@ -11,8 +11,10 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.transformLatest
import org.koitharu.kotatsu.R
import java.util.concurrent.atomic.AtomicInteger
fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> {
var isFirstCall = true
@@ -37,6 +39,14 @@ fun <T> Flow<T>.onEachWhile(action: suspend (T) -> Boolean): Flow<T> {
}
}
fun <T> Flow<T>.onEachIndexed(action: suspend (index: Int, T) -> Unit): Flow<T> {
val counter = AtomicInteger(0)
return transform { value ->
action(counter.getAndIncrement(), value)
return@transform emit(value)
}
}
inline fun <T, R> Flow<List<T>>.mapItems(crossinline transform: (T) -> R): Flow<List<R>> {
return map { list -> list.map(transform) }
}

View File

@@ -2,14 +2,20 @@ package org.koitharu.kotatsu.details.domain
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.findById
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.data.ReadingTime
import javax.inject.Inject
import kotlin.math.roundToInt
class ReadingTimeUseCase @Inject constructor() {
class ReadingTimeUseCase @Inject constructor(
private val settings: AppSettings,
) {
fun invoke(manga: MangaDetails?, branch: String?, history: MangaHistory?): ReadingTime? {
if (!settings.isReadingTimeEstimationEnabled) {
return null
}
// FIXME MAXIMUM HARDCODE!!! To do calculation with user's page read speed and his favourites/history mangas average pages in chapter
val chapters = manga?.chapters?.get(branch)
if (chapters.isNullOrEmpty()) {

View File

@@ -7,6 +7,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.annotation.IdRes
import androidx.core.view.isEmpty
import androidx.core.view.iterator
import androidx.core.view.size
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
@@ -180,6 +181,9 @@ class MainNavigationDelegate(
for (item in items) {
menu.add(Menu.NONE, item.id, Menu.NONE, item.title)
.setIcon(item.icon)
if (menu.size >= navBar.maxItemCount) {
break
}
}
}

View File

@@ -9,19 +9,22 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.BatteryManager
import android.util.AttributeSet
import android.view.View
import android.view.WindowInsets
import androidx.annotation.AttrRes
import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes
import androidx.core.graphics.ColorUtils
import androidx.core.graphics.withScale
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.measureDimension
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
import java.time.LocalTime
@@ -29,6 +32,7 @@ import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import com.google.android.material.R as materialR
class ReaderInfoBarView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -38,7 +42,7 @@ class ReaderInfoBarView @JvmOverloads constructor(
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val textBounds = Rect()
private val timeFormat = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
private val timeReceiver = TimeReceiver()
private val systemStateReceiver = SystemStateReceiver()
private var insetLeft: Int = 0
private var insetRight: Int = 0
private var insetTop: Int = 0
@@ -52,9 +56,12 @@ class ReaderInfoBarView @JvmOverloads constructor(
context.getThemeColor(materialR.attr.colorSurface, Color.WHITE),
200,
)
private val batteryIcon = ContextCompat.getDrawable(context, R.drawable.ic_battery_outline)
private var timeText = timeFormat.format(LocalTime.now())
private var batteryText = ""
private var text: String = ""
private var prevTextHeight: Int = 0
private val innerHeight
get() = height - paddingTop - paddingBottom - insetTop
@@ -69,7 +76,10 @@ class ReaderInfoBarView @JvmOverloads constructor(
}
init {
paint.strokeWidth = context.resources.resolveDp(2f)
context.withStyledAttributes(attrs, R.styleable.ReaderInfoBarView, defStyleAttr) {
paint.strokeWidth = getDimension(R.styleable.ReaderInfoBarView_android_strokeWidth, 2f)
paint.textSize = getDimension(R.styleable.ReaderInfoBarView_android_textSize, 16f)
}
val insetCorner = getSystemUiDimensionOffset("rounded_corner_content_padding")
val fallbackInset = resources.getDimensionPixelOffset(R.dimen.reader_bar_inset_fallback)
val insetStart = getSystemUiDimensionOffset("status_bar_padding_start", fallbackInset) + insetCorner
@@ -82,7 +92,10 @@ class ReaderInfoBarView @JvmOverloads constructor(
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight + insetLeft + insetRight
val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom + insetTop
val desiredHeight = maxOf(
computeTextHeight().also { prevTextHeight = it },
suggestedMinimumHeight,
) + paddingTop + paddingBottom + insetTop
setMeasuredDimension(
measureDimension(desiredWidth, widthMeasureSpec),
measureDimension(desiredHeight, heightMeasureSpec),
@@ -91,7 +104,9 @@ class ReaderInfoBarView @JvmOverloads constructor(
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val ty = innerHeight / 2f + textBounds.height() / 2f - textBounds.bottom
computeTextHeight()
val h = innerHeight.toFloat()
val ty = h / 2f + textBounds.height() / 2f - textBounds.bottom
paint.textAlign = Paint.Align.LEFT
canvas.drawTextOutline(
text,
@@ -100,18 +115,32 @@ class ReaderInfoBarView @JvmOverloads constructor(
)
if (isTimeVisible) {
paint.textAlign = Paint.Align.RIGHT
canvas.drawTextOutline(
timeText,
(width - paddingRight - insetRight - cutoutInsetRight).toFloat(),
paddingTop + insetTop + ty,
)
var endX = (width - paddingRight - insetRight - cutoutInsetRight).toFloat()
canvas.drawTextOutline(timeText, endX, paddingTop + insetTop + ty)
if (batteryText.isNotEmpty()) {
paint.getTextBounds(timeText, 0, timeText.length, textBounds)
endX -= textBounds.width()
endX -= h * 0.6f
canvas.drawTextOutline(batteryText, endX, paddingTop + insetTop + ty)
batteryIcon?.let {
paint.getTextBounds(batteryText, 0, batteryText.length, textBounds)
endX -= textBounds.width()
val iconCenter = paddingTop + insetTop + textBounds.height() / 2
it.setBounds(
(endX - h).toInt(),
(iconCenter - h / 2).toInt(),
endX.toInt(),
(iconCenter + h / 2).toInt(),
)
it.drawWithOutline(canvas)
}
}
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
updateCutoutInsets(ViewCompat.getRootWindowInsets(this))
updateTextSize()
}
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
@@ -123,8 +152,11 @@ class ReaderInfoBarView @JvmOverloads constructor(
super.onAttachedToWindow()
ContextCompat.registerReceiver(
context,
timeReceiver,
IntentFilter(Intent.ACTION_TIME_TICK),
systemStateReceiver,
IntentFilter().apply {
addAction(Intent.ACTION_TIME_TICK)
addAction(Intent.ACTION_BATTERY_CHANGED)
},
ContextCompat.RECEIVER_EXPORTED,
)
updateCutoutInsets(ViewCompat.getRootWindowInsets(this))
@@ -132,7 +164,7 @@ class ReaderInfoBarView @JvmOverloads constructor(
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
context.unregisterReceiver(timeReceiver)
context.unregisterReceiver(systemStateReceiver)
}
fun update(state: ReaderUiState?) {
@@ -151,17 +183,18 @@ class ReaderInfoBarView @JvmOverloads constructor(
} else {
""
}
updateTextSize()
val newHeight = computeTextHeight()
if (newHeight != prevTextHeight) {
prevTextHeight = newHeight
requestLayout()
}
invalidate()
}
private fun updateTextSize() {
val str = text + timeText
val testTextSize = 48f
paint.textSize = testTextSize
paint.getTextBounds(str, 0, str.length, textBounds)
paint.textSize = testTextSize * innerHeight / textBounds.height()
private fun computeTextHeight(): Int {
val str = text + batteryText + timeText
paint.getTextBounds(str, 0, str.length, textBounds)
return textBounds.height()
}
private fun Canvas.drawTextOutline(text: String, x: Float, y: Float) {
@@ -173,6 +206,20 @@ class ReaderInfoBarView @JvmOverloads constructor(
drawText(text, x, y, paint)
}
private fun Drawable.drawWithOutline(canvas: Canvas) {
var requiredScale = (bounds.width() + paint.strokeWidth * 2f) / bounds.width().toFloat()
setTint(colorOutline)
canvas.withScale(requiredScale, requiredScale, bounds.exactCenterX(), bounds.exactCenterY()) {
draw(canvas)
}
requiredScale = 1f / requiredScale
canvas.withScale(requiredScale, requiredScale, bounds.exactCenterX(), bounds.exactCenterY()) {
draw(canvas)
}
setTint(colorText)
draw(canvas)
}
private fun updateCutoutInsets(insetsCompat: WindowInsetsCompat?) {
val cutouts = (insetsCompat ?: return).displayCutout?.boundingRects.orEmpty()
cutoutInsetLeft = 0
@@ -187,9 +234,15 @@ class ReaderInfoBarView @JvmOverloads constructor(
}
}
private inner class TimeReceiver : BroadcastReceiver() {
private inner class SystemStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
if (level != -1 && scale != -1) {
batteryText = context.getString(R.string.percent_string_pattern, (level * 100 / scale).toString())
}
override fun onReceive(context: Context?, intent: Intent?) {
timeText = timeFormat.format(LocalTime.now())
if (isTimeVisible) {
invalidate()

View File

@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.NavItem
import org.koitharu.kotatsu.core.ui.BaseViewModel
@@ -20,6 +21,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.main.ui.MainActivity
import org.koitharu.kotatsu.parsers.util.move
import org.koitharu.kotatsu.settings.nav.model.NavItemAddModel
import org.koitharu.kotatsu.settings.nav.model.NavItemConfigModel
import javax.inject.Inject
@HiltViewModel
@@ -31,15 +33,18 @@ class NavConfigViewModel @Inject constructor(
private val items = MutableStateFlow(settings.mainNavItems)
val content: StateFlow<List<ListModel>> = items.map { snapshot ->
if (snapshot.size < NavItem.entries.size) {
snapshot + NavItemAddModel(snapshot.size < 5)
} else {
snapshot
buildList(snapshot.size + 1) {
snapshot.mapTo(this) {
NavItemConfigModel(it, getUnavailabilityHint(it))
}
if (size < NavItem.entries.size) {
add(NavItemAddModel(size < 5))
}
}
}.stateIn(
viewModelScope + Dispatchers.Default,
SharingStarted.WhileSubscribed(5000),
emptyList()
emptyList(),
)
private var commitJob: Job? = null
@@ -81,4 +86,12 @@ class NavConfigViewModel @Inject constructor(
activityRecreationHandle.recreate(MainActivity::class.java)
}
}
private fun getUnavailabilityHint(item: NavItem) = if (item.isAvailable(settings)) {
0
} else when (item) {
NavItem.FEED -> R.string.check_for_new_chapters_disabled
NavItem.SUGGESTIONS -> R.string.suggestions_unavailable_text
else -> 0
}
}

View File

@@ -7,33 +7,37 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.NavItem
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.databinding.ItemNavAvailableBinding
import org.koitharu.kotatsu.databinding.ItemNavConfigBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.settings.nav.model.NavItemAddModel
import org.koitharu.kotatsu.settings.nav.model.NavItemConfigModel
@SuppressLint("ClickableViewAccessibility")
fun navConfigAD(
clickListener: OnListItemClickListener<NavItem>,
) = adapterDelegateViewBinding<NavItem, ListModel, ItemNavConfigBinding>(
) = adapterDelegateViewBinding<NavItemConfigModel, ListModel, ItemNavConfigBinding>(
{ layoutInflater, parent -> ItemNavConfigBinding.inflate(layoutInflater, parent, false) },
) {
val eventListener = object : View.OnClickListener, View.OnTouchListener {
override fun onClick(v: View) = clickListener.onItemClick(item, v)
override fun onClick(v: View) = clickListener.onItemClick(item.item, v)
override fun onTouch(v: View?, event: MotionEvent): Boolean =
event.actionMasked == MotionEvent.ACTION_DOWN &&
clickListener.onItemLongClick(item, itemView)
clickListener.onItemLongClick(item.item, itemView)
}
binding.imageViewRemove.setOnClickListener(eventListener)
binding.imageViewReorder.setOnTouchListener(eventListener)
bind {
with(binding.textViewTitle) {
setText(item.title)
setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0)
isEnabled = item.disabledHintResId == 0
setText(item.item.title)
setCompoundDrawablesRelativeWithIntrinsicBounds(item.item.icon, 0, 0, 0)
}
binding.textViewHint.setTextAndVisible(item.disabledHintResId)
}
}

View File

@@ -0,0 +1,15 @@
package org.koitharu.kotatsu.settings.nav.model
import androidx.annotation.StringRes
import org.koitharu.kotatsu.core.prefs.NavItem
import org.koitharu.kotatsu.list.ui.model.ListModel
data class NavItemConfigModel(
val item: NavItem,
@StringRes val disabledHintResId: Int,
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {
return other is NavItemConfigModel && other.item == item
}
}

View File

@@ -68,6 +68,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
@@ -76,6 +77,7 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.pow
import kotlin.random.Random
import com.google.android.material.R as materialR
@HiltWorker
class SuggestionsWorker @AssistedInject constructor(
@@ -86,6 +88,7 @@ class SuggestionsWorker @AssistedInject constructor(
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val appSettings: AppSettings,
private val workManager: WorkManager,
private val mangaRepositoryFactory: MangaRepository.Factory,
private val sourcesRepository: MangaSourcesRepository,
) : CoroutineWorker(appContext, params) {
@@ -116,6 +119,19 @@ class SuggestionsWorker @AssistedInject constructor(
val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID)
.setContentTitle(title)
.setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,
0,
SettingsActivity.newSuggestionsSettingsIntent(applicationContext),
0,
false,
),
).addAction(
materialR.drawable.material_ic_clear_black_24dp,
applicationContext.getString(android.R.string.cancel),
workManager.createCancelPendingIntent(id),
)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setDefaults(0)
@@ -123,7 +139,13 @@ class SuggestionsWorker @AssistedInject constructor(
.setSilent(true)
.setProgress(0, 0, true)
.setSmallIcon(android.R.drawable.stat_notify_sync)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
.setForegroundServiceBehavior(
if (TAG_ONESHOT in tags) {
NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
} else {
NotificationCompat.FOREGROUND_SERVICE_DEFERRED
},
)
.build()
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -207,7 +229,7 @@ class SuggestionsWorker @AssistedInject constructor(
val order = preferredSortOrders.first { it in availableOrders }
val availableTags = repository.getTags()
val tag = tags.firstNotNullOfOrNull { title ->
availableTags.find { x -> x.title.almostEquals(title, TAG_EQ_THRESHOLD) }
availableTags.find { x -> x !in blacklist && x.title.almostEquals(title, TAG_EQ_THRESHOLD) }
}
val list = repository.getList(
offset = 0,

View File

@@ -39,7 +39,6 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
@@ -54,6 +53,7 @@ import org.koitharu.kotatsu.core.logs.TrackerLogger
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.onEachIndexed
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.core.util.ext.trySetForeground
import org.koitharu.kotatsu.details.ui.DetailsActivity
@@ -61,11 +61,13 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
import org.koitharu.kotatsu.tracker.domain.Tracker
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import com.google.android.material.R as materialR
@HiltWorker
class TrackWorker @AssistedInject constructor(
@@ -74,6 +76,7 @@ class TrackWorker @AssistedInject constructor(
private val coil: ImageLoader,
private val settings: AppSettings,
private val tracker: Tracker,
private val workManager: WorkManager,
@TrackerLogger private val logger: FileLogger,
) : CoroutineWorker(context, workerParams) {
@@ -164,7 +167,10 @@ class TrackWorker @AssistedInject constructor(
}
}
}
}.onEach {
}.onEachIndexed { index, it ->
if (applicationContext.checkNotificationPermission()) {
notificationManager.notify(WORKER_NOTIFICATION_ID, createWorkerNotification(tracks.size, index + 1))
}
when (it) {
is MangaUpdates.Failure -> {
val e = it.error
@@ -254,12 +260,11 @@ class TrackWorker @AssistedInject constructor(
}
override suspend fun getForegroundInfo(): ForegroundInfo {
val title = applicationContext.getString(R.string.check_for_new_chapters)
val channel = NotificationChannelCompat.Builder(
WORKER_CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_LOW
NotificationManagerCompat.IMPORTANCE_LOW,
)
.setName(title)
.setName(applicationContext.getString(R.string.check_for_new_chapters))
.setShowBadge(false)
.setVibrationEnabled(false)
.setSound(null, null)
@@ -267,28 +272,56 @@ class TrackWorker @AssistedInject constructor(
.build()
notificationManager.createNotificationChannel(channel)
val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID)
.setContentTitle(title)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setDefaults(0)
.setOngoing(false)
.setSilent(true)
.setProgress(0, 0, true)
.setSmallIcon(android.R.drawable.stat_notify_sync)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
.build()
val notification = createWorkerNotification(0, 0)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ForegroundInfo(
WORKER_NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
} else {
ForegroundInfo(WORKER_NOTIFICATION_ID, notification)
}
}
private fun createWorkerNotification(max: Int, progress: Int) = NotificationCompat.Builder(
applicationContext,
WORKER_CHANNEL_ID,
).apply {
setContentTitle(applicationContext.getString(R.string.check_for_new_chapters))
setPriority(NotificationCompat.PRIORITY_MIN)
setCategory(NotificationCompat.CATEGORY_SERVICE)
setDefaults(0)
setOngoing(false)
setSilent(true)
setContentIntent(
PendingIntentCompat.getActivity(
applicationContext,
0,
SettingsActivity.newTrackerSettingsIntent(applicationContext),
0,
false,
),
)
addAction(
materialR.drawable.material_ic_clear_black_24dp,
applicationContext.getString(android.R.string.cancel),
workManager.createCancelPendingIntent(id),
)
if (max > 0) {
setSubText(applicationContext.getString(R.string.fraction_pattern, progress, max))
}
setProgress(max, progress, max == 0)
setSmallIcon(android.R.drawable.stat_notify_sync)
setForegroundServiceBehavior(
if (TAG_ONESHOT in tags) {
NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE
} else {
NotificationCompat.FOREGROUND_SERVICE_DEFERRED
},
)
}.build()
private suspend fun setRetryIds(ids: Set<Long>) = runInterruptible(Dispatchers.IO) {
val prefs = applicationContext.getSharedPreferences(TAG, Context.MODE_PRIVATE)
prefs.edit(commit = true) {

View File

@@ -0,0 +1,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M16,20H8V6H16M16.67,4H15V2H9V4H7.33A1.33,1.33 0 0,0 6,5.33V20.67C6,21.4 6.6,22 7.33,22H16.67A1.33,1.33 0 0,0 18,20.67V5.33C18,4.6 17.4,4 16.67,4Z" />
</vector>

View File

@@ -28,7 +28,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:minHeight="12sp"
android:textSize="12sp"
android:visibility="gone"
tools:visibility="visible" />

View File

@@ -5,43 +5,57 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal"
android:orientation="vertical"
android:paddingVertical="@dimen/margin_small"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawablePadding="?listPreferredItemPaddingStart"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge"
tools:drawableStart="@drawable/ic_explore_selector"
tools:text="@string/explore" />
<ImageView
android:id="@+id/imageView_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/remove"
android:padding="@dimen/margin_small"
android:scaleType="center"
android:src="@drawable/ic_delete" />
<ImageView
android:id="@+id/imageView_reorder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/reorder"
android:padding="@dimen/margin_small"
android:scaleType="center"
android:src="@drawable/ic_reorder_handle" />
</LinearLayout>
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:id="@+id/textView_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawablePadding="?listPreferredItemPaddingStart"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge"
tools:drawableStart="@drawable/ic_explore_selector"
tools:text="@string/explore" />
<ImageView
android:id="@+id/imageView_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/remove"
android:padding="@dimen/margin_small"
android:scaleType="center"
android:src="@drawable/ic_delete" />
<ImageView
android:id="@+id/imageView_reorder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/reorder"
android:padding="@dimen/margin_small"
android:scaleType="center"
android:src="@drawable/ic_reorder_handle" />
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="@string/suggestions_unavailable_text" />
</LinearLayout>

View File

@@ -240,7 +240,7 @@
<string name="removed_from_history">Выдалена з гісторыі</string>
<string name="dns_over_https">DNS праз HTTPS</string>
<string name="detect_reader_mode">Аўтавызначэнне рэжыму чытання</string>
<string name="detect_reader_mode_summary">Аўтаматычна вызначае, ці з\'яўляецца манга вэб-коміксам</string>
<string name="detect_reader_mode_summary">Аўтаматычна вызначаць, ці з\'яўляецца манга манхвой</string>
<string name="new_sources_text">Даступныя новыя крыніцы мангі</string>
<string name="download_slowdown">Запавольванне спампоўкі</string>
<string name="suggestions_excluded_genres">Выключыць жанры</string>
@@ -426,7 +426,7 @@
<string name="show_pages_numbers_summary">Паказаць нумары старонак у ніжнім куце</string>
<string name="network">Сетка</string>
<string name="data_and_privacy">Дадзеныя і канфідэнцыяльнасць</string>
<string name="webtoon_zoom_summary">Уключыць жэст павелічэння маштабу ў рэжыме манхвы</string>
<string name="webtoon_zoom_summary">Дазволіць жэсты маштабавання ў рэжыме манхвы</string>
<string name="details_button_tip">Націсніце і ўтрымлівайце кнопку \"Чытаць\", каб убачыць дадатковыя параметры</string>
<string name="restore_summary">Аднавіць раней створаную рэзервовую копію</string>
<string name="reader_info_bar_summary">Паказаць бягучы час і ход чытання ў верхняй частцы экрана</string>
@@ -581,4 +581,7 @@
<string name="email_password_enter_hint">Каб працягнуць, увядзіце адрас электроннай пошты і пароль</string>
<string name="config_reset_confirm">Скінуць налады да значэнняў па змаўчанні? Гэта дзеянне нельга адмяніць.</string>
<string name="use_two_pages_landscape">Выкарыстоўвайце двухстаронкавы макет у альбомнай арыентацыі (бэта)</string>
<string name="default_webtoon_zoom_out">Аддаленне ў рэжыме манхвы</string>
<string name="fullscreen_mode">Поўнаэкранны рэжым</string>
<string name="reader_fullscreen_summary">Схаваць інтэрфейс сістэмы</string>
</resources>

View File

@@ -582,4 +582,6 @@
<string name="none">Ninguno</string>
<string name="config_reset_confirm">¿Restablecer los valores por defecto? Esta acción no se puede deshacer.</string>
<string name="default_webtoon_zoom_out">Alejar el zoom del webtoon predeterminado</string>
<string name="fullscreen_mode">Modo de pantalla completa</string>
<string name="reader_fullscreen_summary">Ocultar las barras de estado y navegación del sistema</string>
</resources>

View File

@@ -8,4 +8,32 @@
<item quantity="one">%1$d fejezet</item>
<item quantity="other">%1$d fejezetek</item>
</plurals>
<plurals name="items">
<item quantity="one">%1$d tétel</item>
<item quantity="other">%1$d tételek</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">%1$d perce</item>
<item quantity="other">%1$d perce</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">%1$d órája</item>
<item quantity="other">%1$d órája</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">%1$d napja</item>
<item quantity="other">%1$d napja</item>
</plurals>
<plurals name="months_ago">
<item quantity="one">%1$d hónapja</item>
<item quantity="other">%1$d hónapja</item>
</plurals>
<plurals name="hours">
<item quantity="one">%1$d óra</item>
<item quantity="other">%1$d óra</item>
</plurals>
<plurals name="minutes">
<item quantity="one">%1$d perc</item>
<item quantity="other">%1$d perc</item>
</plurals>
</resources>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="history">Előzmények</string>
<string name="list">Lista</string>
<string name="detailed_list">Részletes lista</string>
<string name="grid">Rács</string>
<string name="list_mode">Lista mód</string>
<string name="settings">Beállítások</string>
<string name="remote_sources">Manga források</string>
<string name="loading_">Betöltés…</string>
<string name="computing_">Számítás…</string>
<string name="chapter_d_of_d">Fejezet %1$d / %2$d</string>
<string name="try_again">Próbálja újra</string>
<string name="nothing_found">Nem található</string>
<string name="history_is_empty">Nincs még előzmény</string>
<string name="read">Olvas</string>
<string name="you_have_not_favourites_yet">Nincsenek még kedvencek</string>
<string name="add">Hozzáadás</string>
<string name="save">Mentés</string>
<string name="share">Megosztás</string>
<string name="create_shortcut">Parancsikon létrehozása…</string>
<string name="share_s">Megosztás %s</string>
<string name="search">Keresés</string>
<string name="search_manga">Manga keresése</string>
<string name="processing_">Feldolgozás…</string>
<string name="download_complete">Letöltött</string>
<string name="downloads">Letöltések</string>
<string name="by_name">Név</string>
<string name="updated">Frissített</string>
<string name="newest">Legújabb</string>
<string name="sort_order">Rendezési sorrend</string>
<string name="by_rating">Értékelés</string>
<string name="filter">Szűrő</string>
<string name="light">Fényes</string>
<string name="automatic">Rendszer alapján</string>
<string name="clear">Törlés</string>
<string name="text_clear_history_prompt">Véglegesen törli az összes olvasási előzményt?</string>
<string name="remove">Eltávolítás</string>
<string name="save_page">Oldal mentése</string>
<string name="share_image">Kép megosztása</string>
<string name="_import">Importálás</string>
<string name="operation_not_supported">Ez a művelet nem engedélyezett</string>
<string name="text_file_not_supported">Csak ZIP és CBZ kiterjesztés elfogadott.</string>
<string name="no_description">Nincs leírás</string>
<string name="local_storage">Helyi tárhely</string>
<string name="favourites">Kedvencek</string>
<string name="error_occurred">Hiba</string>
<string name="network_error">Hálózati hiba</string>
<string name="close">Bezárás</string>
<string name="details">Részletek</string>
<string name="chapters">Fejezetek</string>
<string name="clear_history">Előzmények törlése</string>
<string name="add_to_favourites">Kedvencekhez adás</string>
<string name="add_new_category">Új kategória</string>
<string name="manga_downloading_">Letöltés…</string>
<string name="popular">Népszerű</string>
<string name="theme">Téma</string>
<string name="dark">Sötét</string>
<string name="pages">Oldalak</string>
<string name="_s_deleted_from_local_storage">%s törölve lett a helyi tárhelyből</string>
<string name="page_saved">Mentve</string>
<string name="delete">Törlés</string>
</resources>

View File

@@ -267,13 +267,13 @@
<string name="_s_deleted_from_local_storage">„%s” usunięte z pamięci lokalnej</string>
<string name="clear_updates_feed">Wyczyść tablicę aktualizacji</string>
<string name="feed">Tablica</string>
<string name="text_delete_local_manga">Usunąć trwale „%s” z urządzenia?</string>
<string name="text_delete_local_manga">Trwale usunąć „%s” z urządzenia?</string>
<string name="clear_thumbs_cache">Wyczyść pamięć podręczną miniatur</string>
<string name="text_search_holder_secondary">Spróbuj przeformułować zapytanie.</string>
<string name="text_history_holder_primary">To co czytasz będzie wyświetlane tutaj</string>
<string name="text_history_holder_secondary">Znajdź rzeczy do przeczytania w sekcji Przegląd</string>
<string name="text_local_holder_secondary">Zapisz ze źródeł online lub zaimportuj pliki.</string>
<string name="manga_save_location">Folder pobranych</string>
<string name="text_local_holder_secondary">Zapisz coś z katalogu online lub zaimportuj z pliku.</string>
<string name="manga_save_location">Folder Pobrane</string>
<string name="feed_will_update_soon">Aktualizacja tablicy rozpocznie się wkrótce</string>
<string name="passwords_mismatch">Niezgodne hasła</string>
<string name="right_to_left">Od prawej do lewej</string>
@@ -476,4 +476,14 @@
<string name="manga_list">Lista mang</string>
<string name="disable_nsfw">Wyłącz NSFW</string>
<string name="show">Pokazywać</string>
<string name="email_password_enter_hint">Wpisz swój adres e-mail i hasło, aby kontynuować</string>
<string name="items_limit_exceeded">Nie można dodać więcej elementów</string>
<string name="to_top">W górę</string>
<string name="moved_to_top">Przeniesiono na górę</string>
<string name="zoom_out">Pomniejsz</string>
<string name="zoom_in">Zbliżenie</string>
<string name="reader_zoom_buttons">Pokaż przyciski powiększania</string>
<string name="reader_zoom_buttons_summary">Określa, czy wyświetlać elementy sterujące powiększeniem w prawym dolnym rogu</string>
<string name="keep_screen_on">Pozostaw ekran włączony</string>
<string name="keep_screen_on_summary">Nie wyłączaj ekranu podczas czytania mangi</string>
</resources>

View File

@@ -581,4 +581,7 @@
<string name="use_two_pages_landscape">Usar layout de duas páginas na orientação paisagem (beta)</string>
<string name="last_read">Última leitura</string>
<string name="none">Nenhum</string>
<string name="default_webtoon_zoom_out">Diminuir zoom padrão do webtoon</string>
<string name="fullscreen_mode">Modo de tela cheia</string>
<string name="reader_fullscreen_summary">Ocultar a barra de status e navegação</string>
</resources>

View File

@@ -554,7 +554,7 @@
<string name="volume_">Volume %d</string>
<string name="volume_unknown">Volume desconhecido</string>
<string name="approximate_reading_time">Tempo aproximado de leitura</string>
<string name="incognito_mode_hint">Seu processo de leitura não será salvo</string>
<string name="incognito_mode_hint">Seu progresso de leitura não será salvo</string>
<string name="vertical">Vertical</string>
<string name="email_password_enter_hint">Coloque seu email e senha para continuar</string>
<string name="mark_as_completed_prompt">Marcar o mangá selecionado como lido completamente?
@@ -580,4 +580,8 @@
<string name="two_pages">Duas páginas</string>
<string name="mark_as_completed">Marcar como concluído</string>
<string name="none">nenhum</string>
<string name="last_read">Última leitura</string>
<string name="default_webtoon_zoom_out">Diminuir para zoom padrão do webtoon</string>
<string name="fullscreen_mode">Modo de tela cheia</string>
<string name="reader_fullscreen_summary">Ocultar a barra de status e navegação</string>
</resources>

View File

@@ -251,7 +251,7 @@
<string name="dns_over_https">DNS поверх HTTPS</string>
<string name="default_mode">Режим по умолчанию</string>
<string name="detect_reader_mode">Автоопределение режима чтения</string>
<string name="detect_reader_mode_summary">Автоматически определяет, является ли манга веб-комиксом</string>
<string name="detect_reader_mode_summary">Автоматически определять, является ли манга манхвой</string>
<string name="disable_battery_optimization">Отключить оптимизацию батареи</string>
<string name="disable_battery_optimization_summary">Помогает с фоновой проверкой обновлений</string>
<string name="crash_text">Что-то пошло не так. Пожалуйста, отправьте отчёт разработчикам, чтобы помочь всё исправить.</string>
@@ -423,7 +423,7 @@
<string name="images_proxy_title">Прокси для оптимизации изображений</string>
<string name="images_procy_description">Используйте службу wsrv.nl, чтобы уменьшить использование трафика и ускорить загрузку изображений, если это возможно</string>
<string name="invert_colors">Инвертировать цвета</string>
<string name="webtoon_zoom_summary">Включить жест увеличения масштаба в режиме манхвы</string>
<string name="webtoon_zoom_summary">Разрешить жесты масштабирования в режиме манхвы</string>
<string name="network">Сеть</string>
<string name="data_and_privacy">Данные и конфиденциальность</string>
<string name="restore_summary">Восстановить ранее созданную резервную копию</string>
@@ -583,5 +583,7 @@
<string name="use_two_pages_landscape">Использовать двухстраничный макет в альбомной ориентации (бета)</string>
<string name="default_webtoon_zoom_out">Отдаление в режиме манхвы</string>
<string name="fullscreen_mode">На весь экран</string>
<string name="reader_fullscreen_summary">Скрывать системный интерфейс</string>
<string name="reader_fullscreen_summary">Скрывать интерфейс системы</string>
<string name="reading_time_estimation">Отображать предполагаемое время чтения</string>
<string name="reading_time_estimation_summary">Данное значение может быть неточным</string>
</resources>

View File

@@ -581,4 +581,7 @@
<string name="config_reset_confirm">Ayarlar öntanımlı değerlere sıfırlansın mı? Bu eylem geri alınamaz.</string>
<string name="use_two_pages_landscape">Yatay yönde iki sayfa düzeni kullan (beta)</string>
<string name="email_password_enter_hint">Devam etmek için e-posta adresinizi ve parolanızı girin</string>
<string name="default_webtoon_zoom_out">Öntanımlı webtoon uzaklaştırması</string>
<string name="fullscreen_mode">Tam ekran modu</string>
<string name="reader_fullscreen_summary">Sistem durumunu ve gezinme çubuklarını gizle</string>
</resources>

View File

@@ -58,7 +58,7 @@
<string name="clear_pages_cache">Очистити кеш сторінок</string>
<string name="text_file_sizes">Б|кБ|МБ|ГБ|ТБ</string>
<string name="standard">Стандартний</string>
<string name="webtoon">Вебтун</string>
<string name="webtoon">Манхва</string>
<string name="read_mode">Режим читання</string>
<string name="grid_size">Розмір сітки</string>
<string name="search_on_s">Пошук по %s</string>
@@ -250,7 +250,7 @@
<string name="removed_from_history">Видалено з історії</string>
<string name="dns_over_https">DNS через HTTPS</string>
<string name="default_mode">Режим за умовчанням</string>
<string name="detect_reader_mode_summary">Автоматично визначати, чи є манґа вебтуном</string>
<string name="detect_reader_mode_summary">Автоматично визначати, чи є манґа манхвою</string>
<string name="detect_reader_mode">Автовизначення режиму читання</string>
<string name="disable_battery_optimization">Вимкнути оптимізацію акумулятора</string>
<string name="disable_battery_optimization_summary">Допомагає з перевірками фонових оновлень</string>
@@ -342,7 +342,7 @@
<string name="different_languages">Різні мови</string>
<string name="network_unavailable">Мережа недоступна</string>
<string name="network_unavailable_hint">Увімкніть Wi-Fi або мобільну мережу, щоб читати манґу онлайн</string>
<string name="webtoon_zoom">Масштабування в режимі вебтуну</string>
<string name="webtoon_zoom">Масштабування в режимі манхви</string>
<string name="reader_slider">Відображати повзунок перемикання сторінок</string>
<string name="compact">Компактно</string>
<string name="prefetch_content">Передвчасне завантаження контенту</string>
@@ -430,7 +430,7 @@
<string name="show_pages_numbers_summary">Показати номери сторінок у нижньому куті</string>
<string name="pages_animation_summary">Анімація перегортання сторінок</string>
<string name="details_button_tip">Натисніть і утримуйте кнопку «Читати», щоб переглянути додаткові параметри</string>
<string name="webtoon_zoom_summary">Дозволити жест збільшення в режимі webtoon</string>
<string name="webtoon_zoom_summary">Дозволити жести масштабування в режимі манхви</string>
<string name="clear_source_cookies_summary">Очистити файли cookie лише для вказаного домену. У більшості випадків авторизація анулюється</string>
<string name="pick_custom_directory">Вибрати користувальницький каталог</string>
<string name="no_access_to_file">Ви не маєте доступу до цього файлу чи каталогу</string>
@@ -581,4 +581,9 @@
<string name="email_password_enter_hint">Введіть свою адресу електронної пошти та пароль, щоб продовжити</string>
<string name="config_reset_confirm">Повернути налаштування до значень за замовчуванням? Цю дію неможливо скасувати.</string>
<string name="use_two_pages_landscape">Використовувати макет двох сторінок у альбомній орієнтації (бета)</string>
<string name="fullscreen_mode">Повноекранний режим</string>
<string name="reader_fullscreen_summary">Приховувати інтерфейс системи</string>
<string name="default_webtoon_zoom_out">Віддалення у режимі манхви</string>
<string name="reading_time_estimation">Відображати передбачуваний час читання</string>
<string name="reading_time_estimation_summary">Це значення може бути неточним</string>
</resources>

View File

@@ -37,9 +37,9 @@
<string name="various_languages">多语言</string>
<string name="search_chapters">查找章节</string>
<string name="suggestions_excluded_genres">排除分类</string>
<string name="suggestions_updating">漫画推荐更新</string>
<string name="suggestions_updating">正在更新漫画推荐</string>
<string name="check_new_chapters_title">检查新章节并通知结果</string>
<string name="details">作品详情界面</string>
<string name="details">作品详情</string>
<string name="detailed_list">卡片</string>
<string name="grid">网格</string>
<string name="list_mode">视图选项</string>
@@ -48,7 +48,7 @@
<string name="computing_">计算中…</string>
<string name="chapter_d_of_d">%1$d/%2$d 章</string>
<string name="close">关闭</string>
<string name="try_again">再试一次</string>
<string name="try_again">重试</string>
<string name="clear_history">清除历史</string>
<string name="nothing_found">结果为空</string>
<string name="history_is_empty">暂无历史记录</string>
@@ -121,11 +121,11 @@
<string name="notification_sound">通知声音</string>
<string name="light_indicator">LED指示器</string>
<string name="vibration">振动</string>
<string name="favourites_categories">收藏更新范围</string>
<string name="favourites_categories">收藏分类管理</string>
<string name="remove_category">删除分类</string>
<string name="text_empty_holder_primary">什么都没有…</string>
<string name="text_search_holder_secondary">试试换一个词再搜索。</string>
<string name="text_history_holder_primary">看过的内容将在这里显示</string>
<string name="text_history_holder_primary">看过的漫画将在这里显示</string>
<string name="text_history_holder_secondary">在&lt;浏览&gt;页面搜索要读的漫画</string>
<string name="text_local_holder_primary">请先在线下载或进行本地导入</string>
<string name="text_local_holder_secondary">下载图源内容或通过本地文件导入。</string>
@@ -141,7 +141,7 @@
<string name="favourites_category_empty">目前分类为空</string>
<string name="read_later">稍后阅读</string>
<string name="updates">更新内容</string>
<string name="text_feed_holder">正在阅读的新章节将在这里显示</string>
<string name="text_feed_holder">正在阅读漫画的新章节将在这里显示</string>
<string name="search_results">搜索结果</string>
<string name="new_version_s">新版本: %s</string>
<string name="clear_updates_feed">清除订阅更新记录</string>
@@ -227,7 +227,7 @@
<string name="suggestions_excluded_genres_summary">输入不希望在推荐中看到的分类</string>
<string name="text_delete_local_manga_batch">要从系统中永久删除所选项目吗?</string>
<string name="removal_completed">删除成功</string>
<string name="download_slowdown">下载速度减慢</string>
<string name="download_slowdown">减慢下载速度</string>
<string name="download_slowdown_summary">有助于避免封禁你的IP地址</string>
<string name="local_manga_processing">正在处理已保存漫画</string>
<string name="chapters_will_removed_background">章节将在后台被删除</string>
@@ -244,7 +244,7 @@
<string name="bookmark_removed">书签已删除</string>
<string name="bookmark_added">书签已添加</string>
<string name="undo">撤销</string>
<string name="removed_from_history">从历史记录中删除</string>
<string name="removed_from_history">已从历史记录中删除</string>
<string name="dns_over_https">基于 HTTPS 的 DNS</string>
<string name="default_mode">默认阅读模式</string>
<string name="detect_reader_mode">自动检测阅读模式</string>
@@ -271,7 +271,7 @@
<string name="exclude_nsfw_from_history_summary">标记为成人内容的漫画将不会添加到历史记录,也不会保存阅读记录</string>
<string name="clear_cookies_summary">能对部分问题起到一些作用。所有网站的授权将会失效</string>
<string name="show_all">显示全部</string>
<string name="manga_error_description_pattern">错误详情:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1.尝试&lt;a href=%2$s&gt;在网络浏览器中打开漫画&lt;/a&gt;以确保在其图源中可用&lt;br&gt;2. 请确保使用的是&lt;a href=kotatsu://about&gt;最新版本的Kotatsu&lt;/a&gt;&lt;br&gt;3.若图源没有问题,请向开发人员发送错误报告。</string>
<string name="manga_error_description_pattern">错误详情:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1.尝试&lt;a href=%2$s&gt;在网络浏览器中打开漫画&lt;/a&gt;以确保在其图源中可用&lt;br&gt;2.请确保使用的是&lt;a href=kotatsu://about&gt;最新版本的Kotatsu&lt;/a&gt;&lt;br&gt;3.若图源没有问题,请向开发人员发送错误报告。</string>
<string name="invalid_domain_message">无效域名</string>
<string name="text_shelf_holder_primary">此处将显示你的漫画</string>
<string name="text_shelf_holder_secondary">在&lt;浏览&gt;页面搜索要读的漫画</string>
@@ -315,7 +315,7 @@
<string name="other_cache">其他缓存</string>
<string name="storage_usage">存储占用</string>
<string name="available">可用</string>
<string name="removed_from_favourites">从收藏中删除</string>
<string name="removed_from_favourites">已从收藏中删除</string>
<string name="options">选项</string>
<string name="incognito_mode">无痕模式</string>
<string name="no_chapters">没有章节</string>
@@ -411,7 +411,7 @@
<string name="address">地址</string>
<string name="clear_network_cache">清除网络缓存</string>
<string name="proxy">代理</string>
<string name="type">漫画类型</string>
<string name="type">类型</string>
<string name="invalid_value_message">无效值</string>
<string name="network">网络</string>
<string name="restore_summary">从以前创建的备份恢复</string>
@@ -449,7 +449,7 @@
<string name="languages">语言</string>
<string name="captcha_required_summary">%s 需要通过验证才能正常运行</string>
<string name="progress">阅读进度</string>
<string name="error_corrupted_file">无效数据回传或文件已损坏</string>
<string name="error_corrupted_file">回传的数据无效或文件已损坏</string>
<string name="related_manga_summary">显示相关漫画。可能并不准确或缺失</string>
<string name="tracker_wifi_only_summary">使用移动网络时停止检查新章节</string>
<string name="order_added">添加日期</string>
@@ -520,7 +520,7 @@
<string name="downloads_settings_info">若遇到服务器端阻塞,可以在图源设置中为每个图源单独开启限速下载功能</string>
<string name="skip">跳过</string>
<string name="backup_date_">备份日期:%s</string>
<string name="content_rating">内容评级</string>
<string name="content_rating">内容分级</string>
<string name="genres_exclude">排除分类</string>
<string name="rating_safe">全年龄</string>
<string name="rating_suggestive">R15</string>
@@ -539,10 +539,10 @@
<string name="available_d">%1$d 个可用</string>
<string name="disable_nsfw_summary">禁用成人内容图源并尽可能从列表中隐藏成人漫画</string>
<string name="speed_value">x%.1f</string>
<string name="error_filter_locale_genre_not_supported">此图源不支持同时按分类和区域过滤</string>
<string name="error_filter_states_genre_not_supported">此图源不支持同时按分类和状态过滤</string>
<string name="error_filter_locale_genre_not_supported">此图源不支持同时按分类和区域筛选</string>
<string name="error_filter_states_genre_not_supported">此图源不支持同时按分类和状态筛选</string>
<string name="genres_search_hint">开始输入分类名称</string>
<string name="state_paused">暂停</string>
<string name="state_paused">休刊中</string>
<string name="reader_optimize">降低内存占用(测试)</string>
<string name="reader_optimize_summary">降低当前画面外的页面质量以减少内存占用</string>
<string name="disable_battery_optimization_summary_downloads">可能帮助解决下载过程相关的问题</string>
@@ -551,19 +551,19 @@
<string name="error_multiple_genres_not_supported">此图源不支持按多个分类筛选</string>
<string name="error_multiple_states_not_supported">此图源不支持按多个状态筛选</string>
<string name="by_relevance">关联</string>
<string name="mark_as_completed">标记为已读完</string>
<string name="mark_as_completed_prompt">要将选定的漫画标记为已读完吗?
<string name="mark_as_completed">标记为已读</string>
<string name="mark_as_completed_prompt">要将选定的漫画标记为已读吗?
\n
\n警告: 当前的阅读进度将会丢失。</string>
<string name="category_hidden_done">此分类已从主页隐藏,可通过菜单 → 管理分类来访问</string>
<string name="approximate_reading_time">大约需要的阅读时间</string>
<string name="approximate_remaining_time">大约剩余时间</string>
<string name="approximate_reading_time">估计所需阅读时间</string>
<string name="approximate_remaining_time">估计剩余阅读时间</string>
<string name="remaining_time_pattern">%1$s %2$s</string>
<string name="volume_">第 %d 卷</string>
<string name="volume_unknown">未知卷</string>
<string name="incognito_mode_hint">阅读进度不会保存</string>
<string name="vertical">垂直</string>
<string name="last_read">上次阅读</string>
<string name="last_read">最近阅读</string>
<string name="switch_pages_volume_buttons_summary">使用音量键翻页</string>
<string name="two_pages">双页</string>
<string name="show_menu">显示菜单</string>
@@ -581,4 +581,7 @@
<string name="use_two_pages_landscape">在横屏时开启双页模式(测试)</string>
<string name="config_reset_confirm">要恢复为默认设置吗?恢复后无法撤销。</string>
<string name="email_password_enter_hint">输入邮箱和密码以继续</string>
<string name="fullscreen_mode">全屏模式</string>
<string name="default_webtoon_zoom_out">条漫默认缩小值</string>
<string name="reader_fullscreen_summary">将系统状态栏与通知栏隐藏</string>
</resources>

View File

@@ -167,4 +167,9 @@
<attr name="chipStyle" />
</declare-styleable>
<declare-styleable name="ReaderInfoBarView">
<attr name="android:textSize" />
<attr name="android:strokeWidth" />
</declare-styleable>
</resources>

View File

@@ -588,4 +588,9 @@
<string name="default_webtoon_zoom_out">Default webtoon zoom out</string>
<string name="fullscreen_mode">Fullscreen mode</string>
<string name="reader_fullscreen_summary">Hide system status and navigation bars</string>
<string name="fraction_pattern" translatable="false">%1$d/%2$d</string>
<string name="reading_time_estimation">Show estimated reading time</string>
<string name="reading_time_estimation_summary">The time estimation value may be inaccurate</string>
<string name="suggestions_unavailable_text">Suggestions feature is disabled</string>
<string name="check_for_new_chapters_disabled">Checking for new chapters is disabled</string>
</resources>

View File

@@ -49,6 +49,12 @@
<PreferenceCategory
android:title="@string/details">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="reading_time"
android:summary="@string/reading_time_estimation_summary"
android:title="@string/reading_time_estimation" />
<ListPreference
android:defaultValue="0"
android:entries="@array/details_tabs"