diff --git a/app/build.gradle b/app/build.gradle index b9300fa75..0a21fc925 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdkVersion 21 targetSdkVersion 32 - versionCode 416 - versionName '3.4.4' + versionCode 417 + versionName '3.4.5' generatedDensities = [] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -83,7 +83,7 @@ afterEvaluate { } } dependencies { - implementation('com.github.nv95:kotatsu-parsers:6af8cec134') { + implementation('com.github.nv95:kotatsu-parsers:30071709af') { exclude group: 'org.json', module: 'json' } diff --git a/app/src/androidTest/java/org/koitharu/kotatsu/core/db/MangaDatabaseTest.kt b/app/src/androidTest/java/org/koitharu/kotatsu/core/db/MangaDatabaseTest.kt index 78aa09903..6ff4fe4b2 100644 --- a/app/src/androidTest/java/org/koitharu/kotatsu/core/db/MangaDatabaseTest.kt +++ b/app/src/androidTest/java/org/koitharu/kotatsu/core/db/MangaDatabaseTest.kt @@ -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 } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt index 3f48543fe..5552e10d5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -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() \ No newline at end of file +val databaseMigrations: Array + 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() diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CaughtException.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CaughtException.kt new file mode 100644 index 000000000..907917537 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CaughtException.kt @@ -0,0 +1,3 @@ +package org.koitharu.kotatsu.core.exceptions + +class CaughtException(cause: Throwable, override val message: String?) : RuntimeException(cause) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt index c0e5328ac..4b8328e28 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt @@ -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() + 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 } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt index c6c45ecc1..39ad3003b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt @@ -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 diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index b83d1d959..690aa1db9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -433,4 +433,4 @@ class ReaderActivity : .putExtra(MangaIntent.KEY_ID, mangaId) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 85acb6c89..4a46f4904 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -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) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt index 7b7f76408..79ee12775 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt @@ -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) \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_select_range.xml b/app/src/main/res/drawable/ic_select_range.xml new file mode 100644 index 000000000..617fdc0cb --- /dev/null +++ b/app/src/main/res/drawable/ic_select_range.xml @@ -0,0 +1,15 @@ + + + + diff --git a/app/src/main/res/menu/mode_chapters.xml b/app/src/main/res/menu/mode_chapters.xml index 8e3d52bab..6b5169c05 100644 --- a/app/src/main/res/menu/mode_chapters.xml +++ b/app/src/main/res/menu/mode_chapters.xml @@ -15,6 +15,12 @@ android:title="@string/delete" app:showAsAction="ifRoom|withText" /> + + Can help in case of some issues. All authorizations will be invalidated Show all Invalid domain + Select range Clear all history Last 2 hours History cleared @@ -346,4 +347,4 @@ Storage usage Available %s - %s - \ No newline at end of file +