fixed bug with returning from the reading state and search
This commit is contained in:
parent
b0f45aaf1b
commit
1c6a80c43b
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@ captures
|
||||
!*.xcworkspace/contents.xcworkspacedata
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
node_modules/
|
||||
/composeApp/release/
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,37 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "net.sergeych.toread",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0",
|
||||
"outputFile": "composeApp-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
"baselineProfiles": [
|
||||
{
|
||||
"minApi": 28,
|
||||
"maxApi": 30,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/1/composeApp-release.dm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"minApi": 31,
|
||||
"maxApi": 2147483647,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/0/composeApp-release.dm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"minSdkVersionForDexing": 26
|
||||
}
|
||||
@ -120,6 +120,7 @@ internal fun LibraryScreen(
|
||||
val sourceItems = if (searchActive) visibleSearchResults else libraryItems
|
||||
val recentlyAdded = recentlyAddedItems.filterNot { it.fileId in hiddenFileIds }
|
||||
val visibleItems = selectedFilter.apply(sourceItems, recentlyAdded, searchActive)
|
||||
.withoutDuplicateFileIds()
|
||||
val canLoadMore = !searchActive && selectedFilter.usesPagedLibrary && !endReached
|
||||
|
||||
suspend fun loadPage(reset: Boolean = false) {
|
||||
@ -143,7 +144,7 @@ internal fun LibraryScreen(
|
||||
if (fileId !in visibleFileIds) coverCache.remove(fileId)
|
||||
}
|
||||
} else {
|
||||
items = items + page
|
||||
items = items.appendNewLibraryItems(page)
|
||||
}
|
||||
nextOffset = offset + page.size
|
||||
endReached = page.size < limit
|
||||
@ -1032,13 +1033,7 @@ private enum class LibraryFilter(val usesPagedLibrary: Boolean = true) {
|
||||
.filter { it.readingStatus == BookReadingStatus.NEW }
|
||||
.sortedByImportedThenTitle()
|
||||
}
|
||||
MyLibrary -> sourceItems.filter {
|
||||
it.fileId !in recentlyAddedIds &&
|
||||
it.readingStatus != BookReadingStatus.READING &&
|
||||
it.readingStatus != BookReadingStatus.TO_READ &&
|
||||
it.readingStatus != BookReadingStatus.READ &&
|
||||
it.readingStatus != BookReadingStatus.NOT_INTERESTED
|
||||
}.sortedByTitleNaturally()
|
||||
MyLibrary -> sourceItems.myLibraryItems()
|
||||
ToRead -> sourceItems
|
||||
.filter { it.readingStatus == BookReadingStatus.TO_READ }
|
||||
.sortedByLastReadThenTitle()
|
||||
@ -1070,6 +1065,10 @@ private fun List<LibraryItem>.sortedByImportedThenTitle(): List<LibraryItem> =
|
||||
private fun List<LibraryItem>.sortedByTitleNaturally(): List<LibraryItem> =
|
||||
sortedWith(LibraryItemNaturalTitleComparator)
|
||||
|
||||
internal fun List<LibraryItem>.myLibraryItems(): List<LibraryItem> =
|
||||
filter { it.readingStatus != BookReadingStatus.NOT_INTERESTED }
|
||||
.sortedByTitleNaturally()
|
||||
|
||||
private val LibraryItemNaturalTitleComparator = Comparator<LibraryItem> { left, right ->
|
||||
val titleCompare = naturalCompare(left.title, right.title)
|
||||
if (titleCompare != 0) titleCompare else left.fileId.compareTo(right.fileId)
|
||||
@ -1194,6 +1193,15 @@ private fun Long.formatBytes(): String =
|
||||
private fun List<LibraryItem>.replaceLibraryItem(item: LibraryItem): List<LibraryItem> =
|
||||
map { current -> if (current.fileId == item.fileId) item else current }
|
||||
|
||||
internal fun List<LibraryItem>.appendNewLibraryItems(page: List<LibraryItem>): List<LibraryItem> {
|
||||
if (isEmpty()) return page.withoutDuplicateFileIds()
|
||||
val fileIds = mapTo(mutableSetOf()) { it.fileId }
|
||||
return this + page.filter { fileIds.add(it.fileId) }
|
||||
}
|
||||
|
||||
internal fun List<LibraryItem>.withoutDuplicateFileIds(): List<LibraryItem> =
|
||||
distinctBy { it.fileId }
|
||||
|
||||
private fun Key.isEnterKey(): Boolean = this == Key.Enter || this == Key.NumPadEnter
|
||||
|
||||
private fun LibraryScanProgress.toCatalogScanMessage(): String {
|
||||
|
||||
@ -34,6 +34,7 @@ data class ReadAloudVoiceOption(
|
||||
data class ReadAloudTextReplacement(
|
||||
val from: String,
|
||||
val to: String,
|
||||
val caseSensitive: Boolean = false,
|
||||
)
|
||||
|
||||
data class ReadAloudSettingsState(
|
||||
|
||||
@ -537,7 +537,7 @@ private fun ReaderText(
|
||||
private fun readerParagraphTextStyle(language: String?): TextStyle =
|
||||
MaterialTheme.typography.bodyLarge.copy(
|
||||
fontWeight = if( isAndroidPlatform) FontWeight(350) else FontWeight.Normal,
|
||||
fontSize = if( isAndroidPlatform) 19.sp else 18.sp,
|
||||
fontSize = if( isAndroidPlatform) 20.sp else 18.sp,
|
||||
lineHeight = 26.sp,
|
||||
letterSpacing = if (isAndroidPlatform) 0.sp else MaterialTheme.typography.bodyLarge.letterSpacing,
|
||||
hyphens = if (isAndroidPlatform) Hyphens.Auto else Hyphens.Unspecified,
|
||||
@ -808,7 +808,7 @@ private fun String.toReadAloudSpokenText(): String =
|
||||
|
||||
private fun String.applyReadAloudTextReplacements(replacements: List<ReadAloudTextReplacement>): String =
|
||||
replacements.fold(this) { text, replacement ->
|
||||
text.replace(replacement.from, replacement.to)
|
||||
text.replace(replacement.from, replacement.to, ignoreCase = !replacement.caseSensitive)
|
||||
}
|
||||
|
||||
private fun String.withReadAloudStressMarkers(): String = buildString(length) {
|
||||
@ -886,9 +886,13 @@ private const val CombiningAcuteAccent = '\u0301'
|
||||
private const val ReadAloudStressableLetters = "аеёиоуыэюяАЕЁИОУЫЭЮЯaeiouyAEIOUY"
|
||||
|
||||
private val ReadAloudHardcodedTextReplacements = listOf(
|
||||
ReadAloudTextReplacement("Господа,", "Господ/а,"),
|
||||
ReadAloudTextReplacement("господа", "господ/а"),
|
||||
ReadAloudTextReplacement("прошуршала", "прошурш/ала"),
|
||||
ReadAloudTextReplacement(from = "Господа,", to = "Господ/а,", caseSensitive = true),
|
||||
ReadAloudTextReplacement(from = "господа", to = "господ/а", caseSensitive = true),
|
||||
ReadAloudTextReplacement(from = "прошуршала", to = "прошурш/ала", caseSensitive = false),
|
||||
ReadAloudTextReplacement(from = "железной дорогой", to = "железной дор/огой"),
|
||||
ReadAloudTextReplacement(from = "пустовавшими", to = "пустов/авшими"),
|
||||
ReadAloudTextReplacement(from = "непарадной", to = "непар/адной"),
|
||||
ReadAloudTextReplacement(from = "свертывали", to = "свёртывали"),
|
||||
)
|
||||
|
||||
private fun List<Fb2Section>.flattenSections(depth: Int = 0): List<ChapterEntry> =
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
package net.sergeych.toread
|
||||
|
||||
import net.sergeych.toread.storage.BookReadingStatus
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class LibraryScreenItemListTest {
|
||||
@Test
|
||||
fun appendNewLibraryItemsSkipsItemsAlreadyInMemory() {
|
||||
val existing = listOf(
|
||||
libraryItem("file-1", "First"),
|
||||
libraryItem("file-2", "Second"),
|
||||
)
|
||||
val page = listOf(
|
||||
libraryItem("file-2", "Second duplicate"),
|
||||
libraryItem("file-3", "Third"),
|
||||
)
|
||||
|
||||
val merged = existing.appendNewLibraryItems(page)
|
||||
|
||||
assertEquals(listOf("file-1", "file-2", "file-3"), merged.map { it.fileId })
|
||||
assertEquals("Second", merged[1].title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun appendNewLibraryItemsDeduplicatesFirstPage() {
|
||||
val page = listOf(
|
||||
libraryItem("file-1", "First"),
|
||||
libraryItem("file-1", "First duplicate"),
|
||||
libraryItem("file-2", "Second"),
|
||||
)
|
||||
|
||||
val merged = emptyList<LibraryItem>().appendNewLibraryItems(page)
|
||||
|
||||
assertEquals(listOf("file-1", "file-2"), merged.map { it.fileId })
|
||||
assertEquals("First", merged.first().title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun myLibraryShowsAllBooksExceptNotInterested() {
|
||||
val items = listOf(
|
||||
libraryItem("file-reading", "Reading", BookReadingStatus.READING),
|
||||
libraryItem("file-to-read", "To Read", BookReadingStatus.TO_READ),
|
||||
libraryItem("file-read", "Read", BookReadingStatus.READ),
|
||||
libraryItem("file-new", "New", BookReadingStatus.NEW),
|
||||
libraryItem("file-not-interested", "Not Interested", BookReadingStatus.NOT_INTERESTED),
|
||||
)
|
||||
|
||||
val filtered = items.myLibraryItems()
|
||||
|
||||
assertEquals(
|
||||
listOf("file-new", "file-read", "file-reading", "file-to-read"),
|
||||
filtered.map { it.fileId },
|
||||
)
|
||||
}
|
||||
|
||||
private fun libraryItem(
|
||||
fileId: String,
|
||||
title: String,
|
||||
readingStatus: BookReadingStatus = BookReadingStatus.NEW,
|
||||
): LibraryItem =
|
||||
LibraryItem(
|
||||
fileId = fileId,
|
||||
bookId = "book-$fileId",
|
||||
title = title,
|
||||
format = "fb2",
|
||||
sizeBytes = null,
|
||||
storageUri = null,
|
||||
lastSeenAt = null,
|
||||
readingStatus = readingStatus,
|
||||
)
|
||||
}
|
||||
@ -89,6 +89,20 @@ class ReadAloudContentPlanTest {
|
||||
assertEquals("Господа́, идите.", plan.sentences.single().spokenText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hardcodedReadAloudReplacementsAreCaseSensitive() {
|
||||
val plan = buildReaderContentPlan(
|
||||
Fb2Book(
|
||||
title = "Book",
|
||||
sections = listOf(
|
||||
Fb2Section(blocks = listOf(paragraph("ГОСПОДА, идите."))),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals("ГОСПОДА, идите.", plan.sentences.single().spokenText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stressMarkersSupportRussianAndEnglishVowels() {
|
||||
val plan = buildReaderContentPlan(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user