feat(media): add detailed metadata for file on MediaInfoFragment
(#246)
* feat: add video file metadata on `MediaInfoFragment` * feat(metadata): add chips within a chipgroup to showcase major parameters Set a "temp" text as default for chips since without it, the style resets when text is changed through code (kind of a hacky fix) * feat(parser): implement data model for VideoMetadata and parse function * feat(metadata): show dolby/dts audio codecs and hide SDR display profile * feat(dolby): add a dolby logo after the rating and per-theme color * feat(settings): add a preference switch for showing detailed A/V & Subs info * feat: add dolby logo for video and audio profile inside chip * feat: handle different audio profiles and change raw names * feat(audio): add atmos text with the audio codec itself * feat: only parse metadata when item is a movie Also correct spacing when there are no chips * fix(metadata): check for DoVi title since codec shows as HDR10 * fixup!: parsing of audio codecs and display name --------- Co-authored-by: Jarne Demeulemeester <jarnedemeulemeester@gmail.com>
This commit is contained in:
parent
b74c313a4e
commit
a6570d8a02
13 changed files with 686 additions and 39 deletions
|
@ -17,6 +17,7 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.jdtech.jellyfin.AppPreferences
|
||||
import dev.jdtech.jellyfin.R
|
||||
import dev.jdtech.jellyfin.adapters.PersonListAdapter
|
||||
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
|
||||
|
@ -25,6 +26,8 @@ import dev.jdtech.jellyfin.bindItemBackdropImage
|
|||
import dev.jdtech.jellyfin.databinding.FragmentMediaInfoBinding
|
||||
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
|
||||
import dev.jdtech.jellyfin.dialogs.VideoVersionDialogFragment
|
||||
import dev.jdtech.jellyfin.models.AudioCodec
|
||||
import dev.jdtech.jellyfin.models.DisplayProfile
|
||||
import dev.jdtech.jellyfin.models.PlayerItem
|
||||
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
|
||||
import dev.jdtech.jellyfin.utils.setTintColor
|
||||
|
@ -36,6 +39,7 @@ import kotlinx.coroutines.launch
|
|||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MediaInfoFragment : Fragment() {
|
||||
|
@ -47,6 +51,9 @@ class MediaInfoFragment : Fragment() {
|
|||
|
||||
lateinit var errorDialog: ErrorDialogFragment
|
||||
|
||||
@Inject
|
||||
lateinit var appPreferences: AppPreferences
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -157,8 +164,12 @@ class MediaInfoFragment : Fragment() {
|
|||
when (viewModel.played) {
|
||||
true -> {
|
||||
viewModel.markAsUnplayed(args.itemId)
|
||||
binding.checkButton.setTintColorAttribute(R.attr.colorOnSecondaryContainer, requireActivity().theme)
|
||||
binding.checkButton.setTintColorAttribute(
|
||||
R.attr.colorOnSecondaryContainer,
|
||||
requireActivity().theme
|
||||
)
|
||||
}
|
||||
|
||||
false -> {
|
||||
viewModel.markAsPlayed(args.itemId)
|
||||
binding.checkButton.setTintColor(R.color.red, requireActivity().theme)
|
||||
|
@ -171,8 +182,12 @@ class MediaInfoFragment : Fragment() {
|
|||
true -> {
|
||||
viewModel.unmarkAsFavorite(args.itemId)
|
||||
binding.favoriteButton.setImageResource(R.drawable.ic_heart)
|
||||
binding.favoriteButton.setTintColorAttribute(R.attr.colorOnSecondaryContainer, requireActivity().theme)
|
||||
binding.favoriteButton.setTintColorAttribute(
|
||||
R.attr.colorOnSecondaryContainer,
|
||||
requireActivity().theme
|
||||
)
|
||||
}
|
||||
|
||||
false -> {
|
||||
viewModel.markAsFavorite(args.itemId)
|
||||
binding.favoriteButton.setImageResource(R.drawable.ic_heart_filled)
|
||||
|
@ -225,7 +240,10 @@ class MediaInfoFragment : Fragment() {
|
|||
// Check icon
|
||||
when (played) {
|
||||
true -> binding.checkButton.setTintColor(R.color.red, requireActivity().theme)
|
||||
false -> binding.checkButton.setTintColorAttribute(R.attr.colorOnSecondaryContainer, requireActivity().theme)
|
||||
false -> binding.checkButton.setTintColorAttribute(
|
||||
R.attr.colorOnSecondaryContainer,
|
||||
requireActivity().theme
|
||||
)
|
||||
}
|
||||
|
||||
// Favorite icon
|
||||
|
@ -241,8 +259,12 @@ class MediaInfoFragment : Fragment() {
|
|||
binding.downloadButton.isVisible = true
|
||||
binding.downloadButton.isEnabled = !downloaded
|
||||
|
||||
if (downloaded) binding.downloadButton.setTintColor(R.color.red, requireActivity().theme)
|
||||
if (downloaded) binding.downloadButton.setTintColor(
|
||||
R.color.red,
|
||||
requireActivity().theme
|
||||
)
|
||||
}
|
||||
|
||||
false -> {
|
||||
binding.downloadButton.isVisible = false
|
||||
}
|
||||
|
@ -264,6 +286,68 @@ class MediaInfoFragment : Fragment() {
|
|||
binding.communityRating.text = item.communityRating.toString()
|
||||
binding.genresLayout.isVisible = item.genres?.isNotEmpty() ?: false
|
||||
binding.genres.text = genresString
|
||||
binding.videoMeta.text = videoString
|
||||
binding.audio.text = audioString
|
||||
binding.subtitles.text = subtitleString
|
||||
binding.subsChip.isVisible = subtitleString.isNotEmpty()
|
||||
|
||||
if (appPreferences.displayExtraInfo) {
|
||||
binding.subtitlesLayout.isVisible = subtitleString.isNotEmpty()
|
||||
binding.videoMetaLayout.isVisible = videoString.isNotEmpty()
|
||||
binding.audioLayout.isVisible = audioString.isNotEmpty()
|
||||
}
|
||||
|
||||
videoMetadata?.let {
|
||||
with(binding) {
|
||||
videoMetaChips.isVisible = true
|
||||
audioChannelChip.text = it.audioChannels.firstOrNull()?.raw
|
||||
resChip.text = it.resolution.firstOrNull()?.raw
|
||||
audioChannelChip.isVisible = it.audioChannels.isNotEmpty()
|
||||
resChip.isVisible = it.resolution.isNotEmpty()
|
||||
|
||||
it.displayProfiles.firstOrNull()?.apply {
|
||||
videoProfileChip.text = this.raw
|
||||
videoProfileChip.isVisible = when (this) {
|
||||
DisplayProfile.HDR,
|
||||
DisplayProfile.HDR10,
|
||||
DisplayProfile.HLG -> {
|
||||
videoProfileChip.chipStartPadding = .0f
|
||||
true
|
||||
}
|
||||
|
||||
DisplayProfile.DOLBY_VISION -> {
|
||||
videoProfileChip.isChipIconVisible = true
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
audioCodecChip.text = when (val codec = it.audioCodecs.firstOrNull()) {
|
||||
AudioCodec.AC3, AudioCodec.EAC3, AudioCodec.TRUEHD -> {
|
||||
audioCodecChip.isVisible = true
|
||||
if (it.isAtmos.firstOrNull() == true) {
|
||||
"${codec.raw} | Atmos"
|
||||
} else codec.raw
|
||||
}
|
||||
|
||||
AudioCodec.DTS -> {
|
||||
audioCodecChip.apply {
|
||||
isVisible = true
|
||||
isChipIconVisible = false
|
||||
chipStartPadding = .0f
|
||||
}
|
||||
codec.raw
|
||||
}
|
||||
|
||||
else -> {
|
||||
audioCodecChip.isVisible = false
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.directorLayout.isVisible = director != null
|
||||
binding.director.text = director?.name
|
||||
binding.writersLayout.isVisible = writers.isNotEmpty()
|
||||
|
@ -324,7 +408,8 @@ class MediaInfoFragment : Fragment() {
|
|||
)
|
||||
binding.progressCircular.visibility = View.INVISIBLE
|
||||
binding.playerItemsErrorDetails.setOnClickListener {
|
||||
ErrorDialogFragment.newInstance(error.error).show(parentFragmentManager, ErrorDialogFragment.TAG)
|
||||
ErrorDialogFragment.newInstance(error.error)
|
||||
.show(parentFragmentManager, ErrorDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,15 +72,14 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
android:layout_marginHorizontal="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/year"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="2019" />
|
||||
|
||||
|
@ -89,6 +88,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="122 min" />
|
||||
|
||||
|
@ -97,6 +97,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="PG-13" />
|
||||
|
||||
|
@ -104,19 +105,93 @@
|
|||
android:id="@+id/community_rating"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:drawableStartCompat="@drawable/ic_star"
|
||||
app:drawableTint="@color/yellow"
|
||||
tools:text="7.3" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/video_meta_chips"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:visibility="gone"
|
||||
app:singleLine="true"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/res_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/temp"
|
||||
android:visibility="gone"
|
||||
tools:text="4K"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/video_profile_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/temp"
|
||||
android:visibility="gone"
|
||||
app:chipIcon="@drawable/ic_dolby"
|
||||
app:chipIconSize="0dp"
|
||||
app:chipIconTint="?attr/colorOnPrimarySurface"
|
||||
app:chipIconVisible="false"
|
||||
app:chipStartPadding="8dp"
|
||||
tools:text="Vision"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/audio_codec_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/temp"
|
||||
android:visibility="gone"
|
||||
app:chipIcon="@drawable/ic_dolby"
|
||||
app:chipIconSize="0dp"
|
||||
app:chipIconTint="?attr/colorOnBackground"
|
||||
app:chipIconVisible="true"
|
||||
app:chipStartPadding="8dp"
|
||||
tools:text="ATMOS"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/audio_channel_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/temp"
|
||||
android:visibility="gone"
|
||||
tools:text="5.1"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/subs_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/subtitle_chip_text"
|
||||
android:visibility="gone"
|
||||
tools:text="CC"
|
||||
tools:visibility="visible" />
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
android:layout_marginVertical="15dp"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
|
@ -253,6 +328,102 @@
|
|||
android:layout_marginBottom="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/video_meta_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_meta_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/video"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_meta"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="4K HEVC HDR" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/audio_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/audio_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/audio"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/audio"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="AC-3 Eng 5.1, AC-3 iTA 5.1" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/subtitles_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitles_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/subtitle"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitles"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="English - SUBRIP, SDH - English - SUBRIP" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="An angel falls. A warrior rises. When Alita awakens with no memory of who she is in a future world she does not recognize, she is taken in by Ido, a compassionate doctor who realizes that somewhere in this abandoned cyborg shell is the heart and soul of a young woman with an extraordinary past." />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/genres_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -336,15 +507,6 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="An angel falls. A warrior rises. When Alita awakens with no memory of who she is in a future world she does not recognize, she is taken in by Ido, a compassionate doctor who realizes that somewhere in this abandoned cyborg shell is the heart and soul of a young woman with an extraordinary past." />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -72,15 +72,14 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
android:layout_marginHorizontal="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/year"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="2019" />
|
||||
|
||||
|
@ -89,6 +88,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="122 min" />
|
||||
|
||||
|
@ -97,6 +97,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="PG-13" />
|
||||
|
||||
|
@ -104,19 +105,93 @@
|
|||
android:id="@+id/community_rating"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:drawableStartCompat="@drawable/ic_star"
|
||||
app:drawableTint="@color/yellow"
|
||||
tools:text="7.3" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/video_meta_chips"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:visibility="gone"
|
||||
app:singleLine="true"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/res_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/temp"
|
||||
android:visibility="gone"
|
||||
tools:text="4K"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/video_profile_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/temp"
|
||||
android:visibility="gone"
|
||||
app:chipIcon="@drawable/ic_dolby"
|
||||
app:chipIconSize="0dp"
|
||||
app:chipIconTint="?attr/colorOnPrimarySurface"
|
||||
app:chipIconVisible="false"
|
||||
app:chipStartPadding="8dp"
|
||||
tools:text="Vision"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/audio_codec_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/temp"
|
||||
android:visibility="gone"
|
||||
app:chipIcon="@drawable/ic_dolby"
|
||||
app:chipIconSize="0dp"
|
||||
app:chipIconTint="?attr/colorOnBackground"
|
||||
app:chipIconVisible="true"
|
||||
app:chipStartPadding="8dp"
|
||||
tools:text="ATMOS"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/audio_channel_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/temp"
|
||||
android:visibility="gone"
|
||||
tools:text="5.1"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/subs_chip"
|
||||
style="@style/MetaChip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/subtitle_chip_text"
|
||||
android:visibility="gone"
|
||||
tools:text="CC"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="24dp">
|
||||
android:layout_marginVertical="15dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -233,7 +308,6 @@
|
|||
android:textColor="?attr/colorError" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/info"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -242,6 +316,102 @@
|
|||
android:layout_marginBottom="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/video_meta_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_meta_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/video"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/video_meta"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="4K HEVC HDR" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/audio_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/audio_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/audio"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/audio"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="AC-3 Eng 5.1, AC-3 iTA 5.1" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/subtitles_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitles_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/subtitle"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitles"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="English - SUBRIP, SDH - English - SUBRIP" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="An angel falls. A warrior rises. When Alita awakens with no memory of who she is in a future world she does not recognize, she is taken in by Ido, a compassionate doctor who realizes that somewhere in this abandoned cyborg shell is the heart and soul of a young woman with an extraordinary past." />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/genres_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -325,15 +495,6 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
tools:text="An angel falls. A warrior rises. When Alita awakens with no memory of who she is in a future world she does not recognize, she is taken in by Ido, a compassionate doctor who realizes that somewhere in this abandoned cyborg shell is the heart and soul of a young woman with an extraordinary past." />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/next_up_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -5,7 +5,12 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.jdtech.jellyfin.database.DownloadDatabaseDao
|
||||
import dev.jdtech.jellyfin.models.AudioChannel
|
||||
import dev.jdtech.jellyfin.models.AudioCodec
|
||||
import dev.jdtech.jellyfin.models.DisplayProfile
|
||||
import dev.jdtech.jellyfin.models.PlayerItem
|
||||
import dev.jdtech.jellyfin.models.Resolution
|
||||
import dev.jdtech.jellyfin.models.VideoMetadata
|
||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||
import dev.jdtech.jellyfin.utils.canRetryDownload
|
||||
import dev.jdtech.jellyfin.utils.deleteDownloadedEpisode
|
||||
|
@ -21,10 +26,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.exception.ApiClientException
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.BaseItemPerson
|
||||
import org.jellyfin.sdk.model.api.PlayAccess
|
||||
import org.jellyfin.sdk.model.api.*
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltViewModel
|
||||
|
@ -44,8 +46,12 @@ constructor(
|
|||
val actors: List<BaseItemPerson>,
|
||||
val director: BaseItemPerson?,
|
||||
val writers: List<BaseItemPerson>,
|
||||
val videoMetadata: VideoMetadata?,
|
||||
val writersString: String,
|
||||
val genresString: String,
|
||||
val videoString: String,
|
||||
val audioString: String,
|
||||
val subtitleString: String,
|
||||
val runTime: String,
|
||||
val dateString: String,
|
||||
val nextUp: BaseItemDto?,
|
||||
|
@ -67,8 +73,12 @@ constructor(
|
|||
private var actors: List<BaseItemPerson> = emptyList()
|
||||
private var director: BaseItemPerson? = null
|
||||
private var writers: List<BaseItemPerson> = emptyList()
|
||||
private var videoMetadata: VideoMetadata? = null
|
||||
private var writersString: String = ""
|
||||
private var genresString: String = ""
|
||||
private var videoString: String = ""
|
||||
private var audioString: String = ""
|
||||
private var subtitleString: String = ""
|
||||
private var runTime: String = ""
|
||||
private var dateString: String = ""
|
||||
var nextUp: BaseItemDto? = null
|
||||
|
@ -93,7 +103,12 @@ constructor(
|
|||
director = getDirector(tempItem)
|
||||
writers = getWriters(tempItem)
|
||||
writersString = writers.joinToString(separator = ", ") { it.name.toString() }
|
||||
videoMetadata =
|
||||
if (tempItem.type == BaseItemKind.MOVIE) parseVideoMetadata(tempItem) else null
|
||||
genresString = tempItem.genres?.joinToString(separator = ", ") ?: ""
|
||||
videoString = getMediaString(tempItem, MediaStreamType.VIDEO)
|
||||
audioString = getMediaString(tempItem, MediaStreamType.AUDIO)
|
||||
subtitleString = getMediaString(tempItem, MediaStreamType.SUBTITLE)
|
||||
runTime = "${tempItem.runTimeTicks?.div(600000000)} min"
|
||||
dateString = getDateString(tempItem)
|
||||
played = tempItem.userData?.played ?: false
|
||||
|
@ -111,8 +126,12 @@ constructor(
|
|||
actors,
|
||||
director,
|
||||
writers,
|
||||
videoMetadata,
|
||||
writersString,
|
||||
genresString,
|
||||
videoString,
|
||||
audioString,
|
||||
subtitleString,
|
||||
runTime,
|
||||
dateString,
|
||||
nextUp,
|
||||
|
@ -141,7 +160,12 @@ constructor(
|
|||
director = getDirector(tempItem)
|
||||
writers = getWriters(tempItem)
|
||||
writersString = writers.joinToString(separator = ", ") { it.name.toString() }
|
||||
videoMetadata =
|
||||
if (tempItem.type == BaseItemKind.MOVIE) parseVideoMetadata(tempItem) else null
|
||||
genresString = tempItem.genres?.joinToString(separator = ", ") ?: ""
|
||||
videoString = getMediaString(tempItem, MediaStreamType.VIDEO)
|
||||
audioString = getMediaString(tempItem, MediaStreamType.AUDIO)
|
||||
subtitleString = getMediaString(tempItem, MediaStreamType.SUBTITLE)
|
||||
runTime = ""
|
||||
dateString = ""
|
||||
played = tempItem.userData?.played ?: false
|
||||
|
@ -154,8 +178,12 @@ constructor(
|
|||
actors,
|
||||
director,
|
||||
writers,
|
||||
videoMetadata,
|
||||
writersString,
|
||||
genresString,
|
||||
videoString,
|
||||
audioString,
|
||||
subtitleString,
|
||||
runTime,
|
||||
dateString,
|
||||
nextUp,
|
||||
|
@ -196,6 +224,119 @@ constructor(
|
|||
return writers
|
||||
}
|
||||
|
||||
private suspend fun getMediaString(item: BaseItemDto, type: MediaStreamType): String {
|
||||
val streams: List<MediaStream>
|
||||
withContext(Dispatchers.Default) {
|
||||
streams = item.mediaStreams?.filter { it.type == type } ?: emptyList()
|
||||
}
|
||||
return streams.map { it.displayTitle }.joinToString(separator = ", ")
|
||||
}
|
||||
|
||||
private suspend fun parseVideoMetadata(item: BaseItemDto): VideoMetadata {
|
||||
val resolution = mutableListOf<Resolution>()
|
||||
val audioChannels = mutableListOf<AudioChannel>()
|
||||
val displayProfile = mutableListOf<DisplayProfile>()
|
||||
val audioCodecs = mutableListOf<AudioCodec>()
|
||||
val isAtmosAudio = mutableListOf<Boolean>()
|
||||
|
||||
withContext(Dispatchers.Default) {
|
||||
item.mediaStreams?.filter { stream ->
|
||||
when (stream.type) {
|
||||
MediaStreamType.AUDIO -> {
|
||||
/**
|
||||
* Match audio profile from [MediaStream.channelLayout]
|
||||
*/
|
||||
audioChannels.add(
|
||||
when (stream.channelLayout) {
|
||||
AudioChannel.CH_2_1.raw -> AudioChannel.CH_2_1
|
||||
AudioChannel.CH_5_1.raw -> AudioChannel.CH_5_1
|
||||
AudioChannel.CH_7_1.raw -> AudioChannel.CH_7_1
|
||||
else -> AudioChannel.CH_2_0
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Match [MediaStream.displayTitle] for Dolby Atmos
|
||||
*/
|
||||
stream.displayTitle?.apply {
|
||||
isAtmosAudio.add(contains("ATMOS", true))
|
||||
}
|
||||
|
||||
/**
|
||||
* Match audio codec from [MediaStream.codec]
|
||||
*/
|
||||
audioCodecs.add(
|
||||
when (stream.codec?.lowercase()) {
|
||||
AudioCodec.FLAC.toString() -> AudioCodec.FLAC
|
||||
AudioCodec.AAC.toString() -> AudioCodec.AAC
|
||||
AudioCodec.AC3.toString() -> AudioCodec.AC3
|
||||
AudioCodec.EAC3.toString() -> AudioCodec.EAC3
|
||||
AudioCodec.VORBIS.toString() -> AudioCodec.VORBIS
|
||||
AudioCodec.OPUS.toString() -> AudioCodec.OPUS
|
||||
AudioCodec.TRUEHD.toString() -> AudioCodec.TRUEHD
|
||||
AudioCodec.DTS.toString() -> AudioCodec.DTS
|
||||
else -> AudioCodec.MP3
|
||||
}
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
MediaStreamType.VIDEO -> {
|
||||
with(stream) {
|
||||
/**
|
||||
* Match dynamic range from [MediaStream.videoRangeType]
|
||||
*/
|
||||
displayProfile.add(
|
||||
/**
|
||||
* Since [MediaStream.videoRangeType] is [DisplayProfile.HDR10]
|
||||
* Check if [MediaStream.videoDoViTitle] is not null and return
|
||||
* [DisplayProfile.DOLBY_VISION] accordingly
|
||||
*/
|
||||
if (stream.videoDoViTitle != null) {
|
||||
DisplayProfile.DOLBY_VISION
|
||||
} else when (videoRangeType) {
|
||||
DisplayProfile.HDR.raw -> DisplayProfile.HDR
|
||||
DisplayProfile.HDR10.raw -> DisplayProfile.HDR10
|
||||
DisplayProfile.HLG.raw -> DisplayProfile.HLG
|
||||
else -> DisplayProfile.SDR
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Force stream [MediaStream.height] and [MediaStream.width] as not null
|
||||
* since we are inside [MediaStreamType.VIDEO] block
|
||||
*/
|
||||
resolution.add(
|
||||
when {
|
||||
height!! <= 1080 && width!! <= 1920 -> {
|
||||
Resolution.HD
|
||||
}
|
||||
|
||||
height!! <= 2160 && width!! <= 3840 -> {
|
||||
Resolution.UHD
|
||||
}
|
||||
|
||||
else -> Resolution.SD
|
||||
}
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VideoMetadata(
|
||||
resolution,
|
||||
displayProfile.toSet().toList(),
|
||||
audioChannels.toSet().toList(),
|
||||
audioCodecs.toSet().toList(),
|
||||
isAtmosAudio
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getNextUp(seriesId: UUID): BaseItemDto? {
|
||||
val nextUpItems = jellyfinRepository.getNextUp(seriesId)
|
||||
return if (nextUpItems.isNotEmpty()) {
|
||||
|
@ -256,6 +397,7 @@ constructor(
|
|||
"Continuing" -> {
|
||||
dateRange.add("Present")
|
||||
}
|
||||
|
||||
"Ended" -> {
|
||||
item.endDate?.let { dateRange.add(it.year.toString()) }
|
||||
}
|
||||
|
|
12
core/src/main/res/drawable/ic_dolby.xml
Normal file
12
core/src/main/res/drawable/ic_dolby.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="17dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="17"
|
||||
android:viewportHeight="12">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h17v12h-17z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M0,12.017H1.773C5.083,12.017 7.779,9.319 7.779,6.013C7.779,2.704 5.083,0.005 1.773,0.005H0V12.017ZM17.088,0.005H15.317C12.007,0.005 9.309,2.704 9.309,6.013C9.309,9.319 12.007,12.017 15.317,12.017H17.088V0.005Z" />
|
||||
</group>
|
||||
</vector>
|
|
@ -15,5 +15,8 @@
|
|||
<!-- Surface -->
|
||||
<item name="android:colorBackground">@color/neutral_1000</item>
|
||||
<item name="colorSurface">@color/neutral_900</item>
|
||||
|
||||
<!-- Extra -->
|
||||
<item name="dolbyColor">#FFF</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -153,4 +153,12 @@
|
|||
<string name="add_server_address">Add server address</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="quick_connect">Quick Connect</string>
|
||||
<string name="video">Video</string>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="subtitle">Subtitles</string>
|
||||
<string name="subtitle_chip_text">CC</string>
|
||||
<string name="temp">temp</string>
|
||||
<string name="dolby_logo_desc">Dolby Logo</string>
|
||||
<string name="extra_info">Display Extra Info.</string>
|
||||
<string name="extra_info_summary">Displays detailed information about Audio, Video and Subtitles</string>
|
||||
</resources>
|
|
@ -18,4 +18,17 @@
|
|||
<item name="android:widgetLayout">@layout/preference_material3_switch</item>
|
||||
</style>
|
||||
|
||||
<style name="MetaChip" parent="Widget.Material3.Chip.Assist">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Material3.LabelLarge</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="chipBackgroundColor">?attr/colorSecondaryContainer</item>
|
||||
<item name="chipCornerRadius">6dp</item>
|
||||
<item name="chipEndPadding">0dp</item>
|
||||
<item name="chipStartPadding">0dp</item>
|
||||
<item name="chipMinTouchTargetSize">20dp</item>
|
||||
<item name="chipStrokeWidth">0dp</item>
|
||||
</style>
|
||||
|
||||
<attr name="dolbyColor" format="reference|color" />
|
||||
|
||||
</resources>
|
|
@ -45,6 +45,7 @@
|
|||
<item name="alertDialogTheme">@style/ThemeOverlay.Material3.MaterialAlertDialog</item>
|
||||
<item name="dialogCornerRadius">28dp</item>
|
||||
<item name="preferenceTheme">@style/ThemeOverlay.Findroid.Preference</item>
|
||||
<item name="dolbyColor">#000</item>
|
||||
</style>
|
||||
|
||||
<string-array name="themes">
|
||||
|
|
|
@ -12,4 +12,9 @@
|
|||
app:key="dynamic_colors"
|
||||
app:summary="@string/dynamic_colors_summary"
|
||||
app:title="@string/dynamic_colors" />
|
||||
<SwitchPreferenceCompat
|
||||
app:defaultValue="false"
|
||||
app:key="pref_display_extra_info"
|
||||
app:summary="@string/extra_info_summary"
|
||||
app:title="@string/extra_info" />
|
||||
</PreferenceScreen>
|
|
@ -0,0 +1,46 @@
|
|||
@file:Suppress("Unused")
|
||||
|
||||
package dev.jdtech.jellyfin.models
|
||||
|
||||
data class VideoMetadata(
|
||||
val resolution: List<Resolution>,
|
||||
val displayProfiles: List<DisplayProfile>,
|
||||
val audioChannels: List<AudioChannel>,
|
||||
val audioCodecs: List<AudioCodec>,
|
||||
val isAtmos: List<Boolean>
|
||||
)
|
||||
|
||||
enum class Resolution(val raw: String) {
|
||||
SD("SD"),
|
||||
HD("HD"),
|
||||
UHD("4K");
|
||||
}
|
||||
|
||||
enum class DisplayProfile(val raw: String) {
|
||||
SDR("SDR"),
|
||||
HDR("HDR"),
|
||||
HDR10("HDR10"),
|
||||
DOLBY_VISION("Vision"),
|
||||
HLG("HLG");
|
||||
}
|
||||
|
||||
enum class AudioChannel(val raw: String) {
|
||||
CH_2_0("2.0"),
|
||||
CH_2_1("2.1"),
|
||||
CH_5_1("5.1"),
|
||||
CH_7_1("7.1");
|
||||
}
|
||||
|
||||
enum class AudioCodec(val raw: String) {
|
||||
FLAC("FLAC"),
|
||||
MP3("MP3"),
|
||||
AAC("AAC"),
|
||||
AC3("Digital"),
|
||||
EAC3("Digital+"),
|
||||
VORBIS("VORBIS"),
|
||||
DTS("DTS"),
|
||||
TRUEHD("TrueHD"),
|
||||
OPUS("OPUS");
|
||||
|
||||
override fun toString() = super.toString().lowercase()
|
||||
}
|
|
@ -24,6 +24,13 @@ constructor(
|
|||
// Appearance
|
||||
val theme get() = sharedPreferences.getString(Constants.PREF_THEME, null)
|
||||
val dynamicColors get() = sharedPreferences.getBoolean(Constants.PREF_DYNAMIC_COLORS, true)
|
||||
var displayExtraInfo: Boolean
|
||||
get() = sharedPreferences.getBoolean(Constants.PREF_DISPLAY_EXTRA_INFO, false)
|
||||
set(value) {
|
||||
sharedPreferences.edit {
|
||||
putBoolean(Constants.PREF_DISPLAY_EXTRA_INFO, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Player
|
||||
val displayExtendedTitle get() = sharedPreferences.getBoolean(Constants.PREF_DISPLAY_EXTENDED_TITLE, false)
|
||||
|
@ -126,4 +133,5 @@ constructor(
|
|||
putString(Constants.PREF_SORT_ORDER, value)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ object Constants {
|
|||
const val PREF_DOWNLOADS_ROAMING = "pref_downloads_roaming"
|
||||
const val PREF_SORT_BY = "pref_sort_by"
|
||||
const val PREF_SORT_ORDER = "pref_sort_order"
|
||||
const val PREF_DISPLAY_EXTRA_INFO = "pref_display_extra_info"
|
||||
|
||||
// caching
|
||||
const val DEFAULT_CACHE_SIZE = 20
|
||||
|
|
Loading…
Reference in a new issue