Temporary remove all TV code (#229)

* Remove all tv code

* Remove banner
This commit is contained in:
Jarne Demeulemeester 2023-01-07 00:52:39 +01:00 committed by GitHub
parent 4a611e160d
commit 25efbb6eab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 54 additions and 2522 deletions

View file

@ -74,7 +74,6 @@ dependencies {
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.leanback)
implementation(libs.androidx.lifecycle.runtime) implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.exoplayer)

View file

@ -3,14 +3,11 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.wifi" android:required="false" /> <uses-feature android:name="android.hardware.wifi" android:required="false" />
<application <application
android:name=".BaseApplication" android:name=".BaseApplication"
android:allowBackup="true" android:allowBackup="true"
android:banner="@mipmap/ic_banner"
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
android:fullBackupOnly="true" android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -23,10 +20,6 @@
android:name=".PlayerActivity" android:name=".PlayerActivity"
android:screenOrientation="sensorLandscape" /> android:screenOrientation="sensorLandscape" />
<activity
android:name=".tv.TvPlayerActivity"
android:screenOrientation="userLandscape" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@ -34,9 +27,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>

View file

@ -1,7 +1,5 @@
package dev.jdtech.jellyfin package dev.jdtech.jellyfin
import android.app.UiModeManager
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
@ -26,7 +24,6 @@ import javax.inject.Inject
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var uiModeManager: UiModeManager
private val viewModel: MainViewModel by viewModels() private val viewModel: MainViewModel by viewModels()
@ -43,7 +40,6 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
uiModeManager = getSystemService(UI_MODE_SERVICE) as UiModeManager
setContentView(binding.root) setContentView(binding.root)
@ -53,52 +49,40 @@ class MainActivity : AppCompatActivity() {
val inflater = navController.navInflater val inflater = navController.navInflater
val graph = inflater.inflate(R.navigation.app_navigation) val graph = inflater.inflate(R.navigation.app_navigation)
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { checkServersEmpty(graph) {
graph.setStartDestination(R.id.homeFragmentTv) navController.setGraph(graph, intent.extras)
checkServersEmpty(graph) }
checkUser(graph) checkUser(graph) {
if (!viewModel.startDestinationTvChanged) { navController.setGraph(graph, intent.extras)
viewModel.startDestinationTvChanged = true
navController.setGraph(graph, intent.extras)
}
} else {
checkServersEmpty(graph) {
navController.setGraph(graph, intent.extras)
}
checkUser(graph) {
navController.setGraph(graph, intent.extras)
}
} }
if (uiModeManager.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION) { val navView: NavigationBarView = binding.navView as NavigationBarView
val navView: NavigationBarView = binding.navView as NavigationBarView
setSupportActionBar(binding.mainToolbar) setSupportActionBar(binding.mainToolbar)
// Passing each menu ID as a set of Ids because each // Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations. // menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration( val appBarConfiguration = AppBarConfiguration(
setOf( setOf(
R.id.homeFragment, R.id.homeFragment,
R.id.mediaFragment, R.id.mediaFragment,
R.id.favoriteFragment, R.id.favoriteFragment,
R.id.downloadFragment R.id.downloadFragment
)
) )
)
setupActionBarWithNavController(navController, appBarConfiguration) setupActionBarWithNavController(navController, appBarConfiguration)
// navView.setupWithNavController(navController) // navView.setupWithNavController(navController)
// Don't save the state of other main navigation items, only this experimental function allows turning off this behavior // Don't save the state of other main navigation items, only this experimental function allows turning off this behavior
NavigationUI.setupWithNavController(navView, navController, false) NavigationUI.setupWithNavController(navView, navController, false)
navController.addOnDestinationChangedListener { _, destination, _ -> navController.addOnDestinationChangedListener { _, destination, _ ->
binding.navView!!.visibility = when (destination.id) { binding.navView.visibility = when (destination.id) {
R.id.twoPaneSettingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, R.id.about_libraries_dest, R.id.usersFragment, R.id.serverAddressesFragment -> View.GONE R.id.twoPaneSettingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, R.id.about_libraries_dest, R.id.usersFragment, R.id.serverAddressesFragment -> View.GONE
else -> View.VISIBLE else -> View.VISIBLE
}
if (destination.id == R.id.about_libraries_dest) binding.mainToolbar?.title =
getString(R.string.app_info)
} }
if (destination.id == R.id.about_libraries_dest) binding.mainToolbar.title =
getString(R.string.app_info)
} }
loadDownloadLocation(applicationContext) loadDownloadLocation(applicationContext)

View file

@ -1,12 +1,8 @@
package dev.jdtech.jellyfin.dialogs package dev.jdtech.jellyfin.dialogs
import android.app.Dialog import android.app.Dialog
import android.app.UiModeManager
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
@ -17,16 +13,10 @@ class AddServerAddressDialog(
private val viewModel: ServerAddressesViewModel private val viewModel: ServerAddressesViewModel
) : DialogFragment() { ) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val uiModeManager =
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
val editText = EditText(this.context) val editText = EditText(this.context)
editText.hint = "http://<server_ip>:8096" editText.hint = "http://<server_ip>:8096"
return activity?.let { activity -> return activity?.let { activity ->
val builder = if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { val builder = MaterialAlertDialogBuilder(activity)
AlertDialog.Builder(activity)
} else {
MaterialAlertDialogBuilder(activity)
}
builder builder
.setTitle("Add server address") .setTitle("Add server address")
.setView(editText) .setView(editText)

View file

@ -1,13 +1,10 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.app.UiModeManager
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -27,7 +24,6 @@ import timber.log.Timber
class AddServerFragment : Fragment() { class AddServerFragment : Fragment() {
private lateinit var binding: FragmentAddServerBinding private lateinit var binding: FragmentAddServerBinding
private lateinit var uiModeManager: UiModeManager
private val viewModel: AddServerViewModel by viewModels() private val viewModel: AddServerViewModel by viewModels()
override fun onCreateView( override fun onCreateView(
@ -36,8 +32,6 @@ class AddServerFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentAddServerBinding.inflate(inflater) binding = FragmentAddServerBinding.inflate(inflater)
uiModeManager =
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
(binding.editTextServerAddress as AppCompatEditText).setOnEditorActionListener { _, actionId, _ -> (binding.editTextServerAddress as AppCompatEditText).setOnEditorActionListener { _, actionId, _ ->
return@setOnEditorActionListener when (actionId) { return@setOnEditorActionListener when (actionId) {
@ -98,42 +92,24 @@ class AddServerFragment : Fragment() {
private fun bindUiStateNormal() { private fun bindUiStateNormal() {
binding.buttonConnect.isEnabled = true binding.buttonConnect.isEnabled = true
binding.progressCircular.isVisible = false binding.progressCircular.isVisible = false
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { binding.editTextServerAddressLayout.isEnabled = true
(binding.editTextServerAddress as AppCompatEditText).isEnabled = true
} else {
binding.editTextServerAddressLayout!!.isEnabled = true
}
} }
private fun bindUiStateError(uiState: AddServerViewModel.UiState.Error) { private fun bindUiStateError(uiState: AddServerViewModel.UiState.Error) {
binding.buttonConnect.isEnabled = true binding.buttonConnect.isEnabled = true
binding.progressCircular.isVisible = false binding.progressCircular.isVisible = false
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { binding.editTextServerAddressLayout.apply {
(binding.editTextServerAddress as AppCompatEditText).apply { error = uiState.message
error = uiState.message isEnabled = true
isEnabled = true
}
} else {
binding.editTextServerAddressLayout!!.apply {
error = uiState.message
isEnabled = true
}
} }
} }
private fun bindUiStateLoading() { private fun bindUiStateLoading() {
binding.buttonConnect.isEnabled = false binding.buttonConnect.isEnabled = false
binding.progressCircular.isVisible = true binding.progressCircular.isVisible = true
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { binding.editTextServerAddressLayout.apply {
(binding.editTextServerAddress as AppCompatEditText).apply { error = null
error = null isEnabled = false
isEnabled = false
}
} else {
binding.editTextServerAddressLayout!!.apply {
error = null
isEnabled = false
}
} }
} }

View file

@ -1,8 +1,6 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.app.UiModeManager
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -10,7 +8,6 @@ import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.MenuHost import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -22,7 +19,6 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearSnapHelper
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.ViewItemPagingAdapter import dev.jdtech.jellyfin.adapters.ViewItemPagingAdapter
@ -42,7 +38,6 @@ import org.jellyfin.sdk.model.api.SortOrder
class LibraryFragment : Fragment() { class LibraryFragment : Fragment() {
private lateinit var binding: FragmentLibraryBinding private lateinit var binding: FragmentLibraryBinding
private lateinit var uiModeManager: UiModeManager
private val viewModel: LibraryViewModel by viewModels() private val viewModel: LibraryViewModel by viewModels()
private val args: LibraryFragmentArgs by navArgs() private val args: LibraryFragmentArgs by navArgs()
@ -57,8 +52,6 @@ class LibraryFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentLibraryBinding.inflate(inflater, container, false) binding = FragmentLibraryBinding.inflate(inflater, container, false)
uiModeManager =
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
return binding.root return binding.root
} }
@ -105,8 +98,6 @@ class LibraryFragment : Fragment() {
viewLifecycleOwner, Lifecycle.State.RESUMED viewLifecycleOwner, Lifecycle.State.RESUMED
) )
binding.title?.text = args.libraryName
binding.errorLayout.errorRetryButton.setOnClickListener { binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadItems(args.libraryId, args.libraryType) viewModel.loadItems(args.libraryId, args.libraryType)
} }
@ -118,11 +109,6 @@ class LibraryFragment : Fragment() {
) )
} }
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
val snapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(binding.itemsRecyclerView)
}
binding.itemsRecyclerView.adapter = binding.itemsRecyclerView.adapter =
ViewItemPagingAdapter( ViewItemPagingAdapter(
ViewItemPagingAdapter.OnClickListener { item -> ViewItemPagingAdapter.OnClickListener { item ->
@ -205,22 +191,12 @@ class LibraryFragment : Fragment() {
} }
private fun navigateToMediaInfoFragment(item: BaseItemDto) { private fun navigateToMediaInfoFragment(item: BaseItemDto) {
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { findNavController().navigate(
findNavController().navigate( LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment(
LibraryFragmentDirections.actionLibraryFragmentToMediaDetailFragment( item.id,
item.id, item.name,
item.name, item.type
item.type
)
) )
} else { )
findNavController().navigate(
LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment(
item.id,
item.name,
item.type
)
)
}
} }
} }

View file

@ -1,13 +1,10 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.app.UiModeManager
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatEditText import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -31,7 +28,6 @@ import timber.log.Timber
class LoginFragment : Fragment() { class LoginFragment : Fragment() {
private lateinit var binding: FragmentLoginBinding private lateinit var binding: FragmentLoginBinding
private lateinit var uiModeManager: UiModeManager
private val viewModel: LoginViewModel by viewModels() private val viewModel: LoginViewModel by viewModels()
private val args: LoginFragmentArgs by navArgs() private val args: LoginFragmentArgs by navArgs()
@ -47,8 +43,6 @@ class LoginFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentLoginBinding.inflate(inflater) binding = FragmentLoginBinding.inflate(inflater)
uiModeManager =
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
if (args.reLogin) { if (args.reLogin) {
appPreferences.currentServer?.let { currentServerId -> appPreferences.currentServer?.let { currentServerId ->
@ -117,49 +111,28 @@ class LoginFragment : Fragment() {
private fun bindUiStateNormal() { private fun bindUiStateNormal() {
binding.buttonLogin.isEnabled = true binding.buttonLogin.isEnabled = true
binding.progressCircular.isVisible = false binding.progressCircular.isVisible = false
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { binding.editTextUsernameLayout.isEnabled = true
(binding.editTextUsername as AppCompatEditText).isEnabled = true binding.editTextPasswordLayout.isEnabled = true
(binding.editTextPassword as AppCompatEditText).isEnabled = true
} else {
binding.editTextUsernameLayout!!.isEnabled = true
binding.editTextPasswordLayout!!.isEnabled = true
}
} }
private fun bindUiStateError(uiState: LoginViewModel.UiState.Error) { private fun bindUiStateError(uiState: LoginViewModel.UiState.Error) {
binding.buttonLogin.isEnabled = true binding.buttonLogin.isEnabled = true
binding.progressCircular.isVisible = false binding.progressCircular.isVisible = false
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { binding.editTextUsernameLayout.apply {
(binding.editTextUsername as AppCompatEditText).apply { error = uiState.message
error = uiState.message isEnabled = true
isEnabled = true
}
(binding.editTextPassword as AppCompatEditText).isEnabled = true
} else {
binding.editTextUsernameLayout!!.apply {
error = uiState.message
isEnabled = true
}
binding.editTextPasswordLayout!!.isEnabled = true
} }
binding.editTextPasswordLayout.isEnabled = true
} }
private fun bindUiStateLoading() { private fun bindUiStateLoading() {
binding.buttonLogin.isEnabled = false binding.buttonLogin.isEnabled = false
binding.progressCircular.isVisible = true binding.progressCircular.isVisible = true
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { binding.editTextUsernameLayout.apply {
(binding.editTextUsername as AppCompatEditText).apply { error = null
error = null isEnabled = false
isEnabled = false
}
(binding.editTextPassword as AppCompatEditText).isEnabled = false
} else {
binding.editTextUsernameLayout!!.apply {
error = null
isEnabled = false
}
binding.editTextPasswordLayout!!.isEnabled = false
} }
binding.editTextPasswordLayout.isEnabled = false
} }
private fun bindUsersStateUsers(usersState: LoginViewModel.UsersState.Users) { private fun bindUsersStateUsers(usersState: LoginViewModel.UsersState.Users) {
@ -179,10 +152,6 @@ class LoginFragment : Fragment() {
} }
private fun navigateToHomeFragment() { private fun navigateToHomeFragment() {
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeFragment())
findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeFragmentTv())
} else {
findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeFragment())
}
} }
} }

View file

@ -1,12 +1,9 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.app.UiModeManager
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -27,7 +24,6 @@ import timber.log.Timber
class ServerAddressesFragment : Fragment() { class ServerAddressesFragment : Fragment() {
private lateinit var binding: FragmentServerAddressesBinding private lateinit var binding: FragmentServerAddressesBinding
private lateinit var uiModeManager: UiModeManager
private val viewModel: ServerAddressesViewModel by viewModels() private val viewModel: ServerAddressesViewModel by viewModels()
private val args: UsersFragmentArgs by navArgs() private val args: UsersFragmentArgs by navArgs()
@ -37,8 +33,6 @@ class ServerAddressesFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentServerAddressesBinding.inflate(inflater) binding = FragmentServerAddressesBinding.inflate(inflater)
uiModeManager =
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
binding.addressesRecyclerView.adapter = binding.addressesRecyclerView.adapter =
ServerAddressAdapter( ServerAddressAdapter(
@ -98,10 +92,6 @@ class ServerAddressesFragment : Fragment() {
} }
private fun navigateToMainActivity() { private fun navigateToMainActivity() {
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragment())
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragmentTv())
} else {
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragment())
}
} }
} }

View file

@ -1,12 +1,9 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.app.UiModeManager
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -24,7 +21,6 @@ import kotlinx.coroutines.launch
class ServerSelectFragment : Fragment() { class ServerSelectFragment : Fragment() {
private lateinit var binding: FragmentServerSelectBinding private lateinit var binding: FragmentServerSelectBinding
private lateinit var uiModeManager: UiModeManager
private val viewModel: ServerSelectViewModel by viewModels() private val viewModel: ServerSelectViewModel by viewModels()
override fun onCreateView( override fun onCreateView(
@ -33,8 +29,6 @@ class ServerSelectFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentServerSelectBinding.inflate(inflater) binding = FragmentServerSelectBinding.inflate(inflater)
uiModeManager =
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
binding.lifecycleOwner = viewLifecycleOwner binding.lifecycleOwner = viewLifecycleOwner
@ -78,10 +72,6 @@ class ServerSelectFragment : Fragment() {
} }
private fun navigateToMainActivity() { private fun navigateToMainActivity() {
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { findNavController().navigate(ServerSelectFragmentDirections.actionServerSelectFragmentToHomeFragment())
findNavController().navigate(ServerSelectFragmentDirections.actionServerSelectFragmentToHomeFragmentTv())
} else {
findNavController().navigate(ServerSelectFragmentDirections.actionServerSelectFragmentToHomeFragment())
}
} }
} }

View file

@ -1,12 +1,9 @@
package dev.jdtech.jellyfin.fragments package dev.jdtech.jellyfin.fragments
import android.app.UiModeManager
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -27,7 +24,6 @@ import timber.log.Timber
class UsersFragment : Fragment() { class UsersFragment : Fragment() {
private lateinit var binding: FragmentUsersBinding private lateinit var binding: FragmentUsersBinding
private lateinit var uiModeManager: UiModeManager
private val viewModel: UsersViewModel by viewModels() private val viewModel: UsersViewModel by viewModels()
private val args: UsersFragmentArgs by navArgs() private val args: UsersFragmentArgs by navArgs()
@ -37,8 +33,6 @@ class UsersFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentUsersBinding.inflate(inflater) binding = FragmentUsersBinding.inflate(inflater)
uiModeManager =
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
binding.usersRecyclerView.adapter = binding.usersRecyclerView.adapter =
UserListAdapter( UserListAdapter(
@ -101,10 +95,6 @@ class UsersFragment : Fragment() {
} }
private fun navigateToMainActivity() { private fun navigateToMainActivity() {
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragment())
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragmentTv())
} else {
findNavController().navigate(UsersFragmentDirections.actionUsersFragmentToHomeFragment())
}
} }
} }

View file

@ -1,161 +0,0 @@
package dev.jdtech.jellyfin.tv
import android.os.Bundle
import android.view.Gravity
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.ImageButton
import android.widget.PopupWindow
import android.widget.TextView
import androidx.activity.viewModels
import androidx.core.content.res.ResourcesCompat
import androidx.navigation.navArgs
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.BasePlayerActivity
import dev.jdtech.jellyfin.PlayerActivityArgs
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.databinding.ActivityPlayerTvBinding
import dev.jdtech.jellyfin.mpv.MPVPlayer
import dev.jdtech.jellyfin.mpv.TrackType
import dev.jdtech.jellyfin.mpv.TrackType.AUDIO
import dev.jdtech.jellyfin.mpv.TrackType.SUBTITLE
import dev.jdtech.jellyfin.tv.ui.TrackSelectorAdapter
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import timber.log.Timber
@AndroidEntryPoint
internal class TvPlayerActivity : BasePlayerActivity() {
private lateinit var binding: ActivityPlayerTvBinding
override val viewModel: PlayerActivityViewModel by viewModels()
private val args: PlayerActivityArgs by navArgs()
private var displayedPopup: PopupWindow? = null
override fun onCreate(savedInstanceState: Bundle?) {
Timber.d("Player activity created.")
super.onCreate(savedInstanceState)
binding = ActivityPlayerTvBinding.inflate(layoutInflater)
setContentView(binding.root)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding.playerView.player = viewModel.player
val playerControls = binding.playerView.findViewById<View>(R.id.player_controls)
configureInsets(playerControls)
bind()
viewModel.initializePlayer(args.items)
hideSystemUI()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return if (!binding.playerView.isControllerFullyVisible) {
binding.playerView.showController()
true
} else {
false
}
}
private fun bind() = with(binding.playerView) {
val videoNameTextView = findViewById<TextView>(R.id.video_name)
viewModel.currentItemTitle.observe(this@TvPlayerActivity) { title ->
videoNameTextView.text = title
}
findViewById<ImageButton>(R.id.exo_play_pause).apply {
setOnClickListener {
when {
viewModel.player.isPlaying -> {
viewModel.player.pause()
setImageDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.ic_play, theme)
)
}
viewModel.player.isLoading -> Unit
else -> {
viewModel.player.play()
setImageDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.ic_play, theme)
)
}
}
}
}
findViewById<View>(R.id.back_button).setOnClickListener {
finish()
}
bindAudioControl()
bindSubtitleControl()
}
private fun bindAudioControl() {
val audioBtn = binding.playerView.findViewById<ImageButton>(R.id.btn_audio_track)
audioBtn.setOnFocusChangeListener { _, hasFocus ->
displayedPopup = if (hasFocus) {
val items = viewModel.currentAudioTracks.toUiTrack()
audioBtn.showPopupWindowAbove(items, AUDIO)
} else {
displayedPopup?.dismiss()
null
}
}
}
private fun bindSubtitleControl() {
val subtitleBtn = binding.playerView.findViewById<ImageButton>(R.id.btn_subtitle)
subtitleBtn.setOnFocusChangeListener { v, hasFocus ->
v.isFocusable = true
displayedPopup = if (hasFocus) {
val items = viewModel.currentSubtitleTracks.toUiTrack()
subtitleBtn.showPopupWindowAbove(items, SUBTITLE)
} else {
displayedPopup?.dismiss()
null
}
}
}
private fun List<MPVPlayer.Companion.Track>.toUiTrack() = map { track ->
TrackSelectorAdapter.Track(
title = track.title,
language = track.lang,
codec = track.codec,
selected = track.selected,
playerTrack = track
)
}
private fun View.showPopupWindowAbove(
items: List<TrackSelectorAdapter.Track>,
type: TrackType
): PopupWindow {
val popup = PopupWindow(this.context)
popup.contentView = LayoutInflater.from(context).inflate(R.layout.track_selector, null)
val recyclerView = popup.contentView.findViewById<RecyclerView>(R.id.track_selector)
recyclerView.adapter = TrackSelectorAdapter(items, viewModel, type) { popup.dismiss() }
val startViewCoords = IntArray(2)
getLocationInWindow(startViewCoords)
val itemHeight = resources.getDimension(R.dimen.track_selection_item_height).toInt()
val totalHeight = items.size * itemHeight
popup.showAsDropDown(
binding.root,
startViewCoords.first(),
startViewCoords.last() - totalHeight,
Gravity.TOP
)
return popup
}
}

View file

@ -1,209 +0,0 @@
package dev.jdtech.jellyfin.tv.ui
import android.app.UiModeManager
import android.content.res.Configuration
import android.os.Bundle
import android.view.KeyEvent.KEYCODE_DPAD_DOWN
import android.view.KeyEvent.KEYCODE_DPAD_DOWN_LEFT
import android.view.View
import android.widget.ImageButton
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.viewModels
import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.DiffCallback
import androidx.leanback.widget.HeaderItem
import androidx.leanback.widget.ListRow
import androidx.leanback.widget.ListRowPresenter
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.HomeItem
import dev.jdtech.jellyfin.fragments.HomeFragmentDirections
import dev.jdtech.jellyfin.viewmodels.HomeViewModel
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
import timber.log.Timber
@AndroidEntryPoint
internal class HomeFragment : BrowseSupportFragment() {
private val viewModel: HomeViewModel by viewModels()
private lateinit var rowsAdapter: ArrayObjectAdapter
private lateinit var uiModeManager: UiModeManager
private val adapterMap = mutableMapOf<String, ArrayObjectAdapter>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
uiModeManager =
requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
val rowPresenter = ListRowPresenter()
rowPresenter.selectEffectEnabled = false
headersState = HEADERS_ENABLED
rowsAdapter = ArrayObjectAdapter(rowPresenter)
adapter = rowsAdapter
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<ImageButton>(R.id.settings).apply {
setOnKeyListener { _, keyCode, _ ->
if (keyCode == KEYCODE_DPAD_DOWN || keyCode == KEYCODE_DPAD_DOWN_LEFT) {
headersSupportFragment.view?.requestFocus()
true
} else {
false
}
}
setOnClickListener { navigateToSettingsFragment() }
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
Timber.d("$uiState")
when (uiState) {
is HomeViewModel.UiState.Normal -> bindUiStateNormal(uiState)
is HomeViewModel.UiState.Loading -> bindUiStateLoading()
is HomeViewModel.UiState.Error -> Unit
}
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.loadData(includeLibraries = true)
}
}
}
private val diffCallbackListRow = object : DiffCallback<ListRow>() {
override fun areItemsTheSame(oldItem: ListRow, newItem: ListRow): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ListRow, newItem: ListRow): Boolean {
Timber.d((oldItem.adapter.size() == newItem.adapter.size()).toString())
return oldItem.adapter.size() == newItem.adapter.size()
}
}
private fun bindUiStateNormal(uiState: HomeViewModel.UiState.Normal) {
progressBarManager.hide()
uiState.apply {
rowsAdapter.setItems(homeItems.map { homeItem -> homeItem.toListRow() }, diffCallbackListRow)
}
}
private fun bindUiStateLoading() {
progressBarManager.show()
}
private fun HomeItem.toListRow(): ListRow {
return ListRow(
toHeader(),
toItems()
)
}
private fun HomeItem.toHeader(): HeaderItem {
return when (this) {
is HomeItem.Libraries -> HeaderItem(section.name)
is HomeItem.Section -> HeaderItem(homeSection.name)
is HomeItem.ViewItem -> HeaderItem(
String.format(
resources.getString(R.string.latest_library),
view.name
)
)
}
}
val diffCallback = object : DiffCallback<BaseItemDto>() {
override fun areItemsTheSame(oldItem: BaseItemDto, newItem: BaseItemDto): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: BaseItemDto, newItem: BaseItemDto): Boolean {
return oldItem == newItem
}
}
private fun HomeItem.toItems(): ArrayObjectAdapter {
val name = this.toHeader().name
val items = when (this) {
is HomeItem.Libraries -> section.items
is HomeItem.Section -> homeSection.items
is HomeItem.ViewItem -> view.items
}
if (name in adapterMap) {
adapterMap[name]?.setItems(items, diffCallback)
} else {
adapterMap[name] = when (this) {
is HomeItem.Libraries -> ArrayObjectAdapter(
LibaryItemPresenter { item ->
navigateToLibraryFragment(item)
}
).apply { setItems(items, diffCallback) }
is HomeItem.Section -> ArrayObjectAdapter(
DynamicMediaItemPresenter { item ->
navigateToMediaDetailFragment(item)
}
).apply { setItems(items, diffCallback) }
is HomeItem.ViewItem -> ArrayObjectAdapter(
MediaItemPresenter { item ->
navigateToMediaDetailFragment(item)
}
).apply { setItems(items, diffCallback) }
}
}
return adapterMap[name]!!
}
private fun navigateToLibraryFragment(library: BaseItemDto) {
if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
findNavController().navigate(
dev.jdtech.jellyfin.tv.ui.HomeFragmentDirections.actionHomeFragmentToLibraryFragment(
library.id,
library.name,
library.collectionType
)
)
} else {
findNavController().navigate(
HomeFragmentDirections.actionNavigationHomeToLibraryFragment(
library.id,
library.name,
library.collectionType
)
)
}
}
private fun navigateToMediaDetailFragment(item: BaseItemDto) {
findNavController().navigate(
HomeFragmentDirections.actionHomeFragmentToMediaDetailFragment(
item.id,
item.seriesName ?: item.name,
item.type
)
)
}
private fun navigateToSettingsFragment() {
findNavController().navigate(
HomeFragmentDirections.actionHomeFragmentToSettingsFragment()
)
}
}

View file

@ -1,258 +0,0 @@
package dev.jdtech.jellyfin.tv.ui
import android.content.Intent
import android.content.res.ColorStateList
import android.net.Uri
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.PersonListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.bindBaseItemImage
import dev.jdtech.jellyfin.databinding.MediaDetailFragmentBinding
import dev.jdtech.jellyfin.dialogs.VideoVersionDialogFragment
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.viewmodels.MediaInfoViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel.PlayerItemError
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel.PlayerItems
import kotlinx.coroutines.launch
import timber.log.Timber
@AndroidEntryPoint
internal class MediaDetailFragment : Fragment() {
private lateinit var binding: MediaDetailFragmentBinding
private val viewModel: MediaInfoViewModel by viewModels()
private val playerViewModel: PlayerViewModel by viewModels()
private val args: MediaDetailFragmentArgs by navArgs()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.loadData(args.itemId, args.itemType)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = MediaDetailFragmentBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
Timber.d("$uiState")
when (uiState) {
is MediaInfoViewModel.UiState.Normal -> bindUiStateNormal(uiState)
is MediaInfoViewModel.UiState.Loading -> Unit
is MediaInfoViewModel.UiState.Error -> Unit
}
}
}
}
val seasonsAdapter = ViewItemListAdapter(
fixedWidth = true,
onClickListener = ViewItemListAdapter.OnClickListener {}
)
binding.seasonsRow.gridView.adapter = seasonsAdapter
binding.seasonsRow.gridView.verticalSpacing = 25
val castAdapter = PersonListAdapter {
Toast.makeText(requireContext(), "Not yet implemented", Toast.LENGTH_SHORT).show()
}
binding.castRow.gridView.adapter = castAdapter
binding.castRow.gridView.verticalSpacing = 25
playerViewModel.onPlaybackRequested(lifecycleScope) { playerItems ->
when (playerItems) {
is PlayerItemError -> bindPlayerItemsError(playerItems)
is PlayerItems -> bindPlayerItems(playerItems)
}
}
binding.playButton.setOnClickListener {
binding.playButton.setImageResource(android.R.color.transparent)
binding.progressCircular.isVisible = true
viewModel.item?.let { item ->
playerViewModel.loadPlayerItems(item) {
VideoVersionDialogFragment(item, playerViewModel).show(
parentFragmentManager,
"videoversiondialog"
)
}
}
}
binding.trailerButton.setOnClickListener {
if (viewModel.item?.remoteTrailers.isNullOrEmpty()) return@setOnClickListener
val intent = Intent(
Intent.ACTION_VIEW,
Uri.parse(viewModel.item?.remoteTrailers?.get(0)?.url)
)
startActivity(intent)
}
binding.checkButton.setOnClickListener {
when (viewModel.played) {
true -> {
viewModel.markAsUnplayed(args.itemId)
binding.checkButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
R.color.white,
requireActivity().theme
)
)
}
false -> {
viewModel.markAsPlayed(args.itemId)
binding.checkButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
R.color.red,
requireActivity().theme
)
)
}
}
}
binding.favoriteButton.setOnClickListener {
when (viewModel.favorite) {
true -> {
viewModel.unmarkAsFavorite(args.itemId)
binding.favoriteButton.setImageResource(R.drawable.ic_heart)
binding.favoriteButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
R.color.white,
requireActivity().theme
)
)
}
false -> {
viewModel.markAsFavorite(args.itemId)
binding.favoriteButton.setImageResource(R.drawable.ic_heart_filled)
binding.favoriteButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
R.color.red,
requireActivity().theme
)
)
}
}
}
}
private fun bindUiStateNormal(uiState: MediaInfoViewModel.UiState.Normal) {
uiState.apply {
binding.seasonsLayout.isVisible = seasons.isNotEmpty()
val seasonsAdapter = binding.seasonsRow.gridView.adapter as ViewItemListAdapter
seasonsAdapter.submitList(seasons)
binding.castLayout.isVisible = actors.isNotEmpty()
val actorsAdapter = binding.castRow.gridView.adapter as PersonListAdapter
actorsAdapter.submitList(actors)
// Check icon
when (played) {
true -> {
binding.checkButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
R.color.red,
requireActivity().theme
)
)
}
false -> {
val typedValue = TypedValue()
requireActivity().theme.resolveAttribute(R.attr.colorOnSecondaryContainer, typedValue, true)
/*binding.checkButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
typedValue.resourceId,
requireActivity().theme
)
)*/
}
}
// Favorite icon
val favoriteDrawable = when (favorite) {
true -> R.drawable.ic_heart_filled
false -> R.drawable.ic_heart
}
binding.favoriteButton.setImageResource(favoriteDrawable)
if (favorite) binding.favoriteButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
R.color.red,
requireActivity().theme
)
)
binding.title.text = item.name
binding.genres.text = genresString
binding.year.text = dateString
binding.playtime.text = runTime
binding.officialRating.text = item.officialRating
binding.communityRating.text = item.communityRating.toString()
binding.description.text = item.overview
bindBaseItemImage(binding.poster, item)
}
}
private fun bindPlayerItems(items: PlayerItems) {
navigateToPlayerActivity(items.items.toTypedArray())
binding.playButton.setImageDrawable(
ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_play
)
)
binding.progressCircular.visibility = View.INVISIBLE
}
private fun bindPlayerItemsError(error: PlayerItemError) {
Timber.e(error.error.message)
binding.errorLayout.errorPanel.isVisible = true
binding.playButton.setImageDrawable(
ContextCompat.getDrawable(
requireActivity(),
R.drawable.ic_play
)
)
binding.progressCircular.visibility = View.INVISIBLE
}
private fun navigateToPlayerActivity(
playerItems: Array<PlayerItem>,
) {
findNavController().navigate(
MediaDetailFragmentDirections.actionMediaDetailFragmentToPlayerActivity(
playerItems
)
)
}
}

View file

@ -1,108 +0,0 @@
package dev.jdtech.jellyfin.tv.ui
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.leanback.widget.Presenter
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.databinding.BaseItemBinding
import dev.jdtech.jellyfin.databinding.CollectionItemBinding
import dev.jdtech.jellyfin.databinding.HomeEpisodeItemBinding
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
class LibaryItemPresenter(private val onClick: (BaseItemDto) -> Unit) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
return ViewHolder(
CollectionItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).root
)
}
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
if (item is BaseItemDto) {
DataBindingUtil.getBinding<CollectionItemBinding>(viewHolder.view)?.apply {
this.collection = item
viewHolder.view.setOnClickListener { onClick(item) }
}
}
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) = Unit
}
class MediaItemPresenter(private val onClick: (BaseItemDto) -> Unit) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
return ViewHolder(
BaseItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).root
)
}
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
if (item is BaseItemDto) {
DataBindingUtil.getBinding<BaseItemBinding>(viewHolder.view)?.apply {
this.item = item
this.itemName.text =
if (item.type == BaseItemKind.EPISODE) item.seriesName else item.name
this.itemCount.visibility =
if (item.userData?.unplayedItemCount != null && item.userData?.unplayedItemCount!! > 0) View.VISIBLE else View.GONE
this.itemLayout.layoutParams.width =
this.itemLayout.resources.getDimension(R.dimen.overview_media_width).toInt()
(this.itemLayout.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = 0
viewHolder.view.setOnClickListener { onClick(item) }
}
}
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) = Unit
}
class DynamicMediaItemPresenter(private val onClick: (BaseItemDto) -> Unit) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
return ViewHolder(
HomeEpisodeItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).root
)
}
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
if (item is BaseItemDto) {
DataBindingUtil.getBinding<HomeEpisodeItemBinding>(viewHolder.view)?.apply {
episode = item
item.userData?.playedPercentage?.toInt()?.let {
progressBar.layoutParams.width = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
(it.times(2.24)).toFloat(), progressBar.context.resources.displayMetrics
).toInt()
progressBar.isVisible = true
}
if (item.type == BaseItemKind.MOVIE) {
primaryName.text = item.name
secondaryName.visibility = View.GONE
} else if (item.type == BaseItemKind.EPISODE) {
primaryName.text = item.seriesName
}
viewHolder.view.setOnClickListener { onClick(item) }
}
}
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) = Unit
}

View file

@ -1,62 +0,0 @@
package dev.jdtech.jellyfin.tv.ui
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.mpv.MPVPlayer
import dev.jdtech.jellyfin.mpv.TrackType
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
class TrackSelectorAdapter(
private val items: List<Track>,
private val viewModel: PlayerActivityViewModel,
private val trackType: TrackType,
private val dismissWindow: () -> Unit
) : RecyclerView.Adapter<TrackSelectorAdapter.TrackSelectorViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackSelectorViewHolder {
return TrackSelectorViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.track_item, parent, false)
)
}
override fun onBindViewHolder(holder: TrackSelectorViewHolder, position: Int) {
holder.bind(items[position], viewModel, trackType, dismissWindow)
}
override fun getItemCount(): Int = items.size
class TrackSelectorViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bind(
item: Track,
viewModel: PlayerActivityViewModel,
trackType: TrackType,
dismissWindow: () -> Unit
) {
view.findViewById<Button>(R.id.track_name).apply {
text = String.format(
view.resources.getString(R.string.track_selection),
item.language,
item.title,
item.codec
)
setOnClickListener {
viewModel.switchToTrack(trackType, item.playerTrack)
dismissWindow()
}
}
}
}
data class Track(
val title: String,
val language: String,
val codec: String,
val selected: Boolean,
val playerTrack: MPVPlayer.Companion.Track
)
}

View file

@ -4,5 +4,4 @@ import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() { class MainViewModel : ViewModel() {
var startDestinationChanged = false var startDestinationChanged = false
var startDestinationTvChanged = false
} }

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true" android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="@color/neutral_700"/>
<stroke android:width="2dp" android:color="@color/white" />
<corners android:radius="10dp" />
</shape>
</item>
<item android:state_enabled="true">
<shape android:shape="rectangle">
<solid android:color="@color/neutral_700"/>
<corners android:radius="10dp" />
</shape>
</item>
</selector>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="true" android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="?attr/colorPrimary"/>
<stroke android:width="2dp" android:color="@color/white" />
<corners android:radius="10dp" />
</shape>
</item>
<item android:state_enabled="true">
<shape android:shape="rectangle">
<solid android:color="?attr/colorPrimary"/>
<corners android:radius="10dp" />
</shape>
</item>
</selector>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="?attr/colorPrimary" />
</shape>
</item>
<item
android:bottom="18dp"
android:drawable="@drawable/ic_user"
android:end="18dp"
android:gravity="center"
android:start="18dp"
android:top="18dp" />
</layer-list>

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:navGraph="@navigation/app_navigation" />
</FrameLayout>

View file

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="item"
type="org.jellyfin.sdk.model.api.BaseItemDto" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/item_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="24dp"
android:background="@drawable/focus_border"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/item_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:itemImage="@{item}"
app:layout_constraintDimensionRatio="H,2:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Findroid.Image"
app:strokeColor="@null" />
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/item_image"
tools:text="Movie title" />
<TextView
android:id="@+id/item_count"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/circle_background"
android:gravity="center"
android:text="@{item.userData.unplayedItemCount.toString()}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="9" />
<ImageView
android:id="@+id/played_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/circle_background"
android:contentDescription="@string/episode_watched_indicator"
android:padding="4dp"
android:src="@drawable/ic_check"
android:visibility="@{item.userData.played == true ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="@id/item_image"
app:layout_constraintTop_toTopOf="@id/item_image" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="16dp"
tools:ignore="MissingDefaultResource">
<ImageButton
android:id="@+id/settings"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="24dp"
android:background="@drawable/focus_border"
android:contentDescription="@string/title_settings"
android:focusable="true"
android:focusableInTouchMode="true"
android:src="@drawable/ic_settings"
app:layout_constraintEnd_toStartOf="@+id/clock"
app:layout_constraintTop_toTopOf="parent" />
<TextClock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_marginEnd="24dp"
android:gravity="center_vertical"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="12:00" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="collection"
type="org.jellyfin.sdk.model.api.BaseItemDto" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="24dp"
android:background="@drawable/focus_border"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/collection_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
app:baseItemImage="@{collection}"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Findroid.Image"
app:strokeColor="@null" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@{collection.name}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/collection_image"
tools:text="Movies" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="server"
type="dev.jdtech.jellyfin.models.DiscoveredServer" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:background="@drawable/focus_border"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true">
<FrameLayout
android:id="@+id/server_icon"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:background="@drawable/button_setup_background"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_server"
app:tint="@android:color/white" />
</FrameLayout>
<TextView
android:id="@+id/server_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="@{server.name}"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/server_icon"
tools:text="username" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,136 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/player_controls"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/player_background"
tools:ignore="MissingDefaultResource"
>
<ImageButton
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/transparent_circle_background"
android:contentDescription="@string/player_controls_exit"
android:padding="@dimen/tv_controls_padding"
android:src="@drawable/ic_arrow_left"
android:focusable="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@+id/video_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="1"
android:padding="@dimen/tv_controls_padding"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:textColor="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/back_button"
app:layout_constraintTop_toTopOf="parent"
tools:text="The Dawn of Despair"
/>
<ImageButton
android:id="@+id/btn_audio_track"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/transparent_circle_background"
android:contentDescription="@string/select_audio_track"
android:padding="@dimen/tv_controls_padding"
android:src="@drawable/ic_speaker"
android:focusable="true"
app:layout_constraintBottom_toTopOf="@id/exo_progress"
app:layout_constraintStart_toStartOf="parent"
/>
<ImageButton
android:id="@+id/btn_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/tv_controls_margin_start"
android:background="@drawable/transparent_circle_background"
android:contentDescription="@string/select_subtile_track"
android:padding="@dimen/tv_controls_padding"
android:src="@drawable/ic_closed_caption"
android:focusable="true"
app:layout_constraintBottom_toTopOf="@id/exo_progress"
app:layout_constraintStart_toEndOf="@id/btn_audio_track"
/>
<androidx.media3.ui.DefaultTimeBar
android:id="@+id/exo_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/exo_play_pause"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:focusable="true"
app:played_color="?colorPrimary"
/>
<ImageButton
android:id="@+id/exo_rew"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/transparent_circle_background"
android:contentDescription="@string/player_controls_rewind"
android:padding="@dimen/tv_controls_padding"
android:src="@drawable/ic_rewind"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
<ImageButton
android:id="@+id/exo_play_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/tv_controls_margin_start"
android:background="@drawable/transparent_circle_background"
android:contentDescription="@string/play_button_description"
android:padding="@dimen/tv_controls_padding"
android:src="@drawable/ic_pause"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/exo_rew"
android:focusedByDefault="true"
/>
<ImageButton
android:id="@+id/exo_ffwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/tv_controls_margin_start"
android:background="@drawable/transparent_circle_background"
android:contentDescription="@string/player_controls_fast_forward"
android:padding="@dimen/tv_controls_padding"
android:src="@drawable/ic_fast_forward"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/exo_play_pause"
/>
<ImageButton
android:id="@+id/exo_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/tv_controls_margin_start"
android:background="@drawable/transparent_circle_background"
android:contentDescription="@string/player_controls_skip"
android:padding="@dimen/tv_controls_padding"
android:src="@drawable/ic_skip_forward"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/exo_ffwd"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,96 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context=".fragments.AddServerFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image_banner"
android:layout_width="268dp"
android:layout_height="75dp"
android:layout_marginTop="64dp"
android:contentDescription="@string/jellyfin_banner"
android:src="@drawable/ic_banner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="@dimen/setup_container_width"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_banner"
app:layout_constraintVertical_bias="0.36">
<TextView
android:id="@+id/text_add_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="32dp"
android:text="@string/add_server"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="?android:textColorPrimary" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/servers_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingHorizontal="12dp"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2"
tools:listitem="@layout/discovered_server_item"
tools:visibility="visible" />
<EditText
android:id="@+id/edit_text_server_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="8dp"
android:autofillHints=""
android:focusedByDefault="true"
android:hint="@string/edit_text_server_address_hint"
android:imeOptions="actionGo"
android:inputType="textUri" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp">
<Button
android:id="@+id/button_connect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/button_connect" />
<ProgressBar
android:id="@+id/progress_circular"
android:layout_width="48dp"
android:layout_height="48dp"
android:elevation="8dp"
android:indeterminateTint="@color/white"
android:padding="8dp"
android:visibility="invisible" />
</RelativeLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.LibraryFragment">
<include
android:id="@+id/error_layout"
layout="@layout/error_panel" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Movies" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/items_recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:paddingHorizontal="12dp"
android:paddingTop="16dp"
android:scrollbars="none"
android:layout_marginTop="12dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:spanCount="@integer/library_columns"
tools:itemCount="14"
tools:listitem="@layout/base_item">
<requestFocus/>
</androidx.recyclerview.widget.RecyclerView>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/loading_indicator"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,107 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context=".fragments.LoginFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image_banner"
android:layout_width="268dp"
android:layout_height="75dp"
android:layout_marginTop="64dp"
android:contentDescription="@string/jellyfin_banner"
android:src="@drawable/ic_banner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="@dimen/setup_container_width"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_banner"
app:layout_constraintVertical_bias="0.36">
<TextView
android:id="@+id/text_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="32dp"
android:text="@string/login"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="?android:textColorPrimary" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/users_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingHorizontal="12dp"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2"
tools:listitem="@layout/user_item"
tools:visibility="visible" />
<EditText
android:id="@+id/edit_text_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="12dp"
android:autofillHints="username"
android:hint="@string/edit_text_username_hint"
android:inputType="text" />
<EditText
android:id="@+id/edit_text_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="24dp"
android:autofillHints="password"
android:hint="@string/edit_text_password_hint"
android:imeOptions="actionGo"
android:inputType="textPassword"
app:passwordToggleEnabled="true" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp">
<Button
android:id="@+id/button_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/button_login" />
<ProgressBar
android:id="@+id/progress_circular"
android:layout_width="48dp"
android:layout_height="48dp"
android:elevation="8dp"
android:indeterminateTint="@color/white"
android:padding="8dp"
android:visibility="invisible" />
</RelativeLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/addresses_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="4"
tools:listitem="@layout/server_address_list_item" />
<Button
android:id="@+id/button_add_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:text="@string/add_address"
app:icon="@drawable/ic_plus" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="dev.jdtech.jellyfin.viewmodels.ServerSelectViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/servers_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:servers="@{viewModel.servers}"
app:spanCount="10"
tools:itemCount="4"
tools:listitem="@layout/server_item" />
<Button
android:id="@+id/button_add_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:text="@string/add_server" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/users_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="4"
tools:listitem="@layout/user_list_item" />
<Button
android:id="@+id/button_add_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:text="@string/add_user"
app:icon="@drawable/ic_plus" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="episode"
type="org.jellyfin.sdk.model.api.BaseItemDto" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:background="@drawable/focus_border"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/episode_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:baseItemImage="@{episode}"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Findroid.Image"
app:strokeColor="@null" />
<TextView
android:id="@+id/primary_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/episode_image"
tools:text="Wonder Egg Priority" />
<TextView
android:id="@+id/secondary_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="@{String.format(@string/episode_name_extended, episode.parentIndexNumber, episode.indexNumber, episode.name)}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/primary_name"
tools:text="The Girl Flautist" />
<FrameLayout
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="4dp"
android:layout_marginHorizontal="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/button_setup_background"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/episode_image"
app:layout_constraintStart_toStartOf="parent"
tools:layout_width="50dp"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:orientation="horizontal"
tools:ignore="MissingDefaultResource"
>
<ImageView
android:id="@+id/header_icon"
android:layout_width="32dp"
android:layout_height="32dp"
/>
<TextView
android:id="@+id/header_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
/>
</LinearLayout>

View file

@ -1,258 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="MissingDefaultResource">
<include
android:id="@+id/error_layout"
layout="@layout/error_panel"
tools:visibility="gone" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/horizontal_margin"
android:layout_marginTop="12dp"
android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Alita: Battle Angel" />
<TextClock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:gravity="center_vertical"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="12:00" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/poster"
android:layout_width="320dp"
android:layout_height="180dp"
android:layout_marginStart="@dimen/horizontal_margin"
android:layout_marginTop="12dp"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Findroid.Image"
app:strokeColor="@null" />
<LinearLayout
android:id="@+id/main_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/horizontal_margin"
android:orientation="vertical"
app:layout_constraintStart_toEndOf="@id/poster"
app:layout_constraintTop_toTopOf="@id/poster">
<LinearLayout
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<TextView
android:id="@+id/year"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
tools:text="2019" />
<TextView
android:id="@+id/playtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
tools:text="122 min" />
<TextView
android:id="@+id/official_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
tools:text="PG-13" />
<TextView
android:id="@+id/community_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="4dp"
android:gravity="bottom"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:drawableStartCompat="@drawable/ic_star"
app:drawableTint="@color/yellow"
tools:text="7.3" />
</LinearLayout>
<TextView
android:id="@+id/genres"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="Action, Science Fiction, Adventure" />
<TextView
android:id="@+id/description"
android:layout_width="400dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="5"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
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/buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp">
<ImageButton
android:id="@+id/play_button"
android:layout_width="72dp"
android:layout_height="48dp"
android:background="@drawable/button_setup_background"
android:contentDescription="@string/play_button_description"
android:focusable="true"
android:focusableInTouchMode="true"
android:focusedByDefault="true"
android:nextFocusLeft="@id/play_button"
android:paddingHorizontal="24dp"
android:paddingVertical="12dp"
android:src="@drawable/ic_play">
<requestFocus />
</ImageButton>
<ProgressBar
android:id="@+id/progress_circular"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerHorizontal="true"
android:elevation="8dp"
android:indeterminateTint="@color/white"
android:padding="8dp"
android:visibility="invisible" />
</RelativeLayout>
<ImageButton
android:id="@+id/trailer_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:background="@drawable/button_accent_background"
android:contentDescription="@string/trailer_button_description"
android:focusable="true"
android:focusableInTouchMode="true"
android:padding="12dp"
android:src="@drawable/ic_film" />
<ImageButton
android:id="@+id/check_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:background="@drawable/button_accent_background"
android:contentDescription="@string/check_button_description"
android:focusable="true"
android:focusableInTouchMode="true"
android:padding="12dp"
android:src="@drawable/ic_check" />
<ImageButton
android:id="@+id/favorite_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button_accent_background"
android:contentDescription="@string/favorite_button_description"
android:focusable="true"
android:focusableInTouchMode="true"
android:padding="12dp"
android:src="@drawable/ic_heart" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/seasons_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/main_info">
<TextView
android:id="@+id/season_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/horizontal_margin"
android:layout_marginTop="32dp"
android:text="@string/seasons"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium" />
<androidx.leanback.widget.ListRowView
android:id="@+id/seasons_row"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/cast_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/seasons_layout">
<TextView
android:id="@+id/cast_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/horizontal_margin"
android:layout_marginTop="16dp"
android:text="@string/cast_amp_crew"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium" />
<androidx.leanback.widget.ListRowView
android:id="@+id/cast_row"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</FrameLayout>

View file

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="person"
type="org.jellyfin.sdk.model.api.BaseItemPerson" />
</data>
<LinearLayout
android:id="@+id/item_layout"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:background="@drawable/focus_border"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/person_image"
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_marginBottom="4dp"
android:scaleType="centerCrop"
app:personImage="@{person}"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Findroid.Image"
app:strokeColor="@null" />
<TextView
android:id="@+id/person_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="@{person.name}"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
tools:text="Rosa Salazar" />
<TextView
android:id="@+id/person_role"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="@{person.role}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
tools:text="Alita" />
</LinearLayout>
</layout>

View file

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="server"
type="dev.jdtech.jellyfin.models.Server" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/ripple_background"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingBottom="24dp">
<FrameLayout
android:id="@+id/server_icon"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:background="@drawable/button_setup_background"
app:layout_constraintBottom_toTopOf="@id/server_name"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:importantForAccessibility="no"
app:srcCompat="@drawable/ic_server" />
</FrameLayout>
<TextView
android:id="@+id/server_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{server.name}"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/server_icon"
tools:text="JDTech" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="@dimen/track_selection_item_height"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:ignore="MissingDefaultResource"
android:focusable="true"
android:focusableInTouchMode="true"
>
<Button
android:id="@+id/track_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:textSize="@dimen/track_selection_item_text_size"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:maxLines="1"
android:ellipsize="end"
tools:text="subtitle track"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.leanback.widget.BrowseFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/selector_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="afterDescendants"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:ignore="MissingDefaultResource"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/track_selector"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="afterDescendants"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:ignore="MissingDefaultResource"
/>
</androidx.leanback.widget.BrowseFrameLayout>

View file

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="dev.jdtech.jellyfin.models.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:background="@drawable/focus_border"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/user_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:duplicateParentState="true"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Findroid.Image"
app:userImage="@{user}" />
<TextView
android:id="@+id/user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="1"
android:text="@{user.name}"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_image"
tools:text="username" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:navGraph="@navigation/app_navigation" />
</FrameLayout>

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PlayerActivity">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
app:animation_enabled="false"
app:show_buffering="always" />
</FrameLayout>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@drawable/ic_banner_foreground"/>
</adaptive-icon>

View file

@ -39,42 +39,11 @@
app:destination="@id/addServerFragment" app:destination="@id/addServerFragment"
app:popUpTo="@id/homeFragment" app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" /> app:popUpToInclusive="true" />
<action
android:id="@+id/action_homeFragment_to_mediaDetailFragment"
app:destination="@id/mediaDetailFragment" />
<action <action
android:id="@+id/action_homeFragment_to_searchResultFragment" android:id="@+id/action_homeFragment_to_searchResultFragment"
app:destination="@id/searchResultFragment" /> app:destination="@id/searchResultFragment" />
</fragment> </fragment>
<fragment
android:id="@+id/homeFragmentTv"
android:name="dev.jdtech.jellyfin.tv.ui.HomeFragment"
android:label="@string/title_home" >
<action
android:id="@+id/action_homeFragment_to_mediaDetailFragment"
app:destination="@id/mediaDetailFragment" />
<action
android:id="@+id/action_homeFragment_to_settingsFragment"
app:destination="@id/twoPaneSettingsFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_homeFragment_to_addServerFragment"
app:destination="@id/addServerFragment"
app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_homeFragment_to_libraryFragment"
app:destination="@id/libraryFragment" />
</fragment>
<fragment <fragment
android:id="@+id/mediaFragment" android:id="@+id/mediaFragment"
android:name="dev.jdtech.jellyfin.fragments.MediaFragment" android:name="dev.jdtech.jellyfin.fragments.MediaFragment"
@ -133,9 +102,6 @@
app:exitAnim="@anim/nav_default_exit_anim" app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" /> app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_libraryFragment_to_mediaDetailFragment"
app:destination="@id/mediaDetailFragment" />
<argument <argument
android:name="libraryType" android:name="libraryType"
android:defaultValue="unknown" android:defaultValue="unknown"
@ -181,29 +147,7 @@
app:argType="boolean" app:argType="boolean"
android:defaultValue="false" /> android:defaultValue="false" />
</fragment> </fragment>
<fragment
android:id="@+id/mediaDetailFragment"
android:name="dev.jdtech.jellyfin.tv.ui.MediaDetailFragment"
android:label="{itemName}"
tools:layout="@layout/media_detail_fragment">
<argument
android:name="itemId"
app:argType="java.util.UUID" />
<argument
android:name="itemName"
android:defaultValue="Media Info"
app:argType="string"
app:nullable="true" />
<argument
android:name="itemType"
app:argType="org.jellyfin.sdk.model.api.BaseItemKind"
android:defaultValue="MOVIE" />
<action
android:id="@+id/action_mediaDetailFragment_to_playerActivity"
app:destination="@id/playerActivityTv" />
</fragment>
<fragment <fragment
android:id="@+id/seasonFragment" android:id="@+id/seasonFragment"
android:name="dev.jdtech.jellyfin.fragments.SeasonFragment" android:name="dev.jdtech.jellyfin.fragments.SeasonFragment"
@ -334,11 +278,6 @@
app:destination="@id/homeFragment" app:destination="@id/homeFragment"
app:popUpTo="@id/homeFragment" app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" /> app:popUpToInclusive="true" />
<action
android:id="@+id/action_serverSelectFragment_to_homeFragmentTv"
app:destination="@id/homeFragmentTv"
app:popUpTo="@id/homeFragmentTv"
app:popUpToInclusive="true" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/loginFragment" android:id="@+id/loginFragment"
@ -350,11 +289,6 @@
app:destination="@id/homeFragment" app:destination="@id/homeFragment"
app:popUpTo="@id/homeFragment" app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" /> app:popUpToInclusive="true" />
<action
android:id="@+id/action_loginFragment_to_homeFragmentTv"
app:destination="@id/homeFragmentTv"
app:popUpTo="@id/homeFragmentTv"
app:popUpToInclusive="true" />
<argument <argument
android:name="reLogin" android:name="reLogin"
app:argType="boolean" app:argType="boolean"
@ -386,16 +320,6 @@
app:argType="dev.jdtech.jellyfin.models.PlayerItem[]" /> app:argType="dev.jdtech.jellyfin.models.PlayerItem[]" />
</activity> </activity>
<activity
android:id="@+id/playerActivityTv"
android:name="dev.jdtech.jellyfin.tv.TvPlayerActivity"
android:label="activity_player_tv"
tools:layout="@layout/activity_player_tv">
<argument
android:name="items"
app:argType="dev.jdtech.jellyfin.models.PlayerItem[]" />
</activity>
<include app:graph="@navigation/aboutlibs_navigation" /> <include app:graph="@navigation/aboutlibs_navigation" />
<action <action
android:id="@+id/action_global_loginFragment" android:id="@+id/action_global_loginFragment"
@ -414,11 +338,6 @@
app:destination="@id/homeFragment" app:destination="@id/homeFragment"
app:popUpTo="@id/homeFragment" app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" /> app:popUpToInclusive="true" />
<action
android:id="@+id/action_usersFragment_to_homeFragmentTv"
app:destination="@id/homeFragmentTv"
app:popUpTo="@id/homeFragmentTv"
app:popUpToInclusive="true" />
<argument <argument
android:name="serverId" android:name="serverId"
app:argType="string" /> app:argType="string" />
@ -434,11 +353,6 @@
app:destination="@id/homeFragment" app:destination="@id/homeFragment"
app:popUpTo="@id/homeFragment" app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" /> app:popUpToInclusive="true" />
<action
android:id="@+id/action_usersFragment_to_homeFragmentTv"
app:destination="@id/homeFragmentTv"
app:popUpTo="@id/homeFragmentTv"
app:popUpToInclusive="true" />
<argument <argument
android:name="serverId" android:name="serverId"
app:argType="string" /> app:argType="string" />

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingDefaultResource">
<dimen name="horizontal_margin">48dp</dimen>
<dimen name="vertical_margin">48dp</dimen>
<dimen name="tv_controls_padding">8dp</dimen>
<dimen name="tv_controls_margin_start">32dp</dimen>
<dimen name="track_selection_item_height">48dp</dimen>
<dimen name="track_selection_item_text_size">16sp</dimen>
<item name="library_columns" type="integer">6</item>
</resources>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="roundedCardView">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">40dp</item>
</style>
</resources>

View file

@ -1,17 +0,0 @@
<resources>
<!-- Base application theme. -->
<style name="Theme.Findroid" parent="Theme.AppCompat.Leanback">
<item name="colorPrimary">@color/blue_600</item>
<item name="colorPrimaryVariant">@color/blue_800</item>
<item name="colorSecondary">@color/green_500</item>
<item name="colorSecondaryVariant">@color/green_800</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">@color/neutral_900</item>
<!-- Customize your theme here. -->
<item name="android:windowBackground">@color/neutral_900</item>
<item name="android:colorBackgroundFloating">@color/neutral_900</item>
<item name="defaultBrandColor">@color/neutral_900</item>
<item name="browseTitleViewLayout">@layout/browse_support_fragment_title_view</item>
</style>
</resources>