Glimpse: Rework media player
We now have fullscreen mode! Change-Id: Iad8b93145c79413473d20fa510e28ec608a44e38
This commit is contained in:
parent
74389a9725
commit
39c039e38d
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package androidx.media3.ui
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
fun PlayerControlView.updateAll() {
|
||||
updateAll()
|
||||
}
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
fun PlayerControlView.requestPlayPauseFocus() {
|
||||
requestPlayPauseFocus()
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.lineageos.glimpse.ext
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
|
||||
val Configuration.isNightMode
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
isNightModeActive
|
||||
} else when (uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
|
||||
Configuration.UI_MODE_NIGHT_UNDEFINED -> null
|
||||
Configuration.UI_MODE_NIGHT_NO -> false
|
||||
Configuration.UI_MODE_NIGHT_YES -> true
|
||||
else -> null
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.lineageos.glimpse.ext
|
||||
|
||||
import android.view.View
|
||||
import androidx.media3.ui.PlayerControlView
|
||||
import androidx.media3.ui.requestPlayPauseFocus
|
||||
import androidx.media3.ui.updateAll
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
fun PlayerControlView.fade(visible: Boolean) {
|
||||
(this as View).fade(visible)
|
||||
|
||||
// This is needed to resume progress updating
|
||||
if (visible) {
|
||||
updateAll()
|
||||
requestPlayPauseFocus()
|
||||
show()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.lineageos.glimpse.ext
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
|
||||
/**
|
||||
* Get the system's default short animation time.
|
||||
*/
|
||||
val View.shortAnimTime
|
||||
get() = resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||
|
||||
/**
|
||||
* Update the [View]'s visibility using a fade animation.
|
||||
* @param visible Whether the view should be visible or not.
|
||||
*/
|
||||
fun View.fade(visible: Boolean) {
|
||||
with(animate()) {
|
||||
cancel()
|
||||
|
||||
if (visible && !isVisible) {
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
alpha(
|
||||
when (visible) {
|
||||
true -> 1f
|
||||
false -> 0f
|
||||
}
|
||||
)
|
||||
|
||||
duration = shortAnimTime.toLong()
|
||||
|
||||
setListener(null)
|
||||
|
||||
withEndAction {
|
||||
isVisible = visible
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.lineageos.glimpse.ext
|
||||
|
||||
import android.view.Window
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
|
||||
private val Window.windowInsetsController
|
||||
get() = WindowInsetsControllerCompat(this, decorView)
|
||||
|
||||
var Window.isAppearanceLightStatusBars
|
||||
get() = windowInsetsController.isAppearanceLightStatusBars
|
||||
set(value) {
|
||||
windowInsetsController.isAppearanceLightStatusBars = value
|
||||
}
|
||||
|
||||
fun Window.resetStatusBarAppearance() {
|
||||
windowInsetsController.isAppearanceLightStatusBars =
|
||||
context.resources.configuration.isNightMode != true
|
||||
}
|
||||
|
||||
fun Window.setBarsVisibility(
|
||||
systemBars: Boolean? = null,
|
||||
statusBars: Boolean? = null,
|
||||
navigationBars: Boolean? = null,
|
||||
) {
|
||||
// Configure the behavior of the hidden bars
|
||||
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
||||
|
||||
val systemBarsType = WindowInsetsCompat.Type.systemBars()
|
||||
val statusBarsType = WindowInsetsCompat.Type.statusBars()
|
||||
val navigationBarsType = WindowInsetsCompat.Type.navigationBars()
|
||||
|
||||
// Set the system bars visibility
|
||||
systemBars?.let {
|
||||
when (it) {
|
||||
true -> windowInsetsController.show(systemBarsType)
|
||||
false -> windowInsetsController.hide(systemBarsType)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the status bars visibility
|
||||
statusBars?.let {
|
||||
when (it) {
|
||||
true -> windowInsetsController.show(statusBarsType)
|
||||
false -> windowInsetsController.hide(statusBarsType)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the navigation bars visibility
|
||||
navigationBars?.let {
|
||||
when (it) {
|
||||
true -> windowInsetsController.show(navigationBarsType)
|
||||
false -> windowInsetsController.hide(navigationBarsType)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,10 +7,11 @@ package org.lineageos.glimpse.fragments
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.View.MeasureSpec
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
|
@ -19,14 +20,16 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
|||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
|
@ -43,7 +46,7 @@ import org.lineageos.glimpse.models.MediaType
|
|||
import org.lineageos.glimpse.recyclerview.MediaViewerAdapter
|
||||
import org.lineageos.glimpse.ui.MediaInfoBottomSheetDialog
|
||||
import org.lineageos.glimpse.utils.PermissionsGatedCallback
|
||||
import org.lineageos.glimpse.viewmodels.MediaViewModel
|
||||
import org.lineageos.glimpse.viewmodels.MediaViewerViewModel
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
/**
|
||||
|
@ -51,9 +54,10 @@ import java.text.SimpleDateFormat
|
|||
* Use the [MediaViewerFragment.newInstance] factory method to
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
||||
// View models
|
||||
private val mediaViewModel: MediaViewModel by viewModels { MediaViewModel.Factory }
|
||||
private val mediaViewModel: MediaViewerViewModel by viewModels { MediaViewerViewModel.Factory }
|
||||
|
||||
// Views
|
||||
private val adjustButton by getViewProperty<ImageButton>(R.id.adjustButton)
|
||||
|
@ -81,9 +85,19 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
|||
}
|
||||
|
||||
// Player
|
||||
private val exoPlayerListener = object : Player.Listener {
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
super.onIsPlayingChanged(isPlaying)
|
||||
|
||||
view?.keepScreenOn = isPlaying
|
||||
}
|
||||
}
|
||||
|
||||
private val exoPlayerLazy = lazy {
|
||||
ExoPlayer.Builder(requireContext()).build().apply {
|
||||
repeatMode = ExoPlayer.REPEAT_MODE_ONE
|
||||
|
||||
addListener(exoPlayerListener)
|
||||
}
|
||||
}
|
||||
private val exoPlayer
|
||||
|
@ -95,7 +109,7 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
|||
|
||||
// Adapter
|
||||
private val mediaViewerAdapter by lazy {
|
||||
MediaViewerAdapter(exoPlayerLazy, mediaViewModel.mediaPositionLiveData)
|
||||
MediaViewerAdapter(exoPlayerLazy, mediaViewModel)
|
||||
}
|
||||
|
||||
// Arguments
|
||||
|
@ -208,6 +222,9 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
|||
super.onResume()
|
||||
|
||||
exoPlayer?.play()
|
||||
|
||||
// Force status bar icons to be light
|
||||
requireActivity().window.isAppearanceLightStatusBars = false
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -270,15 +287,22 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
|||
ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
topSheetConstraintLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
leftMargin = insets.left
|
||||
rightMargin = insets.right
|
||||
topMargin = insets.top
|
||||
}
|
||||
bottomSheetLinearLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = insets.bottom
|
||||
leftMargin = insets.left
|
||||
rightMargin = insets.right
|
||||
// Avoid updating the sheets height when they're hidden.
|
||||
// Once the system bars will be made visible again, this function
|
||||
// will be called again.
|
||||
if (mediaViewModel.fullscreenModeLiveData.value != true) {
|
||||
topSheetConstraintLayout.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
top = insets.top,
|
||||
)
|
||||
bottomSheetLinearLayout.updatePadding(
|
||||
bottom = insets.bottom,
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
|
||||
updateSheetsHeight()
|
||||
}
|
||||
|
||||
windowInsets
|
||||
|
@ -310,6 +334,22 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
|||
}
|
||||
}
|
||||
|
||||
view.findViewTreeLifecycleOwner()?.let {
|
||||
mediaViewModel.fullscreenModeLiveData.observe(it) { fullscreenMode ->
|
||||
topSheetConstraintLayout.fade(!fullscreenMode)
|
||||
bottomSheetLinearLayout.fade(!fullscreenMode)
|
||||
|
||||
requireActivity().window.setBarsVisibility(systemBars = !fullscreenMode)
|
||||
|
||||
// If the sheets are being made visible again, update the values
|
||||
if (!fullscreenMode) {
|
||||
updateSheetsHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSheetsHeight()
|
||||
|
||||
permissionsGatedCallback.runAfterPermissionsCheck()
|
||||
}
|
||||
|
||||
|
@ -325,6 +365,12 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
|||
super.onPause()
|
||||
|
||||
exoPlayer?.pause()
|
||||
|
||||
// Restore status bar icons appearance
|
||||
requireActivity().window.resetStatusBarAppearance()
|
||||
|
||||
// Restore system bars visibility
|
||||
requireActivity().window.setBarsVisibility(systemBars = true)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -333,6 +379,12 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
|||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
updateSheetsHeight()
|
||||
}
|
||||
|
||||
private fun initData(data: List<Media>) {
|
||||
mediaViewerAdapter.data = data.toTypedArray()
|
||||
viewPager.setCurrentItem(mediaViewModel.mediaPosition, false)
|
||||
|
@ -359,6 +411,16 @@ class MediaViewerFragment : Fragment(R.layout.fragment_media_viewer) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateSheetsHeight() {
|
||||
topSheetConstraintLayout.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
|
||||
bottomSheetLinearLayout.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
|
||||
|
||||
mediaViewModel.sheetsHeightLiveData.value = Pair(
|
||||
topSheetConstraintLayout.measuredHeight,
|
||||
bottomSheetLinearLayout.measuredHeight,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_ALBUM = "album"
|
||||
private const val KEY_MEDIA = "media"
|
||||
|
|
|
@ -10,19 +10,23 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.ui.PlayerControlView
|
||||
import androidx.media3.ui.PlayerView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.load
|
||||
import org.lineageos.glimpse.R
|
||||
import org.lineageos.glimpse.ext.fade
|
||||
import org.lineageos.glimpse.models.Media
|
||||
import org.lineageos.glimpse.models.MediaType
|
||||
import org.lineageos.glimpse.viewmodels.MediaViewerViewModel
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
class MediaViewerAdapter(
|
||||
private val exoPlayer: Lazy<ExoPlayer>,
|
||||
private val currentPositionLiveData: LiveData<Int>,
|
||||
private val mediaViewerViewModel: MediaViewerViewModel,
|
||||
) : RecyclerView.Adapter<MediaViewerAdapter.MediaViewHolder>() {
|
||||
var data: Array<Media> = arrayOf()
|
||||
set(value) {
|
||||
|
@ -47,7 +51,7 @@ class MediaViewerAdapter(
|
|||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MediaViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(R.layout.media_view, parent, false),
|
||||
exoPlayer, currentPositionLiveData,
|
||||
exoPlayer, mediaViewerViewModel
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {
|
||||
|
@ -69,25 +73,62 @@ class MediaViewerAdapter(
|
|||
class MediaViewHolder(
|
||||
private val view: View,
|
||||
private val exoPlayer: Lazy<ExoPlayer>,
|
||||
private val currentPositionLiveData: LiveData<Int>,
|
||||
private val mediaViewerViewModel: MediaViewerViewModel,
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
// Views
|
||||
private val imageView = view.findViewById<ImageView>(R.id.imageView)
|
||||
private val playerControlView = view.findViewById<PlayerControlView>(R.id.exo_controller)
|
||||
private val playerView = view.findViewById<PlayerView>(R.id.playerView)
|
||||
|
||||
private lateinit var media: Media
|
||||
private var position = -1
|
||||
|
||||
private val observer = { currentPosition: Int ->
|
||||
private val mediaPositionObserver = { currentPosition: Int ->
|
||||
val isNowVideoPlayer = currentPosition == position && media.mediaType == MediaType.VIDEO
|
||||
|
||||
imageView.isVisible = !isNowVideoPlayer
|
||||
playerView.isVisible = isNowVideoPlayer
|
||||
|
||||
playerView.player = when (isNowVideoPlayer) {
|
||||
if (!isNowVideoPlayer || mediaViewerViewModel.fullscreenModeLiveData.value == true) {
|
||||
playerControlView.hideImmediately()
|
||||
} else {
|
||||
playerControlView.show()
|
||||
}
|
||||
|
||||
val player = when (isNowVideoPlayer) {
|
||||
true -> exoPlayer.value
|
||||
false -> null
|
||||
}
|
||||
|
||||
playerView.player = player
|
||||
playerControlView.player = player
|
||||
}
|
||||
|
||||
private val sheetsHeightObserver = { sheetsHeight: Pair<Int, Int> ->
|
||||
if (mediaViewerViewModel.fullscreenModeLiveData.value != true) {
|
||||
val (topHeight, bottomHeight) = sheetsHeight
|
||||
|
||||
// Place the player controls between the two sheets
|
||||
playerControlView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = topHeight
|
||||
bottomMargin = bottomHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val fullscreenModeObserver = { fullscreenMode: Boolean ->
|
||||
if (media.mediaType == MediaType.VIDEO) {
|
||||
playerControlView.fade(!fullscreenMode)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
imageView.setOnClickListener {
|
||||
mediaViewerViewModel.toggleFullscreenMode()
|
||||
}
|
||||
playerView.setOnClickListener {
|
||||
mediaViewerViewModel.toggleFullscreenMode()
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(media: Media, position: Int) {
|
||||
|
@ -101,12 +142,16 @@ class MediaViewerAdapter(
|
|||
|
||||
fun onViewAttachedToWindow() {
|
||||
view.findViewTreeLifecycleOwner()?.let {
|
||||
currentPositionLiveData.observe(it, observer)
|
||||
mediaViewerViewModel.mediaPositionLiveData.observe(it, mediaPositionObserver)
|
||||
mediaViewerViewModel.sheetsHeightLiveData.observe(it, sheetsHeightObserver)
|
||||
mediaViewerViewModel.fullscreenModeLiveData.observe(it, fullscreenModeObserver)
|
||||
}
|
||||
}
|
||||
|
||||
fun onViewDetachedFromWindow() {
|
||||
currentPositionLiveData.removeObserver(observer)
|
||||
mediaViewerViewModel.mediaPositionLiveData.removeObserver(mediaPositionObserver)
|
||||
mediaViewerViewModel.sheetsHeightLiveData.removeObserver(sheetsHeightObserver)
|
||||
mediaViewerViewModel.fullscreenModeLiveData.removeObserver(fullscreenModeObserver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.flatMapLatest
|
|||
import org.lineageos.glimpse.GlimpseApplication
|
||||
import org.lineageos.glimpse.repository.MediaRepository
|
||||
|
||||
class MediaViewModel(
|
||||
open class MediaViewModel(
|
||||
private val savedStateHandle: SavedStateHandle, private val mediaRepository: MediaRepository
|
||||
) : ViewModel() {
|
||||
private val mediaPositionInternal = savedStateHandle.getLiveData<Int>(MEDIA_POSITION_KEY)
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.lineageos.glimpse.viewmodels
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||
import androidx.lifecycle.createSavedStateHandle
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import org.lineageos.glimpse.GlimpseApplication
|
||||
import org.lineageos.glimpse.repository.MediaRepository
|
||||
|
||||
class MediaViewerViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
mediaRepository: MediaRepository,
|
||||
) : MediaViewModel(savedStateHandle, mediaRepository) {
|
||||
/**
|
||||
* The current height of top and bottom sheets, used to apply padding to media view UI.
|
||||
*/
|
||||
val sheetsHeightLiveData = MutableLiveData<Pair<Int, Int>>()
|
||||
|
||||
/**
|
||||
* Fullscreen mode, set by the user with a single tap on the viewed media.
|
||||
*/
|
||||
val fullscreenModeLiveData = MutableLiveData(false)
|
||||
|
||||
/**
|
||||
* Toggle fullscreen mode.
|
||||
*/
|
||||
fun toggleFullscreenMode() {
|
||||
fullscreenModeLiveData.value = when (fullscreenModeLiveData.value) {
|
||||
true -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
MediaViewerViewModel(
|
||||
savedStateHandle = createSavedStateHandle(),
|
||||
mediaRepository = (this[APPLICATION_KEY] as GlimpseApplication).mediaRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="@android:color/black"
|
||||
android:startColor="@android:color/transparent"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="@android:color/transparent"
|
||||
android:startColor="@android:color/black"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -8,14 +8,24 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:theme="@style/Theme.Glimpse.MediaViewer"
|
||||
tools:context=".fragments.MediaViewerFragment">
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/topSheetConstraintLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:background="@drawable/bg_media_viewer_top_sheet"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
@ -26,6 +36,7 @@
|
|||
android:layout_height="24dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
android:src="@drawable/ic_back"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -38,6 +49,7 @@
|
|||
style="@style/Theme.Glimpse.MediaViewer.DateTimeText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
|
@ -50,6 +62,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -57,19 +70,11 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/dateTextView" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/bottomSheetLinearLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/topSheetConstraintLayout" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottomSheetLinearLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_media_viewer_bottom_sheet"
|
||||
android:gravity="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- Images -->
|
||||
<ImageView
|
||||
|
@ -29,6 +28,25 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:show_timeout="0"
|
||||
app:use_controller="false">
|
||||
|
||||
<androidx.media3.ui.PlayerControlView
|
||||
android:id="@+id/exo_controller"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:show_next_button="false"
|
||||
app:show_previous_button="false"
|
||||
app:show_shuffle_button="false"
|
||||
app:show_subtitle_button="true" />
|
||||
|
||||
</androidx.media3.ui.PlayerView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Media3: Remove color overlay on visible player controls -->
|
||||
<color name="exo_bottom_bar_background" tools:override="true">@android:color/transparent</color>
|
||||
<color name="exo_black_opacity_60" tools:override="true">@android:color/transparent</color>
|
||||
</resources>
|
|
@ -3,12 +3,15 @@
|
|||
SPDX-FileCopyrightText: 2023 The LineageOS Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme -->
|
||||
<style name="Theme.Glimpse" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">
|
||||
shortEdges
|
||||
</item>
|
||||
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
|
||||
</style>
|
||||
|
||||
<!-- Collapsing toolbar style -->
|
||||
|
@ -34,7 +37,7 @@
|
|||
</style>
|
||||
|
||||
<!-- Media viewer -->
|
||||
<style name="Theme.Glimpse.MediaViewer" />
|
||||
<style name="Theme.Glimpse.MediaViewer" parent="Theme.Material3.Dark.NoActionBar" />
|
||||
|
||||
<!-- Media viewer top sheet -->
|
||||
<style name="Theme.Glimpse.MediaViewer.TopSheet" />
|
||||
|
|
Loading…
Reference in New Issue