Эх сурвалжийг харах

Reimplement chapter download indicator longpress (#7412)

AntsyLich 2 жил өмнө
parent
commit
deaded5af2

+ 4 - 0
app/src/main/java/eu/kanade/data/chapter/ChapterRepositoryImpl.kt

@@ -92,6 +92,10 @@ class ChapterRepositoryImpl(
         return handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, chapterMapper) }
     }
 
+    override suspend fun getChapterById(id: Long): Chapter? {
+        return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, chapterMapper) }
+    }
+
     override suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> {
         return handler.subscribeToList { chaptersQueries.getChaptersByMangaId(mangaId, chapterMapper) }
     }

+ 2 - 0
app/src/main/java/eu/kanade/domain/DomainModule.kt

@@ -12,6 +12,7 @@ import eu.kanade.domain.category.interactor.InsertCategory
 import eu.kanade.domain.category.interactor.MoveMangaToCategories
 import eu.kanade.domain.category.interactor.UpdateCategory
 import eu.kanade.domain.category.repository.CategoryRepository
+import eu.kanade.domain.chapter.interactor.GetChapter
 import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
 import eu.kanade.domain.chapter.interactor.ShouldUpdateDbChapter
 import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
@@ -84,6 +85,7 @@ class DomainModule : InjektModule {
         addFactory { InsertTrack(get()) }
 
         addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
+        addFactory { GetChapter(get()) }
         addFactory { GetChapterByMangaId(get()) }
         addFactory { UpdateChapter(get()) }
         addFactory { ShouldUpdateDbChapter() }

+ 20 - 0
app/src/main/java/eu/kanade/domain/chapter/interactor/GetChapter.kt

@@ -0,0 +1,20 @@
+package eu.kanade.domain.chapter.interactor
+
+import eu.kanade.domain.chapter.model.Chapter
+import eu.kanade.domain.chapter.repository.ChapterRepository
+import eu.kanade.tachiyomi.util.system.logcat
+import logcat.LogPriority
+
+class GetChapter(
+    private val chapterRepository: ChapterRepository,
+) {
+
+    suspend fun await(id: Long): Chapter? {
+        return try {
+            chapterRepository.getChapterById(id)
+        } catch (e: Exception) {
+            logcat(LogPriority.ERROR, e)
+            null
+        }
+    }
+}

+ 2 - 0
app/src/main/java/eu/kanade/domain/chapter/repository/ChapterRepository.kt

@@ -16,5 +16,7 @@ interface ChapterRepository {
 
     suspend fun getChapterByMangaId(mangaId: Long): List<Chapter>
 
+    suspend fun getChapterById(id: Long): Chapter?
+
     suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>>
 }

+ 8 - 1
app/src/main/java/eu/kanade/presentation/components/ChapterDownloadIndicator.kt

@@ -10,7 +10,6 @@ import androidx.compose.material.icons.filled.CheckCircle
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
 import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.ProgressIndicatorDefaults
@@ -51,6 +50,14 @@ fun ChapterDownloadIndicator(
                         onClick(ChapterDownloadAction.START)
                     }
                 },
+                onLongClick = {
+                    val chapterDownloadAction = when {
+                        isDownloaded -> ChapterDownloadAction.DELETE
+                        isDownloading -> ChapterDownloadAction.CANCEL
+                        else -> ChapterDownloadAction.START_NOW
+                    }
+                    onClick(chapterDownloadAction)
+                },
             ) {
                 if (isDownloaded) {
                     Icon(

+ 105 - 0
app/src/main/java/eu/kanade/presentation/components/IconButton.kt

@@ -0,0 +1,105 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package eu.kanade.presentation.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.unit.dp
+import eu.kanade.presentation.util.minimumTouchTargetSize
+
+/**
+ * <a href="https://m3.material.io/components/icon-button/overview" class="external" target="_blank">Material Design standard icon button</a>.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * ![Standard icon button image](https://developer.android.com/images/reference/androidx/compose/material3/standard-icon-button.png)
+ *
+ * [content] should typically be an [Icon] (see [androidx.compose.material.icons.Icons]). If using a
+ * custom icon, note that the typical size for the internal icon is 24 x 24 dp.
+ * This icon button has an overall minimum touch target size of 48 x 48dp, to meet accessibility
+ * guidelines.
+ *
+ * @sample androidx.compose.material3.samples.IconButtonSample
+ *
+ * Tachiyomi changes:
+ * * Add on long click
+ *
+ * @param onClick called when this icon button is clicked
+ * @param modifier the [Modifier] to be applied to this icon button
+ * @param enabled controls the enabled state of this icon button. When `false`, this component will
+ * not respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this icon button. You can create and pass in your own `remember`ed instance to observe
+ * [Interaction]s and customize the appearance / behavior of this icon button in different states.
+ * @param colors [IconButtonColors] that will be used to resolve the colors used for this icon
+ * button in different states. See [IconButtonDefaults.iconButtonColors].
+ * @param content the content of this icon button, typically an [Icon]
+ */
+@Composable
+fun IconButton(
+    onClick: () -> Unit,
+    onLongClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
+    content: @Composable () -> Unit,
+) {
+    Box(
+        modifier =
+        modifier
+            .minimumTouchTargetSize()
+            .size(IconButtonTokens.StateLayerSize)
+            .background(color = colors.containerColor(enabled).value)
+            .combinedClickable(
+                onClick = onClick,
+                onLongClick = onLongClick,
+                enabled = enabled,
+                role = Role.Button,
+                interactionSource = interactionSource,
+                indication = rememberRipple(
+                    bounded = false,
+                    radius = IconButtonTokens.StateLayerSize / 2,
+                ),
+            ),
+        contentAlignment = Alignment.Center,
+    ) {
+        val contentColor = colors.contentColor(enabled).value
+        CompositionLocalProvider(LocalContentColor provides contentColor, content = content)
+    }
+}
+
+object IconButtonTokens {
+    val StateLayerSize = 40.0.dp
+}

+ 6 - 3
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt

@@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.util.lang.launchIO
 import eu.kanade.tachiyomi.util.system.logcat
+import kotlinx.coroutines.runBlocking
 import logcat.LogPriority
 import rx.Observable
 import uy.kohesive.injekt.Injekt
@@ -104,10 +105,12 @@ class DownloadManager(
 
     fun startDownloadNow(chapterId: Long?) {
         if (chapterId == null) return
-        val download = downloader.queue.find { it.chapter.id == chapterId } ?: return
+        val download = downloader.queue.find { it.chapter.id == chapterId }
+        // If not in queue try to start a new download
+        val toAdd = download ?: runBlocking { Download.fromChapterId(chapterId) } ?: return
         val queue = downloader.queue.toMutableList()
-        queue.remove(download)
-        queue.add(0, download)
+        download?.let { queue.remove(it) }
+        queue.add(0, toAdd)
         reorderQueue(queue)
         if (isPaused()) {
             if (DownloadService.isRunning(context)) {

+ 22 - 0
app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt

@@ -1,10 +1,17 @@
 package eu.kanade.tachiyomi.data.download.model
 
+import eu.kanade.domain.chapter.interactor.GetChapter
+import eu.kanade.domain.chapter.model.toDbChapter
+import eu.kanade.domain.manga.interactor.GetMangaById
+import eu.kanade.domain.manga.model.toDbManga
 import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.source.online.HttpSource
 import rx.subjects.PublishSubject
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
 
 data class Download(
     val source: HttpSource,
@@ -57,4 +64,19 @@ data class Download(
         DOWNLOADED(3),
         ERROR(4),
     }
+
+    companion object {
+        suspend fun fromChapterId(
+            chapterId: Long,
+            getChapter: GetChapter = Injekt.get(),
+            getMangaById: GetMangaById = Injekt.get(),
+            sourceManager: SourceManager = Injekt.get(),
+        ): Download? {
+            val chapter = getChapter.await(chapterId) ?: return null
+            val manga = getMangaById.await(chapter.mangaId) ?: return null
+            val source = sourceManager.get(manga.source) as? HttpSource ?: return null
+
+            return Download(source, manga.toDbManga(), chapter.toDbChapter())
+        }
+    }
 }