Compare commits

...

19 Commits

Author SHA1 Message Date
4db61d3c04 Merge branch 'master' into devel 2024-01-24 12:32:04 +02:00
cd0575a524 Update parsers 2024-01-24 12:28:50 +02:00
6eb2608f88 Disable autofill for protect password fields #702
(cherry picked from commit e7c9d1943d)
2024-01-24 12:16:18 +02:00
39e21ff93c Last read order in favorites #705
(cherry picked from commit b1240e7efa)
2024-01-24 12:16:12 +02:00
5ec2eab6b8 Fix webtoon scroll dispatching
(cherry picked from commit a0a72b1192)
2024-01-24 12:15:41 +02:00
850f6c2f3e Fix pages numbers
(cherry picked from commit 83cb35fe6e)
2024-01-24 12:09:11 +02:00
ec53eb9c70 Incognito mode indicator in reader
(cherry picked from commit db1ddf539c)
2024-01-24 12:08:52 +02:00
cdd76f723f Fix favorites counters
(cherry picked from commit d56fc674ab)
2024-01-24 12:08:17 +02:00
e7c9d1943d Disable autofill for protect password fields #702 2024-01-24 11:56:04 +02:00
b1240e7efa Last read order in favorites #705 2024-01-24 11:53:53 +02:00
a0a72b1192 Fix webtoon scroll dispatching 2024-01-24 11:36:48 +02:00
5d9a59d577 Update parsers and migrate to float chapter numbers 2024-01-22 17:11:00 +02:00
a37e8825b0 All favorites item change payload 2024-01-20 09:31:17 +02:00
2450544454 Update parsers
(cherry picked from commit da2ad40adf)
2024-01-20 09:22:28 +02:00
f6a510653e Fix import dialog injection
(cherry picked from commit af5716a8ce)
2024-01-20 09:21:43 +02:00
5990da587c Update supported links domains
(cherry picked from commit a98202e15e)
2024-01-20 09:21:40 +02:00
91e3d2f5db Skip unsupported sources in global search
(cherry picked from commit d6887e2d75)
2024-01-20 09:21:35 +02:00
971c683746 Show all favorites on categories screen
(cherry picked from commit 2a5300a634)
2024-01-20 09:21:27 +02:00
15e9aaab26 Fix pages list scrolling
(cherry picked from commit a1120ea709)
2024-01-20 09:21:18 +02:00
24 changed files with 122 additions and 72 deletions

View File

@@ -17,7 +17,7 @@ android {
minSdk = 21
targetSdk = 34
versionCode = 615
versionName = '6.6.5'
versionName = '6.6.6'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
@@ -82,7 +82,7 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:8852d1e22e') {
implementation('com.github.KotatsuApp:kotatsu-parsers:a8f9423307') {
exclude group: 'org.json', module: 'json'
}

View File

@@ -13,6 +13,8 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.mapToSet
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import com.google.android.material.R as materialR
@JvmName("mangaIds")
@@ -113,3 +115,16 @@ val Manga.appUrl: Uri
.appendQueryParameter("name", title)
.appendQueryParameter("url", url)
.build()
private val chaptersNumberFormat = DecimalFormat("#.#").also { f ->
f.decimalFormatSymbols = DecimalFormatSymbols.getInstance().also {
it.decimalSeparator = '.'
}
}
fun MangaChapter.formatNumber(): String? {
if (number <= 0f) {
return null
}
return chaptersNumberFormat.format(number.toDouble())
}

View File

@@ -19,7 +19,8 @@ data class ParcelableChapter(
MangaChapter(
id = parcel.readLong(),
name = parcel.readString().orEmpty(),
number = parcel.readInt(),
number = parcel.readFloat(),
volume = parcel.readInt(),
url = parcel.readString().orEmpty(),
scanlator = parcel.readString(),
uploadDate = parcel.readLong(),
@@ -31,7 +32,8 @@ data class ParcelableChapter(
override fun ParcelableChapter.write(parcel: Parcel, flags: Int) = with(chapter) {
parcel.writeLong(id)
parcel.writeString(name)
parcel.writeInt(number)
parcel.writeFloat(number)
parcel.writeInt(volume)
parcel.writeString(url)
parcel.writeString(scanlator)
parcel.writeLong(uploadDate)

View File

@@ -347,7 +347,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
var historySortOrder: ListSortOrder
get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.UPDATED)
get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.LAST_READ)
set(value) = prefs.edit { putEnumValue(KEY_HISTORY_ORDER, value) }
var allFavoritesSortOrder: ListSortOrder

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.details.ui.model
import android.text.format.DateUtils
import org.jsoup.internal.StringUtil.StringJoiner
import org.koitharu.kotatsu.core.model.formatNumber
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.MangaChapter
@@ -49,8 +50,8 @@ data class ChapterListItem(
private fun buildDescription(): String {
val joiner = StringJoiner(" • ")
if (chapter.number != 0) {
joiner.add("#").append(chapter.number.toString())
chapter.formatNumber()?.let {
joiner.add("#").append(it)
}
uploadDate?.let { date ->
joiner.add(date.toString())

View File

@@ -21,6 +21,7 @@ import kotlinx.coroutines.plus
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.formatNumber
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
@@ -306,7 +307,7 @@ class DownloadsViewModel @Inject constructor(
return chapters.mapNotNullTo(ArrayList(size)) {
if (chapterIds == null || it.id in chapterIds) {
DownloadChapter(
number = it.number,
number = it.formatNumber(),
name = it.name,
isDownloaded = it.id in localChapters,
)

View File

@@ -4,7 +4,7 @@ import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
data class DownloadChapter(
val number: Int,
val number: String?,
val name: String,
val isDownloaded: Boolean,
) : ListModel {

View File

@@ -193,8 +193,8 @@ abstract class FavouritesDao {
ListSortOrder.ALPHABETIC -> "manga.title ASC"
ListSortOrder.ALPHABETIC_REVERSE -> "manga.title DESC"
ListSortOrder.NEW_CHAPTERS -> "IFNULL((SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC"
ListSortOrder.UPDATED, // for legacy support
ListSortOrder.PROGRESS -> "IFNULL((SELECT percent FROM history WHERE history.manga_id = manga.manga_id), 0) DESC"
ListSortOrder.LAST_READ -> "IFNULL((SELECT updated_at FROM history WHERE history.manga_id = manga.manga_id), 0) DESC"
else -> throw IllegalArgumentException("Sort order $sortOrder is not supported")
}

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.favourites.ui.categories.adapter
import org.koitharu.kotatsu.favourites.domain.model.Cover
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
data class AllCategoriesListModel(
@@ -12,4 +13,12 @@ data class AllCategoriesListModel(
override fun areItemsTheSame(other: ListModel): Boolean {
return other is AllCategoriesListModel
}
override fun getChangePayload(previousState: ListModel): Any? {
return if (previousState is AllCategoriesListModel && previousState.isVisible != isVisible) {
ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED
} else {
super.getChangePayload(previousState)
}
}
}

View File

@@ -35,7 +35,7 @@ abstract class HistoryDao {
fun observeAll(order: ListSortOrder): Flow<List<HistoryWithManga>> {
val orderBy = when (order) {
ListSortOrder.UPDATED -> "history.updated_at DESC"
ListSortOrder.LAST_READ -> "history.updated_at DESC"
ListSortOrder.NEWEST -> "history.created_at DESC"
ListSortOrder.PROGRESS -> "history.percent DESC"
ListSortOrder.ALPHABETIC -> "manga.title"

View File

@@ -172,7 +172,7 @@ class HistoryListViewModel @Inject constructor(
}
private fun MangaHistory.header(order: ListSortOrder): ListHeader? = when (order) {
ListSortOrder.UPDATED -> ListHeader(calculateTimeAgo(updatedAt))
ListSortOrder.LAST_READ -> ListHeader(calculateTimeAgo(updatedAt))
ListSortOrder.NEWEST -> ListHeader(calculateTimeAgo(createdAt))
ListSortOrder.PROGRESS -> ListHeader(
when (percent) {

View File

@@ -9,7 +9,6 @@ enum class ListSortOrder(
@StringRes val titleResId: Int,
) {
UPDATED(R.string.updated),
NEWEST(R.string.order_added),
PROGRESS(R.string.progress),
ALPHABETIC(R.string.by_name),
@@ -17,14 +16,15 @@ enum class ListSortOrder(
RATING(R.string.by_rating),
RELEVANCE(R.string.by_relevance),
NEW_CHAPTERS(R.string.new_chapters),
LAST_READ(R.string.last_read),
;
fun isGroupingSupported() = this == UPDATED || this == NEWEST || this == PROGRESS
fun isGroupingSupported() = this == LAST_READ || this == NEWEST || this == PROGRESS
companion object {
val HISTORY: Set<ListSortOrder> = EnumSet.of(UPDATED, NEWEST, PROGRESS, ALPHABETIC, ALPHABETIC_REVERSE, NEW_CHAPTERS)
val FAVORITES: Set<ListSortOrder> = EnumSet.of(ALPHABETIC, ALPHABETIC_REVERSE, NEWEST, RATING, NEW_CHAPTERS, PROGRESS)
val HISTORY: Set<ListSortOrder> = EnumSet.of(LAST_READ, NEWEST, PROGRESS, ALPHABETIC, ALPHABETIC_REVERSE, NEW_CHAPTERS)
val FAVORITES: Set<ListSortOrder> = EnumSet.of(ALPHABETIC, ALPHABETIC_REVERSE, NEWEST, RATING, NEW_CHAPTERS, PROGRESS, LAST_READ)
val SUGGESTIONS: Set<ListSortOrder> = EnumSet.of(RELEVANCE)
operator fun invoke(value: String, fallback: ListSortOrder) = entries.find(value) ?: fallback

View File

@@ -12,6 +12,8 @@ import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.find
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault
import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
@@ -91,6 +93,7 @@ class MangaIndex(source: String?) {
if (!chapters.has(chapter.id.toString())) {
val jo = JSONObject()
jo.put("number", chapter.number)
jo.put("volume", chapter.volume)
jo.put("url", chapter.url)
jo.put("name", chapter.name)
jo.put("uploadDate", chapter.uploadDate)
@@ -162,7 +165,8 @@ class MangaIndex(source: String?) {
id = k.toLong(),
name = v.getString("name"),
url = v.getString("url"),
number = v.getInt("number"),
number = v.getFloatOrDefault("number", 0f),
volume = v.getIntOrDefault("volume", 0),
uploadDate = v.getLongOrDefault("uploadDate", 0L),
scanlator = v.getStringOrNull("scanlator"),
branch = v.getStringOrNull("branch"),

View File

@@ -71,7 +71,8 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) {
MangaChapter(
id = "$i${f.name}".longHashCode(),
name = f.nameWithoutExtension.toHumanReadable(),
number = i + 1,
number = 0f,
volume = 0,
source = MangaSource.LOCAL,
uploadDate = f.creationTime,
url = f.toUri().toString(),

View File

@@ -100,6 +100,7 @@ sealed class LocalMangaInput(
id = id,
name = name,
number = number,
volume = volume,
url = url,
scanlator = scanlator,
uploadDate = uploadDate,

View File

@@ -76,7 +76,8 @@ class LocalMangaZipInput(root: File) : LocalMangaInput(root) {
MangaChapter(
id = "$i$s".longHashCode(),
name = s.ifEmpty { title },
number = i + 1,
number = 0f,
volume = 0,
source = MangaSource.LOCAL,
uploadDate = 0L,
url = uriBuilder.fragment(s).build().toString(),

View File

@@ -51,6 +51,7 @@ import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.history.domain.HistoryUpdateUseCase
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.assertNotNull
import org.koitharu.kotatsu.reader.domain.ChaptersLoader
import org.koitharu.kotatsu.reader.domain.DetectReaderModeUseCase
import org.koitharu.kotatsu.reader.domain.PageLoader
@@ -262,7 +263,7 @@ class ReaderViewModel @Inject constructor(
}
@MainThread
fun onCurrentPageChanged(position: Int) {
fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) {
val prevJob = stateChangeJob
val pages = content.value.pages // capture immediately
stateChangeJob = launchJob(Dispatchers.Default) {
@@ -271,7 +272,8 @@ class ReaderViewModel @Inject constructor(
if (pages.size != content.value.pages.size) {
return@launchJob // TODO
}
pages.getOrNull(position)?.let { page ->
val centerPos = (lowerPos + upperPos) / 2
pages.getOrNull(centerPos)?.let { page ->
currentState.update { cs ->
cs?.copy(chapterId = page.chapterId, page = page.index)
}
@@ -281,14 +283,14 @@ class ReaderViewModel @Inject constructor(
return@launchJob
}
ensureActive()
if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.last().chapterId, isNext = true)
}
if (position <= BOUNDS_PAGE_OFFSET) {
if (lowerPos <= BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.first().chapterId, isNext = false)
}
if (pageLoader.isPrefetchApplicable()) {
pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT))
pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT))
}
}
}
@@ -381,18 +383,20 @@ class ReaderViewModel @Inject constructor(
@WorkerThread
private fun notifyStateChanged() {
val state = getCurrentState()
val chapter = state?.chapterId?.let { chaptersLoader.peekChapter(it) }
val state = getCurrentState().assertNotNull("state") ?: return
val chapter = chaptersLoader.peekChapter(state.chapterId).assertNotNull("chapter") ?: return
val m = manga.assertNotNull("manga") ?: return
val chapterIndex = m.chapters[chapter.branch]?.indexOf(chapter) ?: -1
val newState = ReaderUiState(
mangaName = manga?.toManga()?.title,
branch = chapter?.branch,
chapterName = chapter?.name,
chapterNumber = chapter?.number ?: 0,
chaptersTotal = manga?.chapters?.get(chapter?.branch)?.size ?: 0,
totalPages = if (chapter != null) chaptersLoader.getPagesCount(chapter.id) else 0,
currentPage = state?.page ?: 0,
mangaName = m.toManga().title,
branch = chapter.branch,
chapterName = chapter.name,
chapterNumber = chapterIndex + 1,
chaptersTotal = m.chapters[chapter.branch]?.size ?: 0,
totalPages = chaptersLoader.getPagesCount(chapter.id),
currentPage = state.page,
isSliderEnabled = settings.isReaderSliderEnabled,
percent = if (state != null) computePercent(state.chapterId, state.page) else PROGRESS_NONE,
percent = computePercent(state.chapterId, state.page),
)
uiState.value = newState
}

View File

@@ -179,7 +179,7 @@ abstract class BasePagerReaderFragment : BaseReaderFragment<FragmentReaderPagerB
}
protected open fun notifyPageChanged(page: Int) {
viewModel.onCurrentPageChanged(page)
viewModel.onCurrentPageChanged(page, page)
}
companion object {

View File

@@ -32,7 +32,8 @@ class ReversedReaderFragment : BasePagerReaderFragment() {
}
override fun notifyPageChanged(page: Int) {
viewModel.onCurrentPageChanged(reversed(page))
val pos = reversed(page)
viewModel.onCurrentPageChanged(pos, pos)
}
private fun reversed(position: Int): Int {

View File

@@ -24,7 +24,8 @@ import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import javax.inject.Inject
@AndroidEntryPoint
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>() {
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>(),
WebtoonRecyclerView.OnWebtoonScrollListener {
@Inject
lateinit var networkState: NetworkState
@@ -46,7 +47,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
with(binding.recyclerView) {
setHasFixedSize(true)
adapter = readerAdapter
addOnPageScrollListener(PageScrollListener())
addOnPageScrollListener(this@WebtoonReaderFragment)
recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {
addOnScrollListener(it)
}
@@ -70,6 +71,15 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
exceptionResolver = exceptionResolver,
)
override fun onScrollChanged(
recyclerView: WebtoonRecyclerView,
dy: Int,
firstVisiblePosition: Int,
lastVisiblePosition: Int,
) {
viewModel.onCurrentPageChanged(firstVisiblePosition, lastVisiblePosition)
}
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
val setItems = launch {
requireAdapter().setItems(pages)
@@ -91,7 +101,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
?.restoreScroll(pendingState.scroll)
}
}
notifyPageChanged(position)
viewModel.onCurrentPageChanged(position, position)
} else {
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
.show()
@@ -121,10 +131,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
viewBinding?.frame?.onZoomOut()
}
private fun notifyPageChanged(page: Int) {
viewModel.onCurrentPageChanged(page)
}
override fun switchPageBy(delta: Int) {
with(requireViewBinding().recyclerView) {
if (isAnimationEnabled()) {
@@ -147,12 +153,4 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
}
return true
}
private inner class PageScrollListener : WebtoonRecyclerView.OnPageScrollListener() {
override fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) {
super.onPageChanged(recyclerView, index)
notifyPageChanged(index)
}
}
}

View File

@@ -6,8 +6,8 @@ import android.view.View
import androidx.core.view.ViewCompat.TYPE_TOUCH
import androidx.core.view.forEach
import androidx.core.view.iterator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
import java.util.LinkedList
import java.util.WeakHashMap
@@ -15,7 +15,8 @@ class WebtoonRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
private var onPageScrollListeners: MutableList<OnPageScrollListener>? = null
private var onPageScrollListeners = LinkedList<OnWebtoonScrollListener>()
private val scrollDispatcher = WebtoonScrollDispatcher()
private val detachedViews = WeakHashMap<View, Unit>()
private var isFixingScroll: Boolean = false
@@ -103,22 +104,20 @@ class WebtoonRecyclerView @JvmOverloads constructor(
return 0
}
fun addOnPageScrollListener(listener: OnPageScrollListener) {
val list = onPageScrollListeners ?: LinkedList<OnPageScrollListener>().also { onPageScrollListeners = it }
list.add(listener)
fun addOnPageScrollListener(listener: OnWebtoonScrollListener) {
onPageScrollListeners.add(listener)
}
fun removeOnPageScrollListener(listener: OnPageScrollListener) {
onPageScrollListeners?.remove(listener)
fun removeOnPageScrollListener(listener: OnWebtoonScrollListener) {
onPageScrollListeners.remove(listener)
}
private fun notifyScrollChanged(dy: Int) {
val listeners = onPageScrollListeners
if (listeners.isNullOrEmpty()) {
if (listeners.isEmpty()) {
return
}
val centerPosition = findCenterViewPosition()
listeners.forEach { it.dispatchScroll(this, dy, centerPosition) }
scrollDispatcher.dispatchScroll(this, dy)
}
fun relayoutChildren() {
@@ -162,20 +161,30 @@ class WebtoonRecyclerView @JvmOverloads constructor(
else -> false
}
abstract class OnPageScrollListener {
private class WebtoonScrollDispatcher {
private var lastPosition = NO_POSITION
private var firstPos = NO_POSITION
private var lastPos = NO_POSITION
fun dispatchScroll(recyclerView: WebtoonRecyclerView, dy: Int, centerPosition: Int) {
onScroll(recyclerView, dy)
if (centerPosition != NO_POSITION && centerPosition != lastPosition) {
lastPosition = centerPosition
onPageChanged(recyclerView, centerPosition)
fun dispatchScroll(rv: WebtoonRecyclerView, dy: Int) {
val lm = rv.layoutManager as? LinearLayoutManager
if (lm == null) {
firstPos = NO_POSITION
lastPos = NO_POSITION
return
}
val newFirstPos = lm.findFirstVisibleItemPosition()
val newLastPos = lm.findLastVisibleItemPosition()
if (newFirstPos != firstPos || newLastPos != lastPos) {
firstPos = newFirstPos
lastPos = newLastPos
rv.onPageScrollListeners.forEach { it.onScrollChanged(rv, dy, newFirstPos, newLastPos) }
}
}
}
open fun onScroll(recyclerView: WebtoonRecyclerView, dy: Int) = Unit
interface OnWebtoonScrollListener {
open fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) = Unit
fun onScrollChanged(recyclerView: WebtoonRecyclerView, dy: Int, firstVisiblePosition: Int, lastVisiblePosition: Int)
}
}

View File

@@ -52,6 +52,7 @@
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:imeOptions="actionDone"
android:importantForAutofill="no"
android:inputType="textPassword"
android:maxLength="24"
android:singleLine="true"
@@ -80,4 +81,4 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -53,6 +53,7 @@
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:imeOptions="actionDone"
android:importantForAutofill="no"
android:inputType="textPassword"
android:maxLength="24"
android:singleLine="true"
@@ -94,4 +95,4 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -566,4 +566,5 @@
<string name="volume_unknown">Unknown volume</string>
<string name="incognito_mode_hint">Your reading progress will not be saved</string>
<string name="vertical">Vertical</string>
<string name="last_read">Last read</string>
</resources>