diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 55162b0ed..e60f76fea 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,7 +6,6 @@ plugins { kotlin("android") kotlin("plugin.serialization") id("com.github.zellius.shortcut-helper") - id("com.squareup.sqldelight") } if (gradle.startParameter.taskRequests.toString().contains("Standard")) { @@ -110,7 +109,6 @@ android { buildFeatures { viewBinding = true - compose = true // Disable some unused things aidl = false @@ -124,10 +122,6 @@ android { checkReleaseBuilds = false } - composeOptions { - kotlinCompilerExtensionVersion = compose.versions.compose.get() - } - compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -139,19 +133,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) @@ -282,9 +263,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" ) } 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 445428b8b..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.* - -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/history/HistoryMapper.kt b/app/src/main/java/eu/kanade/data/history/HistoryMapper.kt deleted file mode 100644 index ff94b1d61..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.* - -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 96ede4e5b..000000000 --- a/app/src/main/java/eu/kanade/data/manga/MangaMapper.kt +++ /dev/null @@ -1,26 +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) -> Manga = - { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewer, chapterFlags, coverLastModified, dateAdded -> - 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, - ) - } 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 b0ba50695..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.* - -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 5faa8eb24..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.* - -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 9226c3f61..000000000 --- a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt +++ /dev/null @@ -1,36 +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 -) { - - 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 74ced7ff0..32a290be7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -24,7 +24,6 @@ import coil.decode.GifDecoder import coil.decode.ImageDecoderDecoder import coil.disk.DiskCache import coil.util.DebugLogger -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 @@ -76,7 +75,6 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { } Injekt.importModule(AppModule(this)) - Injekt.importModule(DomainModule()) setupAcra() setupNotificationChannels() diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index c9147b6c6..39c42dd64 100644 --- 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.preference.PreferencesHelper import eu.kanade.tachiyomi.data.saver.ImageSaver @@ -34,37 +25,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) } @@ -92,8 +57,6 @@ class AppModule(val app: Application) : InjektModule { get() - get() - get() get() 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 f12047891..933cc7b66 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 @@ -299,7 +299,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { } } } - databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking() + databaseHelper.updateHistoryLastRead(historyToBeUpdated).executeAsBlocking() } /** 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 8d42245e0..4984b242c 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 @@ -168,7 +168,7 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab } } } - databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking() + databaseHelper.updateHistoryLastRead(historyToBeUpdated).executeAsBlocking() } /** 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 36c1d3d49..474d2d6b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt @@ -26,15 +26,12 @@ 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, CategoryQueries, MangaCategoryQueries, HistoryQueries { 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..31702be03 100644 --- 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,98 @@ 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 -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 = 14 } - 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) + + // DB indexes + execSQL(MangaTable.createUrlIndexQuery) + execSQL(MangaTable.createLibraryIndexQuery) + execSQL(ChapterTable.createMangaIdIndexQuery) + execSQL(ChapterTable.createUnreadChaptersIndexQuery) + execSQL(HistoryTable.createChapterIdIndexQuery) } 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(ChapterTable.sourceOrderUpdateQuery) + + // Fix kissmanga covers after supporting cloudflare + db.execSQL( + """UPDATE mangas SET thumbnail_url = + REPLACE(thumbnail_url, '93.174.95.110', 'kissmanga.com') WHERE source = 4""", + ) + } + if (oldVersion < 3) { + // Initialize history tables + db.execSQL(HistoryTable.createTableQuery) + db.execSQL(HistoryTable.createChapterIdIndexQuery) + } + if (oldVersion < 4) { + db.execSQL(ChapterTable.bookmarkUpdateQuery) + } + if (oldVersion < 5) { + db.execSQL(ChapterTable.addScanlator) + } + if (oldVersion < 6) { + db.execSQL(TrackTable.addTrackingUrl) + } + if (oldVersion < 7) { + db.execSQL(TrackTable.addLibraryId) + } + if (oldVersion < 8) { + db.execSQL("DROP INDEX IF EXISTS mangas_favorite_index") + db.execSQL(MangaTable.createLibraryIndexQuery) + db.execSQL(ChapterTable.createUnreadChaptersIndexQuery) + } + if (oldVersion < 9) { + db.execSQL(TrackTable.addStartDate) + db.execSQL(TrackTable.addFinishDate) + } + if (oldVersion < 10) { + db.execSQL(MangaTable.addCoverLastModified) + } + if (oldVersion < 11) { + db.execSQL(MangaTable.addDateAdded) + db.execSQL(MangaTable.backfillDateAdded) + } + if (oldVersion < 12) { + db.execSQL(MangaTable.addNextUpdateCol) + } + if (oldVersion < 13) { + db.execSQL(TrackTable.renameTableToTemp) + db.execSQL(TrackTable.createTableQuery) + db.execSQL(TrackTable.insertFromTempTable) + db.execSQL(TrackTable.dropTempTable) + } + if (oldVersion < 14) { + db.execSQL(ChapterTable.fixDateUploadIfNeeded) + } } 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 fd9349789..24b57f48e 100644 --- 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,11 +4,39 @@ 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.resolvers.HistoryUpsertResolver +import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory +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( @@ -36,9 +64,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() /** @@ -46,12 +74,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 348811939..6c1a424a0 100644 --- 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 @@ -70,8 +70,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 100644 --- 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 3697ee60e..76ffd7187 100644 --- 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 @@ -11,4 +11,13 @@ object CategoryTable { const val COL_ORDER = "sort" const val COL_FLAGS = "flags" + + 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 + )""" } 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 100644 --- 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 100644 --- 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 100644 --- 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 5c32e16bc..fe9e6845f 100644 --- 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 @@ -47,4 +47,53 @@ 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 + )""" + + 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" } 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 100644 --- 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 100644 --- 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/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 8567a0383..82135985c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -7,7 +7,6 @@ import androidx.core.content.edit import androidx.core.net.toUri import androidx.preference.PreferenceManager import com.fredporciuncula.flow.preferences.FlowSharedPreferences -import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.track.TrackService @@ -19,6 +18,7 @@ import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import eu.kanade.tachiyomi.util.system.DeviceUtil +import eu.kanade.tachiyomi.util.system.isDevFlavor import eu.kanade.tachiyomi.widget.ExtendedNavigationView import java.io.File import java.text.DateFormat @@ -26,7 +26,6 @@ import java.text.SimpleDateFormat import java.util.Locale import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values -import eu.kanade.tachiyomi.util.system.isDevFlavor class PreferencesHelper(val context: Context) { 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/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 89f8c7295..c381f1194 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 @@ -119,8 +118,6 @@ class MangaController : DownloadCustomChaptersDialog.Listener, DeleteChaptersDialog.Listener { - constructor(history: HistoryWithRelations) : this(history.mangaId) - constructor(manga: Manga?, fromSource: 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 9053864d8..51fa73cc1 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 @@ -97,19 +97,14 @@ import kotlin.math.max 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) - } - private const val ENABLED_BUTTON_IMAGE_ALPHA = 255 private const val DISABLED_BUTTON_IMAGE_ALPHA = 64 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 8debf6f3c..fb954bab7 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 @@ -449,7 +449,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 eff06438c..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 java.util.* +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/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index 5d01b61ef..95105981f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -9,7 +9,6 @@ import android.webkit.WebView import androidx.core.net.toUri import androidx.preference.PreferenceScreen import com.google.android.material.dialog.MaterialAlertDialogBuilder -import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -40,6 +39,7 @@ import eu.kanade.tachiyomi.util.preference.summaryRes import eu.kanade.tachiyomi.util.preference.switchPreference import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.DeviceUtil +import eu.kanade.tachiyomi.util.system.isDevFlavor import eu.kanade.tachiyomi.util.system.isPackageInstalled import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.powerManager @@ -49,7 +49,6 @@ import logcat.LogPriority import rikka.sui.Sui import uy.kohesive.injekt.injectLazy import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys -import eu.kanade.tachiyomi.util.system.isDevFlavor class SettingsAdvancedController : SettingsController() { 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 9002efc41..a47075667 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 @@ -9,6 +9,7 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.core.view.forEach +import androidx.core.view.get import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -36,6 +37,7 @@ class ClearDatabaseController : private var menu: Menu? = null private var actionFab: ExtendedFloatingActionButton? = null + private var actionFabScrollListener: RecyclerView.OnScrollListener? = null init { setHasOptionsMenu(true) @@ -141,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 192bd81a9..d209779a3 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() @@ -28,7 +26,7 @@ class ClearDatabasePresenter : BasePresenter() { fun clearDatabaseForSourceIds(sources: List) { db.deleteMangasNotInLibraryBySourceIds(sources).executeAsBlocking() - 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/eu/kanade/tachiyomi/widget/preference/ThemesPreferenceAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreferenceAdapter.kt index 38e71410f..4c5bd324d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreferenceAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreferenceAdapter.kt @@ -25,7 +25,7 @@ class ThemesPreferenceAdapter(private val clickListener: OnItemClickListener) : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThemeViewHolder { val themeResIds = ThemingDelegate.getThemeResIds(themes[viewType], preferences.themeDarkAmoled().get()) val themedContext = themeResIds.fold(parent.context) { - context, themeResId -> + context, themeResId -> ContextThemeWrapper(context, themeResId) } 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 628b6df7f..000000000 --- a/app/src/main/sqldelight/data/categories.sq +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE categories( - _id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - sort INTEGER NOT NULL, - flags INTEGER 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/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 30b462be6..000000000 --- a/app/src/main/sqldelight/data/mangas.sq +++ /dev/null @@ -1,31 +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 -); - -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/migrations/1.sqm b/app/src/main/sqldelight/migrations/1.sqm deleted file mode 100644 index 7ae4198c7..000000000 --- a/app/src/main/sqldelight/migrations/1.sqm +++ /dev/null @@ -1,6 +0,0 @@ -ALTER TABLE chapters -ADD COLUMN source_order INTEGER DEFAULT 0; - -UPDATE mangas -SET thumbnail_url = replace(thumbnail_url, '93.174.95.110', 'kissmanga.com') -WHERE source = 4; \ 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 20a2c8444..000000000 --- a/app/src/main/sqldelight/migrations/10.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/11.sqm b/app/src/main/sqldelight/migrations/11.sqm deleted file mode 100644 index 23b429acd..000000000 --- a/app/src/main/sqldelight/migrations/11.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/12.sqm b/app/src/main/sqldelight/migrations/12.sqm deleted file mode 100644 index c80623439..000000000 --- a/app/src/main/sqldelight/migrations/12.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/13.sqm b/app/src/main/sqldelight/migrations/13.sqm deleted file mode 100644 index 78e1ece21..000000000 --- a/app/src/main/sqldelight/migrations/13.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/migrations/14.sqm b/app/src/main/sqldelight/migrations/14.sqm deleted file mode 100644 index 918958392..000000000 --- a/app/src/main/sqldelight/migrations/14.sqm +++ /dev/null @@ -1,149 +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; - -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 -); -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 -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 -); -INSERT INTO categories -SELECT _id,name,sort,flags -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; - -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 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 manga_temp; \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/2.sqm b/app/src/main/sqldelight/migrations/2.sqm deleted file mode 100644 index 345e24c6e..000000000 --- a/app/src/main/sqldelight/migrations/2.sqm +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE history( - history_id INTEGER NOT NULL PRIMARY KEY, - history_chapter_id INTEGER NOT NULL UNIQUE, - history_last_read INTEGER, - history_time_read INTEGER, - FOREIGN KEY(history_chapter_id) REFERENCES chapters (_id) - ON DELETE CASCADE -); - -CREATE INDEX history_history_chapter_id_index ON history(history_chapter_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 42ffba899..000000000 --- a/app/src/main/sqldelight/migrations/3.sqm +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE chapters -ADD COLUMN bookmark INTEGER DEFAULT 0; \ 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 7fc06a612..000000000 --- a/app/src/main/sqldelight/migrations/4.sqm +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE chapters -ADD COLUMN scanlator TEXT DEFAULT NULL; \ No newline at end of file diff --git a/app/src/main/sqldelight/migrations/5.sqm b/app/src/main/sqldelight/migrations/5.sqm deleted file mode 100644 index a1e3b8378..000000000 --- a/app/src/main/sqldelight/migrations/5.sqm +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE manga_sync -ADD COLUMN remote_url TEXT DEFAULT ''; \ 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 00ee92e22..000000000 --- a/app/src/main/sqldelight/migrations/6.sqm +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE manga_sync -ADD COLUMN library_id INTEGER; \ 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 03492df76..000000000 --- a/app/src/main/sqldelight/migrations/7.sqm +++ /dev/null @@ -1,9 +0,0 @@ -DROP INDEX IF EXISTS mangas_favorite_index; - -CREATE INDEX library_favorite_index -ON mangas(favorite) -WHERE favorite = 1; - -CREATE INDEX chapters_unread_by_manga_index -ON chapters(manga_id, read) -WHERE read = 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 dc47263a8..000000000 --- a/app/src/main/sqldelight/migrations/8.sqm +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE manga_sync -ADD COLUMN start_date INTEGER NOT NULL DEFAULT 0; - -ALTER TABLE manga_sync -ADD COLUMN finish_date INTEGER NOT NULL DEFAULT 0; \ 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 6eb647300..000000000 --- a/app/src/main/sqldelight/migrations/9.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/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 efb20b7e9..f1a44de2b 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") } } 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..48409bfd9 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" @@ -80,7 +78,7 @@ flowbinding-viewpager = { module = "io.github.reactivecircus.flowbinding:flowbin logcat = "com.squareup.logcat:logcat:0.1" -acra-http = "ch.acra:acra-http:5.9.1" +acra-http = "ch.acra:acra-http:5.9.3" firebase-analytics = "com.google.firebase:firebase-analytics-ktx:20.0.2" aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlib_version" } @@ -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 170ba7daf..8c3fdeac3 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")) - } } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories {