Merge branch 'devel' into feature/nextgen
This commit is contained in:
@@ -6,8 +6,6 @@ import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.koitharu.kotatsu.core.db.migrations.*
|
||||
import java.io.IOException
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@@ -19,10 +17,20 @@ class MangaDatabaseTest {
|
||||
MangaDatabase::class.java,
|
||||
)
|
||||
|
||||
private val migrations = databaseMigrations
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrateAll() {
|
||||
fun versions() {
|
||||
assertEquals(1, migrations.first().startVersion)
|
||||
repeat(migrations.size) { i ->
|
||||
assertEquals(i + 1, migrations[i].startVersion)
|
||||
assertEquals(i + 2, migrations[i].endVersion)
|
||||
}
|
||||
assertEquals(DATABASE_VERSION, migrations.last().endVersion)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun migrateAll() {
|
||||
helper.createDatabase(TEST_DB, 1).close()
|
||||
for (migration in migrations) {
|
||||
helper.runMigrationsAndValidate(
|
||||
@@ -34,9 +42,18 @@ class MangaDatabaseTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun prePopulate() {
|
||||
val resources = InstrumentationRegistry.getInstrumentation().targetContext.resources
|
||||
helper.createDatabase(TEST_DB, DATABASE_VERSION).use {
|
||||
DatabasePrePopulateCallback(resources).onCreate(it)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
const val TEST_DB = "test-db"
|
||||
<<<<<<< HEAD
|
||||
|
||||
val migrations = arrayOf(
|
||||
Migration1To2(),
|
||||
@@ -52,5 +69,7 @@ class MangaDatabaseTest {
|
||||
Migration11To12(),
|
||||
Migration12To13(),
|
||||
)
|
||||
=======
|
||||
>>>>>>> devel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
|
||||
import org.koitharu.kotatsu.bookmarks.data.BookmarksDao
|
||||
import org.koitharu.kotatsu.core.db.dao.MangaDao
|
||||
@@ -65,23 +66,24 @@ abstract class MangaDatabase : RoomDatabase() {
|
||||
abstract val scrobblingDao: ScrobblingDao
|
||||
}
|
||||
|
||||
fun MangaDatabase(context: Context): MangaDatabase = Room.databaseBuilder(
|
||||
context,
|
||||
MangaDatabase::class.java,
|
||||
"kotatsu-db"
|
||||
).addMigrations(
|
||||
Migration1To2(),
|
||||
Migration2To3(),
|
||||
Migration3To4(),
|
||||
Migration4To5(),
|
||||
Migration5To6(),
|
||||
Migration6To7(),
|
||||
Migration7To8(),
|
||||
Migration8To9(),
|
||||
Migration9To10(),
|
||||
Migration10To11(),
|
||||
Migration11To12(),
|
||||
Migration12To13(),
|
||||
).addCallback(
|
||||
DatabasePrePopulateCallback(context.resources)
|
||||
).build()
|
||||
val databaseMigrations: Array<Migration>
|
||||
get() = arrayOf(
|
||||
Migration1To2(),
|
||||
Migration2To3(),
|
||||
Migration3To4(),
|
||||
Migration4To5(),
|
||||
Migration5To6(),
|
||||
Migration6To7(),
|
||||
Migration7To8(),
|
||||
Migration8To9(),
|
||||
Migration9To10(),
|
||||
Migration10To11(),
|
||||
Migration11To12(),
|
||||
Migration12To13(),
|
||||
)
|
||||
|
||||
fun MangaDatabase(context: Context): MangaDatabase = Room
|
||||
.databaseBuilder(context, MangaDatabase::class.java, "kotatsu-db")
|
||||
.addMigrations(*databaseMigrations)
|
||||
.addCallback(DatabasePrePopulateCallback(context.resources))
|
||||
.build()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.koitharu.kotatsu.core.exceptions
|
||||
|
||||
class CaughtException(cause: Throwable, override val message: String?) : RuntimeException(cause)
|
||||
@@ -135,6 +135,26 @@ class ChaptersFragment :
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_select_range -> {
|
||||
val controller = selectionController ?: return false
|
||||
val items = chaptersAdapter?.items ?: return false
|
||||
val ids = HashSet(controller.peekCheckedIds())
|
||||
val buffer = HashSet<Long>()
|
||||
var isAdding = false
|
||||
for (x in items) {
|
||||
if (x.chapter.id in ids) {
|
||||
isAdding = true
|
||||
if (buffer.isNotEmpty()) {
|
||||
ids.addAll(buffer)
|
||||
buffer.clear()
|
||||
}
|
||||
} else if (isAdding) {
|
||||
buffer.add(x.chapter.id)
|
||||
}
|
||||
}
|
||||
controller.addAll(ids)
|
||||
true
|
||||
}
|
||||
R.id.action_select_all -> {
|
||||
val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false
|
||||
selectionController?.addAll(ids)
|
||||
@@ -158,14 +178,24 @@ class ChaptersFragment :
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val selectedIds = selectionController?.peekCheckedIds() ?: return false
|
||||
val items = chaptersAdapter?.items?.filter { x -> x.chapter.id in selectedIds }.orEmpty()
|
||||
menu.findItem(R.id.action_save).isVisible = items.none { x ->
|
||||
val allItems = chaptersAdapter?.items.orEmpty()
|
||||
val items = allItems.withIndex().filter { (_, x) -> x.chapter.id in selectedIds }
|
||||
menu.findItem(R.id.action_save).isVisible = items.none { (_, x) ->
|
||||
x.chapter.source == MangaSource.LOCAL
|
||||
}
|
||||
menu.findItem(R.id.action_delete).isVisible = items.all { x ->
|
||||
menu.findItem(R.id.action_delete).isVisible = items.all { (_, x) ->
|
||||
x.chapter.source == MangaSource.LOCAL
|
||||
}
|
||||
menu.findItem(R.id.action_select_all).isVisible = items.size < allItems.size
|
||||
mode.title = items.size.toString()
|
||||
var hasGap = false
|
||||
for (i in 0 until items.size - 1) {
|
||||
if (items[i].index + 1 != items[i + 1].index) {
|
||||
hasGap = true
|
||||
break
|
||||
}
|
||||
}
|
||||
menu.findItem(R.id.action_select_range).isVisible = hasGap
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.details.ui
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.acra.ACRA
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
|
||||
@@ -14,7 +13,6 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.model.toListItem
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
@@ -22,7 +20,6 @@ import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.utils.ext.iterator
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.setCurrentManga
|
||||
|
||||
class MangaDetailsDelegate(
|
||||
private val intent: MangaIntent,
|
||||
@@ -45,7 +42,6 @@ class MangaDetailsDelegate(
|
||||
suspend fun doLoad() {
|
||||
var manga = mangaDataRepository.resolveIntent(intent)
|
||||
?: throw MangaNotFoundException("Cannot find manga")
|
||||
ACRA.setCurrentManga(manga)
|
||||
mangaData.value = manga
|
||||
manga = MangaRepository(manga.source).getDetails(manga)
|
||||
// find default branch
|
||||
|
||||
@@ -433,4 +433,4 @@ class ReaderActivity :
|
||||
.putExtra(MangaIntent.KEY_ID, mangaId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.acra.ACRA
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
@@ -32,7 +31,6 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.setCurrentManga
|
||||
import java.util.*
|
||||
|
||||
private const val BOUNDS_PAGE_OFFSET = 2
|
||||
@@ -262,7 +260,6 @@ class ReaderViewModel(
|
||||
private fun loadImpl() {
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
var manga = dataRepository.resolveIntent(intent) ?: throw MangaNotFoundException("Cannot find manga")
|
||||
ACRA.setCurrentManga(manga)
|
||||
mangaData.value = manga
|
||||
val repo = MangaRepository(manga.source)
|
||||
manga = repo.getDetails(manga)
|
||||
|
||||
@@ -3,19 +3,15 @@ package org.koitharu.kotatsu.utils.ext
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.res.Resources
|
||||
import okio.FileNotFoundException
|
||||
import org.acra.ACRA
|
||||
import org.acra.ktx.sendWithAcra
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||
import org.koitharu.kotatsu.core.exceptions.*
|
||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||
import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import java.net.SocketTimeoutException
|
||||
|
||||
fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
|
||||
fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
|
||||
is AuthRequiredException -> resources.getString(R.string.auth_required)
|
||||
is CloudFlareProtectedException -> resources.getString(R.string.captcha_required)
|
||||
is ActivityNotFoundException,
|
||||
@@ -23,22 +19,21 @@ fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
|
||||
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
|
||||
is FileNotFoundException -> resources.getString(R.string.file_not_found)
|
||||
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
||||
is ContentUnavailableException -> message
|
||||
is ParseException -> shortMessage
|
||||
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
||||
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
||||
else -> localizedMessage ?: resources.getString(R.string.error_occurred)
|
||||
}
|
||||
else -> localizedMessage
|
||||
} ?: resources.getString(R.string.error_occurred)
|
||||
|
||||
fun Throwable.isReportable(): Boolean {
|
||||
if (this !is Exception) {
|
||||
return true
|
||||
}
|
||||
return this is ParseException || this is IllegalArgumentException || this is IllegalStateException
|
||||
return this is ParseException || this is IllegalArgumentException ||
|
||||
this is IllegalStateException || this is RuntimeException
|
||||
}
|
||||
|
||||
fun Throwable.report(message: String?) {
|
||||
CaughtException(this, message).sendWithAcra()
|
||||
}
|
||||
|
||||
fun ACRA.setCurrentManga(manga: Manga?) = errorReporter.putCustomData("manga", manga?.publicUrl.toString())
|
||||
|
||||
private class CaughtException(cause: Throwable, override val message: String?) : RuntimeException(cause)
|
||||
}
|
||||
15
app/src/main/res/drawable/ic_select_range.xml
Normal file
15
app/src/main/res/drawable/ic_select_range.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M4,3H5V5H3V4A1,1 0,0 1,4 3M20,3A1,1 0,0 1,21 4V5H19V3H20M15,5V3H17V5H15M11,5V3H13V5H11M7,5V3H9V5H7M21,20A1,1 0,0 1,20 21H19V19H21V20M15,21V19H17V21H15M11,21V19H13V21H11M7,21V19H9V21H7M4,21A1,1 0,0 1,3 20V19H5V21H4M3,15H5V17H3V15M21,15V17H19V15H21M3,11H5V13H3V11M21,11V13H19V11H21M3,7H5V9H3V7M21,7V9H19V7H21Z" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M8.687,5.585L8.687,9.514L6.201,9.514L9.514,12.828 12.828,9.514L10.345,9.514L10.345,5.585ZM14.486,11.172 L11.172,14.486h2.483v3.929h1.658v-3.929h2.486z"
|
||||
android:strokeWidth="0.828309" />
|
||||
</vector>
|
||||
@@ -15,6 +15,12 @@
|
||||
android:title="@string/delete"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_select_range"
|
||||
android:icon="@drawable/ic_select_range"
|
||||
android:title="@string/select_range"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_select_all"
|
||||
android:icon="?actionModeSelectAllDrawable"
|
||||
|
||||
@@ -321,6 +321,7 @@
|
||||
<string name="clear_cookies_summary">Can help in case of some issues. All authorizations will be invalidated</string>
|
||||
<string name="show_all">Show all</string>
|
||||
<string name="invalid_domain_message">Invalid domain</string>
|
||||
<string name="select_range">Select range</string>
|
||||
<string name="clear_all_history">Clear all history</string>
|
||||
<string name="last_2_hours">Last 2 hours</string>
|
||||
<string name="history_cleared">History cleared</string>
|
||||
@@ -346,4 +347,4 @@
|
||||
<string name="storage_usage">Storage usage</string>
|
||||
<string name="available">Available</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user