Fix webtoon scroll
This commit is contained in:
@@ -32,6 +32,6 @@ data class HistoryEntity(
|
||||
updatedAt = Date(updatedAt),
|
||||
chapterId = chapterId,
|
||||
page = page,
|
||||
scroll = scroll
|
||||
scroll = scroll.toInt()
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
import java.lang.NullPointerException
|
||||
|
||||
class MangaNotFoundException(s: String? = null) : RuntimeException(s)
|
||||
@@ -37,7 +37,7 @@ class SetCookieCache : CookieCache {
|
||||
|
||||
override fun iterator(): MutableIterator<Cookie> = SetCookieCacheIterator()
|
||||
|
||||
private inner class SetCookieCacheIterator() : MutableIterator<Cookie> {
|
||||
private inner class SetCookieCacheIterator : MutableIterator<Cookie> {
|
||||
|
||||
private val iterator = cookies.iterator()
|
||||
|
||||
|
||||
@@ -10,5 +10,5 @@ data class MangaHistory(
|
||||
val updatedAt: Date,
|
||||
val chapterId: Long,
|
||||
val page: Int,
|
||||
val scroll: Float
|
||||
val scroll: Int
|
||||
) : Parcelable
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.core.parser
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import java.util.*
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.domain
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.util.Size
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@@ -12,6 +13,7 @@ import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.utils.ext.await
|
||||
import org.koitharu.kotatsu.utils.ext.medianOrNull
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
object MangaUtils : KoinComponent {
|
||||
|
||||
@@ -23,13 +25,22 @@ object MangaUtils : KoinComponent {
|
||||
try {
|
||||
val page = pages.medianOrNull() ?: return null
|
||||
val url = MangaProviderFactory.create(page.source).getPageFullUrl(page)
|
||||
val client = get<OkHttpClient>()
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
val size = client.newCall(request).await().use {
|
||||
getBitmapSize(it.body?.byteStream())
|
||||
val uri = Uri.parse(url)
|
||||
val size = if (uri.scheme == "cbz") {
|
||||
val zip = ZipFile(uri.schemeSpecificPart)
|
||||
val entry = zip.getEntry(uri.fragment)
|
||||
zip.getInputStream(entry).use {
|
||||
getBitmapSize(it)
|
||||
}
|
||||
} else {
|
||||
val client = get<OkHttpClient>()
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
client.newCall(request).await().use {
|
||||
getBitmapSize(it.body?.byteStream())
|
||||
}
|
||||
}
|
||||
return when {
|
||||
size.width * 2 < size.height -> ReaderMode.WEBTOON
|
||||
|
||||
@@ -24,7 +24,7 @@ class HistoryRepository : KoinComponent {
|
||||
return entities.map { it.manga.toManga(it.tags.map(TagEntity::toMangaTag).toSet()) }
|
||||
}
|
||||
|
||||
suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Float) {
|
||||
suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Int) {
|
||||
val tags = manga.tags.map(TagEntity.Companion::fromMangaTag)
|
||||
db.withTransaction {
|
||||
db.tagsDao.upsert(tags)
|
||||
@@ -36,7 +36,7 @@ class HistoryRepository : KoinComponent {
|
||||
updatedAt = System.currentTimeMillis(),
|
||||
chapterId = chapterId,
|
||||
page = page,
|
||||
scroll = scroll
|
||||
scroll = scroll.toFloat() // we migrate to int, but decide to not update database
|
||||
)
|
||||
)
|
||||
trackingRepository.upsert(manga)
|
||||
@@ -45,15 +45,7 @@ class HistoryRepository : KoinComponent {
|
||||
}
|
||||
|
||||
suspend fun getOne(manga: Manga): MangaHistory? {
|
||||
return db.historyDao.find(manga.id)?.let {
|
||||
MangaHistory(
|
||||
createdAt = Date(it.createdAt),
|
||||
updatedAt = Date(it.updatedAt),
|
||||
chapterId = it.chapterId,
|
||||
page = it.page,
|
||||
scroll = it.scroll
|
||||
)
|
||||
}
|
||||
return db.historyDao.find(manga.id)?.toMangaHistory()
|
||||
}
|
||||
|
||||
suspend fun clear() {
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import moxy.MvpAppCompatDialogFragment
|
||||
|
||||
abstract class AlertDialogFragment(@LayoutRes private val layoutResId: Int) : MvpAppCompatDialogFragment() {
|
||||
|
||||
@@ -139,7 +139,16 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
|
||||
if (chaptersCount > 5) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.save_manga)
|
||||
.setMessage(getString(R.string.large_manga_save_confirm, chaptersCount))
|
||||
.setMessage(
|
||||
getString(
|
||||
R.string.large_manga_save_confirm,
|
||||
resources.getQuantityString(
|
||||
R.plurals.chapters,
|
||||
chaptersCount,
|
||||
chaptersCount
|
||||
)
|
||||
)
|
||||
)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.save) { _, _ ->
|
||||
DownloadService.start(this, it)
|
||||
@@ -174,7 +183,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView,
|
||||
}
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
tab.text = when(position) {
|
||||
tab.text = when (position) {
|
||||
0 -> getString(R.string.details)
|
||||
1 -> getString(R.string.chapters)
|
||||
else -> null
|
||||
|
||||
@@ -123,8 +123,8 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View): Boolean {
|
||||
when {
|
||||
v.id == R.id.button_read -> {
|
||||
when (v.id) {
|
||||
R.id.button_read -> {
|
||||
if (history == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.koitharu.kotatsu.ui.main
|
||||
|
||||
import moxy.viewstate.strategy.alias.OneExecution
|
||||
import org.koitharu.kotatsu.core.model.MangaState
|
||||
import org.koitharu.kotatsu.ui.common.BaseMvpView
|
||||
import org.koitharu.kotatsu.ui.reader.ReaderState
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveState(chapterId: Long, page: Int, scroll: Float) {
|
||||
override fun saveState(chapterId: Long, page: Int, scroll: Int) {
|
||||
state = state.copy(chapterId = chapterId, page = page, scroll = scroll)
|
||||
ReaderPresenter.saveState(state)
|
||||
}
|
||||
@@ -311,7 +311,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
||||
state = state.copy(
|
||||
chapterId = chapter.id,
|
||||
page = 0,
|
||||
scroll = 0f
|
||||
scroll = 0
|
||||
)
|
||||
reader?.updateState(chapterId = chapter.id)
|
||||
}
|
||||
@@ -395,7 +395,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
|
||||
chapterId = if (chapterId == -1L) manga.chapters?.firstOrNull()?.id
|
||||
?: -1 else chapterId,
|
||||
page = 0,
|
||||
scroll = 0f
|
||||
scroll = 0
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@ interface ReaderListener : BaseMvpView {
|
||||
|
||||
fun onPageChanged(chapter: MangaChapter, page: Int, total: Int)
|
||||
|
||||
fun saveState(chapterId: Long, page: Int, scroll: Float)
|
||||
fun saveState(chapterId: Long, page: Int, scroll: Int)
|
||||
}
|
||||
@@ -11,7 +11,7 @@ data class ReaderState(
|
||||
val manga: Manga,
|
||||
val chapterId: Long,
|
||||
val page: Int,
|
||||
val scroll: Float
|
||||
val scroll: Int
|
||||
) : Parcelable {
|
||||
|
||||
@IgnoredOnParcel
|
||||
|
||||
@@ -54,7 +54,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
|
||||
pages.addLast(state.chapterId, it)
|
||||
adapter?.notifyDataSetChanged()
|
||||
setCurrentItem(state.page, false)
|
||||
if (state.scroll != 0f) {
|
||||
if (state.scroll != 0) {
|
||||
restorePageScroll(state.page, state.scroll)
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
|
||||
if (pageId == 0L) {
|
||||
0
|
||||
} else {
|
||||
it.indexOfFirst { it.id == pageId }.coerceAtLeast(0)
|
||||
it.indexOfFirst { x -> x.id == pageId }.coerceAtLeast(0)
|
||||
}, false
|
||||
)
|
||||
}
|
||||
@@ -217,9 +217,9 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
|
||||
|
||||
protected abstract fun getCurrentItem(): Int
|
||||
|
||||
protected abstract fun getCurrentPageScroll(): Float
|
||||
protected abstract fun getCurrentPageScroll(): Int
|
||||
|
||||
protected abstract fun restorePageScroll(position: Int, scroll: Float)
|
||||
protected abstract fun restorePageScroll(position: Int, scroll: Int)
|
||||
|
||||
protected abstract fun setCurrentItem(position: Int, isSmooth: Boolean)
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard),
|
||||
pager.setCurrentItem(position, isSmooth)
|
||||
}
|
||||
|
||||
override fun getCurrentPageScroll() = 0f
|
||||
override fun getCurrentPageScroll() = 0
|
||||
|
||||
override fun restorePageScroll(position: Int, scroll: Float) = Unit
|
||||
override fun restorePageScroll(position: Int, scroll: Int) = Unit
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
when (key) {
|
||||
|
||||
@@ -13,5 +13,12 @@ class WebtoonFrameLayout @JvmOverloads constructor(
|
||||
findViewById<WebtoonImageView>(R.id.ssiv)
|
||||
}
|
||||
|
||||
fun dispatchVerticalScroll(dy: Int) = target.dispatchVerticalScroll(dy)
|
||||
fun dispatchVerticalScroll(dy: Int): Int {
|
||||
if (dy == 0) {
|
||||
return 0
|
||||
}
|
||||
val oldScroll = target.getScroll()
|
||||
target.scrollBy(dy)
|
||||
return target.getScroll() - oldScroll
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.ui.reader.wetoon
|
||||
|
||||
import android.graphics.PointF
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.isVisible
|
||||
@@ -21,7 +20,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
SubsamplingScaleImageView.OnImageEventListener, CoroutineScope by loader {
|
||||
|
||||
private var job: Job? = null
|
||||
private var scrollToRestore = 0f
|
||||
private var scrollToRestore = 0
|
||||
|
||||
init {
|
||||
ssiv.setOnImageEventListener(this)
|
||||
@@ -36,7 +35,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
|
||||
private fun doLoad(data: MangaPage, force: Boolean) {
|
||||
job?.cancel()
|
||||
scrollToRestore = 0f
|
||||
scrollToRestore = 0
|
||||
job = launch {
|
||||
layout_error.isVisible = false
|
||||
progressBar.isVisible = true
|
||||
@@ -60,17 +59,11 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
ssiv.recycle()
|
||||
}
|
||||
|
||||
fun getScrollY() = ssiv.center?.y ?: 0f
|
||||
fun getScrollY() = ssiv.getScroll()
|
||||
|
||||
fun restoreScroll(scroll: Float) {
|
||||
fun restoreScroll(scroll: Int) {
|
||||
if (ssiv.isReady) {
|
||||
ssiv.setScaleAndCenter(
|
||||
ssiv.scale,
|
||||
PointF(
|
||||
ssiv.sWidth / 2f,
|
||||
scroll
|
||||
)
|
||||
)
|
||||
ssiv.scrollTo(scroll)
|
||||
} else {
|
||||
scrollToRestore = scroll
|
||||
}
|
||||
@@ -80,17 +73,11 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
|
||||
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
|
||||
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
|
||||
ssiv.setScaleAndCenter(
|
||||
ssiv.minScale,
|
||||
PointF(
|
||||
ssiv.sWidth / 2f,
|
||||
when {
|
||||
scrollToRestore != 0f -> scrollToRestore
|
||||
itemView.top < 0 -> ssiv.sHeight.toFloat()
|
||||
else -> 0f
|
||||
}
|
||||
)
|
||||
)
|
||||
ssiv.scrollTo(when {
|
||||
scrollToRestore != 0 -> scrollToRestore
|
||||
itemView.top < 0 -> ssiv.getScrollRange()
|
||||
else -> 0
|
||||
})
|
||||
}
|
||||
|
||||
override fun onImageLoadError(e: Exception) = onError(e)
|
||||
|
||||
@@ -2,42 +2,66 @@ package org.koitharu.kotatsu.ui.reader.wetoon
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.PointF
|
||||
import android.graphics.RectF
|
||||
import android.util.AttributeSet
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import org.koitharu.kotatsu.utils.ext.toIntUp
|
||||
|
||||
class WebtoonImageView : SubsamplingScaleImageView {
|
||||
class WebtoonImageView @JvmOverloads constructor(context: Context, attr: AttributeSet? = null) :
|
||||
SubsamplingScaleImageView(context, attr) {
|
||||
|
||||
constructor(context: Context?) : super(context)
|
||||
constructor(context: Context?, attr: AttributeSet?) : super(context, attr)
|
||||
|
||||
private val pan = RectF()
|
||||
private val ct = PointF()
|
||||
|
||||
fun dispatchVerticalScroll(dy: Int): Int {
|
||||
private var scrollPos = 0
|
||||
private var scrollRange = SCROLL_UNKNOWN
|
||||
|
||||
fun scrollBy(delta: Int) {
|
||||
val maxScroll = getScrollRange()
|
||||
if (maxScroll == 0) {
|
||||
return
|
||||
}
|
||||
val newScroll = scrollPos + delta
|
||||
scrollToInternal(newScroll.coerceIn(0, maxScroll))
|
||||
}
|
||||
|
||||
fun scrollTo(y: Int) {
|
||||
val maxScroll = getScrollRange()
|
||||
if (maxScroll == 0) {
|
||||
return
|
||||
}
|
||||
scrollToInternal(y.coerceIn(0, maxScroll))
|
||||
}
|
||||
|
||||
fun getScroll() = scrollPos
|
||||
|
||||
fun getScrollRange(): Int {
|
||||
if (scrollRange == SCROLL_UNKNOWN) {
|
||||
computeScrollRange()
|
||||
}
|
||||
return scrollRange.coerceAtLeast(0)
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
scrollRange = SCROLL_UNKNOWN
|
||||
scrollPos = 0
|
||||
super.recycle()
|
||||
}
|
||||
|
||||
private fun scrollToInternal(pos: Int) {
|
||||
scrollPos = pos
|
||||
ct.set(sWidth / 2f, (height / 2f + pos.toFloat()) / minScale)
|
||||
setScaleAndCenter(minScale, ct)
|
||||
}
|
||||
|
||||
private fun computeScrollRange() {
|
||||
if (!isReady) {
|
||||
return 0
|
||||
}
|
||||
getPanRemaining(pan)
|
||||
// pan.offset(0f, -nonConsumedScroll.toFloat())
|
||||
ct.set(width / 2f, height / 2f)
|
||||
viewToSourceCoord(ct.x, ct.y, ct) ?: return 0
|
||||
val s = scale
|
||||
return when {
|
||||
dy > 0 -> {
|
||||
val delta = minOf(pan.bottom.toIntUp(), dy)
|
||||
ct.offset(0f, delta.toFloat() / s)
|
||||
setScaleAndCenter(s, ct)
|
||||
delta
|
||||
}
|
||||
dy < 0 -> {
|
||||
val delta = minOf(pan.top.toInt(), -dy)
|
||||
ct.offset(0f, -delta.toFloat() / s)
|
||||
setScaleAndCenter(s, ct)
|
||||
-delta
|
||||
}
|
||||
else -> 0
|
||||
return
|
||||
}
|
||||
val totalHeight = (sHeight * minScale).toIntUp()
|
||||
scrollRange = (totalHeight - height).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
const val SCROLL_UNKNOWN = -1
|
||||
}
|
||||
}
|
||||
@@ -61,12 +61,12 @@ class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
|
||||
)
|
||||
}
|
||||
|
||||
override fun getCurrentPageScroll(): Float {
|
||||
override fun getCurrentPageScroll(): Int {
|
||||
return (recyclerView.findViewHolderForAdapterPosition(getCurrentItem()) as? WebtoonHolder)
|
||||
?.getScrollY() ?: 0f
|
||||
?.getScrollY() ?: 0
|
||||
}
|
||||
|
||||
override fun restorePageScroll(position: Int, scroll: Float) {
|
||||
override fun restorePageScroll(position: Int, scroll: Int) {
|
||||
recyclerView.post {
|
||||
val holder = recyclerView.findViewHolderForAdapterPosition(position) ?: return@post
|
||||
(holder as WebtoonHolder).restoreScroll(scroll)
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.ui.reader.wetoon
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
@@ -39,13 +38,39 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun consumeVerticalScroll(dy: Int): Int {
|
||||
val child = when {
|
||||
dy > 0 -> children.firstOrNull { it is WebtoonFrameLayout }
|
||||
dy < 0 -> children.lastOrNull { it is WebtoonFrameLayout }
|
||||
else -> null
|
||||
} ?: return 0
|
||||
var scrollY = dy
|
||||
scrollY -= (child as WebtoonFrameLayout).dispatchVerticalScroll(scrollY)
|
||||
return dy - scrollY
|
||||
if (childCount == 0) {
|
||||
return 0
|
||||
}
|
||||
when {
|
||||
dy > 0 -> {
|
||||
val child = getChildAt(0) as WebtoonFrameLayout
|
||||
var consumedByChild = child.dispatchVerticalScroll(dy)
|
||||
if (consumedByChild < dy) {
|
||||
if (childCount > 1) {
|
||||
val nextChild = getChildAt(1) as WebtoonFrameLayout
|
||||
val unconsumed = dy - consumedByChild - nextChild.top //will be consumed by scroll
|
||||
if (unconsumed > 0) {
|
||||
consumedByChild += nextChild.dispatchVerticalScroll(unconsumed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumedByChild
|
||||
}
|
||||
dy < 0 -> {
|
||||
val child = getChildAt(childCount - 1) as WebtoonFrameLayout
|
||||
var consumedByChild = child.dispatchVerticalScroll(dy)
|
||||
if (consumedByChild > dy) {
|
||||
if (childCount > 1) {
|
||||
val nextChild = getChildAt(childCount - 2) as WebtoonFrameLayout
|
||||
val unconsumed = dy - consumedByChild + (height - nextChild.bottom) //will be consumed by scroll
|
||||
if (unconsumed < 0) {
|
||||
consumedByChild += nextChild.dispatchVerticalScroll(unconsumed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumedByChild
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@ class AppUpdateService : BaseService() {
|
||||
}
|
||||
return try {
|
||||
val md: MessageDigest = MessageDigest.getInstance("SHA1")
|
||||
val publicKey: ByteArray = md.digest(c.getEncoded())
|
||||
val publicKey: ByteArray = md.digest(c.encoded)
|
||||
publicKey.byte2HexFormatted()
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.utils.ext
|
||||
import android.annotation.SuppressLint
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun Date.format(pattern: String): String = SimpleDateFormat(pattern).format(this)
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
android:id="@+id/ssiv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:zoomEnabled="false"
|
||||
app:quickScaleEnabled="false"
|
||||
app:panEnabled="false" />
|
||||
|
||||
<ProgressBar
|
||||
|
||||
@@ -1,8 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
|
||||
|
||||
</menu>
|
||||
<menu />
|
||||
@@ -15,6 +15,11 @@
|
||||
<item quantity="few">%1$d новых главы</item>
|
||||
<item quantity="many">%1$d новых глав</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d глава</item>
|
||||
<item quantity="few">%1$d главы</item>
|
||||
<item quantity="many">%1$d глав</item>
|
||||
</plurals>
|
||||
<plurals name="chapters_from_x">
|
||||
<item quantity="one">%1$d глава из %2$d</item>
|
||||
<item quantity="few">%1$d главы из %2$d</item>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<string name="app_update_available">Доступно обновление приложения</string>
|
||||
<string name="show_notification_app_update">Показывать уведомление при наличии новой версии</string>
|
||||
<string name="open_in_browser">Открыть в браузере</string>
|
||||
<string name="large_manga_save_confirm">В этой манге %d глав. Вы уверены, что хотите сохранить их все?</string>
|
||||
<string name="large_manga_save_confirm">В этой манге %s. Вы уверены, что хотите сохранить их все?</string>
|
||||
<string name="save_manga">Сохранить мангу</string>
|
||||
<string name="notifications">Уведомления</string>
|
||||
<string name="enabled_d_from_d">Включено %1$d из %2$d</string>
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
<item quantity="one">%1$d new chapter</item>
|
||||
<item quantity="other">%1$d new chapters</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d chapter</item>
|
||||
<item quantity="other">%1$d chapters</item>
|
||||
</plurals>
|
||||
<plurals name="chapters_from_x">
|
||||
<item quantity="one">%1$d chapter from %2$d</item>
|
||||
<item quantity="other">%1$d chapters from %2$d</item>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="app_name" translatable="false">Kotatsu</string>
|
||||
<string name="close_menu">Close menu</string>
|
||||
<string name="open_menu">Open menu</string>
|
||||
@@ -103,10 +103,10 @@
|
||||
<string name="app_update_available">Application update is available</string>
|
||||
<string name="show_notification_app_update">Show notification if update is available</string>
|
||||
<string name="open_in_browser">Open in browser</string>
|
||||
<string name="large_manga_save_confirm">This manga has %d chapters. Do you want to save all of it?</string>
|
||||
<string name="large_manga_save_confirm">This manga has %s. Do you want to save all of it?</string>
|
||||
<string name="save_manga">Save manga</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="enabled_d_from_d">Enabled %1$d from %2$d</string>
|
||||
<string name="enabled_d_from_d" tools:ignore="PluralsCandidate">Enabled %1$d from %2$d</string>
|
||||
<string name="new_chapters">New chapters</string>
|
||||
<string name="show_notification_new_chapters">Notify about updates of manga you are reading</string>
|
||||
<string name="download">Download</string>
|
||||
|
||||
Reference in New Issue
Block a user