From 6a0b523e86d0c6f4c517236d4b18b7d0d6e9fcbe Mon Sep 17 00:00:00 2001 From: Jobobby04 Date: Fri, 22 Apr 2022 19:24:50 -0400 Subject: [PATCH] Revert history Compose/SQLDelight changes --- app/build.gradle.kts | 22 -- .../eu/kanade/data/AndroidDatabaseHandler.kt | 94 ------ .../java/eu/kanade/data/DatabaseAdapter.kt | 20 -- .../java/eu/kanade/data/DatabaseHandler.kt | 39 --- .../java/eu/kanade/data/TransactionContext.kt | 160 ---------- .../eu/kanade/data/chapter/ChapterMapper.kt | 21 -- .../eu/kanade/data/exh/FeedSavedSearch.kt | 13 - .../java/eu/kanade/data/exh/SavedSearch.kt | 14 - .../eu/kanade/data/history/HistoryMapper.kt | 26 -- .../data/history/HistoryRepositoryImpl.kt | 91 ------ .../java/eu/kanade/data/manga/MangaMapper.kt | 27 -- .../java/eu/kanade/domain/DomainModule.kt | 26 -- .../eu/kanade/domain/chapter/model/Chapter.kt | 16 - .../history/interactor/DeleteHistoryTable.kt | 12 - .../domain/history/interactor/GetHistory.kt | 21 -- .../interactor/GetNextChapterForManga.kt | 13 - .../history/interactor/RemoveHistoryById.kt | 13 - .../interactor/RemoveHistoryByMangaId.kt | 12 - .../eu/kanade/domain/history/model/History.kt | 9 - .../history/model/HistoryWithRelations.kt | 13 - .../history/repository/HistoryRepository.kt | 18 -- .../eu/kanade/domain/manga/model/Manga.kt | 39 --- .../presentation/components/EmptyScreen.kt | 49 --- .../presentation/components/MangaCover.kt | 39 --- .../presentation/history/HistoryScreen.kt | 297 ------------------ .../presentation/theme/TachiyomiTheme.kt | 20 -- .../eu/kanade/presentation/util/Constants.kt | 5 - .../kanade/presentation/util/LazyListState.kt | 5 - app/src/main/java/eu/kanade/tachiyomi/App.kt | 2 - .../java/eu/kanade/tachiyomi/AppModule.kt | 39 +-- .../data/backup/AbstractBackupManager.kt | 2 - .../data/backup/full/FullBackupManager.kt | 56 ++-- .../data/backup/full/FullBackupRestore.kt | 2 +- .../data/backup/legacy/LegacyBackupManager.kt | 48 ++- .../data/backup/legacy/LegacyBackupRestore.kt | 2 +- .../tachiyomi/data/database/DatabaseHelper.kt | 7 +- .../tachiyomi/data/database/DbOpenCallback.kt | 107 ++++++- .../data/database/queries/HistoryQueries.kt | 40 ++- .../data/database/queries/RawQueries.kt | 31 +- ...olver.kt => HistoryLastReadPutResolver.kt} | 2 +- .../data/database/tables/CategoryTable.kt | 15 + .../data/database/tables/ChapterTable.kt | 38 +++ .../data/database/tables/HistoryTable.kt | 20 ++ .../database/tables/MangaCategoryTable.kt | 12 + .../data/database/tables/MangaTable.kt | 55 ++++ .../data/database/tables/TrackTable.kt | 40 +++ .../data/notification/NotificationReceiver.kt | 2 +- .../ui/base/controller/ComposeController.kt | 26 -- .../ui/browse/feed/FeedController.kt | 65 ++-- .../tachiyomi/ui/browse/feed/FeedPresenter.kt | 57 ++-- .../process/MigrationProcessAdapter.kt | 2 +- .../migration/search/SearchController.kt | 4 +- .../source/browse/BrowseSourceController.kt | 90 +++--- .../source/browse/BrowseSourcePresenter.kt | 169 +++++----- .../source/feed/SourceFeedController.kt | 91 +++--- .../browse/source/feed/SourceFeedPresenter.kt | 168 +++++----- .../tachiyomi/ui/manga/MangaController.kt | 3 - .../ui/manga/info/MangaCoverImageView.kt | 24 ++ .../tachiyomi/ui/reader/ReaderActivity.kt | 11 +- .../tachiyomi/ui/reader/ReaderPresenter.kt | 2 +- .../history/ClearHistoryDialogController.kt | 21 -- .../ui/recent/history/HistoryAdapter.kt | 51 +++ .../ui/recent/history/HistoryController.kt | 239 +++++++++++--- .../ui/recent/history/HistoryHolder.kt | 71 +++++ .../ui/recent/history/HistoryItem.kt | 42 +++ .../ui/recent/history/HistoryPresenter.kt | 220 +++++++------ .../ui/recent/history/RemoveHistoryDialog.kt | 54 ++++ .../database/ClearDatabaseController.kt | 2 + .../database/ClearDatabasePresenter.kt | 4 +- .../widget/TachiyomiCoordinatorLayout.kt | 13 +- app/src/main/java/exh/EXHMigrations.kt | 55 ++-- app/src/main/java/exh/debug/DebugFunctions.kt | 4 +- .../sql/tables/FavoriteEntryTable.kt | 16 + .../java/exh/merged/sql/tables/MergedTable.kt | 28 ++ .../sql/tables/SearchMetadataTable.kt | 21 ++ .../exh/metadata/sql/tables/SearchTagTable.kt | 20 ++ .../metadata/sql/tables/SearchTitleTable.kt | 19 ++ .../queries/FeedSavedSearchQueries.kt | 83 ++++- .../queries/SavedSearchQueries.kt | 90 +++++- .../tables/FeedSavedSearchTable.kt | 14 + .../savedsearches/tables/SavedSearchTable.kt | 10 + .../main/res/layout/compose_controller.xml | 4 - .../main/res/layout/history_controller.xml | 33 ++ app/src/main/res/layout/history_item.xml | 85 +++++ app/src/main/sqldelight/data/categories.sq | 7 - app/src/main/sqldelight/data/chapters.sq | 29 -- app/src/main/sqldelight/data/eh_favorites.sq | 16 - .../main/sqldelight/data/feed_saved_search.sq | 41 --- app/src/main/sqldelight/data/history.sq | 37 --- app/src/main/sqldelight/data/manga_sync.sq | 18 -- app/src/main/sqldelight/data/mangas.sq | 32 -- .../main/sqldelight/data/mangas_categories.sq | 9 - app/src/main/sqldelight/data/merged.sq | 85 ----- app/src/main/sqldelight/data/saved_search.sq | 32 -- .../main/sqldelight/data/search_metadata.sq | 32 -- app/src/main/sqldelight/data/search_tags.sq | 35 --- app/src/main/sqldelight/data/search_titles.sq | 30 -- app/src/main/sqldelight/migrations/1.sqm | 2 - app/src/main/sqldelight/migrations/10.sqm | 7 - app/src/main/sqldelight/migrations/11.sqm | 7 - app/src/main/sqldelight/migrations/12.sqm | 16 - app/src/main/sqldelight/migrations/13.sqm | 275 ---------------- app/src/main/sqldelight/migrations/2.sqm | 11 - app/src/main/sqldelight/migrations/3.sqm | 19 -- app/src/main/sqldelight/migrations/4.sqm | 0 app/src/main/sqldelight/migrations/5.sqm | 2 - app/src/main/sqldelight/migrations/6.sqm | 1 - app/src/main/sqldelight/migrations/7.sqm | 2 - app/src/main/sqldelight/migrations/8.sqm | 9 - app/src/main/sqldelight/migrations/9.sqm | 3 - app/src/main/sqldelight/view/historyView.sq | 46 --- .../tachiyomi/data/backup/BackupTest.kt | 2 +- build.gradle.kts | 1 - gradle/androidx.versions.toml | 3 - gradle/compose.versions.toml | 9 - gradle/kotlinx.versions.toml | 2 +- gradle/libs.versions.toml | 10 +- settings.gradle.kts | 3 - 118 files changed, 1704 insertions(+), 2694 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/data/AndroidDatabaseHandler.kt delete mode 100644 app/src/main/java/eu/kanade/data/DatabaseAdapter.kt delete mode 100644 app/src/main/java/eu/kanade/data/DatabaseHandler.kt delete mode 100644 app/src/main/java/eu/kanade/data/TransactionContext.kt delete mode 100644 app/src/main/java/eu/kanade/data/chapter/ChapterMapper.kt delete mode 100644 app/src/main/java/eu/kanade/data/exh/FeedSavedSearch.kt delete mode 100644 app/src/main/java/eu/kanade/data/exh/SavedSearch.kt delete mode 100644 app/src/main/java/eu/kanade/data/history/HistoryMapper.kt delete mode 100644 app/src/main/java/eu/kanade/data/history/HistoryRepositoryImpl.kt delete mode 100644 app/src/main/java/eu/kanade/data/manga/MangaMapper.kt delete mode 100644 app/src/main/java/eu/kanade/domain/DomainModule.kt delete mode 100644 app/src/main/java/eu/kanade/domain/chapter/model/Chapter.kt delete mode 100644 app/src/main/java/eu/kanade/domain/history/interactor/DeleteHistoryTable.kt delete mode 100644 app/src/main/java/eu/kanade/domain/history/interactor/GetHistory.kt delete mode 100644 app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapterForManga.kt delete mode 100644 app/src/main/java/eu/kanade/domain/history/interactor/RemoveHistoryById.kt delete mode 100644 app/src/main/java/eu/kanade/domain/history/interactor/RemoveHistoryByMangaId.kt delete mode 100644 app/src/main/java/eu/kanade/domain/history/model/History.kt delete mode 100644 app/src/main/java/eu/kanade/domain/history/model/HistoryWithRelations.kt delete mode 100644 app/src/main/java/eu/kanade/domain/history/repository/HistoryRepository.kt delete mode 100644 app/src/main/java/eu/kanade/domain/manga/model/Manga.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/components/MangaCover.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/util/Constants.kt delete mode 100644 app/src/main/java/eu/kanade/presentation/util/LazyListState.kt rename app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/{HistoryUpsertResolver.kt => HistoryLastReadPutResolver.kt} (97%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaCoverImageView.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/ClearHistoryDialogController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryItem.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt delete mode 100644 app/src/main/res/layout/compose_controller.xml create mode 100644 app/src/main/res/layout/history_controller.xml create mode 100644 app/src/main/res/layout/history_item.xml delete mode 100644 app/src/main/sqldelight/data/categories.sq delete mode 100644 app/src/main/sqldelight/data/chapters.sq delete mode 100644 app/src/main/sqldelight/data/eh_favorites.sq delete mode 100644 app/src/main/sqldelight/data/feed_saved_search.sq delete mode 100644 app/src/main/sqldelight/data/history.sq delete mode 100644 app/src/main/sqldelight/data/manga_sync.sq delete mode 100644 app/src/main/sqldelight/data/mangas.sq delete mode 100644 app/src/main/sqldelight/data/mangas_categories.sq delete mode 100644 app/src/main/sqldelight/data/merged.sq delete mode 100644 app/src/main/sqldelight/data/saved_search.sq delete mode 100644 app/src/main/sqldelight/data/search_metadata.sq delete mode 100644 app/src/main/sqldelight/data/search_tags.sq delete mode 100644 app/src/main/sqldelight/data/search_titles.sq delete mode 100644 app/src/main/sqldelight/migrations/1.sqm delete mode 100644 app/src/main/sqldelight/migrations/10.sqm delete mode 100644 app/src/main/sqldelight/migrations/11.sqm delete mode 100644 app/src/main/sqldelight/migrations/12.sqm delete mode 100644 app/src/main/sqldelight/migrations/13.sqm delete mode 100644 app/src/main/sqldelight/migrations/2.sqm delete mode 100644 app/src/main/sqldelight/migrations/3.sqm delete mode 100644 app/src/main/sqldelight/migrations/4.sqm delete mode 100644 app/src/main/sqldelight/migrations/5.sqm delete mode 100644 app/src/main/sqldelight/migrations/6.sqm delete mode 100644 app/src/main/sqldelight/migrations/7.sqm delete mode 100644 app/src/main/sqldelight/migrations/8.sqm delete mode 100644 app/src/main/sqldelight/migrations/9.sqm delete mode 100644 app/src/main/sqldelight/view/historyView.sq delete mode 100644 gradle/compose.versions.toml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a79ca4bec..1f593c3d9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,7 +7,6 @@ plugins { kotlin("plugin.parcelize") kotlin("plugin.serialization") id("com.github.zellius.shortcut-helper") - id("com.squareup.sqldelight") } if (gradle.startParameter.taskRequests.toString().contains("Standard")) { @@ -93,7 +92,6 @@ android { buildFeatures { viewBinding = true - compose = true // Disable some unused things aidl = false @@ -107,10 +105,6 @@ android { checkReleaseBuilds = false } - composeOptions { - kotlinCompilerExtensionVersion = compose.versions.compose.get() - } - compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -122,19 +116,6 @@ android { } dependencies { - implementation(compose.foundation) - implementation(compose.material3.core) - implementation(compose.material3.adapter) - implementation(compose.animation) - implementation(compose.ui.tooling) - - implementation(androidx.paging.runtime) - implementation(androidx.paging.compose) - - implementation(libs.sqldelight.android.driver) - implementation(libs.sqldelight.coroutines) - implementation(libs.sqldelight.android.paging) - implementation(kotlinx.reflect) implementation(kotlinx.bundles.coroutines) @@ -288,9 +269,6 @@ tasks { "-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi", "-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi", "-Xopt-in=coil.annotation.ExperimentalCoilApi", - "-Xopt-in=androidx.compose.material3.ExperimentalMaterial3Api", - "-Xopt-in=androidx.compose.ui.ExperimentalComposeUiApi", - "-Xopt-in=androidx.compose.foundation.ExperimentalFoundationApi", "-Xopt-in=kotlin.time.ExperimentalTime", ) } diff --git a/app/src/main/java/eu/kanade/data/AndroidDatabaseHandler.kt b/app/src/main/java/eu/kanade/data/AndroidDatabaseHandler.kt deleted file mode 100644 index bd4d99fde..000000000 --- a/app/src/main/java/eu/kanade/data/AndroidDatabaseHandler.kt +++ /dev/null @@ -1,94 +0,0 @@ -package eu.kanade.data - -import androidx.paging.PagingSource -import com.squareup.sqldelight.Query -import com.squareup.sqldelight.Transacter -import com.squareup.sqldelight.android.paging3.QueryPagingSource -import com.squareup.sqldelight.db.SqlDriver -import com.squareup.sqldelight.runtime.coroutines.asFlow -import com.squareup.sqldelight.runtime.coroutines.mapToList -import com.squareup.sqldelight.runtime.coroutines.mapToOne -import com.squareup.sqldelight.runtime.coroutines.mapToOneOrNull -import eu.kanade.tachiyomi.Database -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext - -class AndroidDatabaseHandler( - val db: Database, - private val driver: SqlDriver, - val queryDispatcher: CoroutineDispatcher = Dispatchers.IO, - val transactionDispatcher: CoroutineDispatcher = queryDispatcher -) : DatabaseHandler { - - val suspendingTransactionId = ThreadLocal() - - override suspend fun await(inTransaction: Boolean, block: suspend Database.() -> T): T { - return dispatch(inTransaction, block) - } - - override suspend fun awaitList( - inTransaction: Boolean, - block: suspend Database.() -> Query - ): List { - return dispatch(inTransaction) { block(db).executeAsList() } - } - - override suspend fun awaitOne( - inTransaction: Boolean, - block: suspend Database.() -> Query - ): T { - return dispatch(inTransaction) { block(db).executeAsOne() } - } - - override suspend fun awaitOneOrNull( - inTransaction: Boolean, - block: suspend Database.() -> Query - ): T? { - return dispatch(inTransaction) { block(db).executeAsOneOrNull() } - } - - override fun subscribeToList(block: Database.() -> Query): Flow> { - return block(db).asFlow().mapToList(queryDispatcher) - } - - override fun subscribeToOne(block: Database.() -> Query): Flow { - return block(db).asFlow().mapToOne(queryDispatcher) - } - - override fun subscribeToOneOrNull(block: Database.() -> Query): Flow { - return block(db).asFlow().mapToOneOrNull(queryDispatcher) - } - - override fun subscribeToPagingSource( - countQuery: Database.() -> Query, - transacter: Database.() -> Transacter, - queryProvider: Database.(Long, Long) -> Query - ): PagingSource { - return QueryPagingSource( - countQuery = countQuery(db), - transacter = transacter(db), - dispatcher = queryDispatcher, - queryProvider = { limit, offset -> - queryProvider.invoke(db, limit, offset) - } - ) - } - - private suspend fun dispatch(inTransaction: Boolean, block: suspend Database.() -> T): T { - // Create a transaction if needed and run the calling block inside it. - if (inTransaction) { - return withTransaction { block(db) } - } - - // If we're currently in the transaction thread, there's no need to dispatch our query. - if (driver.currentTransaction() != null) { - return block(db) - } - - // Get the current database context and run the calling block. - val context = getCurrentDatabaseContext() - return withContext(context) { block(db) } - } -} diff --git a/app/src/main/java/eu/kanade/data/DatabaseAdapter.kt b/app/src/main/java/eu/kanade/data/DatabaseAdapter.kt deleted file mode 100644 index d51e2c514..000000000 --- a/app/src/main/java/eu/kanade/data/DatabaseAdapter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.data - -import com.squareup.sqldelight.ColumnAdapter -import java.util.Date - -val dateAdapter = object : ColumnAdapter { - override fun decode(databaseValue: Long): Date = Date(databaseValue) - override fun encode(value: Date): Long = value.time -} - -private const val listOfStringsSeparator = ", " -val listOfStringsAdapter = object : ColumnAdapter, String> { - override fun decode(databaseValue: String) = - if (databaseValue.isEmpty()) { - listOf() - } else { - databaseValue.split(listOfStringsSeparator) - } - override fun encode(value: List) = value.joinToString(separator = listOfStringsSeparator) -} diff --git a/app/src/main/java/eu/kanade/data/DatabaseHandler.kt b/app/src/main/java/eu/kanade/data/DatabaseHandler.kt deleted file mode 100644 index a528b7010..000000000 --- a/app/src/main/java/eu/kanade/data/DatabaseHandler.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.data - -import androidx.paging.PagingSource -import com.squareup.sqldelight.Query -import com.squareup.sqldelight.Transacter -import eu.kanade.tachiyomi.Database -import kotlinx.coroutines.flow.Flow - -interface DatabaseHandler { - - suspend fun await(inTransaction: Boolean = false, block: suspend Database.() -> T): T - - suspend fun awaitList( - inTransaction: Boolean = false, - block: suspend Database.() -> Query - ): List - - suspend fun awaitOne( - inTransaction: Boolean = false, - block: suspend Database.() -> Query - ): T - - suspend fun awaitOneOrNull( - inTransaction: Boolean = false, - block: suspend Database.() -> Query - ): T? - - fun subscribeToList(block: Database.() -> Query): Flow> - - fun subscribeToOne(block: Database.() -> Query): Flow - - fun subscribeToOneOrNull(block: Database.() -> Query): Flow - - fun subscribeToPagingSource( - countQuery: Database.() -> Query, - transacter: Database.() -> Transacter, - queryProvider: Database.(Long, Long) -> Query - ): PagingSource -} diff --git a/app/src/main/java/eu/kanade/data/TransactionContext.kt b/app/src/main/java/eu/kanade/data/TransactionContext.kt deleted file mode 100644 index 156b4cdba..000000000 --- a/app/src/main/java/eu/kanade/data/TransactionContext.kt +++ /dev/null @@ -1,160 +0,0 @@ -package eu.kanade.data - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Job -import kotlinx.coroutines.asContextElement -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import java.util.concurrent.RejectedExecutionException -import java.util.concurrent.atomic.AtomicInteger -import kotlin.coroutines.ContinuationInterceptor -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.coroutines.coroutineContext -import kotlin.coroutines.resume - -/** - * Returns the transaction dispatcher if we are on a transaction, or the database dispatchers. - */ -internal suspend fun AndroidDatabaseHandler.getCurrentDatabaseContext(): CoroutineContext { - return coroutineContext[TransactionElement]?.transactionDispatcher ?: queryDispatcher -} - -/** - * Calls the specified suspending [block] in a database transaction. The transaction will be - * marked as successful unless an exception is thrown in the suspending [block] or the coroutine - * is cancelled. - * - * SQLDelight will only perform at most one transaction at a time, additional transactions are queued - * and executed on a first come, first serve order. - * - * Performing blocking database operations is not permitted in a coroutine scope other than the - * one received by the suspending block. It is recommended that all [Dao] function invoked within - * the [block] be suspending functions. - * - * The dispatcher used to execute the given [block] will utilize threads from SQLDelight's query executor. - */ -internal suspend fun AndroidDatabaseHandler.withTransaction(block: suspend () -> T): T { - // Use inherited transaction context if available, this allows nested suspending transactions. - val transactionContext = - coroutineContext[TransactionElement]?.transactionDispatcher ?: createTransactionContext() - return withContext(transactionContext) { - val transactionElement = coroutineContext[TransactionElement]!! - transactionElement.acquire() - try { - db.transactionWithResult { - runBlocking(transactionContext) { - block() - } - } - } finally { - transactionElement.release() - } - } -} - -/** - * Creates a [CoroutineContext] for performing database operations within a coroutine transaction. - * - * The context is a combination of a dispatcher, a [TransactionElement] and a thread local element. - * - * * The dispatcher will dispatch coroutines to a single thread that is taken over from the SQLDelight - * query executor. If the coroutine context is switched, suspending DAO functions will be able to - * dispatch to the transaction thread. - * - * * The [TransactionElement] serves as an indicator for inherited context, meaning, if there is a - * switch of context, suspending DAO methods will be able to use the indicator to dispatch the - * database operation to the transaction thread. - * - * * The thread local element serves as a second indicator and marks threads that are used to - * execute coroutines within the coroutine transaction, more specifically it allows us to identify - * if a blocking DAO method is invoked within the transaction coroutine. Never assign meaning to - * this value, for now all we care is if its present or not. - */ -private suspend fun AndroidDatabaseHandler.createTransactionContext(): CoroutineContext { - val controlJob = Job() - // make sure to tie the control job to this context to avoid blocking the transaction if - // context get cancelled before we can even start using this job. Otherwise, the acquired - // transaction thread will forever wait for the controlJob to be cancelled. - // see b/148181325 - coroutineContext[Job]?.invokeOnCompletion { - controlJob.cancel() - } - - val dispatcher = transactionDispatcher.acquireTransactionThread(controlJob) - val transactionElement = TransactionElement(controlJob, dispatcher) - val threadLocalElement = - suspendingTransactionId.asContextElement(System.identityHashCode(controlJob)) - return dispatcher + transactionElement + threadLocalElement -} - -/** - * Acquires a thread from the executor and returns a [ContinuationInterceptor] to dispatch - * coroutines to the acquired thread. The [controlJob] is used to control the release of the - * thread by cancelling the job. - */ -private suspend fun CoroutineDispatcher.acquireTransactionThread( - controlJob: Job -): ContinuationInterceptor { - return suspendCancellableCoroutine { continuation -> - continuation.invokeOnCancellation { - // We got cancelled while waiting to acquire a thread, we can't stop our attempt to - // acquire a thread, but we can cancel the controlling job so once it gets acquired it - // is quickly released. - controlJob.cancel() - } - try { - dispatch(EmptyCoroutineContext) { - runBlocking { - // Thread acquired, resume coroutine. - continuation.resume(coroutineContext[ContinuationInterceptor]!!) - controlJob.join() - } - } - } catch (ex: RejectedExecutionException) { - // Couldn't acquire a thread, cancel coroutine. - continuation.cancel( - IllegalStateException( - "Unable to acquire a thread to perform the database transaction.", ex - ) - ) - } - } -} - -/** - * A [CoroutineContext.Element] that indicates there is an on-going database transaction. - */ -private class TransactionElement( - private val transactionThreadControlJob: Job, - val transactionDispatcher: ContinuationInterceptor -) : CoroutineContext.Element { - - companion object Key : CoroutineContext.Key - - override val key: CoroutineContext.Key - get() = TransactionElement - - /** - * Number of transactions (including nested ones) started with this element. - * Call [acquire] to increase the count and [release] to decrease it. If the count reaches zero - * when [release] is invoked then the transaction job is cancelled and the transaction thread - * is released. - */ - private val referenceCount = AtomicInteger(0) - - fun acquire() { - referenceCount.incrementAndGet() - } - - fun release() { - val count = referenceCount.decrementAndGet() - if (count < 0) { - throw IllegalStateException("Transaction was never started or was already released.") - } else if (count == 0) { - // Cancel the job that controls the transaction thread, causing it to be released. - transactionThreadControlJob.cancel() - } - } -} diff --git a/app/src/main/java/eu/kanade/data/chapter/ChapterMapper.kt b/app/src/main/java/eu/kanade/data/chapter/ChapterMapper.kt deleted file mode 100644 index 05de96ef2..000000000 --- a/app/src/main/java/eu/kanade/data/chapter/ChapterMapper.kt +++ /dev/null @@ -1,21 +0,0 @@ -package eu.kanade.data.chapter - -import eu.kanade.domain.chapter.model.Chapter - -val chapterMapper: (Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long) -> Chapter = - { id, mangaId, url, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload -> - Chapter( - id = id, - mangaId = mangaId, - read = read, - bookmark = bookmark, - lastPageRead = lastPageRead, - dateFetch = dateFetch, - sourceOrder = sourceOrder, - url = url, - name = name, - dateUpload = dateUpload, - chapterNumber = chapterNumber, - scanlator = scanlator, - ) - } diff --git a/app/src/main/java/eu/kanade/data/exh/FeedSavedSearch.kt b/app/src/main/java/eu/kanade/data/exh/FeedSavedSearch.kt deleted file mode 100644 index 430699d07..000000000 --- a/app/src/main/java/eu/kanade/data/exh/FeedSavedSearch.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.data.exh - -import exh.savedsearches.models.FeedSavedSearch - -val feedSavedSearchMapper: (Long, Long, Long?, Boolean) -> FeedSavedSearch = - { id, source, savedSearch, global -> - FeedSavedSearch( - id = id, - source = source, - savedSearch = savedSearch, - global = global - ) - } diff --git a/app/src/main/java/eu/kanade/data/exh/SavedSearch.kt b/app/src/main/java/eu/kanade/data/exh/SavedSearch.kt deleted file mode 100644 index a31d4c787..000000000 --- a/app/src/main/java/eu/kanade/data/exh/SavedSearch.kt +++ /dev/null @@ -1,14 +0,0 @@ -package eu.kanade.data.exh - -import exh.savedsearches.models.SavedSearch - -val savedSearchMapper: (Long, Long, String, String?, String?) -> SavedSearch = - { id, source, name, query, filtersJson -> - SavedSearch( - id = id, - source = source, - name = name, - query = query, - filtersJson = filtersJson - ) - } diff --git a/app/src/main/java/eu/kanade/data/history/HistoryMapper.kt b/app/src/main/java/eu/kanade/data/history/HistoryMapper.kt deleted file mode 100644 index e7ebf5cb4..000000000 --- a/app/src/main/java/eu/kanade/data/history/HistoryMapper.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.data.history - -import eu.kanade.domain.history.model.History -import eu.kanade.domain.history.model.HistoryWithRelations -import java.util.Date - -val historyMapper: (Long, Long, Date?, Date?) -> History = { id, chapterId, readAt, _ -> - History( - id = id, - chapterId = chapterId, - readAt = readAt, - ) -} - -val historyWithRelationsMapper: (Long, Long, Long, String, String?, Float, Date?) -> HistoryWithRelations = { - historyId, mangaId, chapterId, title, thumbnailUrl, chapterNumber, readAt -> - HistoryWithRelations( - id = historyId, - chapterId = chapterId, - mangaId = mangaId, - title = title, - thumbnailUrl = thumbnailUrl ?: "", - chapterNumber = chapterNumber, - readAt = readAt - ) -} diff --git a/app/src/main/java/eu/kanade/data/history/HistoryRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/history/HistoryRepositoryImpl.kt deleted file mode 100644 index 15d2d2633..000000000 --- a/app/src/main/java/eu/kanade/data/history/HistoryRepositoryImpl.kt +++ /dev/null @@ -1,91 +0,0 @@ -package eu.kanade.data.history - -import androidx.paging.PagingSource -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.chapter.chapterMapper -import eu.kanade.data.manga.mangaMapper -import eu.kanade.domain.chapter.model.Chapter -import eu.kanade.domain.history.model.HistoryWithRelations -import eu.kanade.domain.history.repository.HistoryRepository -import eu.kanade.domain.manga.model.Manga -import eu.kanade.tachiyomi.util.system.logcat - -class HistoryRepositoryImpl( - private val handler: DatabaseHandler -) : HistoryRepository { - - override fun getHistory(query: String): PagingSource { - return handler.subscribeToPagingSource( - countQuery = { historyViewQueries.countHistory(query) }, - transacter = { historyViewQueries }, - queryProvider = { limit, offset -> - historyViewQueries.history(query, limit, offset, historyWithRelationsMapper) - } - ) - } - - override suspend fun getNextChapterForManga(mangaId: Long, chapterId: Long): Chapter? { - val chapter = handler.awaitOne { chaptersQueries.getChapterById(chapterId, chapterMapper) } - val manga = handler.awaitOne { mangasQueries.getMangaById(mangaId, mangaMapper) } - - if (!chapter.read) { - return chapter - } - - val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) { - Manga.CHAPTER_SORTING_SOURCE -> { c1, c2 -> c2.sourceOrder.compareTo(c1.sourceOrder) } - Manga.CHAPTER_SORTING_NUMBER -> { c1, c2 -> c1.chapterNumber.compareTo(c2.chapterNumber) } - Manga.CHAPTER_SORTING_UPLOAD_DATE -> { c1, c2 -> c1.dateUpload.compareTo(c2.dateUpload) } - else -> throw NotImplementedError("Unknown sorting method") - } - - val chapters = handler.awaitList { chaptersQueries.getChapterByMangaId(mangaId, chapterMapper) } - .sortedWith(sortFunction) - - val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id } - return when (manga.sorting) { - Manga.CHAPTER_SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1) - Manga.CHAPTER_SORTING_NUMBER -> { - val chapterNumber = chapter.chapterNumber - - ((currChapterIndex + 1) until chapters.size) - .map { chapters[it] } - .firstOrNull { - it.chapterNumber > chapterNumber && - it.chapterNumber <= chapterNumber + 1 - } - } - Manga.CHAPTER_SORTING_UPLOAD_DATE -> { - chapters.drop(currChapterIndex + 1) - .firstOrNull { it.dateUpload >= chapter.dateUpload } - } - else -> throw NotImplementedError("Unknown sorting method") - } - } - - override suspend fun resetHistory(historyId: Long) { - try { - handler.await { historyQueries.resetHistoryById(historyId) } - } catch (e: Exception) { - logcat(throwable = e) - } - } - - override suspend fun resetHistoryByMangaId(mangaId: Long) { - try { - handler.await { historyQueries.resetHistoryByMangaId(mangaId) } - } catch (e: Exception) { - logcat(throwable = e) - } - } - - override suspend fun deleteAllHistory(): Boolean { - return try { - handler.await { historyQueries.removeAllHistory() } - true - } catch (e: Exception) { - logcat(throwable = e) - false - } - } -} diff --git a/app/src/main/java/eu/kanade/data/manga/MangaMapper.kt b/app/src/main/java/eu/kanade/data/manga/MangaMapper.kt deleted file mode 100644 index d60316f3b..000000000 --- a/app/src/main/java/eu/kanade/data/manga/MangaMapper.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.data.manga - -import eu.kanade.domain.manga.model.Manga - -val mangaMapper: (Long, Long, String, String?, String?, String?, List?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, String?) -> Manga = - { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewer, chapterFlags, coverLastModified, dateAdded, filteredScanlators -> - Manga( - id = id, - source = source, - favorite = favorite, - lastUpdate = lastUpdate ?: 0, - dateAdded = dateAdded, - viewerFlags = viewer, - chapterFlags = chapterFlags, - coverLastModified = coverLastModified, - url = url, - title = title, - artist = artist, - author = author, - description = description, - genre = genre, - status = status, - thumbnailUrl = thumbnailUrl, - initialized = initialized, - filteredScanlators = filteredScanlators - ) - } diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt deleted file mode 100644 index 9462ae7f9..000000000 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.domain - -import eu.kanade.data.history.HistoryRepositoryImpl -import eu.kanade.domain.history.interactor.DeleteHistoryTable -import eu.kanade.domain.history.interactor.GetHistory -import eu.kanade.domain.history.interactor.GetNextChapterForManga -import eu.kanade.domain.history.interactor.RemoveHistoryById -import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId -import eu.kanade.domain.history.repository.HistoryRepository -import uy.kohesive.injekt.api.InjektModule -import uy.kohesive.injekt.api.InjektRegistrar -import uy.kohesive.injekt.api.addFactory -import uy.kohesive.injekt.api.addSingletonFactory -import uy.kohesive.injekt.api.get - -class DomainModule : InjektModule { - - override fun InjektRegistrar.registerInjectables() { - addSingletonFactory { HistoryRepositoryImpl(get()) } - addFactory { DeleteHistoryTable(get()) } - addFactory { GetHistory(get()) } - addFactory { GetNextChapterForManga(get()) } - addFactory { RemoveHistoryById(get()) } - addFactory { RemoveHistoryByMangaId(get()) } - } -} diff --git a/app/src/main/java/eu/kanade/domain/chapter/model/Chapter.kt b/app/src/main/java/eu/kanade/domain/chapter/model/Chapter.kt deleted file mode 100644 index 6eff7c580..000000000 --- a/app/src/main/java/eu/kanade/domain/chapter/model/Chapter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package eu.kanade.domain.chapter.model - -data class Chapter( - val id: Long, - val mangaId: Long, - val read: Boolean, - val bookmark: Boolean, - val lastPageRead: Long, - val dateFetch: Long, - val sourceOrder: Long, - val url: String, - val name: String, - val dateUpload: Long, - val chapterNumber: Float, - val scanlator: String? -) diff --git a/app/src/main/java/eu/kanade/domain/history/interactor/DeleteHistoryTable.kt b/app/src/main/java/eu/kanade/domain/history/interactor/DeleteHistoryTable.kt deleted file mode 100644 index bebf1209d..000000000 --- a/app/src/main/java/eu/kanade/domain/history/interactor/DeleteHistoryTable.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.domain.history.interactor - -import eu.kanade.domain.history.repository.HistoryRepository - -class DeleteHistoryTable( - private val repository: HistoryRepository -) { - - suspend fun await(): Boolean { - return repository.deleteAllHistory() - } -} diff --git a/app/src/main/java/eu/kanade/domain/history/interactor/GetHistory.kt b/app/src/main/java/eu/kanade/domain/history/interactor/GetHistory.kt deleted file mode 100644 index d2f8302b7..000000000 --- a/app/src/main/java/eu/kanade/domain/history/interactor/GetHistory.kt +++ /dev/null @@ -1,21 +0,0 @@ -package eu.kanade.domain.history.interactor - -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import eu.kanade.domain.history.model.HistoryWithRelations -import eu.kanade.domain.history.repository.HistoryRepository -import kotlinx.coroutines.flow.Flow - -class GetHistory( - private val repository: HistoryRepository -) { - - fun subscribe(query: String): Flow> { - return Pager( - PagingConfig(pageSize = 25) - ) { - repository.getHistory(query) - }.flow - } -} diff --git a/app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapterForManga.kt b/app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapterForManga.kt deleted file mode 100644 index 477408ca3..000000000 --- a/app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapterForManga.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.domain.history.interactor - -import eu.kanade.domain.chapter.model.Chapter -import eu.kanade.domain.history.repository.HistoryRepository - -class GetNextChapterForManga( - private val repository: HistoryRepository -) { - - suspend fun await(mangaId: Long, chapterId: Long): Chapter? { - return repository.getNextChapterForManga(mangaId, chapterId) - } -} diff --git a/app/src/main/java/eu/kanade/domain/history/interactor/RemoveHistoryById.kt b/app/src/main/java/eu/kanade/domain/history/interactor/RemoveHistoryById.kt deleted file mode 100644 index 93012c266..000000000 --- a/app/src/main/java/eu/kanade/domain/history/interactor/RemoveHistoryById.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.domain.history.interactor - -import eu.kanade.domain.history.model.HistoryWithRelations -import eu.kanade.domain.history.repository.HistoryRepository - -class RemoveHistoryById( - private val repository: HistoryRepository -) { - - suspend fun await(history: HistoryWithRelations) { - repository.resetHistory(history.id) - } -} diff --git a/app/src/main/java/eu/kanade/domain/history/interactor/RemoveHistoryByMangaId.kt b/app/src/main/java/eu/kanade/domain/history/interactor/RemoveHistoryByMangaId.kt deleted file mode 100644 index f32fa5f7b..000000000 --- a/app/src/main/java/eu/kanade/domain/history/interactor/RemoveHistoryByMangaId.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.kanade.domain.history.interactor - -import eu.kanade.domain.history.repository.HistoryRepository - -class RemoveHistoryByMangaId( - private val repository: HistoryRepository -) { - - suspend fun await(mangaId: Long) { - repository.resetHistoryByMangaId(mangaId) - } -} diff --git a/app/src/main/java/eu/kanade/domain/history/model/History.kt b/app/src/main/java/eu/kanade/domain/history/model/History.kt deleted file mode 100644 index 58c1c985e..000000000 --- a/app/src/main/java/eu/kanade/domain/history/model/History.kt +++ /dev/null @@ -1,9 +0,0 @@ -package eu.kanade.domain.history.model - -import java.util.Date - -data class History( - val id: Long?, - val chapterId: Long, - val readAt: Date? -) diff --git a/app/src/main/java/eu/kanade/domain/history/model/HistoryWithRelations.kt b/app/src/main/java/eu/kanade/domain/history/model/HistoryWithRelations.kt deleted file mode 100644 index 6f5a8e1fc..000000000 --- a/app/src/main/java/eu/kanade/domain/history/model/HistoryWithRelations.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.domain.history.model - -import java.util.Date - -data class HistoryWithRelations( - val id: Long, - val chapterId: Long, - val mangaId: Long, - val title: String, - val thumbnailUrl: String, - val chapterNumber: Float, - val readAt: Date? -) diff --git a/app/src/main/java/eu/kanade/domain/history/repository/HistoryRepository.kt b/app/src/main/java/eu/kanade/domain/history/repository/HistoryRepository.kt deleted file mode 100644 index 38e0f4192..000000000 --- a/app/src/main/java/eu/kanade/domain/history/repository/HistoryRepository.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.domain.history.repository - -import androidx.paging.PagingSource -import eu.kanade.domain.chapter.model.Chapter -import eu.kanade.domain.history.model.HistoryWithRelations - -interface HistoryRepository { - - fun getHistory(query: String): PagingSource - - suspend fun getNextChapterForManga(mangaId: Long, chapterId: Long): Chapter? - - suspend fun resetHistory(historyId: Long) - - suspend fun resetHistoryByMangaId(mangaId: Long) - - suspend fun deleteAllHistory(): Boolean -} diff --git a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt deleted file mode 100644 index 7511a40a7..000000000 --- a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.domain.manga.model - -data class Manga( - val id: Long, - val source: Long, - val favorite: Boolean, - val lastUpdate: Long, - val dateAdded: Long, - val viewerFlags: Long, - val chapterFlags: Long, - val coverLastModified: Long, - val url: String, - val title: String, - val artist: String?, - val author: String?, - val description: String?, - val genre: List?, - val status: Long, - val thumbnailUrl: String?, - val initialized: Boolean, - // SY --> - val filteredScanlators: String?, -// SY <-- -) { - - val sorting: Long - get() = chapterFlags and CHAPTER_SORTING_MASK - - companion object { - - // Generic filter that does not filter anything - const val SHOW_ALL = 0x00000000L - - const val CHAPTER_SORTING_SOURCE = 0x00000000L - const val CHAPTER_SORTING_NUMBER = 0x00000100L - const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200L - const val CHAPTER_SORTING_MASK = 0x00000300L - } -} diff --git a/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt b/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt deleted file mode 100644 index e94bef827..000000000 --- a/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt +++ /dev/null @@ -1,49 +0,0 @@ -package eu.kanade.presentation.components - -import android.view.ViewGroup -import androidx.annotation.StringRes -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.viewinterop.AndroidView -import eu.kanade.tachiyomi.widget.EmptyView - -@Composable -fun EmptyScreen( - @StringRes textResource: Int, - actions: List? = null, -) { - EmptyScreen( - message = stringResource(id = textResource), - actions = actions, - ) -} - -@Composable -fun EmptyScreen( - message: String, - actions: List? = null, -) { - Box( - modifier = Modifier - .fillMaxSize() - ) { - AndroidView( - factory = { context -> - EmptyView(context).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) - } - }, - modifier = Modifier - .align(Alignment.Center), - ) { view -> - view.show(message, actions) - } - } -} diff --git a/app/src/main/java/eu/kanade/presentation/components/MangaCover.kt b/app/src/main/java/eu/kanade/presentation/components/MangaCover.kt deleted file mode 100644 index 33c8dfaf6..000000000 --- a/app/src/main/java/eu/kanade/presentation/components/MangaCover.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.presentation.components - -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.painter.ColorPainter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage - -enum class MangaCoverAspect(val ratio: Float) { - SQUARE(1f / 1f), - COVER(2f / 3f) -} - -@Composable -fun MangaCover( - modifier: Modifier = Modifier, - data: String?, - aspect: MangaCoverAspect, - contentDescription: String = "", - shape: Shape = RoundedCornerShape(4.dp) -) { - AsyncImage( - model = data, - placeholder = ColorPainter(CoverPlaceholderColor), - contentDescription = contentDescription, - modifier = modifier - .aspectRatio(aspect.ratio) - .clip(shape), - contentScale = ContentScale.Crop - ) -} - -private val CoverPlaceholderColor = Color(0x1F888888) diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt deleted file mode 100644 index 02bafed72..000000000 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt +++ /dev/null @@ -1,297 +0,0 @@ -package eu.kanade.presentation.history - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.selection.toggleable -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.rememberNestedScrollInteropConnection -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.paging.compose.LazyPagingItems -import androidx.paging.compose.collectAsLazyPagingItems -import androidx.paging.compose.items -import eu.kanade.domain.history.model.HistoryWithRelations -import eu.kanade.presentation.components.EmptyScreen -import eu.kanade.presentation.components.MangaCover -import eu.kanade.presentation.components.MangaCoverAspect -import eu.kanade.presentation.util.horizontalPadding -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter -import eu.kanade.tachiyomi.ui.recent.history.UiModel -import eu.kanade.tachiyomi.util.lang.toRelativeString -import eu.kanade.tachiyomi.util.lang.toTimestampString -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.text.DateFormat -import java.text.DecimalFormat -import java.text.DecimalFormatSymbols -import java.util.Date - -@Composable -fun HistoryScreen( - composeView: ComposeView, - presenter: HistoryPresenter, - onClickItem: (HistoryWithRelations) -> Unit, - onClickResume: (HistoryWithRelations) -> Unit, - onClickDelete: (HistoryWithRelations, Boolean) -> Unit, -) { - val nestedScrollInterop = rememberNestedScrollInteropConnection(composeView) - val state by presenter.state.collectAsState() - val history = state.list?.collectAsLazyPagingItems() - when { - history == null -> { - CircularProgressIndicator() - } - history.itemCount == 0 -> { - EmptyScreen( - textResource = R.string.information_no_recent_manga - ) - } - else -> { - HistoryContent( - nestedScroll = nestedScrollInterop, - history = history, - onClickItem = onClickItem, - onClickResume = onClickResume, - onClickDelete = onClickDelete, - ) - } - } -} - -@Composable -fun HistoryContent( - history: LazyPagingItems, - onClickItem: (HistoryWithRelations) -> Unit, - onClickResume: (HistoryWithRelations) -> Unit, - onClickDelete: (HistoryWithRelations, Boolean) -> Unit, - preferences: PreferencesHelper = Injekt.get(), - nestedScroll: NestedScrollConnection -) { - val relativeTime: Int = remember { preferences.relativeTime().get() } - val dateFormat: DateFormat = remember { preferences.dateFormat() } - - val (removeState, setRemoveState) = remember { mutableStateOf(null) } - - val scrollState = rememberLazyListState() - LazyColumn( - modifier = Modifier - .nestedScroll(nestedScroll), - state = scrollState, - ) { - items(history) { item -> - when (item) { - is UiModel.Header -> { - HistoryHeader( - modifier = Modifier - .animateItemPlacement(), - date = item.date, - relativeTime = relativeTime, - dateFormat = dateFormat - ) - } - is UiModel.Item -> { - val value = item.item - HistoryItem( - modifier = Modifier.animateItemPlacement(), - history = value, - onClickItem = { onClickItem(value) }, - onClickResume = { onClickResume(value) }, - onClickDelete = { setRemoveState(value) }, - ) - } - null -> {} - } - } - item { - Spacer(Modifier.navigationBarsPadding()) - } - } - - if (removeState != null) { - RemoveHistoryDialog( - onPositive = { all -> - onClickDelete(removeState, all) - setRemoveState(null) - }, - onNegative = { setRemoveState(null) } - ) - } -} - -@Composable -fun HistoryHeader( - modifier: Modifier = Modifier, - date: Date, - relativeTime: Int, - dateFormat: DateFormat, -) { - Text( - modifier = modifier - .padding(horizontal = horizontalPadding, vertical = 8.dp), - text = date.toRelativeString( - LocalContext.current, - relativeTime, - dateFormat - ), - style = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontWeight = FontWeight.SemiBold, - ) - ) -} - -@Composable -fun HistoryItem( - modifier: Modifier = Modifier, - history: HistoryWithRelations, - onClickItem: () -> Unit, - onClickResume: () -> Unit, - onClickDelete: () -> Unit, -) { - Row( - modifier = modifier - .clickable(onClick = onClickItem) - .height(96.dp) - .padding(horizontal = horizontalPadding, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - MangaCover( - modifier = Modifier.fillMaxHeight(), - data = history.thumbnailUrl, - aspect = MangaCoverAspect.COVER - ) - Column( - modifier = Modifier - .weight(1f) - .padding(start = horizontalPadding, end = 8.dp), - ) { - val textStyle = MaterialTheme.typography.bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurface, - ) - Text( - text = history.title, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - style = textStyle.copy(fontWeight = FontWeight.SemiBold) - ) - Row { - Text( - text = if (history.chapterNumber > -1) { - stringResource( - R.string.recent_manga_time, - chapterFormatter.format(history.chapterNumber), - history.readAt?.toTimestampString() ?: "", - ) - } else { - history.readAt?.toTimestampString() ?: "" - }, - modifier = Modifier.padding(top = 4.dp), - style = textStyle - ) - } - } - IconButton(onClick = onClickDelete) { - Icon( - imageVector = Icons.Outlined.Delete, - contentDescription = stringResource(id = R.string.action_delete), - tint = MaterialTheme.colorScheme.onSurface, - ) - } - IconButton(onClick = onClickResume) { - Icon( - imageVector = Icons.Filled.PlayArrow, - contentDescription = stringResource(id = R.string.action_resume), - tint = MaterialTheme.colorScheme.onSurface, - ) - } - } -} - -@Composable -fun RemoveHistoryDialog( - onPositive: (Boolean) -> Unit, - onNegative: () -> Unit -) { - val (removeEverything, removeEverythingState) = remember { mutableStateOf(false) } - - AlertDialog( - title = { - Text(text = stringResource(id = R.string.action_remove)) - }, - text = { - Column { - Text(text = stringResource(id = R.string.dialog_with_checkbox_remove_description)) - Row( - modifier = Modifier - .padding(top = 16.dp) - .toggleable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - value = removeEverything, - onValueChange = removeEverythingState - ), - verticalAlignment = Alignment.CenterVertically - ) { - Checkbox( - checked = removeEverything, - onCheckedChange = null, - ) - Text( - modifier = Modifier.padding(start = 4.dp), - text = stringResource(id = R.string.dialog_with_checkbox_reset) - ) - } - } - }, - onDismissRequest = onNegative, - confirmButton = { - TextButton(onClick = { onPositive(removeEverything) }) { - Text(text = stringResource(id = R.string.action_remove)) - } - }, - dismissButton = { - TextButton(onClick = onNegative) { - Text(text = stringResource(id = R.string.action_cancel)) - } - }, - ) -} - -private val chapterFormatter = DecimalFormat( - "#.###", - DecimalFormatSymbols().apply { decimalSeparator = '.' }, -) diff --git a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt deleted file mode 100644 index adb6644d2..000000000 --- a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.presentation.theme - -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext -import com.google.android.material.composethemeadapter3.createMdc3Theme - -@Composable -fun TachiyomiTheme(content: @Composable () -> Unit) { - val context = LocalContext.current - val (colorScheme, typography) = createMdc3Theme( - context = context - ) - - MaterialTheme( - colorScheme = colorScheme!!, - typography = typography!!, - content = content - ) -} diff --git a/app/src/main/java/eu/kanade/presentation/util/Constants.kt b/app/src/main/java/eu/kanade/presentation/util/Constants.kt deleted file mode 100644 index fcf64d77b..000000000 --- a/app/src/main/java/eu/kanade/presentation/util/Constants.kt +++ /dev/null @@ -1,5 +0,0 @@ -package eu.kanade.presentation.util - -import androidx.compose.ui.unit.dp - -val horizontalPadding = 16.dp diff --git a/app/src/main/java/eu/kanade/presentation/util/LazyListState.kt b/app/src/main/java/eu/kanade/presentation/util/LazyListState.kt deleted file mode 100644 index adf7cd80c..000000000 --- a/app/src/main/java/eu/kanade/presentation/util/LazyListState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package eu.kanade.presentation.util - -import androidx.compose.foundation.lazy.LazyListState - -fun LazyListState.isScrolledToEnd() = layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 5a388c916..2516f0bfa 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -38,7 +38,6 @@ import com.google.firebase.analytics.ktx.analytics import com.google.firebase.ktx.Firebase import com.ms_square.debugoverlay.DebugOverlay import com.ms_square.debugoverlay.modules.FpsModule -import eu.kanade.domain.DomainModule import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder @@ -102,7 +101,6 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { } Injekt.importModule(AppModule(this)) - Injekt.importModule(DomainModule()) setupNotificationChannels() if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index 5fbf2749b..9bbad01a5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -2,18 +2,9 @@ package eu.kanade.tachiyomi import android.app.Application import androidx.core.content.ContextCompat -import com.squareup.sqldelight.android.AndroidSqliteDriver -import com.squareup.sqldelight.db.SqlDriver -import data.History -import data.Mangas -import eu.kanade.data.AndroidDatabaseHandler -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.dateAdapter -import eu.kanade.data.listOfStringsAdapter import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.DbOpenCallback import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -36,37 +27,11 @@ class AppModule(val app: Application) : InjektModule { override fun InjektRegistrar.registerInjectables() { addSingleton(app) - addSingletonFactory { DbOpenCallback() } - - addSingletonFactory { - AndroidSqliteDriver( - schema = Database.Schema, - context = app, - name = DbOpenCallback.DATABASE_NAME, - callback = get() - ) - } - - addSingletonFactory { - Database( - driver = get(), - historyAdapter = History.Adapter( - history_last_readAdapter = dateAdapter, - history_time_readAdapter = dateAdapter - ), - mangasAdapter = Mangas.Adapter( - genreAdapter = listOfStringsAdapter - ) - ) - } - - addSingletonFactory { AndroidDatabaseHandler(get(), get()) } - addSingletonFactory { Json { ignoreUnknownKeys = true } } addSingletonFactory { PreferencesHelper(app) } - addSingletonFactory { DatabaseHelper(app, get()) } + addSingletonFactory { DatabaseHelper(app) } addSingletonFactory { ChapterCache(app) } @@ -100,8 +65,6 @@ class AppModule(val app: Application) : InjektModule { get() - get() - get() get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt index 66a12f4ca..6d03f2d42 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri -import eu.kanade.data.DatabaseHandler import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga @@ -21,7 +20,6 @@ import uy.kohesive.injekt.injectLazy abstract class AbstractBackupManager(protected val context: Context) { internal val databaseHelper: DatabaseHelper by injectLazy() - internal val databaseHandler: DatabaseHandler by injectLazy() internal val sourceManager: SourceManager by injectLazy() internal val trackManager: TrackManager by injectLazy() protected val preferences: PreferencesHelper by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt index 4820c17d9..05342a3b5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.backup.full import android.content.Context import android.net.Uri import com.hippo.unifile.UniFile -import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.AbstractBackupManager import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY @@ -40,11 +39,11 @@ import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.logcat import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.insertFlatMetadataAsync +import exh.savedsearches.models.SavedSearch import exh.source.MERGED_SOURCE_ID import exh.source.getMainSource import exh.util.executeOnIO import exh.util.nullIfBlank -import kotlinx.coroutines.runBlocking import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import okio.buffer @@ -168,15 +167,13 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @return list of [BackupSavedSearch] to be backed up */ private fun backupSavedSearches(): List { - return runBlocking { - databaseHandler.awaitList { saved_searchQueries.selectAll(savedSearchMapper) }.map { - BackupSavedSearch( - it.name, - it.query.orEmpty(), - it.filtersJson ?: "[]", - it.source, - ) - } + return databaseHelper.getSavedSearches().executeAsBlocking().map { + BackupSavedSearch( + it.name, + it.query.orEmpty(), + it.filtersJson ?: "[]", + it.source, + ) } } // SY <-- @@ -360,7 +357,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { } } } - databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking() + databaseHelper.updateHistoryLastRead(historyToBeUpdated).executeAsBlocking() } /** @@ -434,24 +431,25 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { } // SY --> - internal suspend fun restoreSavedSearches(backupSavedSearches: List) { - val currentSavedSearches = databaseHandler.awaitList { - saved_searchQueries.selectAll(savedSearchMapper) - } + internal fun restoreSavedSearches(backupSavedSearches: List) { + val currentSavedSearches = databaseHelper.getSavedSearches() + .executeAsBlocking() - databaseHandler.await(true) { - backupSavedSearches.filter { backupSavedSearch -> - currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } - }.forEach { - saved_searchQueries.insertSavedSearch( - _id = null, - source = it.source, - name = it.name, - query = it.query.nullIfBlank(), - filters_json = it.filterList.nullIfBlank() - ?.takeUnless { it == "[]" }, - ) - } + val newSavedSearches = backupSavedSearches.filter { backupSavedSearch -> + currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } + }.map { + SavedSearch( + id = null, + it.source, + it.name, + it.query.nullIfBlank(), + filtersJson = it.filterList.nullIfBlank() + ?.takeUnless { it == "[]" }, + ) + }.ifEmpty { null } + + if (newSavedSearches != null) { + databaseHelper.insertSavedSearches(newSavedSearches) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt index 06791f37a..8c7267ed5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt @@ -76,7 +76,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa } // SY --> - private suspend fun restoreSavedSearches(backupSavedSearches: List) { + private fun restoreSavedSearches(backupSavedSearches: List) { backupManager.restoreSavedSearches(backupSavedSearches) restoreProgress += 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt index d86844fab..8a596485e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.backup.legacy import android.content.Context import android.net.Uri -import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.AbstractBackupManager import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.Companion.CURRENT_VERSION @@ -207,7 +206,7 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab } } } - databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking() + databaseHelper.updateHistoryLastRead(historyToBeUpdated).executeAsBlocking() } /** @@ -290,37 +289,28 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab } // SY --> - internal suspend fun restoreSavedSearches(jsonSavedSearches: String) { + internal fun restoreSavedSearches(jsonSavedSearches: String) { val backupSavedSearches = jsonSavedSearches.split("***").toSet() - val currentSavedSearches = databaseHandler.awaitList { - saved_searchQueries.selectAll(savedSearchMapper) - } + val currentSavedSearches = databaseHelper.getSavedSearches().executeAsBlocking() - databaseHandler.await(true) { - backupSavedSearches.mapNotNull { - runCatching { - val content = parser.decodeFromString(it.substringAfter(':')) - SavedSearch( - id = null, - source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null, - content["name"]!!.jsonPrimitive.content, - content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), - Json.encodeToString(content["filters"]!!.jsonArray), - ) - }.getOrNull() - }.filter { backupSavedSearch -> - currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } - }.forEach { - saved_searchQueries.insertSavedSearch( - _id = null, - source = it.source, - name = it.name, - query = it.query.nullIfBlank(), - filters_json = it.filtersJson.nullIfBlank() - ?.takeUnless { it == "[]" }, + val newSavedSearches = backupSavedSearches.mapNotNull { + runCatching { + val content = parser.decodeFromString(it.substringAfter(':')) + SavedSearch( + id = null, + source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null, + content["name"]!!.jsonPrimitive.content, + content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), + Json.encodeToString(content["filters"]!!.jsonArray), ) - } + }.getOrNull() + }.filter { backupSavedSearch -> + currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } + }.ifEmpty { null } + + if (newSavedSearches != null) { + databaseHelper.insertSavedSearches(newSavedSearches) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt index 9e39605df..2f8f5d0f3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt @@ -73,7 +73,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract } // SY --> - private suspend fun restoreSavedSearches(savedSearches: String) { + private fun restoreSavedSearches(savedSearches: String) { backupManager.restoreSavedSearches(savedSearches) restoreProgress += 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt index 5d02ebc64..87ee4a841 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt @@ -47,10 +47,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory /** * This class provides operations to manage the database through its interfaces. */ -open class DatabaseHelper( - context: Context, - callback: DbOpenCallback -) : +open class DatabaseHelper(context: Context) : MangaQueries, ChapterQueries, TrackQueries, @@ -69,7 +66,7 @@ open class DatabaseHelper( private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) .name(DbOpenCallback.DATABASE_NAME) - .callback(callback) + .callback(DbOpenCallback()) .build() override val db = DefaultStorIOSQLite.builder() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt index ef541ac42..93826ab8a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt @@ -2,28 +2,115 @@ package eu.kanade.tachiyomi.data.database import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteOpenHelper -import com.squareup.sqldelight.android.AndroidSqliteDriver -import eu.kanade.tachiyomi.Database +import eu.kanade.tachiyomi.data.database.tables.CategoryTable +import eu.kanade.tachiyomi.data.database.tables.ChapterTable +import eu.kanade.tachiyomi.data.database.tables.HistoryTable +import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable +import eu.kanade.tachiyomi.data.database.tables.MangaTable +import eu.kanade.tachiyomi.data.database.tables.TrackTable +import exh.favorites.sql.tables.FavoriteEntryTable +import exh.merged.sql.tables.MergedTable +import exh.metadata.sql.tables.SearchMetadataTable +import exh.metadata.sql.tables.SearchTagTable +import exh.metadata.sql.tables.SearchTitleTable +import exh.savedsearches.tables.FeedSavedSearchTable +import exh.savedsearches.tables.SavedSearchTable -class DbOpenCallback : SupportSQLiteOpenHelper.Callback(Database.Schema.version) { +class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) { companion object { /** * Name of the database file. */ const val DATABASE_NAME = "tachiyomi.db" + + /** + * Version of the database. + */ + const val DATABASE_VERSION = /* SY --> */ 13 // SY <-- } - override fun onCreate(db: SupportSQLiteDatabase) { - Database.Schema.create(AndroidSqliteDriver(database = db, cacheSize = 1)) + override fun onCreate(db: SupportSQLiteDatabase) = with(db) { + execSQL(MangaTable.createTableQuery) + execSQL(ChapterTable.createTableQuery) + execSQL(TrackTable.createTableQuery) + execSQL(CategoryTable.createTableQuery) + execSQL(MangaCategoryTable.createTableQuery) + execSQL(HistoryTable.createTableQuery) + // SY --> + execSQL(SearchMetadataTable.createTableQuery) + execSQL(SearchTagTable.createTableQuery) + execSQL(SearchTitleTable.createTableQuery) + execSQL(MergedTable.createTableQuery) + execSQL(FavoriteEntryTable.createTableQuery) + execSQL(SavedSearchTable.createTableQuery) + execSQL(FeedSavedSearchTable.createTableQuery) + // SY <-- + + // DB indexes + execSQL(MangaTable.createUrlIndexQuery) + execSQL(MangaTable.createLibraryIndexQuery) + execSQL(ChapterTable.createMangaIdIndexQuery) + execSQL(ChapterTable.createUnreadChaptersIndexQuery) + execSQL(HistoryTable.createChapterIdIndexQuery) + // SY --> + execSQL(SearchMetadataTable.createUploaderIndexQuery) + execSQL(SearchMetadataTable.createIndexedExtraIndexQuery) + execSQL(SearchTagTable.createMangaIdIndexQuery) + execSQL(SearchTagTable.createNamespaceNameIndexQuery) + execSQL(SearchTitleTable.createMangaIdIndexQuery) + execSQL(SearchTitleTable.createTitleIndexQuery) + execSQL(MergedTable.createIndexQuery) + execSQL(FeedSavedSearchTable.createSavedSearchIdIndexQuery) + // SY <-- } override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { - Database.Schema.migrate( - driver = AndroidSqliteDriver(database = db, cacheSize = 1), - oldVersion = oldVersion, - newVersion = newVersion - ) + if (oldVersion < 2) { + db.execSQL(MangaTable.addCoverLastModified) + } + if (oldVersion < 3) { + db.execSQL(MangaTable.addDateAdded) + db.execSQL(MangaTable.backfillDateAdded) + } + if (oldVersion < 4) { + db.execSQL(MergedTable.dropTableQuery) + db.execSQL(MergedTable.createTableQuery) + db.execSQL(MergedTable.createIndexQuery) + } + /*if (oldVersion < 5) { + db.execSQL(SimilarTable.createTableQuery) + db.execSQL(SimilarTable.createMangaIdIndexQuery) + }*/ + if (oldVersion < 6) { + db.execSQL(MangaTable.addFilteredScanlators) + } + if (oldVersion < 7) { + db.execSQL("DROP TABLE IF EXISTS manga_related") + } + if (oldVersion < 8) { + db.execSQL(MangaTable.addNextUpdateCol) + } + if (oldVersion < 9) { + db.execSQL(TrackTable.renameTableToTemp) + db.execSQL(TrackTable.createTableQuery) + db.execSQL(TrackTable.insertFromTempTable) + db.execSQL(TrackTable.dropTempTable) + } + if (oldVersion < 10) { + db.execSQL(ChapterTable.fixDateUploadIfNeeded) + } + if (oldVersion < 11) { + db.execSQL(FavoriteEntryTable.createTableQuery) + } + if (oldVersion < 12) { + db.execSQL(FavoriteEntryTable.fixTableQuery) + } + if (oldVersion < 13) { + db.execSQL(SavedSearchTable.createTableQuery) + db.execSQL(FeedSavedSearchTable.createTableQuery) + db.execSQL(FeedSavedSearchTable.createSavedSearchIdIndexQuery) + } } override fun onConfigure(db: SupportSQLiteDatabase) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt index e2ab2f346..7b2ec55e2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -4,12 +4,40 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery import com.pushtorefresh.storio.sqlite.queries.RawQuery import eu.kanade.tachiyomi.data.database.DbProvider import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory import eu.kanade.tachiyomi.data.database.resolvers.HistoryChapterIdPutResolver -import eu.kanade.tachiyomi.data.database.resolvers.HistoryUpsertResolver +import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver +import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver import eu.kanade.tachiyomi.data.database.tables.HistoryTable +import java.util.Date interface HistoryQueries : DbProvider { + /** + * Insert history into database + * @param history object containing history information + */ + fun insertHistory(history: History) = db.put().`object`(history).prepare() + + /** + * Returns history of recent manga containing last read chapter + * @param date recent date range + * @param limit the limit of manga to grab + * @param offset offset the db by + * @param search what to search in the db history + */ + fun getRecentManga(date: Date, limit: Int = 25, offset: Int = 0, search: String = "") = db.get() + .listOfObjects(MangaChapterHistory::class.java) + .withQuery( + RawQuery.builder() + .query(getRecentMangasQuery(search)) + .args(date.time, limit, offset) + .observesTables(HistoryTable.TABLE) + .build(), + ) + .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) + .prepare() + fun getHistoryByMangaId(mangaId: Long) = db.get() .listOfObjects(History::class.java) .withQuery( @@ -37,9 +65,9 @@ interface HistoryQueries : DbProvider { * Inserts history object if not yet in database * @param history history object */ - fun upsertHistoryLastRead(history: History) = db.put() + fun updateHistoryLastRead(history: History) = db.put() .`object`(history) - .withPutResolver(HistoryUpsertResolver()) + .withPutResolver(HistoryLastReadPutResolver()) .prepare() /** @@ -47,12 +75,12 @@ interface HistoryQueries : DbProvider { * Inserts history object if not yet in database * @param historyList history object list */ - fun upsertHistoryLastRead(historyList: List) = db.put() + fun updateHistoryLastRead(historyList: List) = db.put() .objects(historyList) - .withPutResolver(HistoryUpsertResolver()) + .withPutResolver(HistoryLastReadPutResolver()) .prepare() - fun dropHistoryTable() = db.delete() + fun deleteHistory() = db.delete() .byQuery( DeleteQuery.builder() .table(HistoryTable.TABLE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index 15e3c0e5b..8d3e69f3e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.data.database.queries import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver +import exh.savedsearches.tables.FeedSavedSearchTable +import exh.savedsearches.tables.SavedSearchTable import exh.source.MERGED_SOURCE_ID import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter @@ -74,6 +76,32 @@ fun getReadMangaNotInLibraryQuery() = ) """ +/** + * Query to get the global feed saved searches + */ +fun getGlobalFeedSavedSearchQuery() = + """ + SELECT ${SavedSearchTable.TABLE}.* + FROM ( + SELECT ${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} FROM ${FeedSavedSearchTable.TABLE} WHERE ${FeedSavedSearchTable.COL_GLOBAL} = 1 + ) AS M + JOIN ${SavedSearchTable.TABLE} + ON ${SavedSearchTable.TABLE}.${SavedSearchTable.COL_ID} = M.${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} +""" + +/** + * Query to get the source feed saved searches + */ +fun getSourceFeedSavedSearchQuery() = + """ + SELECT ${SavedSearchTable.TABLE}.* + FROM ( + SELECT ${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} FROM ${FeedSavedSearchTable.TABLE} WHERE ${FeedSavedSearchTable.COL_GLOBAL} = 0 AND ${FeedSavedSearchTable.COL_SOURCE} = ? + ) AS M + JOIN ${SavedSearchTable.TABLE} + ON ${SavedSearchTable.TABLE}.${SavedSearchTable.COL_ID} = M.${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} +""" + /** * Query to get the manga from the library, with their categories, read and unread count. */ @@ -162,8 +190,7 @@ fun getRecentMangasQuery(search: String = "") = SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ} FROM ${Chapter.TABLE} JOIN ${History.TABLE} ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} - GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} - ) AS max_last_read + GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID} WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryUpsertResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryUpsertResolver.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt index 908aca16d..7bcba97f7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryUpsertResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt @@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.data.database.mappers.HistoryPutResolver import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.tables.HistoryTable -class HistoryUpsertResolver : HistoryPutResolver() { +class HistoryLastReadPutResolver : HistoryPutResolver() { /** * Updates last_read time of chapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt index 80a88ca55..5ca03b56e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt @@ -15,4 +15,19 @@ object CategoryTable { // SY --> const val COL_MANGA_ORDER = "manga_order" // SY <-- + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_NAME TEXT NOT NULL, + $COL_ORDER INTEGER NOT NULL, + $COL_FLAGS INTEGER NOT NULL, + $COL_MANGA_ORDER TEXT NOT NULL + )""" + + // SY --> + val addMangaOrder: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_MANGA_ORDER TEXT" + // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt index 8914e6f3c..793349119 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt @@ -27,4 +27,42 @@ object ChapterTable { const val COL_CHAPTER_NUMBER = "chapter_number" const val COL_SOURCE_ORDER = "source_order" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_MANGA_ID INTEGER NOT NULL, + $COL_URL TEXT NOT NULL, + $COL_NAME TEXT NOT NULL, + $COL_SCANLATOR TEXT, + $COL_READ BOOLEAN NOT NULL, + $COL_BOOKMARK BOOLEAN NOT NULL, + $COL_LAST_PAGE_READ INT NOT NULL, + $COL_CHAPTER_NUMBER FLOAT NOT NULL, + $COL_SOURCE_ORDER INTEGER NOT NULL, + $COL_DATE_FETCH LONG NOT NULL, + $COL_DATE_UPLOAD LONG NOT NULL, + FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID}) + ON DELETE CASCADE + )""" + + val createMangaIdIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_MANGA_ID}_index ON $TABLE($COL_MANGA_ID)" + + val createUnreadChaptersIndexQuery: String + get() = "CREATE INDEX ${TABLE}_unread_by_manga_index ON $TABLE($COL_MANGA_ID, $COL_READ) " + + "WHERE $COL_READ = 0" + + val sourceOrderUpdateQuery: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SOURCE_ORDER INTEGER DEFAULT 0" + + val bookmarkUpdateQuery: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_BOOKMARK BOOLEAN DEFAULT FALSE" + + val addScanlator: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SCANLATOR TEXT DEFAULT NULL" + + val fixDateUploadIfNeeded: String + get() = "UPDATE $TABLE SET $COL_DATE_UPLOAD = $COL_DATE_FETCH WHERE $COL_DATE_UPLOAD = 0" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt index 4dfe9f0dd..9d19544a4 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt @@ -26,4 +26,24 @@ object HistoryTable { * Time read column name */ const val COL_TIME_READ = "${TABLE}_time_read" + + /** + * query to create history table + */ + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_CHAPTER_ID INTEGER NOT NULL UNIQUE, + $COL_LAST_READ LONG, + $COL_TIME_READ LONG, + FOREIGN KEY($COL_CHAPTER_ID) REFERENCES ${ChapterTable.TABLE} (${ChapterTable.COL_ID}) + ON DELETE CASCADE + )""" + + /** + * query to index history chapter id + */ + val createChapterIdIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_CHAPTER_ID}_index ON $TABLE($COL_CHAPTER_ID)" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt index d39b32adf..578a85bbc 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt @@ -9,4 +9,16 @@ object MangaCategoryTable { const val COL_MANGA_ID = "manga_id" const val COL_CATEGORY_ID = "category_id" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_MANGA_ID INTEGER NOT NULL, + $COL_CATEGORY_ID INTEGER NOT NULL, + FOREIGN KEY($COL_CATEGORY_ID) REFERENCES ${CategoryTable.TABLE} (${CategoryTable.COL_ID}) + ON DELETE CASCADE, + FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID}) + ON DELETE CASCADE + )""" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt index 3e42dea7a..9f2e46f78 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt @@ -51,4 +51,59 @@ object MangaTable { const val COMPUTED_COL_UNREAD_COUNT = "unread_count" const val COMPUTED_COL_READ_COUNT = "read_count" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_SOURCE INTEGER NOT NULL, + $COL_URL TEXT NOT NULL, + $COL_ARTIST TEXT, + $COL_AUTHOR TEXT, + $COL_DESCRIPTION TEXT, + $COL_GENRE TEXT, + $COL_TITLE TEXT NOT NULL, + $COL_STATUS INTEGER NOT NULL, + $COL_THUMBNAIL_URL TEXT, + $COL_FAVORITE INTEGER NOT NULL, + $COL_LAST_UPDATE LONG, + $COL_NEXT_UPDATE LONG, + $COL_INITIALIZED BOOLEAN NOT NULL, + $COL_VIEWER INTEGER NOT NULL, + $COL_CHAPTER_FLAGS INTEGER NOT NULL, + $COL_COVER_LAST_MODIFIED LONG NOT NULL, + $COL_DATE_ADDED LONG NOT NULL, + $COL_FILTERED_SCANLATORS TEXT + )""" + + val createUrlIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_URL}_index ON $TABLE($COL_URL)" + + val createLibraryIndexQuery: String + get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " + + "WHERE $COL_FAVORITE = 1" + + val addCoverLastModified: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_COVER_LAST_MODIFIED LONG NOT NULL DEFAULT 0" + + val addDateAdded: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_DATE_ADDED LONG NOT NULL DEFAULT 0" + + /** + * Used with addDateAdded to populate it with the oldest chapter fetch date. + */ + val backfillDateAdded: String + get() = "UPDATE $TABLE SET $COL_DATE_ADDED = " + + "(SELECT MIN(${ChapterTable.COL_DATE_FETCH}) " + + "FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " + + "ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " + + "GROUP BY $TABLE.$COL_ID)" + + val addNextUpdateCol: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_NEXT_UPDATE LONG DEFAULT 0" + + // SY --> + val addFilteredScanlators: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_FILTERED_SCANLATORS TEXT" + // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt index 90c38d537..5a9a8f239 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt @@ -30,6 +30,43 @@ object TrackTable { const val COL_FINISH_DATE = "finish_date" + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_MANGA_ID INTEGER NOT NULL, + $COL_SYNC_ID INTEGER NOT NULL, + $COL_MEDIA_ID INTEGER NOT NULL, + $COL_LIBRARY_ID INTEGER, + $COL_TITLE TEXT NOT NULL, + $COL_LAST_CHAPTER_READ REAL NOT NULL, + $COL_TOTAL_CHAPTERS INTEGER NOT NULL, + $COL_STATUS INTEGER NOT NULL, + $COL_SCORE FLOAT NOT NULL, + $COL_TRACKING_URL TEXT NOT NULL, + $COL_START_DATE LONG NOT NULL, + $COL_FINISH_DATE LONG NOT NULL, + UNIQUE ($COL_MANGA_ID, $COL_SYNC_ID) ON CONFLICT REPLACE, + FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID}) + ON DELETE CASCADE + )""" + + val addTrackingUrl: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_TRACKING_URL TEXT DEFAULT ''" + + val addLibraryId: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_LIBRARY_ID INTEGER NULL" + + val addStartDate: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_START_DATE LONG NOT NULL DEFAULT 0" + + val addFinishDate: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_FINISH_DATE LONG NOT NULL DEFAULT 0" + + val renameTableToTemp: String + get() = + "ALTER TABLE $TABLE RENAME TO ${TABLE}_tmp" + val insertFromTempTable: String get() = """ @@ -37,4 +74,7 @@ object TrackTable { |SELECT $COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE |FROM ${TABLE}_tmp """.trimMargin() + + val dropTempTable: String + get() = "DROP TABLE ${TABLE}_tmp" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 594325527..26fc7dea7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -166,7 +166,7 @@ class NotificationReceiver : BroadcastReceiver() { * @param chapterId id of chapter */ private fun openChapter(context: Context, mangaId: Long, chapterId: Long) { - val db = Injekt.get() + val db = DatabaseHelper(context) val manga = db.getManga(mangaId).executeAsBlocking() val chapter = db.getChapter(chapterId).executeAsBlocking() if (manga != null && chapter != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt deleted file mode 100644 index 24c6719a6..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ComposeController.kt +++ /dev/null @@ -1,26 +0,0 @@ -package eu.kanade.tachiyomi.ui.base.controller - -import android.view.LayoutInflater -import android.view.View -import androidx.compose.runtime.Composable -import eu.kanade.presentation.theme.TachiyomiTheme -import eu.kanade.tachiyomi.databinding.ComposeControllerBinding -import nucleus.presenter.Presenter - -abstract class ComposeController

> : NucleusController() { - - override fun createBinding(inflater: LayoutInflater): ComposeControllerBinding = - ComposeControllerBinding.inflate(inflater) - - override fun onViewCreated(view: View) { - super.onViewCreated(view) - - binding.root.setContent { - TachiyomiTheme { - ComposeContent() - } - } - } - - @Composable abstract fun ComposeContent() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt index 6a750ede4..5ebc38bfb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedController.kt @@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.toast import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch @@ -67,45 +66,41 @@ open class FeedController : } private fun addFeed() { - viewScope.launchUI { - if (presenter.hasTooManyFeeds()) { - activity?.toast(R.string.too_many_in_feed) - return@launchUI - } - val items = presenter.getEnabledSources() - val itemsStrings = items.map { it.toString() } - var selectedIndex = 0 - - MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.feed) - .setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which -> - selectedIndex = which - } - .setPositiveButton(android.R.string.ok) { _, _ -> - addFeedSearch(items[selectedIndex]) - } - .setNegativeButton(android.R.string.cancel, null) - .show() + if (presenter.hasTooManyFeeds()) { + activity?.toast(R.string.too_many_in_feed) + return } + val items = presenter.getEnabledSources() + val itemsStrings = items.map { it.toString() } + var selectedIndex = 0 + + MaterialAlertDialogBuilder(activity!!) + .setTitle(R.string.feed) + .setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton(android.R.string.ok) { _, _ -> + addFeedSearch(items[selectedIndex]) + } + .setNegativeButton(android.R.string.cancel, null) + .show() } private fun addFeedSearch(source: CatalogueSource) { - viewScope.launchUI { - val items = presenter.getSourceSavedSearches(source) - val itemsStrings = listOf(activity!!.getString(R.string.latest)) + items.map { it.name } - var selectedIndex = 0 + val items = presenter.getSourceSavedSearches(source) + val itemsStrings = listOf(activity!!.getString(R.string.latest)) + items.map { it.name } + var selectedIndex = 0 - MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.feed) - .setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which -> - selectedIndex = which - } - .setPositiveButton(android.R.string.ok) { _, _ -> - presenter.createFeed(source, items.getOrNull(selectedIndex - 1)) - } - .setNegativeButton(android.R.string.cancel, null) - .show() - } + MaterialAlertDialogBuilder(activity!!) + .setTitle(R.string.feed) + .setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton(android.R.string.ok) { _, _ -> + presenter.createFeed(source, items.getOrNull(selectedIndex - 1)) + } + .setNegativeButton(android.R.string.cancel, null) + .show() } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt index 2fb86e947..21c6b932f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedPresenter.kt @@ -1,9 +1,6 @@ package eu.kanade.tachiyomi.ui.browse.feed import android.os.Bundle -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.exh.feedSavedSearchMapper -import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.toMangaInfo @@ -18,12 +15,9 @@ import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.runAsObservable -import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.logcat import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import logcat.LogPriority @@ -41,12 +35,11 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer * Function calls should be done from here. UI calls should be done from the controller. * * @param sourceManager manages the different sources. - * @param database manages the database calls. + * @param db manages the database calls. * @param preferences manages the preference calls. */ open class FeedPresenter( val sourceManager: SourceManager = Injekt.get(), - val database: DatabaseHandler = Injekt.get(), val db: DatabaseHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(), ) : BasePresenter() { @@ -69,11 +62,14 @@ open class FeedPresenter( override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - database.subscribeToList { feed_saved_searchQueries.selectAllGlobal() } - .onEach { + db.getGlobalFeedSavedSearches() + .asRxObservable() + .observeOn(AndroidSchedulers.mainThread()) + .doOnEach { getFeed() } - .launchIn(presenterScope) + .subscribe() + .let(::add) } override fun onDestroy() { @@ -82,10 +78,8 @@ open class FeedPresenter( super.onDestroy() } - suspend fun hasTooManyFeeds(): Boolean { - return withIOContext { - database.awaitList { feed_saved_searchQueries.selectAllGlobal() }.size > 10 - } + fun hasTooManyFeeds(): Boolean { + return db.getGlobalFeedSavedSearches().executeAsBlocking().size > 10 } fun getEnabledSources(): List { @@ -99,38 +93,33 @@ open class FeedPresenter( return list.sortedBy { it.id.toString() !in pinnedSources } } - suspend fun getSourceSavedSearches(source: CatalogueSource): List { - return withIOContext { - database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } - } + fun getSourceSavedSearches(source: CatalogueSource): List { + return db.getSavedSearches(source.id).executeAsBlocking() } fun createFeed(source: CatalogueSource, savedSearch: SavedSearch?) { launchIO { - database.await { - feed_saved_searchQueries.insertFeedSavedSearch( - _id = null, + db.insertFeedSavedSearch( + FeedSavedSearch( + id = null, source = source.id, - saved_search = savedSearch?.id, + savedSearch = savedSearch?.id, global = true, - ) - } + ), + ).executeAsBlocking() } } fun deleteFeed(feed: FeedSavedSearch) { launchIO { - database.await { - feed_saved_searchQueries.deleteById(feed.id ?: return@await) - } + db.deleteFeedSavedSearch(feed).executeAsBlocking() } } - private suspend fun getSourcesToGetFeed(): List> { - val savedSearches = database.awaitList { - feed_saved_searchQueries.selectGlobalFeedSavedSearch(savedSearchMapper) - }.associateBy { it.id } - return database.awaitList { feed_saved_searchQueries.selectAllGlobal(feedSavedSearchMapper) } + private fun getSourcesToGetFeed(): List> { + val savedSearches = db.getGlobalSavedSearchesFeed().executeAsBlocking() + .associateBy { it.id!! } + return db.getGlobalFeedSavedSearches().executeAsBlocking() .map { it to savedSearches[it.savedSearch] } } @@ -149,7 +138,7 @@ open class FeedPresenter( /** * Initiates get manga per feed. */ - suspend fun getFeed() { + fun getFeed() { // Create image fetch subscription initializeFetchImageSubscription() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt index c45e49ec9..23b9b6c47 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt @@ -137,7 +137,7 @@ class MigrationProcessAdapter( } } db.insertChapters(dbChapters).executeAsBlocking() - db.upsertHistoryLastRead(historyList).executeAsBlocking() + db.updateHistoryLastRead(historyList).executeAsBlocking() } // Update categories if (MigrationFlags.hasCategories(flags)) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt index 94bd21a1b..1b709751f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt @@ -37,8 +37,8 @@ class SearchController( Injekt.get().getManga(mangaId).executeAsBlocking(), sources.map { Injekt.get().getOrStub(it) }.filterIsInstance(), ) { - this.targetController = targetController - } + this.targetController = targetController + } @Suppress("unused") constructor(bundle: Bundle) : this( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt index aff2553df..9ebf73302 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt @@ -44,7 +44,6 @@ import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.more.MoreController import eu.kanade.tachiyomi.ui.webview.WebViewActivity -import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.preference.asImmediateFlow import eu.kanade.tachiyomi.util.system.connectivityManager import eu.kanade.tachiyomi.util.system.openInBrowser @@ -202,7 +201,7 @@ open class BrowseSourceController(bundle: Bundle) : // SY --> this, presenter.source, - emptyList(), + presenter.loadSearches(), // SY <-- onFilterClicked = { showProgressBar() @@ -217,58 +216,54 @@ open class BrowseSourceController(bundle: Bundle) : }, // EXH --> onSaveClicked = { - viewScope.launchUI { + filterSheet?.context?.let { + val names = presenter.loadSearches().map { it.name } + var searchName = "" + MaterialAlertDialogBuilder(it) + .setTitle(R.string.save_search) + .setTextInput(hint = it.getString(R.string.save_search_hint)) { input -> + searchName = input + } + .setPositiveButton(R.string.action_save) { _, _ -> + if (searchName.isNotBlank() && searchName !in names) { + presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters) + } else { + it.toast(R.string.save_search_invalid_name) + } + } + .setNegativeButton(R.string.action_cancel, null) + .show() + } + }, + onSavedSearchClicked = cb@{ idOfSearch -> + val search = presenter.loadSearch(idOfSearch) + + if (search == null) { filterSheet?.context?.let { - val names = presenter.loadSearches().map { it.name } - var searchName = "" MaterialAlertDialogBuilder(it) - .setTitle(R.string.save_search) - .setTextInput(hint = it.getString(R.string.save_search_hint)) { input -> - searchName = input - } - .setPositiveButton(R.string.action_save) { _, _ -> - if (searchName.isNotBlank() && searchName !in names) { - presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters) - } else { - it.toast(R.string.save_search_invalid_name) - } - } - .setNegativeButton(R.string.action_cancel, null) + .setTitle(R.string.save_search_failed_to_load) + .setMessage(R.string.save_search_failed_to_load_message) .show() } + return@cb } - }, - onSavedSearchClicked = { idOfSearch -> - viewScope.launchUI { - val search = presenter.loadSearch(idOfSearch) - if (search == null) { - filterSheet?.context?.let { - MaterialAlertDialogBuilder(it) - .setTitle(R.string.save_search_failed_to_load) - .setMessage(R.string.save_search_failed_to_load_message) - .show() - } - return@launchUI - } - - if (search.filterList == null) { - activity?.toast(R.string.save_search_invalid) - return@launchUI - } - - presenter.sourceFilters = FilterList(search.filterList) - filterSheet?.setFilters(presenter.filterItems) - val allDefault = presenter.sourceFilters == presenter.source.getFilterList() - - showProgressBar() - adapter?.clear() - filterSheet?.dismiss() - presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters) - activity?.invalidateOptionsMenu() + if (search.filterList == null) { + activity?.toast(R.string.save_search_invalid) + return@cb } + + presenter.sourceFilters = FilterList(search.filterList) + filterSheet?.setFilters(presenter.filterItems) + val allDefault = presenter.sourceFilters == presenter.source.getFilterList() + + showProgressBar() + adapter?.clear() + filterSheet?.dismiss() + presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters) + activity?.invalidateOptionsMenu() }, - onSavedSearchDeleteClicked = { idToDelete, name -> + onSavedSearchDeleteClicked = cb@{ idToDelete, name -> filterSheet?.context?.let { MaterialAlertDialogBuilder(it) .setTitle(R.string.save_search_delete) @@ -282,9 +277,6 @@ open class BrowseSourceController(bundle: Bundle) : }, // EXH <-- ) - launchUI { - filterSheet?.setSavedSearches(presenter.loadSearches()) - } filterSheet?.setFilters(presenter.filterItems) filterSheet?.setOnShowListener { actionFab?.hide() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt index 1cd6ad975..b0759389e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt @@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.ui.browse.source.browse import android.os.Bundle import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category @@ -39,7 +37,6 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.system.logcat @@ -53,10 +50,8 @@ import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -83,7 +78,6 @@ open class BrowseSourcePresenter( // SY <-- private val sourceManager: SourceManager = Injekt.get(), private val db: DatabaseHelper = Injekt.get(), - private val database: DatabaseHandler = Injekt.get(), private val prefs: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), ) : BasePresenter() { @@ -146,11 +140,7 @@ open class BrowseSourcePresenter( val jsonFilters = filters if (savedSearchFilters != null) { runCatching { - val savedSearch = runBlocking { - database.awaitOneOrNull { - saved_searchQueries.selectById(savedSearchFilters, savedSearchMapper) - } - } ?: return@runCatching + val savedSearch = db.getSavedSearch(savedSearchFilters).executeAsBlocking() ?: return@runCatching query = savedSearch.query.orEmpty() val filtersJson = savedSearch.filtersJson ?: return@runCatching @@ -166,14 +156,18 @@ open class BrowseSourcePresenter( } } - database.subscribeToList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } - .map { loadSearches(it) } - .onEach { - withUIContext { - view?.setSavedSearches(it) - } + db.getSavedSearches(source.id) + .asRxObservable() + .map { + loadSearches(it) } - .launchIn(presenterScope) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeLatestCache( + { controller, savedSearches -> + controller.setSavedSearches(savedSearches) + }, + ) // SY <-- if (savedState != null) { @@ -491,90 +485,83 @@ open class BrowseSourcePresenter( fun saveSearch(name: String, query: String, filterList: FilterList) { launchIO { kotlin.runCatching { - database.await { - saved_searchQueries.insertSavedSearch( - _id = null, - source = source.id, - name = name.trim(), - query = query.nullIfBlank(), - filters_json = filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) }, - ) - } + val savedSearch = SavedSearch( + id = null, + source = source.id, + name = name.trim(), + query = query.nullIfBlank(), + filtersJson = filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) }, + ) + + db.insertSavedSearch(savedSearch).executeAsBlocking() } } } fun deleteSearch(searchId: Long) { launchIO { - database.await { saved_searchQueries.deleteById(searchId) } + db.deleteSavedSearch(searchId).executeAsBlocking() } } - suspend fun loadSearch(searchId: Long): EXHSavedSearch? { - return withIOContext { - val search = database.awaitOneOrNull { - saved_searchQueries.selectById(searchId, savedSearchMapper) - } ?: return@withIOContext null - EXHSavedSearch( - id = search.id!!, - name = search.name, - query = search.query.orEmpty(), - filterList = runCatching { - val originalFilters = source.getFilterList() - filterSerializer.deserialize( - filters = originalFilters, - json = search.filtersJson - ?.let { Json.decodeFromString(it) } - ?: return@runCatching null, - ) - originalFilters - }.getOrNull(), + fun loadSearch(searchId: Long): EXHSavedSearch? { + val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null + return EXHSavedSearch( + id = search.id!!, + name = search.name, + query = search.query.orEmpty(), + filterList = runCatching { + val originalFilters = source.getFilterList() + filterSerializer.deserialize( + filters = originalFilters, + json = search.filtersJson + ?.let { Json.decodeFromString(it) } + ?: return@runCatching null, + ) + originalFilters + }.getOrNull(), + ) + } + + fun loadSearches(searches: List = db.getSavedSearches(source.id).executeAsBlocking()): List { + return searches.map { + val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) + val filters = try { + Json.decodeFromString(filtersJson) + } catch (e: Exception) { + xLogE("Failed to load saved search!", e) + null + } ?: return@map EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, ) - } - } - suspend fun loadSearches(searches: List? = null): List { - return withIOContext { - (searches ?: (database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) })) - .map { - val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - val filters = try { - Json.decodeFromString(filtersJson) - } catch (e: Exception) { - xLogE("Failed to load saved search!", e) - null - } ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - - try { - val originalFilters = source.getFilterList() - filterSerializer.deserialize(originalFilters, filters) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = originalFilters, - ) - } catch (t: RuntimeException) { - // Load failed - xLogE("Failed to load saved search!", t) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - } - } + try { + val originalFilters = source.getFilterList() + filterSerializer.deserialize(originalFilters, filters) + EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = originalFilters, + ) + } catch (t: RuntimeException) { + // Load failed + xLogE("Failed to load saved search!", t) + EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) + } } } // EXH <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt index 973c2c4c4..491a9e59b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedController.kt @@ -24,8 +24,6 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.util.lang.launchUI -import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.system.toast import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch @@ -186,7 +184,7 @@ open class SourceFeedController : // SY --> this, presenter.source, - emptyList(), + presenter.loadSearches(), // SY <-- onFilterClicked = { val allDefault = presenter.sourceFilters == presenter.source.getFilterList() @@ -204,60 +202,51 @@ open class SourceFeedController : }, onResetClicked = {}, onSaveClicked = {}, - onSavedSearchClicked = { idOfSearch -> - viewScope.launchUI { - val search = presenter.loadSearch(idOfSearch) + onSavedSearchClicked = cb@{ idOfSearch -> + val search = presenter.loadSearch(idOfSearch) - if (search == null) { - filterSheet?.context?.let { - MaterialAlertDialogBuilder(it) - .setTitle(R.string.save_search_failed_to_load) - .setMessage(R.string.save_search_failed_to_load_message) - .show() - } - return@launchUI - } - - if (search.filterList == null) { - activity?.toast(R.string.save_search_invalid) - return@launchUI - } - - presenter.sourceFilters = FilterList(search.filterList) - filterSheet?.setFilters(presenter.filterItems) - val allDefault = presenter.sourceFilters == presenter.source.getFilterList() - filterSheet?.dismiss() - - if (!allDefault) { - onBrowseClick( - search = presenter.query.nullIfBlank(), - savedSearch = search.id, - ) - } - } - }, - onSavedSearchDeleteClicked = { idOfSearch, name -> - viewScope.launchUI { - if (presenter.hasTooManyFeeds()) { - activity?.toast(R.string.too_many_in_feed) - return@launchUI - } - withUIContext { - MaterialAlertDialogBuilder(activity!!) - .setTitle(R.string.feed) - .setMessage(activity!!.getString(R.string.feed_add, name)) - .setPositiveButton(R.string.action_add) { _, _ -> - presenter.createFeed(idOfSearch) - } - .setNegativeButton(android.R.string.cancel, null) + if (search == null) { + filterSheet?.context?.let { + MaterialAlertDialogBuilder(it) + .setTitle(R.string.save_search_failed_to_load) + .setMessage(R.string.save_search_failed_to_load_message) .show() } + return@cb + } + + if (search.filterList == null) { + activity?.toast(R.string.save_search_invalid) + return@cb + } + + presenter.sourceFilters = FilterList(search.filterList) + filterSheet?.setFilters(presenter.filterItems) + val allDefault = presenter.sourceFilters == presenter.source.getFilterList() + filterSheet?.dismiss() + + if (!allDefault) { + onBrowseClick( + search = presenter.query.nullIfBlank(), + savedSearch = search.id, + ) } }, + onSavedSearchDeleteClicked = cb@{ idOfSearch, name -> + if (presenter.hasTooManyFeeds()) { + activity?.toast(R.string.too_many_in_feed) + return@cb + } + MaterialAlertDialogBuilder(activity!!) + .setTitle(R.string.feed) + .setMessage(activity!!.getString(R.string.feed_add, name)) + .setPositiveButton(R.string.action_add) { _, _ -> + presenter.createFeed(idOfSearch) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + }, ) - launchUI { - filterSheet?.setSavedSearches(presenter.loadSearches()) - } filterSheet?.setFilters(presenter.filterItems) // TODO: [ExtendedFloatingActionButton] hide/show methods don't work properly diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt index f4492b8f9..7729e6945 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedPresenter.kt @@ -2,9 +2,6 @@ package eu.kanade.tachiyomi.ui.browse.source.feed import android.os.Bundle import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.data.DatabaseHandler -import eu.kanade.data.exh.feedSavedSearchMapper -import eu.kanade.data.exh.savedSearchMapper import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.toMangaInfo @@ -19,15 +16,11 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter.Companion.toItems import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.runAsObservable -import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.logcat import exh.log.xLogE import exh.savedsearches.EXHSavedSearch import exh.savedsearches.models.FeedSavedSearch import exh.savedsearches.models.SavedSearch -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray @@ -53,12 +46,11 @@ sealed class SourceFeed { * Function calls should be done from here. UI calls should be done from the controller. * * @param source the source. - * @param database manages the database calls. + * @param db manages the database calls. * @param preferences manages the preference calls. */ open class SourceFeedPresenter( val source: CatalogueSource, - val database: DatabaseHandler = Injekt.get(), val db: DatabaseHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(), ) : BasePresenter() { @@ -98,11 +90,14 @@ open class SourceFeedPresenter( sourceFilters = source.getFilterList() - database.subscribeToList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) } - .onEach { + db.getSourceFeedSavedSearches(source.id) + .asRxObservable() + .observeOn(AndroidSchedulers.mainThread()) + .doOnEach { getFeed() } - .launchIn(presenterScope) + .subscribe() + .let(::add) } override fun onDestroy() { @@ -111,39 +106,35 @@ open class SourceFeedPresenter( super.onDestroy() } - suspend fun hasTooManyFeeds(): Boolean { - return withIOContext { - database.awaitList { - feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id) - }.size > 10 - } + fun hasTooManyFeeds(): Boolean { + return db.getSourceFeedSavedSearches(source.id).executeAsBlocking().size > 10 } - suspend fun getSourceSavedSearches(): List { - return database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) } + fun getSourceSavedSearches(): List { + return db.getSavedSearches(source.id).executeAsBlocking() } fun createFeed(savedSearchId: Long) { launchIO { - database.await { - feed_saved_searchQueries.insertFeedSavedSearch( - _id = null, + db.insertFeedSavedSearch( + FeedSavedSearch( + id = null, source = source.id, - saved_search = savedSearchId, - global = false - ) - } + savedSearch = savedSearchId, + global = false, + ), + ).executeAsBlocking() } } fun deleteFeed(feed: FeedSavedSearch) { launchIO { - database.await { feed_saved_searchQueries.deleteById(feed.id ?: return@await) } + db.deleteFeedSavedSearch(feed).executeAsBlocking() } } - private suspend fun getSourcesToGetFeed(): List { - val savedSearches = database.awaitList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) } + private fun getSourcesToGetFeed(): List { + val savedSearches = db.getSourceSavedSearchesFeed(source.id).executeAsBlocking() .associateBy { it.id!! } return listOfNotNull( @@ -151,7 +142,7 @@ open class SourceFeedPresenter( SourceFeed.Latest } else null, SourceFeed.Browse, - ) + database.awaitList { feed_saved_searchQueries.selectBySource(source.id, feedSavedSearchMapper) } + ) + db.getSourceFeedSavedSearches(source.id).executeAsBlocking() .map { SourceFeed.SourceSavedSearch(it, savedSearches[it.savedSearch]!!) } } @@ -168,7 +159,7 @@ open class SourceFeedPresenter( /** * Initiates get manga per feed. */ - suspend fun getFeed() { + fun getFeed() { // Create image fetch subscription initializeFetchImageSubscription() @@ -309,69 +300,62 @@ open class SourceFeedPresenter( return localManga } - suspend fun loadSearch(searchId: Long): EXHSavedSearch? { - return withIOContext { - val search = database.awaitOneOrNull { - saved_searchQueries.selectById(searchId, savedSearchMapper) - } ?: return@withIOContext null - EXHSavedSearch( - id = search.id!!, - name = search.name, - query = search.query.orEmpty(), - filterList = runCatching { - val originalFilters = source.getFilterList() - filterSerializer.deserialize( - filters = originalFilters, - json = search.filtersJson - ?.let { Json.decodeFromString(it) } - ?: return@runCatching null, - ) - originalFilters - }.getOrNull(), - ) - } + fun loadSearch(searchId: Long): EXHSavedSearch? { + val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null + return EXHSavedSearch( + id = search.id!!, + name = search.name, + query = search.query.orEmpty(), + filterList = runCatching { + val originalFilters = source.getFilterList() + filterSerializer.deserialize( + filters = originalFilters, + json = search.filtersJson + ?.let { Json.decodeFromString(it) } + ?: return@runCatching null, + ) + originalFilters + }.getOrNull(), + ) } - suspend fun loadSearches(): List { - return withIOContext { - database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }.map { - val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - val filters = try { - Json.decodeFromString(filtersJson) - } catch (e: Exception) { - if (e is CancellationException) throw e - null - } ?: return@map EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) + fun loadSearches(): List { + return db.getSavedSearches(source.id).executeAsBlocking().map { + val filtersJson = it.filtersJson ?: return@map EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) + val filters = try { + Json.decodeFromString(filtersJson) + } catch (e: Exception) { + null + } ?: return@map EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) - try { - val originalFilters = source.getFilterList() - filterSerializer.deserialize(originalFilters, filters) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = originalFilters, - ) - } catch (t: RuntimeException) { - // Load failed - xLogE("Failed to load saved search!", t) - EXHSavedSearch( - id = it.id!!, - name = it.name, - query = it.query.orEmpty(), - filterList = null, - ) - } + try { + val originalFilters = source.getFilterList() + filterSerializer.deserialize(originalFilters, filters) + EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = originalFilters, + ) + } catch (t: RuntimeException) { + // Load failed + xLogE("Failed to load saved search!", t) + EXHSavedSearch( + id = it.id!!, + name = it.name, + query = it.query.orEmpty(), + filterList = null, + ) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index f88bdce56..f43e8073e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -35,7 +35,6 @@ import com.google.android.material.snackbar.Snackbar import dev.chrisbanes.insetter.applyInsetter import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.SelectableAdapter -import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -137,8 +136,6 @@ class MangaController : DownloadCustomChaptersDialog.Listener, DeleteChaptersDialog.Listener { - constructor(history: HistoryWithRelations) : this(history.mangaId) - constructor(manga: Manga?, fromSource: Boolean = false, smartSearchConfig: SourceController.SmartSearchConfig? = null, update: Boolean = false) : super( bundleOf( MANGA_EXTRA to (manga?.id ?: 0), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaCoverImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaCoverImageView.kt new file mode 100644 index 000000000..f7e5daf1c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaCoverImageView.kt @@ -0,0 +1,24 @@ +package eu.kanade.tachiyomi.ui.manga.info + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView +import kotlin.math.min + +/** + * A custom ImageView for holding a manga cover with: + * - width: min(maxWidth attr, 33% of parent width) + * - height: 2:3 width:height ratio + * + * Should be defined with a width of match_parent. + */ +class MangaCoverImageView(context: Context, attrs: AttributeSet?) : AppCompatImageView(context, attrs) { + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + val width = min(maxWidth, measuredWidth / 3) + val height = width / 2 * 3 + setMeasuredDimension(width, height) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index c222517ce..7ada13307 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -127,19 +127,14 @@ import kotlin.time.Duration.Companion.seconds class ReaderActivity : BaseRxActivity() { companion object { - - fun newIntent(context: Context, mangaId: Long?, chapterId: Long?): Intent { + fun newIntent(context: Context, manga: Manga, chapter: Chapter): Intent { return Intent(context, ReaderActivity::class.java).apply { - putExtra("manga", mangaId) - putExtra("chapter", chapterId) + putExtra("manga", manga.id) + putExtra("chapter", chapter.id) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) } } - fun newIntent(context: Context, manga: Manga, chapter: Chapter): Intent { - return newIntent(context, manga.id, chapter.id) - } - const val SHIFT_DOUBLE_PAGES = "shiftingDoublePages" const val SHIFTED_PAGE_INDEX = "shiftedPageIndex" const val SHIFTED_CHAP_INDEX = "shiftedChapterIndex" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 9f9757222..d02fdc5ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -542,7 +542,7 @@ class ReaderPresenter( private fun saveChapterHistory(chapter: ReaderChapter) { if (!incognitoMode) { val history = History.create(chapter.chapter).apply { last_read = Date().time } - db.upsertHistoryLastRead(history).asRxCompletable() + db.updateHistoryLastRead(history).asRxCompletable() .onErrorComplete() .subscribeOn(Schedulers.io()) .subscribe() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/ClearHistoryDialogController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/ClearHistoryDialogController.kt deleted file mode 100644 index a4080a01d..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/ClearHistoryDialogController.kt +++ /dev/null @@ -1,21 +0,0 @@ -package eu.kanade.tachiyomi.ui.recent.history - -import android.app.Dialog -import android.os.Bundle -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.DialogController - -class ClearHistoryDialogController : DialogController() { - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - return MaterialAlertDialogBuilder(activity!!) - .setMessage(R.string.clear_history_confirmation) - .setPositiveButton(android.R.string.ok) { _, _ -> - (targetController as? HistoryController) - ?.presenter - ?.deleteAllHistory() - } - .setNegativeButton(android.R.string.cancel, null) - .create() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt new file mode 100644 index 000000000..8c1a88ec8 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt @@ -0,0 +1,51 @@ +package eu.kanade.tachiyomi.ui.recent.history + +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.source.SourceManager +import uy.kohesive.injekt.injectLazy +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols + +/** + * Adapter of HistoryHolder. + * Connection between Fragment and Holder + * Holder updates should be called from here. + * + * @param controller a HistoryController object + * @constructor creates an instance of the adapter. + */ +class HistoryAdapter(controller: HistoryController) : + FlexibleAdapter>(null, controller, true) { + + val sourceManager: SourceManager by injectLazy() + + val resumeClickListener: OnResumeClickListener = controller + val removeClickListener: OnRemoveClickListener = controller + val itemClickListener: OnItemClickListener = controller + + /** + * DecimalFormat used to display correct chapter number + */ + val decimalFormat = DecimalFormat( + "#.###", + DecimalFormatSymbols() + .apply { decimalSeparator = '.' }, + ) + + init { + setDisplayHeadersAtStartUp(true) + } + + interface OnResumeClickListener { + fun onResumeClick(position: Int) + } + + interface OnRemoveClickListener { + fun onRemoveClick(position: Int) + } + + interface OnItemClickListener { + fun onItemClick(position: Int) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt index f6877b1fc..333dea3d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt @@ -1,53 +1,193 @@ package eu.kanade.tachiyomi.ui.recent.history +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.View import androidx.appcompat.widget.SearchView -import androidx.compose.runtime.Composable -import eu.kanade.domain.chapter.model.Chapter -import eu.kanade.presentation.history.HistoryScreen +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dev.chrisbanes.insetter.applyInsetter +import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.base.controller.ComposeController +import eu.kanade.tachiyomi.data.backup.BackupRestoreService +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.databinding.HistoryControllerBinding +import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.RootController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.browse.source.browse.ProgressItem +import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.onAnimationsFinished +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import logcat.LogPriority import reactivecircus.flowbinding.appcompat.queryTextChanges +import uy.kohesive.injekt.injectLazy -class HistoryController : ComposeController(), RootController { +/** + * Fragment that shows recently read manga. + */ +class HistoryController : + NucleusController(), + RootController, + FlexibleAdapter.OnUpdateListener, + FlexibleAdapter.EndlessScrollListener, + HistoryAdapter.OnRemoveClickListener, + HistoryAdapter.OnResumeClickListener, + HistoryAdapter.OnItemClickListener, + RemoveHistoryDialog.Listener { + private val db: DatabaseHelper by injectLazy() + + /** + * Adapter containing the recent manga. + */ + var adapter: HistoryAdapter? = null + private set + + /** + * Endless loading item. + */ + private var progressItem: ProgressItem? = null + + /** + * Search query. + */ private var query = "" - override fun getTitle() = resources?.getString(R.string.label_recent_manga) + override fun getTitle(): String? { + return resources?.getString(R.string.label_recent_manga) + } - override fun createPresenter() = HistoryPresenter() + override fun createPresenter(): HistoryPresenter { + return HistoryPresenter() + } - @Composable - override fun ComposeContent() { - HistoryScreen( - composeView = binding.root, - presenter = presenter, - onClickItem = { history -> - router.pushController(MangaController(history).withFadeTransaction()) - }, - onClickResume = { history -> - presenter.getNextChapterForManga(history.mangaId, history.chapterId) - }, - onClickDelete = { history, all -> - if (all) { - // Reset last read of chapter to 0L - presenter.removeAllFromHistory(history.mangaId) - } else { - // Remove all chapters belonging to manga from library - presenter.removeFromHistory(history) - } - }, - ) + override fun createBinding(inflater: LayoutInflater) = HistoryControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + + binding.recycler.applyInsetter { + type(navigationBars = true) { + padding() + } + } + + // Initialize adapter + binding.recycler.layoutManager = LinearLayoutManager(view.context) + adapter = HistoryAdapter(this@HistoryController) + binding.recycler.setHasFixedSize(true) + binding.recycler.adapter = adapter + adapter?.fastScroller = binding.fastScroller + } + + override fun onDestroyView(view: View) { + adapter = null + super.onDestroyView(view) + } + + /** + * Populate adapter with chapters + * + * @param mangaHistory list of manga history + */ + fun onNextManga(mangaHistory: List, cleanBatch: Boolean = false) { + if (adapter?.itemCount ?: 0 == 0) { + resetProgressItem() + } + if (cleanBatch) { + adapter?.updateDataSet(mangaHistory) + } else { + adapter?.onLoadMoreComplete(mangaHistory) + } + binding.recycler.onAnimationsFinished { + (activity as? MainActivity)?.ready = true + } + } + + /** + * Safely error if next page load fails + */ + fun onAddPageError(error: Throwable) { + adapter?.onLoadMoreComplete(null) + adapter?.endlessTargetCount = 1 + logcat(LogPriority.ERROR, error) + } + + override fun onUpdateEmptyView(size: Int) { + if (size > 0) { + binding.emptyView.hide() + } else { + binding.emptyView.show(R.string.information_no_recent_manga) + } + } + + /** + * Sets a new progress item and reenables the scroll listener. + */ + private fun resetProgressItem() { + progressItem = ProgressItem() + adapter?.endlessTargetCount = 0 + adapter?.setEndlessScrollListener(this, progressItem!!) + } + + override fun onLoadMore(lastPosition: Int, currentPage: Int) { + val view = view ?: return + if (BackupRestoreService.isRunning(view.context.applicationContext)) { + onAddPageError(Throwable()) + return + } + val adapter = adapter ?: return + presenter.requestNext(adapter.itemCount - adapter.headerItems.size, query) + } + + override fun noMoreLoad(newItemsSize: Int) {} + + override fun onResumeClick(position: Int) { + val activity = activity ?: return + val (manga, chapter, _) = (adapter?.getItem(position) as? HistoryItem)?.mch ?: return + + val nextChapter = presenter.getNextChapter(chapter, manga) + if (nextChapter != null) { + val intent = ReaderActivity.newIntent(activity, manga, nextChapter) + startActivity(intent) + } else { + activity.toast(R.string.no_next_chapter) + } + } + + override fun onRemoveClick(position: Int) { + val (manga, _, history) = (adapter?.getItem(position) as? HistoryItem)?.mch ?: return + RemoveHistoryDialog(this, manga, history).showDialog(router) + } + + override fun onItemClick(position: Int) { + val manga = (adapter?.getItem(position) as? HistoryItem)?.mch?.manga ?: return + router.pushController(MangaController(manga).withFadeTransaction()) + } + + override fun removeHistory(manga: Manga, history: History, all: Boolean) { + if (all) { + // Reset last read of chapter to 0L + presenter.removeAllFromHistory(manga.id!!) + } else { + // Remove all chapters belonging to manga from library + presenter.removeFromHistory(history) + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -61,33 +201,46 @@ class HistoryController : ComposeController(), RootController searchView.clearFocus() } searchView.queryTextChanges() + .drop(1) // Drop first event after subscribed .filter { router.backstack.lastOrNull()?.controller == this } .onEach { query = it.toString() - presenter.search(query) + presenter.updateList(query) } .launchIn(viewScope) + + // Fixes problem with the overflow icon showing up in lieu of search + searchItem.fixExpand( + onExpand = { invalidateMenuOnExpand() }, + ) } override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { + when (item.itemId) { R.id.action_clear_history -> { - val dialog = ClearHistoryDialogController() - dialog.targetController = this@HistoryController - dialog.showDialog(router) - true + val ctrl = ClearHistoryDialogController() + ctrl.targetController = this@HistoryController + ctrl.showDialog(router) } - else -> super.onOptionsItemSelected(item) + } + + return super.onOptionsItemSelected(item) + } + + class ClearHistoryDialogController : DialogController() { + override fun onCreateDialog(savedViewState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(activity!!) + .setMessage(R.string.clear_history_confirmation) + .setPositiveButton(android.R.string.ok) { _, _ -> + (targetController as? HistoryController)?.clearHistory() + } + .setNegativeButton(android.R.string.cancel, null) + .create() } } - fun openChapter(chapter: Chapter?) { - val activity = activity ?: return - if (chapter != null) { - val intent = ReaderActivity.newIntent(activity, chapter.mangaId, chapter.id) - startActivity(intent) - } else { - activity.toast(R.string.no_next_chapter) - } + private fun clearHistory() { + db.deleteHistory().executeAsBlocking() + activity?.toast(R.string.clear_history_completed) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt new file mode 100644 index 000000000..8164e5cc8 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt @@ -0,0 +1,71 @@ +package eu.kanade.tachiyomi.ui.recent.history + +import android.view.View +import coil.dispose +import coil.load +import eu.davidea.viewholders.FlexibleViewHolder +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +import eu.kanade.tachiyomi.databinding.HistoryItemBinding +import eu.kanade.tachiyomi.util.lang.toTimestampString +import java.util.Date + +/** + * Holder that contains recent manga item + * Uses R.layout.item_recently_read. + * UI related actions should be called from here. + * + * @param view the inflated view for this holder. + * @param adapter the adapter handling this holder. + * @constructor creates a new recent chapter holder. + */ +class HistoryHolder( + view: View, + val adapter: HistoryAdapter, +) : FlexibleViewHolder(view, adapter) { + + private val binding = HistoryItemBinding.bind(view) + + init { + binding.holder.setOnClickListener { + adapter.itemClickListener.onItemClick(bindingAdapterPosition) + } + + binding.remove.setOnClickListener { + adapter.removeClickListener.onRemoveClick(bindingAdapterPosition) + } + + binding.resume.setOnClickListener { + adapter.resumeClickListener.onResumeClick(bindingAdapterPosition) + } + } + + /** + * Set values of view + * + * @param item item containing history information + */ + fun bind(item: MangaChapterHistory) { + // Retrieve objects + val (manga, chapter, history) = item + + // Set manga title + binding.mangaTitle.text = manga.title + + // Set chapter number + timestamp + if (chapter.chapter_number > -1f) { + val formattedNumber = adapter.decimalFormat.format(chapter.chapter_number.toDouble()) + binding.mangaSubtitle.text = itemView.context.getString( + R.string.recent_manga_time, + formattedNumber, + Date(history.last_read).toTimestampString(), + ) + } else { + binding.mangaSubtitle.text = Date(history.last_read).toTimestampString() + } + + // Set cover + binding.cover.dispose() + binding.cover.load(item.manga) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryItem.kt new file mode 100644 index 000000000..58f9e0cc2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryItem.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.ui.recent.history + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractSectionableItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +import eu.kanade.tachiyomi.ui.recent.DateSectionItem + +class HistoryItem(val mch: MangaChapterHistory, header: DateSectionItem) : + AbstractSectionableItem(header) { + + override fun getLayoutRes(): Int { + return R.layout.history_item + } + + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): HistoryHolder { + return HistoryHolder(view, adapter as HistoryAdapter) + } + + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: HistoryHolder, + position: Int, + payloads: List?, + ) { + holder.bind(mch) + } + + override fun equals(other: Any?): Boolean { + if (other is HistoryItem) { + return mch.manga.id == other.mch.manga.id + } + return false + } + + override fun hashCode(): Int { + return mch.manga.id!!.hashCode() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt index 7fd31dfa5..e8feb084d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt @@ -1,127 +1,157 @@ package eu.kanade.tachiyomi.ui.recent.history import android.os.Bundle -import androidx.paging.PagingData -import androidx.paging.cachedIn -import androidx.paging.insertSeparators -import androidx.paging.map -import eu.kanade.domain.history.interactor.DeleteHistoryTable -import eu.kanade.domain.history.interactor.GetHistory -import eu.kanade.domain.history.interactor.GetNextChapterForManga -import eu.kanade.domain.history.interactor.RemoveHistoryById -import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId -import eu.kanade.domain.history.model.HistoryWithRelations -import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import eu.kanade.tachiyomi.util.lang.launchIO -import eu.kanade.tachiyomi.util.lang.launchUI +import eu.kanade.tachiyomi.ui.recent.DateSectionItem import eu.kanade.tachiyomi.util.lang.toDateKey -import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.update -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get +import rx.Observable +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import uy.kohesive.injekt.injectLazy +import java.text.DateFormat +import java.util.Calendar import java.util.Date +import java.util.TreeMap /** * Presenter of HistoryFragment. * Contains information and data for fragment. * Observable updates should be called from here. */ -class HistoryPresenter( - private val getHistory: GetHistory = Injekt.get(), - private val getNextChapterForManga: GetNextChapterForManga = Injekt.get(), - private val deleteHistoryTable: DeleteHistoryTable = Injekt.get(), - private val removeHistoryById: RemoveHistoryById = Injekt.get(), - private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(), -) : BasePresenter() { +class HistoryPresenter : BasePresenter() { - private var _query: MutableStateFlow = MutableStateFlow("") - private var _state: MutableStateFlow = MutableStateFlow(HistoryState.EMPTY) - val state: StateFlow = _state + private val db: DatabaseHelper by injectLazy() + private val preferences: PreferencesHelper by injectLazy() + + private val relativeTime: Int = preferences.relativeTime().get() + private val dateFormat: DateFormat = preferences.dateFormat() + + private var recentMangaSubscription: Subscription? = null override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - presenterScope.launchIO { - _state.update { state -> - state.copy( - list = _query.flatMapLatest { query -> - getHistory.subscribe(query) - .map { pagingData -> - pagingData - .map { - UiModel.Item(it) - } - .insertSeparators { before, after -> - val beforeDate = before?.item?.readAt?.time?.toDateKey() ?: Date(0) - val afterDate = after?.item?.readAt?.time?.toDateKey() ?: Date(0) - when { - beforeDate.time != afterDate.time && afterDate.time != 0L -> UiModel.Header(afterDate) - // Return null to avoid adding a separator between two items. - else -> null - } - } - } - } - .cachedIn(presenterScope), - ) + // Used to get a list of recently read manga + updateList() + } + + fun requestNext(offset: Int, search: String = "") { + getRecentMangaObservable(offset = offset, search = search) + .subscribeLatestCache( + { view, mangas -> + view.onNextManga(mangas) + }, + HistoryController::onAddPageError, + ) + } + + /** + * Get recent manga observable + * @return list of history + */ + private fun getRecentMangaObservable(limit: Int = 25, offset: Int = 0, search: String = ""): Observable> { + // Set date limit for recent manga + val cal = Calendar.getInstance().apply { + time = Date() + add(Calendar.YEAR, -50) + } + + return db.getRecentManga(cal.time, limit, offset, search).asRxObservable() + .map { recents -> + val map = TreeMap> { d1, d2 -> d2.compareTo(d1) } + val byDay = recents + .groupByTo(map) { it.history.last_read.toDateKey() } + byDay.flatMap { entry -> + val dateItem = DateSectionItem(entry.key, relativeTime, dateFormat) + entry.value.map { HistoryItem(it, dateItem) } + } } - } + .observeOn(AndroidSchedulers.mainThread()) } - fun search(query: String) { - presenterScope.launchIO { - _query.emit(query) - } + /** + * Reset last read of chapter to 0L + * @param history history belonging to chapter + */ + fun removeFromHistory(history: History) { + history.last_read = 0L + db.updateHistoryLastRead(history).asRxObservable() + .subscribe() } - fun removeFromHistory(history: HistoryWithRelations) { - presenterScope.launchIO { - removeHistoryById.await(history) - } + /** + * Pull a list of history from the db + * @param search a search query to use for filtering + */ + fun updateList(search: String = "") { + recentMangaSubscription?.unsubscribe() + recentMangaSubscription = getRecentMangaObservable(search = search) + .subscribeLatestCache( + { view, mangas -> + view.onNextManga(mangas, true) + }, + HistoryController::onAddPageError, + ) } + /** + * Removes all chapters belonging to manga from history. + * @param mangaId id of manga + */ fun removeAllFromHistory(mangaId: Long) { - presenterScope.launchIO { - removeHistoryByMangaId.await(mangaId) - } - } - - fun getNextChapterForManga(mangaId: Long, chapterId: Long) { - presenterScope.launchIO { - val chapter = getNextChapterForManga.await(mangaId, chapterId) - launchUI { - view?.openChapter(chapter) + db.getHistoryByMangaId(mangaId).asRxSingle() + .map { list -> + list.forEach { it.last_read = 0L } + db.updateHistoryLastRead(list).executeAsBlocking() } - } + .subscribe() } - fun deleteAllHistory() { - presenterScope.launchIO { - val result = deleteHistoryTable.await() - if (!result) return@launchIO - launchUI { - view?.activity?.toast(R.string.clear_history_completed) + /** + * Retrieves the next chapter of the given one. + * + * @param chapter the chapter of the history object. + * @param manga the manga of the chapter. + */ + fun getNextChapter(chapter: Chapter, manga: Manga): Chapter? { + if (!chapter.read) { + return chapter + } + + val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) { + Manga.CHAPTER_SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) } + Manga.CHAPTER_SORTING_NUMBER -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) } + Manga.CHAPTER_SORTING_UPLOAD_DATE -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) } + else -> throw NotImplementedError("Unknown sorting method") + } + + val chapters = db.getChapters(manga).executeAsBlocking() + .sortedWith { c1, c2 -> sortFunction(c1, c2) } + + val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id } + return when (manga.sorting) { + Manga.CHAPTER_SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1) + Manga.CHAPTER_SORTING_NUMBER -> { + val chapterNumber = chapter.chapter_number + + ((currChapterIndex + 1) until chapters.size) + .map { chapters[it] } + .firstOrNull { + it.chapter_number > chapterNumber && + it.chapter_number <= chapterNumber + 1 + } } + Manga.CHAPTER_SORTING_UPLOAD_DATE -> { + chapters.drop(currChapterIndex + 1) + .firstOrNull { it.date_upload >= chapter.date_upload } + } + else -> throw NotImplementedError("Unknown sorting method") } } } - -sealed class UiModel { - data class Item(val item: HistoryWithRelations) : UiModel() - data class Header(val date: Date) : UiModel() -} - -data class HistoryState( - val list: Flow>? = null, -) { - - companion object { - val EMPTY = HistoryState(null) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt new file mode 100644 index 000000000..6243ed1d8 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt @@ -0,0 +1,54 @@ +package eu.kanade.tachiyomi.ui.recent.history + +import android.app.Dialog +import android.os.Bundle +import com.bluelinelabs.conductor.Controller +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.widget.DialogCheckboxView + +class RemoveHistoryDialog(bundle: Bundle? = null) : DialogController(bundle) + where T : Controller, T : RemoveHistoryDialog.Listener { + + private var manga: Manga? = null + + private var history: History? = null + + constructor(target: T, manga: Manga, history: History) : this() { + this.manga = manga + this.history = history + targetController = target + } + + override fun onCreateDialog(savedViewState: Bundle?): Dialog { + val activity = activity!! + + // Create custom view + val dialogCheckboxView = DialogCheckboxView(activity).apply { + setDescription(R.string.dialog_with_checkbox_remove_description) + setOptionDescription(R.string.dialog_with_checkbox_reset) + } + + return MaterialAlertDialogBuilder(activity) + .setTitle(R.string.action_remove) + .setView(dialogCheckboxView) + .setPositiveButton(R.string.action_remove) { _, _ -> onPositive(dialogCheckboxView.isChecked()) } + .setNegativeButton(android.R.string.cancel, null) + .create() + } + + private fun onPositive(checked: Boolean) { + val target = targetController as? Listener ?: return + val manga = manga ?: return + val history = history ?: return + + target.removeHistory(manga, history, checked) + } + + interface Listener { + fun removeHistory(manga: Manga, history: History, all: Boolean) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabaseController.kt index 626a410ab..e9d62c6b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabaseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabaseController.kt @@ -37,6 +37,7 @@ class ClearDatabaseController : private var menu: Menu? = null private var actionFab: ExtendedFloatingActionButton? = null + private var actionFabScrollListener: RecyclerView.OnScrollListener? = null init { setHasOptionsMenu(true) @@ -142,6 +143,7 @@ class ClearDatabaseController : override fun cleanupFab(fab: ExtendedFloatingActionButton) { actionFab?.setOnClickListener(null) + actionFabScrollListener?.let { recycler?.removeOnScrollListener(it) } actionFab = null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabasePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabasePresenter.kt index e0c694a15..0f796ee12 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabasePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabasePresenter.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.setting.database import android.os.Bundle -import eu.kanade.tachiyomi.Database import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter @@ -14,7 +13,6 @@ import uy.kohesive.injekt.api.get class ClearDatabasePresenter : BasePresenter() { private val db = Injekt.get() - private val database = Injekt.get() private val sourceManager = Injekt.get() @@ -34,7 +32,7 @@ class ClearDatabasePresenter : BasePresenter() { db.deleteMangasNotInLibraryBySourceIds(sources).executeAsBlocking() } // SY <-- - database.historyQueries.removeResettedHistory() + db.deleteHistoryNoLastRead().executeAsBlocking() } private fun getDatabaseSourcesObservable(): Observable> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiCoordinatorLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiCoordinatorLayout.kt index 16b1c96a0..e7d7db22c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiCoordinatorLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiCoordinatorLayout.kt @@ -5,10 +5,8 @@ import android.os.Parcel import android.os.Parcelable import android.util.AttributeSet import android.view.View -import androidx.compose.ui.platform.ComposeView import androidx.coordinatorlayout.R import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.view.ViewCompat import androidx.core.view.doOnLayout import androidx.core.view.isVisible import androidx.customview.view.AbsSavedState @@ -65,16 +63,7 @@ class TachiyomiCoordinatorLayout @JvmOverloads constructor( super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed) // Disable elevation overlay when tabs are visible if (canLiftAppBarOnScroll) { - if (target is ComposeView) { - val scrollCondition = if (type == ViewCompat.TYPE_NON_TOUCH) { - dyUnconsumed >= 0 - } else { - dyConsumed != 0 || dyUnconsumed >= 0 - } - appBarLayout?.isLifted = scrollCondition && tabLayout?.isVisible == false - } else { - appBarLayout?.isLifted = (dyConsumed != 0 || dyUnconsumed >= 0) && tabLayout?.isVisible == false - } + appBarLayout?.isLifted = (dyConsumed != 0 || dyUnconsumed >= 0) && tabLayout?.isVisible == false } } diff --git a/app/src/main/java/exh/EXHMigrations.kt b/app/src/main/java/exh/EXHMigrations.kt index 5cab95914..8af8f23b0 100644 --- a/app/src/main/java/exh/EXHMigrations.kt +++ b/app/src/main/java/exh/EXHMigrations.kt @@ -6,7 +6,6 @@ import androidx.preference.PreferenceManager import com.pushtorefresh.storio.sqlite.queries.DeleteQuery import com.pushtorefresh.storio.sqlite.queries.Query import com.pushtorefresh.storio.sqlite.queries.RawQuery -import eu.kanade.data.DatabaseHandler import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -41,6 +40,8 @@ import exh.eh.EHentaiUpdateWorker import exh.log.xLogE import exh.log.xLogW import exh.merged.sql.models.MergedMangaReference +import exh.savedsearches.models.FeedSavedSearch +import exh.savedsearches.models.SavedSearch import exh.source.BlacklistedSources import exh.source.EH_SOURCE_ID import exh.source.HBROWSE_SOURCE_ID @@ -50,7 +51,6 @@ import exh.source.PERV_EDEN_IT_SOURCE_ID import exh.source.TSUMINO_SOURCE_ID import exh.util.nullIfBlank import exh.util.under -import kotlinx.coroutines.runBlocking import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString @@ -69,7 +69,6 @@ import java.net.URISyntaxException object EXHMigrations { private val db: DatabaseHelper by injectLazy() - private val database: DatabaseHandler by injectLazy() private val sourceManager: SourceManager by injectLazy() /** @@ -405,31 +404,31 @@ object EXHMigrations { BackupCreatorJob.setupTask(context) } if (oldVersion under 31) { - runBlocking { - database.await(true) { - prefs.getStringSet("eh_saved_searches", emptySet())?.forEach { - kotlin.runCatching { - val content = Json.decodeFromString(it.substringAfter(':')) - saved_searchQueries.insertSavedSearch( - _id = null, - source = it.substringBefore(':').toLongOrNull() ?: return@forEach, - name = content["name"]!!.jsonPrimitive.content, - query = content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), - filters_json = Json.encodeToString(content["filters"]!!.jsonArray) - ) - } - } - } - database.await(true) { - prefs.getStringSet("latest_tab_sources", emptySet())?.forEach { - feed_saved_searchQueries.insertFeedSavedSearch( - _id = null, - source = it.toLong(), - saved_search = null, - global = true, - ) - } - } + val savedSearches = prefs.getStringSet("eh_saved_searches", emptySet())?.mapNotNull { + kotlin.runCatching { + val content = Json.decodeFromString(it.substringAfter(':')) + SavedSearch( + id = null, + source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null, + content["name"]!!.jsonPrimitive.content, + content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(), + Json.encodeToString(content["filters"]!!.jsonArray), + ) + }.getOrNull() + }?.ifEmpty { null } + if (savedSearches != null) { + db.insertSavedSearches(savedSearches).executeAsBlocking() + } + val feed = prefs.getStringSet("latest_tab_sources", emptySet())?.map { + FeedSavedSearch( + id = null, + source = it.toLong(), + savedSearch = null, + global = true, + ) + }?.ifEmpty { null } + if (feed != null) { + db.insertFeedSavedSearches(feed).executeAsBlocking() } prefs.edit(commit = true) { remove("eh_saved_searches") diff --git a/app/src/main/java/exh/debug/DebugFunctions.kt b/app/src/main/java/exh/debug/DebugFunctions.kt index 5aea14182..0892efec1 100644 --- a/app/src/main/java/exh/debug/DebugFunctions.kt +++ b/app/src/main/java/exh/debug/DebugFunctions.kt @@ -3,7 +3,6 @@ package exh.debug import android.app.Application import androidx.work.WorkManager import com.pushtorefresh.storio.sqlite.queries.RawQuery -import eu.kanade.data.DatabaseHandler import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.tables.MangaTable @@ -35,7 +34,6 @@ import java.util.UUID object DebugFunctions { val app: Application by injectLazy() val db: DatabaseHelper by injectLazy() - val database: DatabaseHandler by injectLazy() val prefs: PreferencesHelper by injectLazy() val sourceManager: SourceManager by injectLazy() @@ -166,7 +164,7 @@ object DebugFunctions { it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null } - fun clearSavedSearches() = runBlocking { database.await { saved_searchQueries.deleteAll() } } + fun clearSavedSearches() = db.deleteAllSavedSearches().executeAsBlocking() fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") { "${it.id}: ${it.name} (${it.lang.uppercase()})" diff --git a/app/src/main/java/exh/favorites/sql/tables/FavoriteEntryTable.kt b/app/src/main/java/exh/favorites/sql/tables/FavoriteEntryTable.kt index 52b7b0f57..e29e2914c 100644 --- a/app/src/main/java/exh/favorites/sql/tables/FavoriteEntryTable.kt +++ b/app/src/main/java/exh/favorites/sql/tables/FavoriteEntryTable.kt @@ -13,4 +13,20 @@ object FavoriteEntryTable { const val COL_TOKEN = "token" const val COL_CATEGORY = "category" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_TITLE TEXT NOT NULL, + $COL_GID TEXT NOT NULL, + $COL_TOKEN TEXT NOT NULL, + $COL_CATEGORY INTEGER NOT NULL + )""" + + val fixTableQuery: String + get() = createTableQuery.replace( + "CREATE TABLE", + "CREATE TABLE IF NOT EXISTS", + ) } diff --git a/app/src/main/java/exh/merged/sql/tables/MergedTable.kt b/app/src/main/java/exh/merged/sql/tables/MergedTable.kt index 5648c188f..c54bf5cac 100644 --- a/app/src/main/java/exh/merged/sql/tables/MergedTable.kt +++ b/app/src/main/java/exh/merged/sql/tables/MergedTable.kt @@ -1,5 +1,7 @@ package exh.merged.sql.tables +import eu.kanade.tachiyomi.data.database.tables.MangaTable + object MergedTable { const val TABLE = "merged" @@ -25,4 +27,30 @@ object MergedTable { const val COL_MANGA_URL = "manga_url" const val COL_MANGA_SOURCE = "manga_source" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_IS_INFO_MANGA BOOLEAN NOT NULL, + $COL_GET_CHAPTER_UPDATES BOOLEAN NOT NULL, + $COL_CHAPTER_SORT_MODE INTEGER NOT NULL, + $COL_CHAPTER_PRIORITY INTEGER NOT NULL, + $COL_DOWNLOAD_CHAPTERS BOOLEAN NOT NULL, + $COL_MERGE_ID INTEGER NOT NULL, + $COL_MERGE_URL TEXT NOT NULL, + $COL_MANGA_ID INTEGER, + $COL_MANGA_URL TEXT NOT NULL, + $COL_MANGA_SOURCE INTEGER NOT NULL, + FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID}) + ON DELETE SET NULL, + FOREIGN KEY($COL_MERGE_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID}) + ON DELETE CASCADE + )""" + + val dropTableQuery: String + get() = "DROP TABLE $TABLE" + + val createIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_MERGE_ID}_index ON $TABLE($COL_MERGE_ID)" } diff --git a/app/src/main/java/exh/metadata/sql/tables/SearchMetadataTable.kt b/app/src/main/java/exh/metadata/sql/tables/SearchMetadataTable.kt index 2196883c0..434f52f65 100755 --- a/app/src/main/java/exh/metadata/sql/tables/SearchMetadataTable.kt +++ b/app/src/main/java/exh/metadata/sql/tables/SearchMetadataTable.kt @@ -1,5 +1,7 @@ package exh.metadata.sql.tables +import eu.kanade.tachiyomi.data.database.tables.MangaTable + object SearchMetadataTable { const val TABLE = "search_metadata" @@ -12,4 +14,23 @@ object SearchMetadataTable { const val COL_INDEXED_EXTRA = "indexed_extra" const val COL_EXTRA_VERSION = "extra_version" + + // Insane foreign, primary key to avoid touch manga table + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY, + $COL_UPLOADER TEXT, + $COL_EXTRA TEXT NOT NULL, + $COL_INDEXED_EXTRA TEXT, + $COL_EXTRA_VERSION INT NOT NULL, + FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID}) + ON DELETE CASCADE + )""" + + val createUploaderIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_UPLOADER}_index ON $TABLE($COL_UPLOADER)" + + val createIndexedExtraIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_INDEXED_EXTRA}_index ON $TABLE($COL_INDEXED_EXTRA)" } diff --git a/app/src/main/java/exh/metadata/sql/tables/SearchTagTable.kt b/app/src/main/java/exh/metadata/sql/tables/SearchTagTable.kt index a7aabc7ac..a9b3e72c3 100755 --- a/app/src/main/java/exh/metadata/sql/tables/SearchTagTable.kt +++ b/app/src/main/java/exh/metadata/sql/tables/SearchTagTable.kt @@ -1,5 +1,7 @@ package exh.metadata.sql.tables +import eu.kanade.tachiyomi.data.database.tables.MangaTable + object SearchTagTable { const val TABLE = "search_tags" @@ -12,4 +14,22 @@ object SearchTagTable { const val COL_NAME = "name" const val COL_TYPE = "type" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_MANGA_ID INTEGER NOT NULL, + $COL_NAMESPACE TEXT, + $COL_NAME TEXT NOT NULL, + $COL_TYPE INT NOT NULL, + FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID}) + ON DELETE CASCADE + )""" + + val createMangaIdIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_MANGA_ID}_index ON $TABLE($COL_MANGA_ID)" + + val createNamespaceNameIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_NAMESPACE}_${COL_NAME}_index ON $TABLE($COL_NAMESPACE, $COL_NAME)" } diff --git a/app/src/main/java/exh/metadata/sql/tables/SearchTitleTable.kt b/app/src/main/java/exh/metadata/sql/tables/SearchTitleTable.kt index 98e383296..48b71e422 100755 --- a/app/src/main/java/exh/metadata/sql/tables/SearchTitleTable.kt +++ b/app/src/main/java/exh/metadata/sql/tables/SearchTitleTable.kt @@ -1,5 +1,7 @@ package exh.metadata.sql.tables +import eu.kanade.tachiyomi.data.database.tables.MangaTable + object SearchTitleTable { const val TABLE = "search_titles" @@ -10,4 +12,21 @@ object SearchTitleTable { const val COL_TITLE = "title" const val COL_TYPE = "type" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_MANGA_ID INTEGER NOT NULL, + $COL_TITLE TEXT NOT NULL, + $COL_TYPE INT NOT NULL, + FOREIGN KEY($COL_MANGA_ID) REFERENCES ${MangaTable.TABLE} (${MangaTable.COL_ID}) + ON DELETE CASCADE + )""" + + val createMangaIdIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_MANGA_ID}_index ON $TABLE($COL_MANGA_ID)" + + val createTitleIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_TITLE}_index ON $TABLE($COL_TITLE)" } diff --git a/app/src/main/java/exh/savedsearches/queries/FeedSavedSearchQueries.kt b/app/src/main/java/exh/savedsearches/queries/FeedSavedSearchQueries.kt index fd1db5461..fb67ddfce 100644 --- a/app/src/main/java/exh/savedsearches/queries/FeedSavedSearchQueries.kt +++ b/app/src/main/java/exh/savedsearches/queries/FeedSavedSearchQueries.kt @@ -1,5 +1,86 @@ package exh.savedsearches.queries +import com.pushtorefresh.storio.sqlite.queries.DeleteQuery +import com.pushtorefresh.storio.sqlite.queries.Query +import com.pushtorefresh.storio.sqlite.queries.RawQuery import eu.kanade.tachiyomi.data.database.DbProvider +import eu.kanade.tachiyomi.data.database.queries.getGlobalFeedSavedSearchQuery +import eu.kanade.tachiyomi.data.database.queries.getSourceFeedSavedSearchQuery +import exh.savedsearches.models.FeedSavedSearch +import exh.savedsearches.models.SavedSearch +import exh.savedsearches.tables.FeedSavedSearchTable -interface FeedSavedSearchQueries : DbProvider +interface FeedSavedSearchQueries : DbProvider { + fun getGlobalFeedSavedSearches() = db.get() + .listOfObjects(FeedSavedSearch::class.java) + .withQuery( + Query.builder() + .table(FeedSavedSearchTable.TABLE) + .where("${FeedSavedSearchTable.COL_GLOBAL} = 1") + .orderBy(FeedSavedSearchTable.COL_ID) + .build(), + ) + .prepare() + + fun getSourceFeedSavedSearches(sourceId: Long) = db.get() + .listOfObjects(FeedSavedSearch::class.java) + .withQuery( + Query.builder() + .table(FeedSavedSearchTable.TABLE) + .where("${FeedSavedSearchTable.COL_SOURCE} = ? AND ${FeedSavedSearchTable.COL_GLOBAL} = 0") + .whereArgs(sourceId) + .orderBy(FeedSavedSearchTable.COL_ID) + .build(), + ) + .prepare() + + fun insertFeedSavedSearch(savedSearch: FeedSavedSearch) = db.put().`object`(savedSearch).prepare() + + fun insertFeedSavedSearches(savedSearches: List) = db.put().objects(savedSearches).prepare() + + fun deleteFeedSavedSearch(savedSearch: FeedSavedSearch) = db.delete().`object`(savedSearch).prepare() + + fun deleteFeedSavedSearch(id: Long) = db.delete() + .byQuery( + DeleteQuery.builder() + .table(FeedSavedSearchTable.TABLE) + .where("${FeedSavedSearchTable.COL_ID} = ?") + .whereArgs(id) + .build(), + ).prepare() + + fun deleteAllFeedSavedSearches() = db.delete().byQuery( + DeleteQuery.builder() + .table(FeedSavedSearchTable.TABLE) + .build(), + ) + .prepare() + + fun getGlobalSavedSearchesFeed() = db.get() + .listOfObjects(SavedSearch::class.java) + .withQuery( + RawQuery.builder() + .query(getGlobalFeedSavedSearchQuery()) + .build(), + ) + .prepare() + + fun getSourceSavedSearchesFeed(sourceId: Long) = db.get() + .listOfObjects(SavedSearch::class.java) + .withQuery( + RawQuery.builder() + .query(getSourceFeedSavedSearchQuery()) + .args(sourceId) + .build(), + ) + .prepare() + + /*fun setMangasForMergedManga(mergedMangaId: Long, mergedMangases: List) { + db.inTransaction { + deleteSavedSearches(mergedMangaId).executeAsBlocking() + mergedMangases.chunked(100) { chunk -> + insertSavedSearches(chunk).executeAsBlocking() + } + } + }*/ +} diff --git a/app/src/main/java/exh/savedsearches/queries/SavedSearchQueries.kt b/app/src/main/java/exh/savedsearches/queries/SavedSearchQueries.kt index 34d9f616e..558c80c69 100644 --- a/app/src/main/java/exh/savedsearches/queries/SavedSearchQueries.kt +++ b/app/src/main/java/exh/savedsearches/queries/SavedSearchQueries.kt @@ -1,5 +1,93 @@ package exh.savedsearches.queries +import com.pushtorefresh.storio.sqlite.queries.DeleteQuery +import com.pushtorefresh.storio.sqlite.queries.Query import eu.kanade.tachiyomi.data.database.DbProvider +import exh.savedsearches.models.SavedSearch +import exh.savedsearches.tables.SavedSearchTable -interface SavedSearchQueries : DbProvider +interface SavedSearchQueries : DbProvider { + fun getSavedSearches(source: Long) = db.get() + .listOfObjects(SavedSearch::class.java) + .withQuery( + Query.builder() + .table(SavedSearchTable.TABLE) + .where("${SavedSearchTable.COL_SOURCE} = ?") + .whereArgs(source) + .build(), + ) + .prepare() + + fun deleteSavedSearches(source: Long) = db.delete() + .byQuery( + DeleteQuery.builder() + .table(SavedSearchTable.TABLE) + .where("${SavedSearchTable.COL_SOURCE} = ?") + .whereArgs(source) + .build(), + ) + .prepare() + + fun getSavedSearches() = db.get() + .listOfObjects(SavedSearch::class.java) + .withQuery( + Query.builder() + .table(SavedSearchTable.TABLE) + .orderBy(SavedSearchTable.COL_ID) + .build(), + ) + .prepare() + + fun getSavedSearch(id: Long) = db.get() + .`object`(SavedSearch::class.java) + .withQuery( + Query.builder() + .table(SavedSearchTable.TABLE) + .where("${SavedSearchTable.COL_ID} = ?") + .whereArgs(id) + .build(), + ) + .prepare() + + fun getSavedSearches(ids: List) = db.get() + .listOfObjects(SavedSearch::class.java) + .withQuery( + Query.builder() + .table(SavedSearchTable.TABLE) + .where("${SavedSearchTable.COL_ID} IN (?)") + .whereArgs(ids.joinToString()) + .build(), + ) + .prepare() + + fun insertSavedSearch(savedSearch: SavedSearch) = db.put().`object`(savedSearch).prepare() + + fun insertSavedSearches(savedSearches: List) = db.put().objects(savedSearches).prepare() + + fun deleteSavedSearch(savedSearch: SavedSearch) = db.delete().`object`(savedSearch).prepare() + + fun deleteSavedSearch(id: Long) = db.delete() + .byQuery( + DeleteQuery.builder() + .table(SavedSearchTable.TABLE) + .where("${SavedSearchTable.COL_ID} = ?") + .whereArgs(id) + .build(), + ).prepare() + + fun deleteAllSavedSearches() = db.delete().byQuery( + DeleteQuery.builder() + .table(SavedSearchTable.TABLE) + .build(), + ) + .prepare() + + /*fun setMangasForMergedManga(mergedMangaId: Long, mergedMangases: List) { + db.inTransaction { + deleteSavedSearches(mergedMangaId).executeAsBlocking() + mergedMangases.chunked(100) { chunk -> + insertSavedSearches(chunk).executeAsBlocking() + } + } + }*/ +} diff --git a/app/src/main/java/exh/savedsearches/tables/FeedSavedSearchTable.kt b/app/src/main/java/exh/savedsearches/tables/FeedSavedSearchTable.kt index b23dd1d8d..6b6f5b631 100644 --- a/app/src/main/java/exh/savedsearches/tables/FeedSavedSearchTable.kt +++ b/app/src/main/java/exh/savedsearches/tables/FeedSavedSearchTable.kt @@ -11,4 +11,18 @@ object FeedSavedSearchTable { const val COL_SAVED_SEARCH_ID = "saved_search" const val COL_GLOBAL = "global" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_SOURCE INTEGER NOT NULL, + $COL_SAVED_SEARCH_ID INTEGER, + $COL_GLOBAL BOOLEAN NOT NULL, + FOREIGN KEY($COL_SAVED_SEARCH_ID) REFERENCES ${SavedSearchTable.TABLE} (${SavedSearchTable.COL_ID}) + ON DELETE CASCADE + )""" + + val createSavedSearchIdIndexQuery: String + get() = "CREATE INDEX ${TABLE}_${COL_SAVED_SEARCH_ID}_index ON $TABLE($COL_SAVED_SEARCH_ID)" } diff --git a/app/src/main/java/exh/savedsearches/tables/SavedSearchTable.kt b/app/src/main/java/exh/savedsearches/tables/SavedSearchTable.kt index 35e1839b7..3a345fb05 100644 --- a/app/src/main/java/exh/savedsearches/tables/SavedSearchTable.kt +++ b/app/src/main/java/exh/savedsearches/tables/SavedSearchTable.kt @@ -13,4 +13,14 @@ object SavedSearchTable { const val COL_QUERY = "query" const val COL_FILTERS_JSON = "filters_json" + + val createTableQuery: String + get() = + """CREATE TABLE $TABLE( + $COL_ID INTEGER NOT NULL PRIMARY KEY, + $COL_SOURCE INTEGER NOT NULL, + $COL_NAME TEXT NOT NULL, + $COL_QUERY TEXT, + $COL_FILTERS_JSON TEXT + )""" } diff --git a/app/src/main/res/layout/compose_controller.xml b/app/src/main/res/layout/compose_controller.xml deleted file mode 100644 index 617287296..000000000 --- a/app/src/main/res/layout/compose_controller.xml +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/app/src/main/res/layout/history_controller.xml b/app/src/main/res/layout/history_controller.xml new file mode 100644 index 000000000..d33aa20ed --- /dev/null +++ b/app/src/main/res/layout/history_controller.xml @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/history_item.xml b/app/src/main/res/layout/history_item.xml new file mode 100644 index 000000000..ab407a01b --- /dev/null +++ b/app/src/main/res/layout/history_item.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/sqldelight/data/categories.sq b/app/src/main/sqldelight/data/categories.sq deleted file mode 100644 index af3ea448f..000000000 --- a/app/src/main/sqldelight/data/categories.sq +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE categories( - _id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - sort INTEGER NOT NULL, - flags INTEGER NOT NULL, - manga_order TEXT NOT NULL -); \ No newline at end of file diff --git a/app/src/main/sqldelight/data/chapters.sq b/app/src/main/sqldelight/data/chapters.sq deleted file mode 100644 index 337b1163c..000000000 --- a/app/src/main/sqldelight/data/chapters.sq +++ /dev/null @@ -1,29 +0,0 @@ -CREATE TABLE chapters( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - url TEXT NOT NULL, - name TEXT NOT NULL, - scanlator TEXT, - read INTEGER AS Boolean NOT NULL, - bookmark INTEGER AS Boolean NOT NULL, - last_page_read INTEGER NOT NULL, - chapter_number REAL AS Float NOT NULL, - source_order INTEGER NOT NULL, - date_fetch INTEGER AS Long NOT NULL, - date_upload INTEGER AS Long NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); - -CREATE INDEX chapters_manga_id_index ON chapters(manga_id); -CREATE INDEX chapters_unread_by_manga_index ON chapters(manga_id, read) WHERE read = 0; - -getChapterById: -SELECT * -FROM chapters -WHERE _id = :id; - -getChapterByMangaId: -SELECT * -FROM chapters -WHERE manga_id = :mangaId; \ No newline at end of file diff --git a/app/src/main/sqldelight/data/eh_favorites.sq b/app/src/main/sqldelight/data/eh_favorites.sq deleted file mode 100644 index e3b7cace6..000000000 --- a/app/src/main/sqldelight/data/eh_favorites.sq +++ /dev/null @@ -1,16 +0,0 @@ -CREATE TABLE eh_favorites ( - _id INTEGER NOT NULL PRIMARY KEY, - title TEXT NOT NULL, - gid TEXT NOT NULL, - token TEXT NOT NULL, - category INTEGER NOT NULL -); - -selectAll: -SELECT * FROM eh_favorites; - -insertEhFavorites: -INSERT INTO eh_favorites (_id, title, gid, token, category) VALUES (?, ?, ?, ?, ?); - -deleteAll: -DELETE FROM eh_favorites; \ No newline at end of file diff --git a/app/src/main/sqldelight/data/feed_saved_search.sq b/app/src/main/sqldelight/data/feed_saved_search.sq deleted file mode 100644 index 085ebf829..000000000 --- a/app/src/main/sqldelight/data/feed_saved_search.sq +++ /dev/null @@ -1,41 +0,0 @@ -CREATE TABLE feed_saved_search ( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - saved_search INTEGER, - global INTEGER AS Boolean NOT NULL, - FOREIGN KEY(saved_search) REFERENCES saved_search (_id) - ON DELETE CASCADE -); - -CREATE INDEX feed_saved_search_saved_search_index ON feed_saved_search(saved_search); - -selectAllGlobal: -SELECT * FROM feed_saved_search WHERE global = 1; - -selectBySource: -SELECT * FROM feed_saved_search WHERE source = ? AND global = 0; - -insertFeedSavedSearch: -INSERT INTO feed_saved_search (_id, source, saved_search, global) VALUES (?, ?, ?, ?); - -deleteById: -DELETE FROM feed_saved_search WHERE _id = ?; - -deleteAll: -DELETE FROM feed_saved_search; - -selectGlobalFeedSavedSearch: -SELECT saved_search.* -FROM ( - SELECT saved_search FROM feed_saved_search WHERE global = 1 -) AS M -JOIN saved_search -ON saved_search._id = M.saved_search; - -selectSourceFeedSavedSearch: -SELECT saved_search.* -FROM ( - SELECT saved_search FROM feed_saved_search WHERE global = 0 AND source = ? -) AS M -JOIN saved_search -ON saved_search._id = M.saved_search; \ No newline at end of file diff --git a/app/src/main/sqldelight/data/history.sq b/app/src/main/sqldelight/data/history.sq deleted file mode 100644 index a798b325a..000000000 --- a/app/src/main/sqldelight/data/history.sq +++ /dev/null @@ -1,37 +0,0 @@ -import java.util.Date; - -CREATE TABLE history( - history_id INTEGER NOT NULL PRIMARY KEY, - history_chapter_id INTEGER NOT NULL UNIQUE, - history_last_read INTEGER AS Date, - history_time_read INTEGER AS Date, - FOREIGN KEY(history_chapter_id) REFERENCES chapters (_id) - ON DELETE CASCADE -); - -CREATE INDEX history_history_chapter_id_index ON history(history_chapter_id); - -resetHistoryById: -UPDATE history -SET history_last_read = 0 -WHERE history_id = :historyId; - -resetHistoryByMangaId: -UPDATE history -SET history_last_read = 0 -WHERE history_id IN ( - SELECT H.history_id - FROM mangas M - INNER JOIN chapters C - ON M._id = C.manga_id - INNER JOIN history H - ON C._id = H.history_chapter_id - WHERE M._id = :mangaId -); - -removeAllHistory: -DELETE FROM history; - -removeResettedHistory: -DELETE FROM history -WHERE history_last_read = 0; diff --git a/app/src/main/sqldelight/data/manga_sync.sq b/app/src/main/sqldelight/data/manga_sync.sq deleted file mode 100644 index dcd18442b..000000000 --- a/app/src/main/sqldelight/data/manga_sync.sq +++ /dev/null @@ -1,18 +0,0 @@ -CREATE TABLE manga_sync( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - sync_id INTEGER NOT NULL, - remote_id INTEGER NOT NULL, - library_id INTEGER, - title TEXT NOT NULL, - last_chapter_read REAL NOT NULL, - total_chapters INTEGER NOT NULL, - status INTEGER NOT NULL, - score REAL AS Float NOT NULL, - remote_url TEXT NOT NULL, - start_date INTEGER AS Long NOT NULL, - finish_date INTEGER AS Long NOT NULL, - UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); \ No newline at end of file diff --git a/app/src/main/sqldelight/data/mangas.sq b/app/src/main/sqldelight/data/mangas.sq deleted file mode 100644 index f085ba170..000000000 --- a/app/src/main/sqldelight/data/mangas.sq +++ /dev/null @@ -1,32 +0,0 @@ -import java.lang.String; -import kotlin.collections.List; - -CREATE TABLE mangas( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - url TEXT NOT NULL, - artist TEXT, - author TEXT, - description TEXT, - genre TEXT AS List, - title TEXT NOT NULL, - status INTEGER NOT NULL, - thumbnail_url TEXT, - favorite INTEGER AS Boolean NOT NULL, - last_update INTEGER AS Long, - next_update INTEGER AS Long, - initialized INTEGER AS Boolean NOT NULL, - viewer INTEGER NOT NULL, - chapter_flags INTEGER NOT NULL, - cover_last_modified INTEGER AS Long NOT NULL, - date_added INTEGER AS Long NOT NULL, - filtered_scanlators TEXT -); - -CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; -CREATE INDEX mangas_url_index ON mangas(url); - -getMangaById: -SELECT * -FROM mangas -WHERE _id = :id; \ No newline at end of file diff --git a/app/src/main/sqldelight/data/mangas_categories.sq b/app/src/main/sqldelight/data/mangas_categories.sq deleted file mode 100644 index 6db91fe16..000000000 --- a/app/src/main/sqldelight/data/mangas_categories.sq +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE mangas_categories( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - category_id INTEGER NOT NULL, - FOREIGN KEY(category_id) REFERENCES categories (_id) - ON DELETE CASCADE, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); \ No newline at end of file diff --git a/app/src/main/sqldelight/data/merged.sq b/app/src/main/sqldelight/data/merged.sq deleted file mode 100644 index 4abf0ae15..000000000 --- a/app/src/main/sqldelight/data/merged.sq +++ /dev/null @@ -1,85 +0,0 @@ -CREATE TABLE merged( - _id INTEGER NOT NULL PRIMARY KEY, - info_manga INTEGER AS Boolean NOT NULL, - get_chapter_updates INTEGER AS Boolean NOT NULL, - chapter_sort_mode INTEGER NOT NULL, - chapter_priority INTEGER NOT NULL, - download_chapters INTEGER AS Boolean NOT NULL, - merge_id INTEGER NOT NULL, - merge_url TEXT NOT NULL, - manga_id INTEGER, - manga_url TEXT NOT NULL, - manga_source INTEGER NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE SET NULL, - FOREIGN KEY(merge_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); - -CREATE INDEX merged_merge_id_index ON merged(merge_id); - -selectByMergeId: -SELECT * FROM merged WHERE merge_id = ?; - -selectByMergeUrl: -SELECT * FROM merged WHERE merge_url = ?; - -deleteByMergeId: -DELETE FROM merged WHERE merge_id = ?; - -selectMergedMangasById: -SELECT mangas.* -FROM ( - SELECT manga_id FROM merged WHERE merge_id = ? -) AS M -JOIN mangas -ON mangas._id = M.manga_id; - -selectMergedMangasByUrl: -SELECT mangas.* -FROM ( - SELECT manga_id FROM merged WHERE merge_url = ? -) AS M -JOIN mangas -ON mangas._id = M.manga_id; - -selectAllMergedMangas: -SELECT mangas.* -FROM ( - SELECT manga_id FROM merged -) AS M -JOIN mangas -ON mangas._id = M.manga_id; - -deleteByMergeUrl: -DELETE FROM merged WHERE merge_url = ?; - -selectAll: -SELECT * FROM merged; - -selectChaptersByMergedId: -SELECT chapters.* -FROM ( - SELECT manga_id FROM merged WHERE merge_id = ? -) AS M -JOIN chapters -ON chapters.manga_id = M.manga_id; - -insertMerged: -INSERT INTO merged (_id, info_manga, get_chapter_updates, chapter_sort_mode, chapter_priority, download_chapters, merge_id, merge_url, manga_id, manga_url, manga_source) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); - -updateSettingsById: -UPDATE merged -SET - get_chapter_updates = ?, - download_chapters = ?, - info_manga = ?, - chapter_priority = ? -WHERE _id = ?; - -deleteById: -DELETE FROM merged WHERE _id = ?; - -deleteBy: -DELETE FROM merged; diff --git a/app/src/main/sqldelight/data/saved_search.sq b/app/src/main/sqldelight/data/saved_search.sq deleted file mode 100644 index 90cbf82b8..000000000 --- a/app/src/main/sqldelight/data/saved_search.sq +++ /dev/null @@ -1,32 +0,0 @@ -CREATE TABLE saved_search( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - name TEXT NOT NULL, - query TEXT, - filters_json TEXT -); - -selectBySource: -SELECT * FROM saved_search WHERE source = ?; - -deleteBySource: -DELETE FROM saved_search WHERE source = ?; - -selectAll: -SELECT * FROM saved_search; - -selectById: -SELECT * FROM saved_search WHERE _id = ?; - -selectByIds: -SELECT * FROM saved_search WHERE _id IN ?; - -insertSavedSearch: -INSERT INTO saved_search (_id, source, name, query, filters_json) -VALUES (?, ?, ?, ?, ?); - -deleteById: -DELETE FROM saved_search WHERE _id = ?; - -deleteAll: -DELETE FROM saved_search; \ No newline at end of file diff --git a/app/src/main/sqldelight/data/search_metadata.sq b/app/src/main/sqldelight/data/search_metadata.sq deleted file mode 100644 index 9a184a99f..000000000 --- a/app/src/main/sqldelight/data/search_metadata.sq +++ /dev/null @@ -1,32 +0,0 @@ -CREATE TABLE search_metadata ( - manga_id INTEGER NOT NULL PRIMARY KEY, - uploader TEXT, - extra TEXT NOT NULL, - indexed_extra TEXT, - extra_version INTEGER AS Int NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); - -CREATE INDEX search_metadata_uploader_index ON search_metadata(uploader); -CREATE INDEX search_metadata_indexed_extra_index ON search_metadata(indexed_extra); - -selectAll: -SELECT * FROM search_metadata; - -selectByMangaId: -SELECT * FROM search_metadata WHERE manga_id = ?; - -selectByIndexedExtra: -SELECT * FROM search_metadata WHERE indexed_extra = ?; - -insert: -INSERT INTO search_metadata (manga_id, uploader, extra, indexed_extra, extra_version) -VALUES (?, ?, ?, ?, ?); - -insertNew: -INSERT INTO search_metadata (manga_id, uploader, extra, indexed_extra, extra_version) -VALUES ?; - -deleteAll: -DELETE FROM search_metadata; \ No newline at end of file diff --git a/app/src/main/sqldelight/data/search_tags.sq b/app/src/main/sqldelight/data/search_tags.sq deleted file mode 100644 index 5459070cc..000000000 --- a/app/src/main/sqldelight/data/search_tags.sq +++ /dev/null @@ -1,35 +0,0 @@ -CREATE TABLE search_tags ( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - namespace TEXT, - name TEXT NOT NULL, - type INTEGER AS Int NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); - -CREATE INDEX search_tags_manga_id_index ON search_tags(manga_id); -CREATE INDEX search_tags_namespace_name_index ON search_tags(namespace, name); - - -selectByMangaId: -SELECT * FROM search_tags -WHERE manga_id = ?; - -deleteByManga: -DELETE FROM search_tags WHERE manga_id = ?; - -insert: -INSERT INTO search_tags (_id, manga_id, namespace, name, type) -VALUES (?, ?, ?, ?, ?); - -insertNew: -INSERT INTO search_tags (manga_id, namespace, name, type) -VALUES (?, ?, ?, ?); - -insertItem: -INSERT INTO search_tags (_id, manga_id, namespace, name, type) -VALUES ?; - -deleteAll: -DELETE FROM search_titles; \ No newline at end of file diff --git a/app/src/main/sqldelight/data/search_titles.sq b/app/src/main/sqldelight/data/search_titles.sq deleted file mode 100644 index e4d1195bb..000000000 --- a/app/src/main/sqldelight/data/search_titles.sq +++ /dev/null @@ -1,30 +0,0 @@ -CREATE TABLE search_titles ( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - title TEXT NOT NULL, - type INTEGER AS Int NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); - -CREATE INDEX search_titles_manga_id_index ON search_titles(manga_id); -CREATE INDEX search_titles_title_index ON search_titles(title); - -selectByMangaId: -SELECT * FROM search_titles -WHERE manga_id = ?; - -deleteByManga: -DELETE FROM search_titles WHERE manga_id = ?; - -insert: -INSERT INTO search_titles (_id, manga_id, title, type) VALUES (?, ?, ?, ?); - -insertNew: -INSERT INTO search_titles (manga_id, title, type) VALUES (?, ?, ?); - -insertItem: -INSERT INTO search_titles (_id, manga_id, title, type) VALUES ?; - -deleteAll: -DELETE FROM search_titles; \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/1.sqm b/app/src/main/sqldelight/migrations/1.sqm deleted file mode 100644 index 6eb647300..000000000 --- a/app/src/main/sqldelight/migrations/1.sqm +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE mangas -ADD COLUMN cover_last_modified INTEGER NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/10.sqm b/app/src/main/sqldelight/migrations/10.sqm deleted file mode 100644 index d0cd2b495..000000000 --- a/app/src/main/sqldelight/migrations/10.sqm +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE eh_favorites ( - _id INTEGER NOT NULL PRIMARY KEY, - title TEXT NOT NULL, - gid TEXT NOT NULL, - token TEXT NOT NULL, - category INTEGER NOT NULL -); \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/11.sqm b/app/src/main/sqldelight/migrations/11.sqm deleted file mode 100644 index b1fb3d5cb..000000000 --- a/app/src/main/sqldelight/migrations/11.sqm +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE IF NOT EXISTS eh_favorites ( - _id INTEGER NOT NULL PRIMARY KEY, - title TEXT NOT NULL, - gid TEXT NOT NULL, - token TEXT NOT NULL, - category INTEGER NOT NULL -); \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/12.sqm b/app/src/main/sqldelight/migrations/12.sqm deleted file mode 100644 index 28261b1d0..000000000 --- a/app/src/main/sqldelight/migrations/12.sqm +++ /dev/null @@ -1,16 +0,0 @@ -CREATE TABLE saved_search( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - name TEXT NOT NULL, - query TEXT, - filters_json TEXT -); -CREATE TABLE feed_saved_search ( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - saved_search INTEGER, - global INTEGER AS Boolean NOT NULL, - FOREIGN KEY(saved_search) REFERENCES saved_search (_id) - ON DELETE CASCADE -); -CREATE INDEX feed_saved_search_saved_search_index ON feed_saved_search(saved_search); \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/13.sqm b/app/src/main/sqldelight/migrations/13.sqm deleted file mode 100644 index 4aef6884e..000000000 --- a/app/src/main/sqldelight/migrations/13.sqm +++ /dev/null @@ -1,275 +0,0 @@ -DROP INDEX IF EXISTS chapters_manga_id_index; -DROP INDEX IF EXISTS chapters_unread_by_manga_index; -DROP INDEX IF EXISTS history_history_chapter_id_index; -DROP INDEX IF EXISTS library_favorite_index; -DROP INDEX IF EXISTS mangas_url_index; - -DROP INDEX IF EXISTS search_metadata_uploader_index; -DROP INDEX IF EXISTS search_metadata_indexed_extra_index; -DROP INDEX IF EXISTS search_tags_manga_id_index; -DROP INDEX IF EXISTS search_tags_namespace_name_index; -DROP INDEX IF EXISTS search_titles_manga_id_index; -DROP INDEX IF EXISTS search_titles_title_index; -DROP INDEX IF EXISTS merged_merge_id_index; -DROP INDEX IF EXISTS feed_saved_search_saved_search_index; - -ALTER TABLE mangas RENAME TO manga_temp; -CREATE TABLE mangas( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - url TEXT NOT NULL, - artist TEXT, - author TEXT, - description TEXT, - genre TEXT, - title TEXT NOT NULL, - status INTEGER NOT NULL, - thumbnail_url TEXT, - favorite INTEGER NOT NULL, - last_update INTEGER AS Long, - next_update INTEGER AS Long, - initialized INTEGER AS Boolean NOT NULL, - viewer INTEGER NOT NULL, - chapter_flags INTEGER NOT NULL, - cover_last_modified INTEGER AS Long NOT NULL, - date_added INTEGER AS Long NOT NULL, - filtered_scanlators TEXT -); -INSERT INTO mangas -SELECT _id,source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added,filtered_scanlators -FROM manga_temp; - -ALTER TABLE categories RENAME TO categories_temp; -CREATE TABLE categories( - _id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - sort INTEGER NOT NULL, - flags INTEGER NOT NULL, - manga_order TEXT NOT NULL -); -INSERT INTO categories -SELECT _id,name,sort,flags,manga_order -FROM categories_temp; - -ALTER TABLE chapters RENAME TO chapters_temp; -CREATE TABLE chapters( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - url TEXT NOT NULL, - name TEXT NOT NULL, - scanlator TEXT, - read INTEGER AS Boolean NOT NULL, - bookmark INTEGER AS Boolean NOT NULL, - last_page_read INTEGER NOT NULL, - chapter_number REAL AS Float NOT NULL, - source_order INTEGER NOT NULL, - date_fetch INTEGER AS Long NOT NULL, - date_upload INTEGER AS Long NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); -INSERT INTO chapters -SELECT _id,manga_id,url,name,scanlator,read,bookmark,last_page_read,chapter_number,source_order,date_fetch,date_upload -FROM chapters_temp; - -ALTER TABLE history RENAME TO history_temp; -CREATE TABLE history( - history_id INTEGER NOT NULL PRIMARY KEY, - history_chapter_id INTEGER NOT NULL UNIQUE, - history_last_read INTEGER AS Long, - history_time_read INTEGER AS Long, - FOREIGN KEY(history_chapter_id) REFERENCES chapters (_id) - ON DELETE CASCADE -); -INSERT INTO history -SELECT history_id, history_chapter_id, history_last_read, history_time_read -FROM history_temp; - -ALTER TABLE mangas_categories RENAME TO mangas_categories_temp; -CREATE TABLE mangas_categories( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - category_id INTEGER NOT NULL, - FOREIGN KEY(category_id) REFERENCES categories (_id) - ON DELETE CASCADE, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); -INSERT INTO mangas_categories -SELECT _id, manga_id, category_id -FROM mangas_categories_temp; - -ALTER TABLE manga_sync RENAME TO manga_sync_temp; -CREATE TABLE manga_sync( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - sync_id INTEGER NOT NULL, - remote_id INTEGER NOT NULL, - library_id INTEGER, - title TEXT NOT NULL, - last_chapter_read REAL NOT NULL, - total_chapters INTEGER NOT NULL, - status INTEGER NOT NULL, - score REAL AS Float NOT NULL, - remote_url TEXT NOT NULL, - start_date INTEGER AS Long NOT NULL, - finish_date INTEGER AS Long NOT NULL, - UNIQUE (manga_id, sync_id) ON CONFLICT REPLACE, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); -INSERT INTO manga_sync -SELECT _id, manga_id, sync_id, remote_id, library_id, title, last_chapter_read, total_chapters, status, score, remote_url, start_date, finish_date -FROM manga_sync_temp; - -ALTER TABLE eh_favorites RENAME TO eh_favorites_temp; -CREATE TABLE eh_favorites ( - _id INTEGER NOT NULL PRIMARY KEY, - title TEXT NOT NULL, - gid TEXT NOT NULL, - token TEXT NOT NULL, - category INTEGER NOT NULL -); -INSERT INTO eh_favorites -SELECT _id,title,gid,token,category -FROM eh_favorites_temp; - -ALTER TABLE saved_search RENAME TO saved_search_temp; -CREATE TABLE saved_search( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - name TEXT NOT NULL, - query TEXT, - filters_json TEXT -); -INSERT INTO saved_search -SELECT _id,source,name,query,filters_json -FROM saved_search_temp; - -ALTER TABLE feed_saved_search RENAME TO feed_saved_search_temp; -CREATE TABLE feed_saved_search ( - _id INTEGER NOT NULL PRIMARY KEY, - source INTEGER NOT NULL, - saved_search INTEGER, - global INTEGER AS Boolean NOT NULL, - FOREIGN KEY(saved_search) REFERENCES saved_search (_id) - ON DELETE CASCADE -); -INSERT INTO feed_saved_search -SELECT _id, source, saved_search, global -FROM feed_saved_search_temp; - -ALTER TABLE search_metadata RENAME TO search_metadata_temp; -CREATE TABLE search_metadata ( - manga_id INTEGER NOT NULL PRIMARY KEY, - uploader TEXT, - extra TEXT NOT NULL, - indexed_extra TEXT, - extra_version INTEGER AS Int NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); -INSERT INTO search_metadata -SELECT manga_id, uploader, extra, indexed_extra, extra_version -FROM search_metadata_temp; - -ALTER TABLE search_tags RENAME TO search_tags_temp; -CREATE TABLE search_tags ( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - namespace TEXT, - name TEXT NOT NULL, - type INTEGER AS Int NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); -INSERT INTO search_tags -SELECT _id, manga_id, namespace, name, type -FROM search_tags_temp; - -ALTER TABLE search_titles RENAME TO search_titles_temp; -CREATE TABLE search_titles ( - _id INTEGER NOT NULL PRIMARY KEY, - manga_id INTEGER NOT NULL, - title TEXT NOT NULL, - type INTEGER AS Int NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); -INSERT INTO search_titles -SELECT _id, manga_id, title, type -FROM search_titles_temp; - -ALTER TABLE merged RENAME TO merged_temp; -CREATE TABLE merged( - _id INTEGER NOT NULL PRIMARY KEY, - info_manga INTEGER AS Boolean NOT NULL, - get_chapter_updates INTEGER AS Boolean NOT NULL, - chapter_sort_mode INTEGER NOT NULL, - chapter_priority INTEGER NOT NULL, - download_chapters INTEGER AS Boolean NOT NULL, - merge_id INTEGER NOT NULL, - merge_url TEXT NOT NULL, - manga_id INTEGER, - manga_url TEXT NOT NULL, - manga_source INTEGER NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE SET NULL, - FOREIGN KEY(merge_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); -INSERT INTO merged -SELECT _id,info_manga,get_chapter_updates,chapter_sort_mode,chapter_priority,download_chapters,merge_id,merge_url,manga_id,manga_url,manga_source -FROM merged_temp; - -CREATE INDEX chapters_manga_id_index ON chapters(manga_id); -CREATE INDEX chapters_unread_by_manga_index ON chapters(manga_id, read) WHERE read = 0; -CREATE INDEX history_history_chapter_id_index ON history(history_chapter_id); -CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; -CREATE INDEX mangas_url_index ON mangas(url); -CREATE INDEX search_metadata_uploader_index ON search_metadata(uploader); -CREATE INDEX search_metadata_indexed_extra_index ON search_metadata(indexed_extra); -CREATE INDEX search_tags_manga_id_index ON search_tags(manga_id); -CREATE INDEX search_tags_namespace_name_index ON search_tags(namespace, name); -CREATE INDEX search_titles_manga_id_index ON search_titles(manga_id); -CREATE INDEX search_titles_title_index ON search_titles(title); -CREATE INDEX merged_merge_id_index ON merged(merge_id); -CREATE INDEX feed_saved_search_saved_search_index ON feed_saved_search(saved_search); - -CREATE VIEW IF NOT EXISTS historyView AS -SELECT -history.history_id AS id, -mangas._id AS mangaId, -chapters._id AS chapterId, -mangas.title, -mangas.thumbnail_url AS thumnailUrl, -chapters.chapter_number AS chapterNumber, -history.history_last_read AS readAt, -max_last_read.history_last_read AS maxReadAt, -max_last_read.history_chapter_id AS maxReadAtChapterId -FROM mangas -JOIN chapters -ON mangas._id = chapters.manga_id -JOIN history -ON chapters._id = history.history_chapter_id -JOIN ( -SELECT chapters.manga_id,chapters._id AS history_chapter_id, MAX(history.history_last_read) AS history_last_read -FROM chapters JOIN history -ON chapters._id = history.history_chapter_id -GROUP BY chapters.manga_id -) AS max_last_read -ON chapters.manga_id = max_last_read.manga_id; - -DROP TABLE IF EXISTS manga_sync_temp; -DROP TABLE IF EXISTS mangas_categories_temp; -DROP TABLE IF EXISTS history_temp; -DROP TABLE IF EXISTS chapters_temp; -DROP TABLE IF EXISTS categories_temp; -DROP TABLE IF EXISTS eh_favorites_temp; -DROP TABLE IF EXISTS saved_search_temp; -DROP TABLE IF EXISTS feed_saved_search_temp; -DROP TABLE IF EXISTS search_metadata_temp; -DROP TABLE IF EXISTS search_tags_temp; -DROP TABLE IF EXISTS search_titles_temp; -DROP TABLE IF EXISTS merged_temp; -DROP TABLE IF EXISTS manga_temp; diff --git a/app/src/main/sqldelight/migrations/2.sqm b/app/src/main/sqldelight/migrations/2.sqm deleted file mode 100644 index 20a2c8444..000000000 --- a/app/src/main/sqldelight/migrations/2.sqm +++ /dev/null @@ -1,11 +0,0 @@ -ALTER TABLE mangas -ADD COLUMN date_added INTEGER NOT NULL DEFAULT 0; - -UPDATE mangas -SET date_added = ( - SELECT MIN(date_fetch) - FROM mangas M - INNER JOIN chapters C - ON M._id = C.manga_id - GROUP BY M._id -); \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/3.sqm b/app/src/main/sqldelight/migrations/3.sqm deleted file mode 100644 index 553c44029..000000000 --- a/app/src/main/sqldelight/migrations/3.sqm +++ /dev/null @@ -1,19 +0,0 @@ -DROP TABLE merged; -CREATE TABLE merged( - _id INTEGER NOT NULL PRIMARY KEY, - info_manga INTEGER AS Boolean NOT NULL, - get_chapter_updates INTEGER AS Boolean NOT NULL, - chapter_sort_mode INTEGER NOT NULL, - chapter_priority INTEGER NOT NULL, - download_chapters INTEGER AS Boolean NOT NULL, - merge_id INTEGER NOT NULL, - merge_url TEXT NOT NULL, - manga_id INTEGER, - manga_url TEXT NOT NULL, - COL_MANGA_SOURCE INTEGER NOT NULL, - FOREIGN KEY(manga_id) REFERENCES mangas (_id) - ON DELETE SET NULL, - FOREIGN KEY(merge_id) REFERENCES mangas (_id) - ON DELETE CASCADE -); -CREATE INDEX merged_merge_id_index ON merged(merge_id); \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/4.sqm b/app/src/main/sqldelight/migrations/4.sqm deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/sqldelight/migrations/5.sqm b/app/src/main/sqldelight/migrations/5.sqm deleted file mode 100644 index 4a70d4170..000000000 --- a/app/src/main/sqldelight/migrations/5.sqm +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE mangas -ADD COLUMN filtered_scanlators TEXT; \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/6.sqm b/app/src/main/sqldelight/migrations/6.sqm deleted file mode 100644 index 7718ccb35..000000000 --- a/app/src/main/sqldelight/migrations/6.sqm +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS manga_related; \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/7.sqm b/app/src/main/sqldelight/migrations/7.sqm deleted file mode 100644 index 23b429acd..000000000 --- a/app/src/main/sqldelight/migrations/7.sqm +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE mangas -ADD COLUMN next_update INTEGER DEFAULT 0; \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/8.sqm b/app/src/main/sqldelight/migrations/8.sqm deleted file mode 100644 index c80623439..000000000 --- a/app/src/main/sqldelight/migrations/8.sqm +++ /dev/null @@ -1,9 +0,0 @@ -ALTER TABLE manga_sync -RENAME TO manga_sync_tmp; - -INSERT INTO manga_sync(_id, manga_id, sync_id, remote_id, library_id, title, last_chapter_read, total_chapters, status, score, remote_url, start_date, finish_date) -SELECT _id, manga_id, sync_id, remote_id, library_id, title, last_chapter_read, total_chapters, status, score, remote_url, start_date, finish_date -FROM manga_sync_tmp; - - -DROP TABLE manga_sync_tmp; \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/9.sqm b/app/src/main/sqldelight/migrations/9.sqm deleted file mode 100644 index 78e1ece21..000000000 --- a/app/src/main/sqldelight/migrations/9.sqm +++ /dev/null @@ -1,3 +0,0 @@ -UPDATE chapters -SET date_upload = date_fetch -WHERE date_upload = 0; \ No newline at end of file diff --git a/app/src/main/sqldelight/view/historyView.sq b/app/src/main/sqldelight/view/historyView.sq deleted file mode 100644 index 2471f85aa..000000000 --- a/app/src/main/sqldelight/view/historyView.sq +++ /dev/null @@ -1,46 +0,0 @@ -CREATE VIEW historyView AS -SELECT -history.history_id AS id, -mangas._id AS mangaId, -chapters._id AS chapterId, -mangas.title, -mangas.thumbnail_url AS thumnailUrl, -chapters.chapter_number AS chapterNumber, -history.history_last_read AS readAt, -max_last_read.history_last_read AS maxReadAt, -max_last_read.history_chapter_id AS maxReadAtChapterId -FROM mangas -JOIN chapters -ON mangas._id = chapters.manga_id -JOIN history -ON chapters._id = history.history_chapter_id -JOIN ( -SELECT chapters.manga_id,chapters._id AS history_chapter_id, MAX(history.history_last_read) AS history_last_read -FROM chapters JOIN history -ON chapters._id = history.history_chapter_id -GROUP BY chapters.manga_id -) AS max_last_read -ON chapters.manga_id = max_last_read.manga_id; - -countHistory: -SELECT count(*) -FROM historyView -WHERE historyView.readAt > 0 -AND maxReadAtChapterId = historyView.chapterId -AND lower(historyView.title) LIKE ('%' || :query || '%'); - -history: -SELECT -id, -mangaId, -chapterId, -title, -thumnailUrl, -chapterNumber, -readAt -FROM historyView -WHERE historyView.readAt > 0 -AND maxReadAtChapterId = historyView.chapterId -AND lower(historyView.title) LIKE ('%' || :query || '%') -ORDER BY readAt DESC -LIMIT :limit OFFSET :offset; diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt index 881533404..75fe1320c 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt @@ -344,7 +344,7 @@ class BackupTest { private fun clearDatabase() { db.deleteMangas().executeAsBlocking() - db.dropHistoryTable().executeAsBlocking() + db.deleteHistory().executeAsBlocking() } private fun getSingleHistory(chapter: Chapter): DHistory { diff --git a/build.gradle.kts b/build.gradle.kts index 10a3a5a37..8d647913d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,6 @@ buildscript { classpath(libs.google.services.gradle) classpath(libs.aboutlibraries.gradle) classpath(kotlinx.serialization.gradle) - classpath("com.squareup.sqldelight:gradle-plugin:1.5.3") classpath(sylibs.firebase.crashlytics.gradle) } } diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 6c49236bb..b051c785b 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -21,9 +21,6 @@ lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", ve work-runtime = "androidx.work:work-runtime-ktx:2.6.0" guava = "com.google.guava:guava:31.1-android" -paging-runtime = "androidx.paging:paging-runtime:3.1.1" -paging-compose = "androidx.paging:paging-compose:1.0.0-alpha14" - [bundles] lifecycle = ["lifecycle-common", "lifecycle-process", "lifecycle-runtimektx"] workmanager = ["work-runtime", "guava"] diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml deleted file mode 100644 index 6b271fc9b..000000000 --- a/gradle/compose.versions.toml +++ /dev/null @@ -1,9 +0,0 @@ -[versions] -compose = "1.2.0-alpha07" - -[libraries] -foundation = { module = "androidx.compose.foundation:foundation", version.ref="compose" } -material3-core = "androidx.compose.material3:material3:1.0.0-alpha09" -material3-adapter = "com.google.android.material:compose-theme-adapter-3:1.0.6" -animation = { module = "androidx.compose.animation:animation", version.ref="compose" } -ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref="compose" } diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 74448c71b..268e66b5f 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin_version = "1.6.10" +kotlin_version = "1.6.20" coroutines_version = "1.6.1" serialization_version = "1.3.2" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3f332967a..cb3cf78b6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,6 @@ conductor_version = "3.1.2" flowbinding_version = "1.2.0" shizuku_version = "12.1.0" robolectric_version = "3.1.4" -sqldelight = "1.5.3" [libraries] android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2" @@ -50,7 +49,6 @@ injekt-core = "com.github.inorichi.injekt:injekt-core:65b0440" coil-core = { module = "io.coil-kt:coil", version.ref = "coil_version" } coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil_version" } -coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil_version" } subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:846abe0" image-decoder = "com.github.tachiyomiorg:image-decoder:7481a4a" @@ -98,22 +96,18 @@ robolectric-playservices = { module = "org.robolectric:shadows-play-services", v leakcanary-android = "com.squareup.leakcanary:leakcanary-android:2.7" -sqldelight-android-driver = { module = "com.squareup.sqldelight:android-driver", version.ref ="sqldelight" } -sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions-jvm", version.ref ="sqldelight" } -sqldelight-android-paging = { module = "com.squareup.sqldelight:android-paging3-extensions", version.ref ="sqldelight" } - [bundles] reactivex = ["rxandroid","rxjava","rxrelay"] okhttp = ["okhttp-core","okhttp-logging","okhttp-dnsoverhttps"] js-engine = ["quickjs-android", "duktape-android"] sqlite = ["sqlitektx", "sqlite-android"] nucleus = ["nucleus-core","nucleus-supportv7"] -coil = ["coil-core","coil-gif","coil-compose"] +coil = ["coil-core","coil-gif",] flowbinding = ["flowbinding-android","flowbinding-appcompat","flowbinding-recyclerview","flowbinding-swiperefreshlayout","flowbinding-viewpager"] conductor = ["conductor-core","conductor-viewpager","conductor-support-preference"] shizuku = ["shizuku-api","shizuku-provider"] robolectric = ["robolectric-core","robolectric-playservices"] [plugins] -kotlinter = { id = "org.jmailen.kotlinter", version = "3.6.0"} +kotlinter = { id = "org.jmailen.kotlinter", version = "3.10.0"} versionsx = { id = "com.github.ben-manes.versions", version = "0.42.0"} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 6ca65f667..45778f8c2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,9 +22,6 @@ dependencyResolutionManagement { create("androidx") { from(files("gradle/androidx.versions.toml")) } - create("compose") { - from(files("gradle/compose.versions.toml")) - } create("sylibs") { from(files("gradle/sy.versions.toml")) }