feat(manga-folder): Allow open manga folder with system's File Manager (#920)

* feat(manga-folder): Allow open manga folder with system's File Manager

* Add error message for failed folder opening
This commit is contained in:
Cuong-Tran 2025-05-24 01:53:53 +07:00 committed by GitHub
parent fb9409c12f
commit 382d29e303
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 47 additions and 3 deletions

View file

@ -201,6 +201,7 @@ fun MangaScreen(
// KMK --> // KMK -->
getMangaState: @Composable (Manga) -> State<Manga>, getMangaState: @Composable (Manga) -> State<Manga>,
onClickSourceSettingsClicked: (() -> Unit)?, onClickSourceSettingsClicked: (() -> Unit)?,
onOpenMangaFolder: (() -> Unit)?,
onRelatedMangasScreenClick: () -> Unit, onRelatedMangasScreenClick: () -> Unit,
onRelatedMangaClick: (Manga) -> Unit, onRelatedMangaClick: (Manga) -> Unit,
onRelatedMangaLongClick: (Manga) -> Unit, onRelatedMangaLongClick: (Manga) -> Unit,
@ -268,6 +269,7 @@ fun MangaScreen(
// KMK --> // KMK -->
getMangaState = getMangaState, getMangaState = getMangaState,
onClickSourceSettingsClicked = onClickSourceSettingsClicked, onClickSourceSettingsClicked = onClickSourceSettingsClicked,
onOpenMangaFolder = onOpenMangaFolder,
onRelatedMangasScreenClick = onRelatedMangasScreenClick, onRelatedMangasScreenClick = onRelatedMangasScreenClick,
onRelatedMangaClick = onRelatedMangaClick, onRelatedMangaClick = onRelatedMangaClick,
onRelatedMangaLongClick = onRelatedMangaLongClick, onRelatedMangaLongClick = onRelatedMangaLongClick,
@ -328,6 +330,7 @@ fun MangaScreen(
// KMK --> // KMK -->
getMangaState = getMangaState, getMangaState = getMangaState,
onClickSourceSettingsClicked = onClickSourceSettingsClicked, onClickSourceSettingsClicked = onClickSourceSettingsClicked,
onOpenMangaFolder = onOpenMangaFolder,
onRelatedMangasScreenClick = onRelatedMangasScreenClick, onRelatedMangasScreenClick = onRelatedMangasScreenClick,
onRelatedMangaClick = onRelatedMangaClick, onRelatedMangaClick = onRelatedMangaClick,
onRelatedMangaLongClick = onRelatedMangaLongClick, onRelatedMangaLongClick = onRelatedMangaLongClick,
@ -405,6 +408,7 @@ private fun MangaScreenSmallImpl(
// KMK --> // KMK -->
getMangaState: @Composable ((Manga) -> State<Manga>), getMangaState: @Composable ((Manga) -> State<Manga>),
onClickSourceSettingsClicked: (() -> Unit)?, onClickSourceSettingsClicked: (() -> Unit)?,
onOpenMangaFolder: (() -> Unit)?,
onRelatedMangasScreenClick: () -> Unit, onRelatedMangasScreenClick: () -> Unit,
onRelatedMangaClick: (Manga) -> Unit, onRelatedMangaClick: (Manga) -> Unit,
onRelatedMangaLongClick: (Manga) -> Unit, onRelatedMangaLongClick: (Manga) -> Unit,
@ -487,6 +491,7 @@ private fun MangaScreenSmallImpl(
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite }, onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
// KMK --> // KMK -->
onClickSourceSettings = onClickSourceSettingsClicked, onClickSourceSettings = onClickSourceSettingsClicked,
onOpenMangaFolder = onOpenMangaFolder,
onClickRelatedMangas = onRelatedMangasScreenClick.takeIf { onClickRelatedMangas = onRelatedMangasScreenClick.takeIf {
!expandRelatedMangas && !expandRelatedMangas &&
showRelatedMangasInOverflow && showRelatedMangasInOverflow &&
@ -870,6 +875,7 @@ private fun MangaScreenLargeImpl(
// KMK --> // KMK -->
getMangaState: @Composable ((Manga) -> State<Manga>), getMangaState: @Composable ((Manga) -> State<Manga>),
onClickSourceSettingsClicked: (() -> Unit)?, onClickSourceSettingsClicked: (() -> Unit)?,
onOpenMangaFolder: (() -> Unit)?,
onRelatedMangasScreenClick: () -> Unit, onRelatedMangasScreenClick: () -> Unit,
onRelatedMangaClick: (Manga) -> Unit, onRelatedMangaClick: (Manga) -> Unit,
onRelatedMangaLongClick: (Manga) -> Unit, onRelatedMangaLongClick: (Manga) -> Unit,
@ -944,6 +950,7 @@ private fun MangaScreenLargeImpl(
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite }, onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
// KMK --> // KMK -->
onClickSourceSettings = onClickSourceSettingsClicked, onClickSourceSettings = onClickSourceSettingsClicked,
onOpenMangaFolder = onOpenMangaFolder,
onClickRelatedMangas = onRelatedMangasScreenClick.takeIf { onClickRelatedMangas = onRelatedMangasScreenClick.takeIf {
!expandRelatedMangas && !expandRelatedMangas &&
showRelatedMangasInOverflow && showRelatedMangasInOverflow &&

View file

@ -52,11 +52,12 @@ fun MangaToolbar(
onClickEditInfo: (() -> Unit)?, onClickEditInfo: (() -> Unit)?,
// KMK --> // KMK -->
onClickRelatedMangas: (() -> Unit)?, onClickRelatedMangas: (() -> Unit)?,
onClickSourceSettings: (() -> Unit)?,
onOpenMangaFolder: (() -> Unit)?,
// KMK <-- // KMK <--
onClickRecommend: (() -> Unit)?, onClickRecommend: (() -> Unit)?,
onClickMerge: (() -> Unit)?, onClickMerge: (() -> Unit)?,
onClickMergedSettings: (() -> Unit)?, onClickMergedSettings: (() -> Unit)?,
onClickSourceSettings: (() -> Unit)?,
// SY <-- // SY <--
// For action mode // For action mode
@ -234,6 +235,14 @@ fun MangaToolbar(
} }
// SY <-- // SY <--
// KMK --> // KMK -->
if (onOpenMangaFolder != null) {
add(
AppBar.OverflowAction(
title = stringResource(KMR.strings.action_open_folder),
onClick = onOpenMangaFolder,
),
)
}
if (onClickSourceSettings != null) { if (onClickSourceSettings != null) {
add( add(
AppBar.OverflowAction( AppBar.OverflowAction(

View file

@ -383,6 +383,8 @@ class MangaScreen(
else -> {} else -> {}
} }
}.takeIf { isConfigurableSource }, }.takeIf { isConfigurableSource },
onOpenMangaFolder = { screenModel.openMangaFolder() }
.takeIf { successState.source !is StubSource },
onRelatedMangasScreenClick = { onRelatedMangasScreenClick = {
if (successState.isRelatedMangasFetched == null) { if (successState.isRelatedMangasFetched == null) {
scope.launchIO { screenModel.fetchRelatedMangasFromSource(onDemand = true) } scope.launchIO { screenModel.fetchRelatedMangasFromSource(onDemand = true) }

View file

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.ui.manga package eu.kanade.tachiyomi.ui.manga
import android.content.Context import android.content.Context
import android.content.Intent
import android.provider.DocumentsContract
import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
@ -50,6 +52,7 @@ import eu.kanade.presentation.util.formattedMessage
import eu.kanade.tachiyomi.data.coil.getBestColor import eu.kanade.tachiyomi.data.coil.getBestColor
import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.track.EnhancedTracker import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.data.track.TrackerManager
@ -152,13 +155,12 @@ import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.model.Track
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.kmk.KMR
import tachiyomi.source.local.LocalSource import tachiyomi.source.local.LocalSource
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.collections.filter
import kotlin.collections.forEach
import kotlin.math.floor import kotlin.math.floor
import androidx.compose.runtime.State as RuntimeState import androidx.compose.runtime.State as RuntimeState
@ -178,6 +180,7 @@ class MangaScreenModel(
// KMK --> // KMK -->
private val sourcePreferences: SourcePreferences = Injekt.get(), private val sourcePreferences: SourcePreferences = Injekt.get(),
private val refreshTracks: RefreshTracks = Injekt.get(), private val refreshTracks: RefreshTracks = Injekt.get(),
private val downloadProvider: DownloadProvider = Injekt.get(),
// KMK <-- // KMK <--
private val trackerManager: TrackerManager = Injekt.get(), private val trackerManager: TrackerManager = Injekt.get(),
private val trackChapter: TrackChapter = Injekt.get(), private val trackChapter: TrackChapter = Injekt.get(),
@ -886,6 +889,27 @@ class MangaScreenModel(
} }
} }
/**
* Opens manga folder with the system's file manager.
*/
fun openMangaFolder() {
try {
val currentManga = manga
val currentSource = source
if (currentManga == null || currentSource == null || currentSource is StubSource) return
val mangaDir = downloadProvider.findMangaDir(/* SY --> */ currentManga.ogTitle /* SY <-- */, currentSource) ?: return
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(mangaDir.uri, DocumentsContract.Document.MIME_TYPE_DIR)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(intent)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
context.toast(e.message ?: context.stringResource(KMR.strings.error_opening_folder))
}
}
/** /**
* Get user categories. * Get user categories.
* *

View file

@ -14,12 +14,14 @@
<string name="action_hide">Hide</string> <string name="action_hide">Hide</string>
<string name="action_show_hidden_categories">Show hidden categories</string> <string name="action_show_hidden_categories">Show hidden categories</string>
<string name="action_panorama_cover">Panorama cover</string> <string name="action_panorama_cover">Panorama cover</string>
<string name="action_open_folder">Open folder</string>
<!-- Reader --> <!-- Reader -->
<!-- Reader Actions --> <!-- Reader Actions -->
<string name="action_copy_to_clipboard_first_page">Copy first page to clipboard</string> <string name="action_copy_to_clipboard_first_page">Copy first page to clipboard</string>
<string name="action_copy_to_clipboard_second_page">Copy second page to clipboard</string> <string name="action_copy_to_clipboard_second_page">Copy second page to clipboard</string>
<string name="action_copy_to_clipboard_combined_page">Copy combined page to clipboard</string> <string name="action_copy_to_clipboard_combined_page">Copy combined page to clipboard</string>
<string name="pref_viewer_nav_smaller_tap_zone">Smaller tap zones</string> <string name="pref_viewer_nav_smaller_tap_zone">Smaller tap zones</string>
<string name="error_opening_folder">Failed to open folder</string>
<!-- Preferences --> <!-- Preferences -->
<!-- Appearance section --> <!-- Appearance section -->
<string name="pref_custom_theme_style">Custom theme style</string> <string name="pref_custom_theme_style">Custom theme style</string>