mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-28 17:19:00 +03:00
Merge pull request #2 from Quickdesh/move_n_rename_files
Move and rename files
This commit is contained in:
commit
c18e08e2f3
20 changed files with 417 additions and 176 deletions
|
@ -45,7 +45,7 @@ import eu.kanade.tachiyomi.R
|
|||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
||||
import eu.kanade.tachiyomi.source.anime.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.ui.player.EpisodeLoader
|
||||
import eu.kanade.tachiyomi.ui.player.loader.EpisodeLoader
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
|
|
@ -98,6 +98,7 @@ import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
|
|||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -490,7 +491,7 @@ class MainActivity : BaseActivity() {
|
|||
|
||||
// Get the search query provided in extras, and if not null, perform a global search with it.
|
||||
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (query != null && query.isNotEmpty()) {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
navigator.popUntilRoot()
|
||||
navigator.push(GlobalMangaSearchScreen(query))
|
||||
}
|
||||
|
@ -498,7 +499,7 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
INTENT_SEARCH -> {
|
||||
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
||||
if (query != null && query.isNotEmpty()) {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) ?: ""
|
||||
navigator.popUntilRoot()
|
||||
navigator.push(GlobalMangaSearchScreen(query, filter))
|
||||
|
@ -507,7 +508,7 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
INTENT_ANIMESEARCH -> {
|
||||
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
||||
if (query != null && query.isNotEmpty()) {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) ?: ""
|
||||
navigator.popUntilRoot()
|
||||
navigator.push(GlobalAnimeSearchScreen(query, filter))
|
||||
|
@ -548,6 +549,7 @@ class MainActivity : BaseActivity() {
|
|||
registerSecureActivity(this)
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
launchIO { externalIntents.onActivityResult(requestCode, resultCode, data) }
|
||||
|
|
|
@ -27,12 +27,12 @@ import eu.kanade.tachiyomi.animesource.AnimeSource
|
|||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.core.Constants.REQUEST_EXTERNAL
|
||||
import eu.kanade.tachiyomi.data.database.models.anime.toDomainEpisode
|
||||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
||||
import eu.kanade.tachiyomi.data.track.AnimeTrackService
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.source.anime.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.source.anime.LocalAnimeSource
|
||||
import eu.kanade.tachiyomi.ui.player.loader.EpisodeLoader
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||
import eu.kanade.tachiyomi.util.system.isOnline
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
|
@ -40,8 +40,8 @@ import kotlinx.coroutines.async
|
|||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.first
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchUI
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.lang.withUIContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.history.anime.interactor.UpsertAnimeHistory
|
||||
|
@ -57,32 +57,69 @@ import eu.kanade.tachiyomi.data.database.models.anime.Episode as DbEpisode
|
|||
|
||||
class ExternalIntents {
|
||||
|
||||
/**
|
||||
* The common variables
|
||||
* Used to dictate what video is sent an external player.
|
||||
*/
|
||||
lateinit var anime: Anime
|
||||
lateinit var episode: Episode
|
||||
lateinit var source: AnimeSource
|
||||
lateinit var episode: Episode
|
||||
|
||||
/**
|
||||
* Returns the [Intent] to be sent to an external player.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @param animeId the id of the anime.
|
||||
* @param episodeId the id of the episode.
|
||||
*/
|
||||
suspend fun getExternalIntent(context: Context, animeId: Long?, episodeId: Long?): Intent? {
|
||||
anime = getAnime.await(animeId!!) ?: return null
|
||||
source = sourceManager.get(anime.source) ?: return null
|
||||
episode = getEpisodeByAnimeId.await(anime.id).find { it.id == episodeId } ?: return null
|
||||
|
||||
val video = EpisodeLoader.getLinks(episode, anime, source).asFlow().first()[0]
|
||||
|
||||
val videoUrl = if (video.videoUrl == null) {
|
||||
makeErrorToast(context, Exception("video URL is null."))
|
||||
val videoUrl = getVideoUrl(context, video) ?: return null
|
||||
|
||||
val pkgName = playerPreferences.externalPlayerPreference().get()
|
||||
|
||||
return if (pkgName.isEmpty()) {
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndTypeAndNormalize(videoUrl, getMime(videoUrl))
|
||||
addExtrasAndFlags(false, this)
|
||||
addVideoHeaders(false, video, this)
|
||||
}
|
||||
} else {
|
||||
standardIntentForPackage(pkgName, context, videoUrl, video)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [Uri] of the given video.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @param video the video being sent to the external player.
|
||||
*/
|
||||
private suspend fun getVideoUrl(context: Context, video: Video): Uri? {
|
||||
if (video.videoUrl == null) {
|
||||
makeErrorToast(context, Exception("Video URL is null."))
|
||||
return null
|
||||
} else {
|
||||
val uri = video.videoUrl!!.toUri()
|
||||
|
||||
val isOnDevice = if (anime.source == LocalAnimeSource.ID) {
|
||||
true
|
||||
} else {
|
||||
downloadManager.isEpisodeDownloaded(
|
||||
episode.name,
|
||||
episode.scanlator,
|
||||
anime.title,
|
||||
anime.source,
|
||||
episodeName = episode.name,
|
||||
episodeScanlator = episode.scanlator,
|
||||
animeTitle = anime.title,
|
||||
sourceId = anime.source,
|
||||
skipCache = true,
|
||||
)
|
||||
}
|
||||
if (isOnDevice && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && uri.scheme != "content") {
|
||||
|
||||
return if (isOnDevice && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && uri.scheme != "content") {
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
context.applicationContext.packageName + ".provider",
|
||||
|
@ -92,56 +129,41 @@ class ExternalIntents {
|
|||
uri
|
||||
}
|
||||
}
|
||||
val pkgName = playerPreferences.externalPlayerPreference().get()
|
||||
val anime = anime
|
||||
val lastSecondSeen = if (episode.seen) {
|
||||
if ((!playerPreferences.preserveWatchingPosition().get()) ||
|
||||
(
|
||||
playerPreferences.preserveWatchingPosition().get() &&
|
||||
episode.lastSecondSeen == episode.totalSeconds
|
||||
)
|
||||
) {
|
||||
1L
|
||||
} else {
|
||||
episode.lastSecondSeen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second to start the external player at.
|
||||
*/
|
||||
private fun getLastSecondSeen(): Long {
|
||||
val preserveWatchPos = playerPreferences.preserveWatchingPosition().get()
|
||||
val isEpisodeWatched = episode.lastSecondSeen == episode.totalSeconds
|
||||
|
||||
return if (episode.seen && (!preserveWatchPos || (preserveWatchPos && isEpisodeWatched))) {
|
||||
1L
|
||||
} else {
|
||||
episode.lastSecondSeen
|
||||
}
|
||||
|
||||
return if (pkgName.isEmpty()) {
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndTypeAndNormalize(videoUrl, getMime(videoUrl))
|
||||
putExtra("title", anime.title + " - " + episode.name)
|
||||
putExtra("position", lastSecondSeen.toInt())
|
||||
putExtra("return_result", true)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
val headers = video.headers ?: (source as? AnimeHttpSource)?.headers
|
||||
if (headers != null) {
|
||||
var headersArray = arrayOf<String>()
|
||||
for (header in headers) {
|
||||
headersArray += arrayOf(header.first, header.second)
|
||||
}
|
||||
val headersString = headersArray.drop(2).joinToString(": ")
|
||||
putExtra("headers", headersArray)
|
||||
putExtra("http-header-fields", headersString)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
standardIntentForPackage(pkgName, context, videoUrl, episode, video)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeErrorToast(context: Context, e: Exception?) {
|
||||
launchUI { context.toast(e?.message ?: "Cannot open episode") }
|
||||
/**
|
||||
* Display an error toast in this [context].
|
||||
*
|
||||
* @param context the application context.
|
||||
* @param e the exception error to be displayed.
|
||||
*/
|
||||
private suspend fun makeErrorToast(context: Context, e: Exception?) {
|
||||
withUIContext { context.toast(e?.message ?: "Cannot open episode") }
|
||||
}
|
||||
|
||||
private fun standardIntentForPackage(pkgName: String, context: Context, uri: Uri, episode: Episode, video: Video): Intent {
|
||||
val lastSecondSeen = if (episode.seen && !playerPreferences.preserveWatchingPosition().get()) {
|
||||
0L
|
||||
} else {
|
||||
episode.lastSecondSeen
|
||||
}
|
||||
/**
|
||||
* Returns the [Intent] with added data to send to the given external player.
|
||||
*
|
||||
* @param pkgName the name of the package to send the [Intent] to.
|
||||
* @param context the application context.
|
||||
* @param uri the path data of the video.
|
||||
* @param video the video being sent to the external player.
|
||||
*/
|
||||
private fun standardIntentForPackage(pkgName: String, context: Context, uri: Uri, video: Video): Intent {
|
||||
return Intent(Intent.ACTION_VIEW).apply {
|
||||
if (isPackageInstalled(pkgName, context.packageManager)) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && pkgName.contains("vlc")) {
|
||||
|
@ -151,13 +173,13 @@ class ExternalIntents {
|
|||
}
|
||||
}
|
||||
setDataAndType(uri, "video/*")
|
||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
putExtra("title", episode.name)
|
||||
putExtra("position", lastSecondSeen.toInt())
|
||||
putExtra("return_result", true)
|
||||
putExtra("secure_uri", true)
|
||||
addExtrasAndFlags(true, this)
|
||||
addVideoHeaders(true, video, this)
|
||||
|
||||
/*val externalSubs = source.getExternalSubtitleStreams()
|
||||
// Add support for Subtitles to external players
|
||||
|
||||
/*
|
||||
val externalSubs = source.getExternalSubtitleStreams()
|
||||
val enabledSubUrl = when {
|
||||
source.selectedSubtitleStream != null -> {
|
||||
externalSubs.find { stream -> stream.index == source.selectedSubtitleStream?.index }?.let { sub ->
|
||||
|
@ -174,9 +196,35 @@ class ExternalIntents {
|
|||
putExtra("subs.enable", enabledSubUrl?.let { url -> arrayOf(Uri.parse(url)) } ?: emptyArray())
|
||||
|
||||
// VLC
|
||||
if (enabledSubUrl != null) putExtra("subtitles_location", enabledSubUrl)*/
|
||||
if (enabledSubUrl != null) putExtra("subtitles_location", enabledSubUrl)
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
// headers
|
||||
/**
|
||||
* Adds extras and flags to the given [Intent].
|
||||
*
|
||||
* @param isSupportedPlayer is it a supported external player.
|
||||
* @param intent the [Intent] that the extras and flags are added to.
|
||||
*/
|
||||
private fun addExtrasAndFlags(isSupportedPlayer: Boolean, intent: Intent): Intent {
|
||||
return intent.apply {
|
||||
putExtra("title", anime.title + " - " + episode.name)
|
||||
putExtra("position", getLastSecondSeen().toInt())
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
if (isSupportedPlayer) putExtra("secure_uri", true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the headers of the video to the given [Intent].
|
||||
*
|
||||
* @param isSupportedPlayer is it a supported external player.
|
||||
* @param video the [Video] to get the headers from.
|
||||
* @param intent the [Intent] that the headers are added to.
|
||||
*/
|
||||
private fun addVideoHeaders(isSupportedPlayer: Boolean, video: Video, intent: Intent): Intent {
|
||||
return intent.apply {
|
||||
val headers = video.headers ?: (source as? AnimeHttpSource)?.headers
|
||||
if (headers != null) {
|
||||
var headersArray = arrayOf<String>()
|
||||
|
@ -184,10 +232,17 @@ class ExternalIntents {
|
|||
headersArray += arrayOf(header.first, header.second)
|
||||
}
|
||||
putExtra("headers", headersArray)
|
||||
val headersString = headersArray.drop(2).joinToString(": ")
|
||||
if (!isSupportedPlayer) putExtra("http-header-fields", headersString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MIME type based on the video's extension.
|
||||
*
|
||||
* @param uri the path data of the video.
|
||||
*/
|
||||
private fun getMime(uri: Uri): String {
|
||||
return when (uri.path?.substringAfterLast(".")) {
|
||||
"mp4" -> "video/mp4"
|
||||
|
@ -198,7 +253,10 @@ class ExternalIntents {
|
|||
}
|
||||
|
||||
/**
|
||||
* To ensure that the correct activity is called.
|
||||
* Returns the specific activity to be called.
|
||||
* If the package is a part of the supported external players
|
||||
*
|
||||
* @param packageName the name of the package.
|
||||
*/
|
||||
private fun getComponent(packageName: String): ComponentName? {
|
||||
return when (packageName) {
|
||||
|
@ -210,6 +268,12 @@ class ExternalIntents {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given package is installed on the device.
|
||||
*
|
||||
* @param packageName the name of the package to be found.
|
||||
* @param packageManager the instance of the package manager provided by the device.
|
||||
*/
|
||||
private fun isPackageInstalled(packageName: String, packageManager: PackageManager): Boolean {
|
||||
return try {
|
||||
packageManager.getPackageInfo(packageName, 0)
|
||||
|
@ -219,6 +283,13 @@ class ExternalIntents {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the episode's data based on whats returned by the external player.
|
||||
*
|
||||
* @param requestCode the code sent to ensure that the returned [data] is from an external player.
|
||||
* @param resultCode the code sent to ensure that the returned [data] is valid.
|
||||
* @param data the [Intent] that contains the episode's position and duration.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
suspend fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == REQUEST_EXTERNAL && resultCode == Activity.RESULT_OK) {
|
||||
|
@ -227,6 +298,8 @@ class ExternalIntents {
|
|||
val currentPosition: Long
|
||||
val duration: Long
|
||||
val cause = data!!.getStringExtra("end_by") ?: ""
|
||||
|
||||
// Check for position and duration as Long values
|
||||
if (cause.isNotEmpty()) {
|
||||
val positionExtra = data.extras?.get("position")
|
||||
currentPosition = if (positionExtra is Int) {
|
||||
|
@ -249,7 +322,9 @@ class ExternalIntents {
|
|||
duration = data.getIntExtra("duration", 0).toLong()
|
||||
}
|
||||
}
|
||||
launchIO {
|
||||
|
||||
// Update the episode's progress and history
|
||||
withIOContext {
|
||||
if (cause == "playback_completion" || (currentPosition == duration && duration == 0L)) {
|
||||
saveEpisodeProgress(currentExtEpisode, anime, currentExtEpisode.totalSeconds, currentExtEpisode.totalSeconds)
|
||||
} else {
|
||||
|
@ -260,6 +335,7 @@ class ExternalIntents {
|
|||
}
|
||||
}
|
||||
|
||||
// List of all the required Injectable classes
|
||||
private val upsertHistory: UpsertAnimeHistory = Injekt.get()
|
||||
private val updateEpisode: UpdateEpisode = Injekt.get()
|
||||
private val getAnime: GetAnime = Injekt.get()
|
||||
|
@ -274,41 +350,63 @@ class ExternalIntents {
|
|||
private val trackPreferences: TrackPreferences = Injekt.get()
|
||||
private val basePreferences: BasePreferences by injectLazy()
|
||||
|
||||
private suspend fun saveEpisodeHistory(episode: Episode) {
|
||||
/**
|
||||
* Saves this episode's last seen history if incognito mode isn't on.
|
||||
*
|
||||
* @param currentEpisode the episode to update.
|
||||
*/
|
||||
private suspend fun saveEpisodeHistory(currentEpisode: Episode) {
|
||||
if (basePreferences.incognitoMode().get()) return
|
||||
upsertHistory.await(
|
||||
AnimeHistoryUpdate(episode.id, Date()),
|
||||
AnimeHistoryUpdate(currentEpisode.id, Date()),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun saveEpisodeProgress(domainEpisode: Episode?, anime: Anime, seconds: Long, totalSeconds: Long) {
|
||||
/**
|
||||
* Saves this episode's progress (last seen second and whether it's seen).
|
||||
* Only if incognito mode isn't on
|
||||
*
|
||||
* @param currentEpisode the episode to update.
|
||||
* @param anime the anime of the episode.
|
||||
* @param seconds the position of the episode.
|
||||
* @param totalSeconds the duration of the episode.
|
||||
*/
|
||||
private suspend fun saveEpisodeProgress(currentEpisode: Episode?, anime: Anime, seconds: Long, totalSeconds: Long) {
|
||||
if (basePreferences.incognitoMode().get()) return
|
||||
val episode = domainEpisode?.toDbEpisode() ?: return
|
||||
val currentDbEpisode = currentEpisode?.toDbEpisode() ?: return
|
||||
|
||||
if (totalSeconds > 0L) {
|
||||
episode.last_second_seen = seconds
|
||||
episode.total_seconds = totalSeconds
|
||||
currentDbEpisode.last_second_seen = seconds
|
||||
currentDbEpisode.total_seconds = totalSeconds
|
||||
val progress = playerPreferences.progressPreference().get()
|
||||
if (!episode.seen) episode.seen = episode.last_second_seen >= episode.total_seconds * progress
|
||||
if (!currentDbEpisode.seen) currentDbEpisode.seen = currentDbEpisode.last_second_seen >= currentDbEpisode.total_seconds * progress
|
||||
updateEpisode.await(
|
||||
EpisodeUpdate(
|
||||
id = episode.id!!,
|
||||
seen = episode.seen,
|
||||
bookmark = episode.bookmark,
|
||||
lastSecondSeen = episode.last_second_seen,
|
||||
totalSeconds = episode.total_seconds,
|
||||
id = currentDbEpisode.id!!,
|
||||
seen = currentDbEpisode.seen,
|
||||
bookmark = currentDbEpisode.bookmark,
|
||||
lastSecondSeen = currentDbEpisode.last_second_seen,
|
||||
totalSeconds = currentDbEpisode.total_seconds,
|
||||
),
|
||||
)
|
||||
if (trackPreferences.autoUpdateTrack().get() && episode.seen) {
|
||||
updateTrackEpisodeSeen(episode, anime)
|
||||
if (trackPreferences.autoUpdateTrack().get() && currentDbEpisode.seen) {
|
||||
updateTrackEpisodeSeen(currentDbEpisode, anime)
|
||||
}
|
||||
if (episode.seen) {
|
||||
deleteEpisodeIfNeeded(episode.toDomainEpisode()!!, anime)
|
||||
if (currentDbEpisode.seen) {
|
||||
deleteEpisodeIfNeeded(currentEpisode, anime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deleteEpisodeIfNeeded(episode: Episode, anime: Anime) {
|
||||
// Determine which chapter should be deleted and enqueue
|
||||
/**
|
||||
* Determines if deleting option is enabled and nth to last episode actually exists.
|
||||
* If both conditions are satisfied enqueues episode for delete
|
||||
*
|
||||
* @param currentEpisode the episode, which is going to be marked as seen.
|
||||
* @param anime the anime of the episode.
|
||||
*/
|
||||
private suspend fun deleteEpisodeIfNeeded(currentEpisode: Episode, anime: Anime) {
|
||||
// Determine which episode should be deleted and enqueue
|
||||
val sortFunction: (Episode, Episode) -> Int = when (anime.sorting) {
|
||||
Anime.EPISODE_SORTING_SOURCE -> { c1, c2 -> c2.sourceOrder.compareTo(c1.sourceOrder) }
|
||||
Anime.EPISODE_SORTING_NUMBER -> { c1, c2 -> c1.episodeNumber.compareTo(c2.episodeNumber) }
|
||||
|
@ -319,25 +417,32 @@ class ExternalIntents {
|
|||
val episodes = getEpisodeByAnimeId.await(anime.id)
|
||||
.sortedWith { e1, e2 -> sortFunction(e1, e2) }
|
||||
|
||||
val currentEpisodePosition = episodes.indexOf(episode)
|
||||
val currentEpisodePosition = episodes.indexOf(currentEpisode)
|
||||
val removeAfterSeenSlots = downloadPreferences.removeAfterReadSlots().get()
|
||||
val episodeToDelete = episodes.getOrNull(currentEpisodePosition - removeAfterSeenSlots)
|
||||
|
||||
// Check if deleting option is enabled and chapter exists
|
||||
// Check if deleting option is enabled and episode exists
|
||||
if (removeAfterSeenSlots != -1 && episodeToDelete != null) {
|
||||
enqueueDeleteSeenEpisodes(episodeToDelete, anime)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTrackEpisodeSeen(episode: DbEpisode, anime: Anime) {
|
||||
/**
|
||||
* Starts the service that updates the last episode seen in sync services.
|
||||
* This operation will run in a background thread and errors are ignored.
|
||||
*
|
||||
* @param currentDbEpisode the episode to be updated.
|
||||
* @param anime the anime of the episode.
|
||||
*/
|
||||
private suspend fun updateTrackEpisodeSeen(currentDbEpisode: DbEpisode, anime: Anime) {
|
||||
if (!trackPreferences.autoUpdateTrack().get()) return
|
||||
|
||||
val episodeSeen = episode.episode_number.toDouble()
|
||||
val episodeSeen = currentDbEpisode.episode_number.toDouble()
|
||||
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
val context = Injekt.get<Application>()
|
||||
|
||||
launchIO {
|
||||
withIOContext {
|
||||
getTracks.await(anime.id)
|
||||
.mapNotNull { track ->
|
||||
val service = trackManager.getService(track.syncId)
|
||||
|
@ -369,22 +474,34 @@ class ExternalIntents {
|
|||
}
|
||||
}
|
||||
|
||||
private fun enqueueDeleteSeenEpisodes(episode: Episode, anime: Anime) {
|
||||
if (!episode.seen) return
|
||||
|
||||
launchIO {
|
||||
downloadManager.enqueueEpisodesToDelete(listOf(episode), anime)
|
||||
}
|
||||
/**
|
||||
* Enqueues an [Episode] to be deleted later.
|
||||
*
|
||||
* @param currentEpisode the episode being deleted.
|
||||
* @param anime the anime of the episode.
|
||||
*/
|
||||
private suspend fun enqueueDeleteSeenEpisodes(currentEpisode: Episode, anime: Anime) {
|
||||
if (currentEpisode.seen) withIOContext { downloadManager.enqueueEpisodesToDelete(listOf(currentEpisode), anime) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val externalIntents: ExternalIntents by injectLazy()
|
||||
|
||||
/**
|
||||
* Used to direct the [Intent] of a chosen episode to an external player.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @param animeId the id of the anime.
|
||||
* @param episodeId the id of the episode.
|
||||
*/
|
||||
suspend fun newIntent(context: Context, animeId: Long?, episodeId: Long?): Intent? {
|
||||
return externalIntents.getExternalIntent(context, animeId, episodeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List of supported external players and their packages
|
||||
private const val MPV_PLAYER = "is.xyz.mpv"
|
||||
private const val MX_PLAYER_FREE = "com.mxtech.videoplayer.ad"
|
||||
private const val MX_PLAYER_PRO = "com.mxtech.videoplayer.pro"
|
||||
|
|
|
@ -55,7 +55,10 @@ import eu.kanade.tachiyomi.data.notification.Notifications
|
|||
import eu.kanade.tachiyomi.databinding.PlayerActivityBinding
|
||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerOptionsSheet
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerTracksSheet
|
||||
import eu.kanade.tachiyomi.ui.player.viewer.Gestures
|
||||
import eu.kanade.tachiyomi.util.AniSkipApi
|
||||
import eu.kanade.tachiyomi.util.SkipType
|
||||
import eu.kanade.tachiyomi.util.Stamp
|
||||
|
@ -471,11 +474,7 @@ class PlayerActivity :
|
|||
width = height.also { height = width }
|
||||
}
|
||||
|
||||
playerControls.binding.titleMainTxt.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
rightToLeft = playerControls.binding.toggleAutoplay.id
|
||||
rightToRight = ConstraintLayout.LayoutParams.UNSET
|
||||
}
|
||||
playerControls.binding.titleSecondaryTxt.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
playerControls.binding.episodeListBtn.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
rightToLeft = playerControls.binding.toggleAutoplay.id
|
||||
rightToRight = ConstraintLayout.LayoutParams.UNSET
|
||||
}
|
||||
|
@ -485,24 +484,20 @@ class PlayerActivity :
|
|||
}
|
||||
playerControls.binding.toggleAutoplay.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
leftToLeft = ConstraintLayout.LayoutParams.UNSET
|
||||
leftToRight = playerControls.binding.titleMainTxt.id
|
||||
leftToRight = playerControls.binding.episodeListBtn.id
|
||||
}
|
||||
} else {
|
||||
if (width >= height) {
|
||||
width = height.also { height = width }
|
||||
}
|
||||
|
||||
playerControls.binding.titleMainTxt.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
rightToLeft = ConstraintLayout.LayoutParams.UNSET
|
||||
rightToRight = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
}
|
||||
playerControls.binding.titleSecondaryTxt.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
playerControls.binding.episodeListBtn.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
rightToLeft = ConstraintLayout.LayoutParams.UNSET
|
||||
rightToRight = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
}
|
||||
playerControls.binding.playerOverflow.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
topToTop = ConstraintLayout.LayoutParams.UNSET
|
||||
topToBottom = playerControls.binding.backArrowBtn.id
|
||||
topToBottom = playerControls.binding.episodeListBtn.id
|
||||
}
|
||||
playerControls.binding.toggleAutoplay.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
|
@ -519,7 +514,6 @@ class PlayerActivity :
|
|||
* Sets up the gestures to be used
|
||||
*/
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun setupGestures() {
|
||||
val gestures = Gestures(this, width.toFloat(), height.toFloat())
|
||||
|
@ -740,8 +734,8 @@ class PlayerActivity :
|
|||
|
||||
if (!playerControls.binding.controlsView.isVisible) {
|
||||
when {
|
||||
player.paused!! -> { binding.playPauseView.setImageResource(R.drawable.ic_pause_72dp) }
|
||||
!player.paused!! -> { binding.playPauseView.setImageResource(R.drawable.ic_play_arrow_72dp) }
|
||||
player.paused!! -> { binding.playPauseView.setImageResource(R.drawable.ic_pause_64dp) }
|
||||
!player.paused!! -> { binding.playPauseView.setImageResource(R.drawable.ic_play_arrow_64dp) }
|
||||
}
|
||||
|
||||
AnimationUtils.loadAnimation(this, R.anim.player_fade_in).also { fadeAnimation ->
|
||||
|
@ -948,6 +942,7 @@ class PlayerActivity :
|
|||
::setAudio,
|
||||
audioTracks,
|
||||
selectedAudio,
|
||||
null,
|
||||
).show()
|
||||
}
|
||||
|
||||
|
@ -962,6 +957,7 @@ class PlayerActivity :
|
|||
::setSub,
|
||||
subTracks,
|
||||
selectedSub,
|
||||
null,
|
||||
).show()
|
||||
}
|
||||
|
||||
|
@ -980,6 +976,7 @@ class PlayerActivity :
|
|||
::changeQuality,
|
||||
videoTracks,
|
||||
currentQuality,
|
||||
null,
|
||||
).show()
|
||||
}
|
||||
|
||||
|
@ -1206,7 +1203,7 @@ class PlayerActivity :
|
|||
|
||||
private fun updatePlaybackStatus(paused: Boolean) {
|
||||
if (isPipSupportedAndEnabled && isInPipMode) updatePictureInPictureActions(!paused)
|
||||
val r = if (paused) R.drawable.ic_play_arrow_72dp else R.drawable.ic_pause_72dp
|
||||
val r = if (paused) R.drawable.ic_play_arrow_64dp else R.drawable.ic_pause_64dp
|
||||
playerControls.binding.playBtn.setImageResource(r)
|
||||
|
||||
if (paused) {
|
||||
|
@ -1314,7 +1311,6 @@ class PlayerActivity :
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
fun startPiP() {
|
||||
if (isInPipMode) return
|
||||
if (isPipSupportedAndEnabled) {
|
||||
|
@ -1628,7 +1624,6 @@ class PlayerActivity :
|
|||
|
||||
private val nextEpisodeRunnable = Runnable { switchEpisode(previous = false, autoPlay = true) }
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun eventPropertyUi(property: String, value: Boolean) {
|
||||
when (property) {
|
||||
"seeking" -> isSeeking(value)
|
||||
|
|
|
@ -36,6 +36,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
|||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
||||
import eu.kanade.tachiyomi.source.anime.AnimeSourceManager
|
||||
import eu.kanade.tachiyomi.ui.player.loader.EpisodeLoader
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.SaveImageNotifier
|
||||
import eu.kanade.tachiyomi.util.AniSkipApi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.ui.player
|
||||
package eu.kanade.tachiyomi.ui.player.loader
|
||||
|
||||
import android.net.Uri
|
||||
import eu.kanade.domain.items.episode.model.toSEpisode
|
||||
|
@ -16,10 +16,22 @@ import uy.kohesive.injekt.Injekt
|
|||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* Loader used to retrieve the video links for a given episode.
|
||||
*/
|
||||
class EpisodeLoader {
|
||||
companion object {
|
||||
var errorMessage = ""
|
||||
|
||||
companion object {
|
||||
|
||||
private var errorMessage = ""
|
||||
|
||||
/**
|
||||
* Returns an observable list of videos of an [episode] based on the type of [source] used.
|
||||
*
|
||||
* @param episode the episode being parsed.
|
||||
* @param anime the anime of the episode.
|
||||
* @param source the source of the anime.
|
||||
*/
|
||||
fun getLinks(episode: Episode, anime: Anime, source: AnimeSource): Observable<List<Video>> {
|
||||
val downloadManager: AnimeDownloadManager = Injekt.get()
|
||||
val isDownloaded = downloadManager.isEpisodeDownloaded(episode.name, episode.scanlator, anime.title, anime.source, skipCache = true)
|
||||
|
@ -31,11 +43,23 @@ class EpisodeLoader {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given [episode] is downloaded.
|
||||
*
|
||||
* @param episode the episode being parsed.
|
||||
* @param anime the anime of the episode.
|
||||
*/
|
||||
fun isDownloaded(episode: Episode, anime: Anime): Boolean {
|
||||
val downloadManager: AnimeDownloadManager = Injekt.get()
|
||||
return downloadManager.isEpisodeDownloaded(episode.name, episode.scanlator, anime.title, anime.source, skipCache = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable list of videos when the [episode] is online.
|
||||
*
|
||||
* @param episode the episode being parsed.
|
||||
* @param source the online source of the episode.
|
||||
*/
|
||||
private fun isHttp(episode: Episode, source: AnimeHttpSource): Observable<List<Video>> {
|
||||
return source.fetchVideoList(episode.toSEpisode())
|
||||
.flatMapIterable { it }
|
||||
|
@ -44,6 +68,14 @@ class EpisodeLoader {
|
|||
}.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable list of videos when the [episode] is downloaded.
|
||||
*
|
||||
* @param episode the episode being parsed.
|
||||
* @param anime the anime of the episode.
|
||||
* @param source the source of the anime.
|
||||
* @param downloadManager the AnimeDownloadManager instance to use.
|
||||
*/
|
||||
private fun isDownloaded(
|
||||
episode: Episode,
|
||||
anime: Anime,
|
||||
|
@ -61,6 +93,11 @@ class EpisodeLoader {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable list of videos when the [episode] is from local source.
|
||||
*
|
||||
* @param episode the episode being parsed.
|
||||
*/
|
||||
private fun isLocal(
|
||||
episode: Episode,
|
||||
): Observable<List<Video>> {
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.ui.player
|
||||
package eu.kanade.tachiyomi.ui.player.settings
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -7,10 +7,13 @@ import androidx.core.view.isVisible
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.PlayerOptionsSheetBinding
|
||||
import eu.kanade.tachiyomi.ui.player.PlayerActivity
|
||||
import eu.kanade.tachiyomi.widget.sheet.PlayerBottomSheetDialog
|
||||
|
||||
/**
|
||||
* Sheet to show when overflow button in player is clicked.
|
||||
*
|
||||
* @param activity the instance of the PlayerActivity in use.
|
||||
*/
|
||||
class PlayerOptionsSheet(
|
||||
private val activity: PlayerActivity,
|
||||
|
@ -27,15 +30,19 @@ class PlayerOptionsSheet(
|
|||
binding.setAsCover.setOnClickListener { setAsCover(); this.dismiss() }
|
||||
binding.share.setOnClickListener { share(); this.dismiss() }
|
||||
binding.save.setOnClickListener { save(); this.dismiss() }
|
||||
|
||||
binding.toggleSubs.isChecked = activity.screenshotSubs
|
||||
binding.toggleSubs.setOnCheckedChangeListener { _, newValue -> activity.screenshotSubs = newValue }
|
||||
|
||||
binding.toggleVolumeBrightnessGestures.isChecked = activity.gestureVolumeBrightness
|
||||
binding.toggleVolumeBrightnessGestures.setOnCheckedChangeListener { _, newValue -> activity.gestureVolumeBrightness = newValue }
|
||||
binding.toggleHorizontalSeekGesture.isChecked = activity.gestureHorizontalSeek
|
||||
binding.toggleHorizontalSeekGesture.setOnCheckedChangeListener { _, newValue -> activity.gestureHorizontalSeek = newValue }
|
||||
|
||||
binding.toggleStats.isChecked = activity.stats
|
||||
binding.statsPage.isVisible = activity.stats
|
||||
binding.toggleStats.setOnCheckedChangeListener(toggleStats)
|
||||
|
||||
binding.statsPage.isVisible = activity.stats
|
||||
binding.statsPage.setSelection(activity.statsPage)
|
||||
binding.statsPage.onItemSelectedListener = setStatsPage
|
||||
|
|
@ -1,17 +1,26 @@
|
|||
package eu.kanade.tachiyomi.ui.player
|
||||
package eu.kanade.tachiyomi.ui.player.settings
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.animesource.model.Track
|
||||
import eu.kanade.tachiyomi.databinding.PlayerTracksItemBinding
|
||||
import eu.kanade.tachiyomi.databinding.PlayerTracksSheetBinding
|
||||
import eu.kanade.tachiyomi.ui.player.PlayerActivity
|
||||
import eu.kanade.tachiyomi.widget.sheet.PlayerBottomSheetDialog
|
||||
|
||||
/**
|
||||
* Sheet to show when track selection buttons in player are clicked.
|
||||
*
|
||||
* @param activity the instance of the PlayerActivity in use.
|
||||
* @param textRes the header text of the sheet
|
||||
* @param changeTrackMethod the method to run on changing tracks
|
||||
* @param tracks the given array of tracks
|
||||
* @param preselectedTrack the index of the current selected track
|
||||
* @param trackSettings the method to run on clicking the settings button, null if no button
|
||||
*/
|
||||
class PlayerTracksSheet(
|
||||
private val activity: PlayerActivity,
|
||||
|
@ -19,6 +28,7 @@ class PlayerTracksSheet(
|
|||
private val changeTrackMethod: (Int) -> Unit,
|
||||
private val tracks: Array<Track>,
|
||||
private val preselectedTrack: Int,
|
||||
private val trackSettings: (() -> Unit)?,
|
||||
) : PlayerBottomSheetDialog(activity) {
|
||||
|
||||
private lateinit var binding: PlayerTracksSheetBinding
|
||||
|
@ -29,6 +39,11 @@ class PlayerTracksSheet(
|
|||
activity.player.paused = true
|
||||
binding = PlayerTracksSheetBinding.inflate(activity.layoutInflater, null, false)
|
||||
|
||||
if (trackSettings != null) {
|
||||
binding.trackSettingsButton.isVisible = true
|
||||
binding.trackSettingsButton.setOnClickListener { trackSettings.invoke() }
|
||||
}
|
||||
|
||||
binding.trackSelectionHeader.setText(textRes)
|
||||
tracks.forEachIndexed { i, track ->
|
||||
val trackView = PlayerTracksItemBinding.inflate(activity.layoutInflater).root
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.ui.player
|
||||
package eu.kanade.tachiyomi.ui.player.viewer
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.ui.player
|
||||
package eu.kanade.tachiyomi.ui.player.viewer
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ValueAnimator
|
||||
|
@ -6,11 +6,13 @@ import android.content.Context
|
|||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.PlayerDoubleTapSeekViewBinding
|
||||
|
||||
/**
|
||||
* View that shows the arrows animation when double tapping to seek
|
||||
*/
|
||||
class DoubleTapSecondsView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
|
||||
|
||||
var binding: PlayerDoubleTapSeekViewBinding
|
||||
|
@ -45,9 +47,6 @@ class DoubleTapSecondsView(context: Context, attrs: AttributeSet?) : LinearLayou
|
|||
field = value
|
||||
}
|
||||
|
||||
val textView: TextView
|
||||
get() = binding.doubleTapSeconds
|
||||
|
||||
@DrawableRes
|
||||
var icon: Int = R.drawable.ic_play_seek_triangle
|
||||
set(value) {
|
|
@ -1,9 +1,10 @@
|
|||
package eu.kanade.tachiyomi.ui.player
|
||||
package eu.kanade.tachiyomi.ui.player.viewer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import eu.kanade.tachiyomi.ui.player.PlayerActivity
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.math.abs
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.ui.player
|
||||
package eu.kanade.tachiyomi.ui.player.viewer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
|
@ -17,6 +17,7 @@ import androidx.core.view.isVisible
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.PlayerControlsBinding
|
||||
import eu.kanade.tachiyomi.databinding.PrefSkipIntroLengthBinding
|
||||
import eu.kanade.tachiyomi.ui.player.PlayerActivity
|
||||
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
|
||||
import `is`.xyz.mpv.MPVLib
|
||||
import `is`.xyz.mpv.PickerDialog
|
||||
|
@ -183,6 +184,18 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr
|
|||
binding.toggleAutoplay.setOnCheckedChangeListener { _, isChecked ->
|
||||
activity.toggleAutoplay(isChecked)
|
||||
}
|
||||
|
||||
binding.titleMainTxt.setOnClickListener {
|
||||
episodeListDialog()
|
||||
}
|
||||
|
||||
binding.titleSecondaryTxt.setOnClickListener {
|
||||
episodeListDialog()
|
||||
}
|
||||
|
||||
binding.episodeListBtn.setOnClickListener {
|
||||
episodeListDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private val animationHandler = Handler(Looper.getMainLooper())
|
||||
|
@ -454,4 +467,7 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr
|
|||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun episodeListDialog() {
|
||||
}
|
||||
}
|
9
app/src/main/res/drawable/ic_navigate_next_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_navigate_next_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" />
|
||||
</vector>
|
|
@ -1,6 +1,6 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="72dp"
|
||||
android:width="72dp"
|
||||
android:height="64dp"
|
||||
android:width="64dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24" >
|
|
@ -1,6 +1,6 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="72dp"
|
||||
android:width="72dp"
|
||||
android:height="64dp"
|
||||
android:width="64dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24" >
|
|
@ -1,6 +1,6 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="50dp"
|
||||
android:height="50dp"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
|
@ -1,6 +1,6 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="50dp"
|
||||
android:height="50dp"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
|
@ -9,7 +9,7 @@
|
|||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
tools:context=".ui.player.PlayerActivity"
|
||||
tools:ignore="RtlHardcoded,HardcodedText" >
|
||||
tools:ignore="RtlHardcoded,HardcodedText,ContentDescription" >
|
||||
|
||||
<is.xyz.mpv.MPVView
|
||||
android:id="@+id/player"
|
||||
|
@ -20,7 +20,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/rew_tap"
|
||||
|
@ -85,7 +86,7 @@
|
|||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@id/mid_bg" />
|
||||
|
||||
<eu.kanade.tachiyomi.ui.player.PlayerControlsView
|
||||
<eu.kanade.tachiyomi.ui.player.viewer.PlayerControlsView
|
||||
android:id="@+id/player_controls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
@ -121,14 +122,14 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/playPauseView"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:contentDescription="Play/Pause"
|
||||
android:background="@drawable/ic_play_pause_bg"
|
||||
android:visibility="gone"
|
||||
app:tint="?attr/colorOnPrimarySurface"
|
||||
tools:src="@drawable/ic_play_arrow_72dp"
|
||||
tools:src="@drawable/ic_play_arrow_64dp"
|
||||
tools:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
|
@ -137,12 +138,12 @@
|
|||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/loading_indicator"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
app:indicatorColor="?attr/colorPrimary"
|
||||
app:indicatorSize="65dp"
|
||||
app:indicatorSize="64dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
|
@ -158,7 +159,7 @@
|
|||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<eu.kanade.tachiyomi.ui.player.DoubleTapSecondsView
|
||||
<eu.kanade.tachiyomi.ui.player.viewer.DoubleTapSecondsView
|
||||
android:id="@+id/seconds_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -68,23 +68,41 @@
|
|||
android:textColor="?attr/colorOnPrimarySurface"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintLeft_toRightOf="@id/backArrowBtn"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@id/episodeListBtn"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/titleSecondaryTxt"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:alpha="0.5"
|
||||
android:text=""
|
||||
android:textColor="?attr/colorOnPrimarySurface"
|
||||
android:textSize="12sp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintLeft_toRightOf="@id/backArrowBtn"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@id/episodeListBtn"
|
||||
app:layout_constraintTop_toBottomOf="@id/titleMainTxt" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/episodeListBtn"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="Episode list"
|
||||
android:src="@drawable/ic_navigate_next_24dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@id/titleMainTxt"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:tint="?attr/colorOnPrimarySurface" />
|
||||
|
||||
<!-- Top Controls (Left)-->
|
||||
|
||||
<ImageButton
|
||||
|
@ -99,7 +117,7 @@
|
|||
android:src="@drawable/ic_overflow_24dp"
|
||||
app:layout_constraintLeft_toRightOf="@id/qualityBtn"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/backArrowBtn"
|
||||
app:layout_constraintTop_toBottomOf="@id/episodeListBtn"
|
||||
app:tint="?attr/colorOnPrimarySurface" />
|
||||
|
||||
<ImageButton
|
||||
|
@ -199,9 +217,9 @@
|
|||
|
||||
<ImageButton
|
||||
android:id="@+id/prevBtn"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginRight="255dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginRight="256dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_previous_episode"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
|
@ -209,13 +227,13 @@
|
|||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_skip_previous_50dp"
|
||||
app:srcCompat="@drawable/ic_skip_previous_40dp"
|
||||
app:tint="?attr/colorOnPrimarySurface" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/play_btn"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Play/Pause"
|
||||
android:onClick="playPause"
|
||||
|
@ -226,14 +244,14 @@
|
|||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?attr/colorOnPrimarySurface"
|
||||
tools:src="@drawable/ic_play_arrow_72dp"
|
||||
tools:src="@drawable/ic_play_arrow_64dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/nextBtn"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginLeft="255dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="256dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_next_episode"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
|
@ -241,7 +259,7 @@
|
|||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_skip_next_50dp"
|
||||
app:srcCompat="@drawable/ic_skip_next_40dp"
|
||||
app:tint="?attr/colorOnPrimarySurface" />
|
||||
|
||||
<TextView
|
||||
|
@ -375,7 +393,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent" />
|
||||
|
||||
<eu.kanade.tachiyomi.ui.player.CustomSeekBar
|
||||
<eu.kanade.tachiyomi.ui.player.viewer.CustomSeekBar
|
||||
android:id="@+id/playbackSeekbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/optionsScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_layout"
|
||||
|
@ -13,16 +12,40 @@
|
|||
android:orientation="vertical"
|
||||
android:layout_marginBottom="32dp" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/track_selection_header"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
tools:text="@string/quality_dialog_header"
|
||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader" />
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginVertical="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/track_selection_header"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="16dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
tools:text="@string/quality_dialog_header"
|
||||
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/track_settings_button"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/track_settings_button"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_settings_24dp"
|
||||
android:contentDescription="@string/settings"
|
||||
app:tint="?attr/colorOnSurface"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
Loading…
Reference in a new issue