diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index aa8206fb..0c463e68 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -36,19 +36,6 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt b/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt
index d143ab49..ea934785 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt
@@ -1,69 +1,86 @@
package dev.jdtech.jellyfin
+import android.app.UiModeManager
+import android.content.res.Configuration
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
+import androidx.navigation.ui.NavigationUiSaveStateControl
import androidx.navigation.ui.setupActionBarWithNavController
import com.google.android.material.navigation.NavigationBarView
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.database.ServerDatabaseDao
-import dev.jdtech.jellyfin.databinding.ActivityMainAppBinding
+import dev.jdtech.jellyfin.databinding.ActivityMainBinding
import dev.jdtech.jellyfin.utils.loadDownloadLocation
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
- private lateinit var binding: ActivityMainAppBinding
+ private lateinit var binding: ActivityMainBinding
+ private lateinit var uiModeManager: UiModeManager
+
@Inject
lateinit var database: ServerDatabaseDao
+ @OptIn(NavigationUiSaveStateControl::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = ActivityMainAppBinding.inflate(layoutInflater)
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ uiModeManager = getSystemService(UI_MODE_SERVICE) as UiModeManager
setContentView(binding.root)
- val navView: NavigationBarView = binding.navView as NavigationBarView
-
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main) as NavHostFragment
-
- setSupportActionBar(binding.mainToolbar)
-
val navController = navHostFragment.navController
+ val inflater = navController.navInflater
+ val graph = inflater.inflate(R.navigation.app_navigation)
+
+ if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
+ graph.setStartDestination(R.id.homeFragmentTv)
+ }
val nServers = database.getServersCount()
if (nServers < 1) {
- val inflater = navController.navInflater
- val graph = inflater.inflate(R.navigation.app_navigation)
graph.setStartDestination(R.id.addServerFragment)
- navController.setGraph(graph, intent.extras)
}
- // Passing each menu ID as a set of Ids because each
- // menu should be considered as top level destinations.
- val appBarConfiguration = AppBarConfiguration(
- setOf(
- R.id.homeFragment, R.id.mediaFragment, R.id.favoriteFragment, R.id.downloadFragment
+ navController.setGraph(graph, intent.extras)
+
+ if (uiModeManager.currentModeType != Configuration.UI_MODE_TYPE_TELEVISION) {
+ val navView: NavigationBarView = binding.navView as NavigationBarView
+
+ setSupportActionBar(binding.mainToolbar)
+
+ // Passing each menu ID as a set of Ids because each
+ // menu should be considered as top level destinations.
+ val appBarConfiguration = AppBarConfiguration(
+ setOf(
+ R.id.homeFragment,
+ R.id.mediaFragment,
+ R.id.favoriteFragment,
+ R.id.downloadFragment
+ )
)
- )
- setupActionBarWithNavController(navController, appBarConfiguration)
- // navView.setupWithNavController(navController)
- // Don't save the state of other main navigation items, only this experimental function allows turning off this behavior
- NavigationUI.setupWithNavController(navView, navController, false)
+ setupActionBarWithNavController(navController, appBarConfiguration)
+ // navView.setupWithNavController(navController)
+ // Don't save the state of other main navigation items, only this experimental function allows turning off this behavior
+ NavigationUI.setupWithNavController(navView, navController, false)
- navController.addOnDestinationChangedListener { _, destination, _ ->
- binding.navView.visibility = when (destination.id) {
- R.id.twoPaneSettingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, R.id.about_libraries_dest -> View.GONE
- else -> View.VISIBLE
+ navController.addOnDestinationChangedListener { _, destination, _ ->
+ binding.navView!!.visibility = when (destination.id) {
+ R.id.twoPaneSettingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, R.id.about_libraries_dest -> View.GONE
+ 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)
diff --git a/app/src/main/java/dev/jdtech/jellyfin/MainActivityTv.kt b/app/src/main/java/dev/jdtech/jellyfin/MainActivityTv.kt
deleted file mode 100644
index f7c7a1fb..00000000
--- a/app/src/main/java/dev/jdtech/jellyfin/MainActivityTv.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package dev.jdtech.jellyfin
-
-import android.os.Bundle
-import androidx.fragment.app.FragmentActivity
-import androidx.navigation.fragment.NavHostFragment
-import dagger.hilt.android.AndroidEntryPoint
-import dev.jdtech.jellyfin.database.ServerDatabaseDao
-import dev.jdtech.jellyfin.databinding.ActivityMainTvBinding
-import dev.jdtech.jellyfin.utils.loadDownloadLocation
-import javax.inject.Inject
-
-@AndroidEntryPoint
-internal class MainActivityTv : FragmentActivity() {
-
- private lateinit var binding: ActivityMainTvBinding
- @Inject
- lateinit var database: ServerDatabaseDao
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- binding = ActivityMainTvBinding.inflate(layoutInflater)
- setContentView(binding.root)
- val navHostFragment =
- supportFragmentManager.findFragmentById(R.id.tv_nav_host) as NavHostFragment
- val navController = navHostFragment.navController
- val nServers = database.getServersCount()
- if (nServers < 1) {
- val inflater = navController.navInflater
- val graph = inflater.inflate(R.navigation.tv_navigation)
- graph.setStartDestination(R.id.addServerTvFragment)
- navController.setGraph(graph, intent.extras)
- }
-
- loadDownloadLocation(applicationContext)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/adapters/ViewListAdapter.kt b/app/src/main/java/dev/jdtech/jellyfin/adapters/ViewListAdapter.kt
index 1d4448bf..6cef5147 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/adapters/ViewListAdapter.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/adapters/ViewListAdapter.kt
@@ -94,6 +94,7 @@ class ViewListAdapter(
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
+ is HomeItem.Libraries -> -1
is HomeItem.Section -> ITEM_VIEW_TYPE_NEXT_UP
is HomeItem.ViewItem -> ITEM_VIEW_TYPE_VIEW
}
@@ -105,6 +106,10 @@ class ViewListAdapter(
}
sealed class HomeItem {
+ data class Libraries(val section: HomeSection) : HomeItem() {
+ override val id = section.id
+ }
+
data class Section(val homeSection: HomeSection) : HomeItem() {
override val id = homeSection.id
}
diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt
index b09da829..5adf1656 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt
@@ -1,10 +1,14 @@
package dev.jdtech.jellyfin.fragments
+import android.app.UiModeManager
+import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -22,6 +26,7 @@ import timber.log.Timber
class AddServerFragment : Fragment() {
private lateinit var binding: FragmentAddServerBinding
+ private lateinit var uiModeManager: UiModeManager
private val viewModel: AddServerViewModel by viewModels()
override fun onCreateView(
@@ -29,8 +34,10 @@ class AddServerFragment : Fragment() {
savedInstanceState: Bundle?
): View {
binding = FragmentAddServerBinding.inflate(inflater)
+ uiModeManager =
+ requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
- binding.editTextServerAddress.setOnEditorActionListener { _, actionId, _ ->
+ (binding.editTextServerAddress as AppCompatEditText).setOnEditorActionListener { _, actionId, _ ->
return@setOnEditorActionListener when (actionId) {
EditorInfo.IME_ACTION_GO -> {
connectToServer()
@@ -56,6 +63,7 @@ class AddServerFragment : Fragment() {
}
}
}
+
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToLogin.collect {
@@ -77,17 +85,25 @@ class AddServerFragment : Fragment() {
private fun bindUiStateError(uiState: AddServerViewModel.UiState.Error) {
binding.buttonConnect.isEnabled = true
binding.progressCircular.isVisible = false
- binding.editTextServerAddressLayout.error = uiState.message
+ if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
+ (binding.editTextServerAddress as AppCompatEditText).error = uiState.message
+ } else {
+ binding.editTextServerAddressLayout!!.error = uiState.message
+ }
}
private fun bindUiStateLoading() {
binding.buttonConnect.isEnabled = false
binding.progressCircular.isVisible = true
- binding.editTextServerAddressLayout.error = null
+ if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
+ (binding.editTextServerAddress as AppCompatEditText).error = null
+ } else {
+ binding.editTextServerAddressLayout!!.error = null
+ }
}
private fun connectToServer() {
- val serverAddress = binding.editTextServerAddress.text.toString()
+ val serverAddress = (binding.editTextServerAddress as AppCompatEditText).text.toString()
viewModel.checkServer(serverAddress.removeSuffix("/"))
}
diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt
index d0a9fc01..ad653229 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/HomeFragment.kt
@@ -77,12 +77,12 @@ class HomeFragment : Fragment() {
override fun onResume() {
super.onResume()
- viewModel.refreshData()
+ viewModel.loadData()
}
private fun setupView() {
binding.refreshLayout.setOnRefreshListener {
- viewModel.refreshData()
+ viewModel.loadData()
}
binding.viewsRecyclerView.adapter = ViewListAdapter(
@@ -100,7 +100,7 @@ class HomeFragment : Fragment() {
})
binding.errorLayout.errorRetryButton.setOnClickListener {
- viewModel.refreshData()
+ viewModel.loadData()
}
binding.errorLayout.errorDetailsButton.setOnClickListener {
@@ -187,7 +187,7 @@ class HomeFragment : Fragment() {
private fun navigateToSettingsFragment() {
findNavController().navigate(
- HomeFragmentDirections.actionNavigationHomeToNavigationSettings()
+ HomeFragmentDirections.actionHomeFragmentToSettingsFragment()
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt
index 771afffb..cb635204 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/LibraryFragment.kt
@@ -1,8 +1,11 @@
package dev.jdtech.jellyfin.fragments
+import android.app.UiModeManager
import android.content.SharedPreferences
+import android.content.res.Configuration
import android.os.Bundle
import android.view.*
+import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible
@@ -14,6 +17,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.paging.LoadState
+import androidx.recyclerview.widget.LinearSnapHelper
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
@@ -33,6 +37,7 @@ import javax.inject.Inject
class LibraryFragment : Fragment() {
private lateinit var binding: FragmentLibraryBinding
+ private lateinit var uiModeManager: UiModeManager
private val viewModel: LibraryViewModel by viewModels()
private val args: LibraryFragmentArgs by navArgs()
@@ -46,6 +51,8 @@ class LibraryFragment : Fragment() {
savedInstanceState: Bundle?
): View {
binding = FragmentLibraryBinding.inflate(inflater, container, false)
+ uiModeManager =
+ requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
return binding.root
}
@@ -91,6 +98,8 @@ class LibraryFragment : Fragment() {
}, viewLifecycleOwner, Lifecycle.State.RESUMED
)
+ binding.title?.text = args.libraryName
+
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadItems(args.libraryId, args.libraryType)
}
@@ -102,6 +111,11 @@ class LibraryFragment : Fragment() {
)
}
+ if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
+ val snapHelper = LinearSnapHelper()
+ snapHelper.attachToRecyclerView(binding.itemsRecyclerView)
+ }
+
binding.itemsRecyclerView.adapter =
ViewItemPagingAdapter(ViewItemPagingAdapter.OnClickListener { item ->
navigateToMediaInfoFragment(item)
@@ -182,12 +196,22 @@ class LibraryFragment : Fragment() {
}
private fun navigateToMediaInfoFragment(item: BaseItemDto) {
- findNavController().navigate(
- LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment(
- item.id,
- item.name,
- item.type
+ if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
+ findNavController().navigate(
+ LibraryFragmentDirections.actionLibraryFragmentToMediaDetailFragment(
+ item.id,
+ item.name,
+ item.type
+ )
)
- )
+ } else {
+ findNavController().navigate(
+ LibraryFragmentDirections.actionLibraryFragmentToMediaInfoFragment(
+ item.id,
+ item.name,
+ item.type
+ )
+ )
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/LoginFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/LoginFragment.kt
index 285fc87f..af712d24 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/fragments/LoginFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/LoginFragment.kt
@@ -1,10 +1,14 @@
package dev.jdtech.jellyfin.fragments
+import android.app.UiModeManager
+import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -22,6 +26,7 @@ import timber.log.Timber
class LoginFragment : Fragment() {
private lateinit var binding: FragmentLoginBinding
+ private lateinit var uiModeManager: UiModeManager
private val viewModel: LoginViewModel by viewModels()
override fun onCreateView(
@@ -29,8 +34,10 @@ class LoginFragment : Fragment() {
savedInstanceState: Bundle?
): View {
binding = FragmentLoginBinding.inflate(inflater)
+ uiModeManager =
+ requireContext().getSystemService(AppCompatActivity.UI_MODE_SERVICE) as UiModeManager
- binding.editTextPassword.setOnEditorActionListener { _, actionId, _ ->
+ (binding.editTextPassword as AppCompatEditText).setOnEditorActionListener { _, actionId, _ ->
return@setOnEditorActionListener when (actionId) {
EditorInfo.IME_ACTION_GO -> {
login()
@@ -61,7 +68,7 @@ class LoginFragment : Fragment() {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToMain.collect {
if (it) {
- navigateToMainActivity()
+ navigateToHomeFragment()
}
}
}
@@ -78,22 +85,34 @@ class LoginFragment : Fragment() {
private fun bindUiStateError(uiState: LoginViewModel.UiState.Error) {
binding.buttonLogin.isEnabled = true
binding.progressCircular.isVisible = false
- binding.editTextUsernameLayout.error = uiState.message
+ if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
+ (binding.editTextUsername as AppCompatEditText).error = uiState.message
+ } else {
+ binding.editTextUsernameLayout!!.error = uiState.message
+ }
}
private fun bindUiStateLoading() {
binding.buttonLogin.isEnabled = false
binding.progressCircular.isVisible = true
- binding.editTextUsernameLayout.error = null
+ if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
+ (binding.editTextUsername as AppCompatEditText).error = null
+ } else {
+ binding.editTextUsernameLayout!!.error = null
+ }
}
private fun login() {
- val username = binding.editTextUsername.text.toString()
- val password = binding.editTextPassword.text.toString()
+ val username = (binding.editTextUsername as AppCompatEditText).text.toString()
+ val password = (binding.editTextPassword as AppCompatEditText).text.toString()
viewModel.login(username, password)
}
- private fun navigateToMainActivity() {
- findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToNavigationHome())
+ private fun navigateToHomeFragment() {
+ if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) {
+ findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeFragmentTv())
+ } else {
+ findNavController().navigate(LoginFragmentDirections.actionLoginFragmentToHomeFragment())
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/HomeFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/tv/ui/HomeFragment.kt
index a956dc81..73a70b2f 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/HomeFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/tv/ui/HomeFragment.kt
@@ -1,16 +1,16 @@
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.HeaderItem
-import androidx.leanback.widget.ListRow
-import androidx.leanback.widget.ListRowPresenter
+import androidx.leanback.widget.*
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
@@ -18,6 +18,7 @@ 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
@@ -29,12 +30,21 @@ internal class HomeFragment : BrowseSupportFragment() {
private val viewModel: HomeViewModel by viewModels()
private lateinit var rowsAdapter: ArrayObjectAdapter
+ private lateinit var uiModeManager: UiModeManager
+
+ private val adapterMap = mutableMapOf()
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(ListRowPresenter())
+ rowsAdapter = ArrayObjectAdapter(rowPresenter)
adapter = rowsAdapter
}
@@ -59,21 +69,42 @@ internal class HomeFragment : BrowseSupportFragment() {
Timber.d("$uiState")
when (uiState) {
is HomeViewModel.UiState.Normal -> bindUiStateNormal(uiState)
- is HomeViewModel.UiState.Loading -> Unit
+ 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() {
+ 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.clear()
- homeItems.map { section -> rowsAdapter.add(section.toListRow()) }
+ rowsAdapter.setItems(homeItems.map { homeItem -> homeItem.toListRow() }, diffCallbackListRow)
}
}
+ private fun bindUiStateLoading() {
+ progressBarManager.show()
+ }
+
private fun HomeItem.toListRow(): ListRow {
return ListRow(
toHeader(),
@@ -83,6 +114,7 @@ internal class HomeFragment : BrowseSupportFragment() {
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(
@@ -93,14 +125,59 @@ internal class HomeFragment : BrowseSupportFragment() {
}
}
+ val diffCallback = object : DiffCallback() {
+ 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 {
- return when (this) {
- is HomeItem.Section -> ArrayObjectAdapter(DynamicMediaItemPresenter { item ->
- navigateToMediaDetailFragment(item)
- }).apply { addAll(0, homeSection.items) }
- is HomeItem.ViewItem -> ArrayObjectAdapter(MediaItemPresenter { item ->
- navigateToMediaDetailFragment(item)
- }).apply { addAll(0, view.items) }
+ 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
+ )
+ )
}
}
@@ -116,7 +193,7 @@ internal class HomeFragment : BrowseSupportFragment() {
private fun navigateToSettingsFragment() {
findNavController().navigate(
- HomeFragmentDirections.actionNavigationHomeToSettings()
+ HomeFragmentDirections.actionHomeFragmentToSettingsFragment()
)
}
}
diff --git a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/MediaDetailFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/tv/ui/MediaDetailFragment.kt
index c8b543e6..1fd8b9e6 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/MediaDetailFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/tv/ui/MediaDetailFragment.kt
@@ -121,11 +121,9 @@ internal class MediaDetailFragment : Fragment() {
when (viewModel.played) {
true -> {
viewModel.markAsUnplayed(args.itemId)
- val typedValue = TypedValue()
- requireActivity().theme.resolveAttribute(R.attr.colorOnSecondaryContainer, typedValue, true)
binding.checkButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
- typedValue.resourceId,
+ R.color.white,
requireActivity().theme
)
)
@@ -147,11 +145,9 @@ internal class MediaDetailFragment : Fragment() {
true -> {
viewModel.unmarkAsFavorite(args.itemId)
binding.favoriteButton.setImageResource(R.drawable.ic_heart)
- val typedValue = TypedValue()
- requireActivity().theme.resolveAttribute(R.attr.colorOnSecondaryContainer, typedValue, true)
binding.favoriteButton.imageTintList = ColorStateList.valueOf(
resources.getColor(
- typedValue.resourceId,
+ R.color.white,
requireActivity().theme
)
)
@@ -168,16 +164,14 @@ internal class MediaDetailFragment : Fragment() {
}
}
}
-
- binding.backButton.setOnClickListener { activity?.onBackPressed() }
}
private fun bindUiStateNormal(uiState: MediaInfoViewModel.UiState.Normal) {
uiState.apply {
- binding.seasonTitle.isVisible = seasons.isNotEmpty()
+ binding.seasonsLayout.isVisible = seasons.isNotEmpty()
val seasonsAdapter = binding.seasonsRow.gridView.adapter as ViewItemListAdapter
seasonsAdapter.submitList(seasons)
- binding.castTitle.isVisible = actors.isNotEmpty()
+ binding.castLayout.isVisible = actors.isNotEmpty()
val actorsAdapter = binding.castRow.gridView.adapter as PersonListAdapter
actorsAdapter.submitList(actors)
@@ -217,11 +211,6 @@ internal class MediaDetailFragment : Fragment() {
)
binding.title.text = item.name
- binding.subtitle.text = item.seriesName
- item.seriesName.let {
- binding.subtitle.text = it
- binding.subtitle.isVisible = true
- }
binding.genres.text = genresString
binding.year.text = dateString
binding.playtime.text = runTime
diff --git a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/MediaSectionPresenter.kt b/app/src/main/java/dev/jdtech/jellyfin/tv/ui/MediaSectionPresenter.kt
index f7707a25..9f72ffdf 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/MediaSectionPresenter.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/tv/ui/MediaSectionPresenter.kt
@@ -1,6 +1,5 @@
package dev.jdtech.jellyfin.tv.ui
-import android.content.Context.LAYOUT_INFLATER_SERVICE
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
@@ -10,25 +9,52 @@ 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(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 {
- val mediaView =
- BaseItemBinding
- .inflate(parent.context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater)
- .root
- return ViewHolder(mediaView)
+ return ViewHolder(
+ BaseItemBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ ).root
+ )
}
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
if (item is BaseItemDto) {
DataBindingUtil.getBinding(viewHolder.view)?.apply {
this.item = item
- this.itemName.text = if (item.type == BaseItemKind.EPISODE) item.seriesName else item.name
+ 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 =
@@ -45,11 +71,13 @@ class MediaItemPresenter(private val onClick: (BaseItemDto) -> Unit) : Presenter
class DynamicMediaItemPresenter(private val onClick: (BaseItemDto) -> Unit) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
- val mediaView =
- HomeEpisodeItemBinding
- .inflate(parent.context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater)
- .root
- return ViewHolder(mediaView)
+ return ViewHolder(
+ HomeEpisodeItemBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ ).root
+ )
}
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
@@ -59,7 +87,8 @@ class DynamicMediaItemPresenter(private val onClick: (BaseItemDto) -> Unit) : Pr
item.userData?.playedPercentage?.toInt()?.let {
progressBar.layoutParams.width = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
- (it.times(2.24)).toFloat(), progressBar.context.resources.displayMetrics).toInt()
+ (it.times(2.24)).toFloat(), progressBar.context.resources.displayMetrics
+ ).toInt()
progressBar.isVisible = true
}
diff --git a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/TvAddServerFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/tv/ui/TvAddServerFragment.kt
deleted file mode 100644
index e5aff469..00000000
--- a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/TvAddServerFragment.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-package dev.jdtech.jellyfin.tv.ui
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.inputmethod.EditorInfo
-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 dagger.hilt.android.AndroidEntryPoint
-import dev.jdtech.jellyfin.databinding.TvAddServerFragmentBinding
-import dev.jdtech.jellyfin.viewmodels.AddServerViewModel
-import kotlinx.coroutines.launch
-import timber.log.Timber
-
-@AndroidEntryPoint
-internal class TvAddServerFragment : Fragment() {
-
- private lateinit var binding: TvAddServerFragmentBinding
- private val viewModel: AddServerViewModel by viewModels()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- binding = TvAddServerFragmentBinding.inflate(inflater)
-
- binding.editTextServerAddress.setOnEditorActionListener { _, actionId, _ ->
- return@setOnEditorActionListener when (actionId) {
- EditorInfo.IME_ACTION_GO -> {
- connectToServer()
- true
- }
- else -> false
- }
- }
-
- binding.buttonConnect.setOnClickListener {
- connectToServer()
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.uiState.collect { uiState ->
- Timber.d("$uiState")
- when (uiState) {
- is AddServerViewModel.UiState.Normal -> bindUiStateNormal()
- is AddServerViewModel.UiState.Error -> bindUiStateError(uiState)
- is AddServerViewModel.UiState.Loading -> bindUiStateLoading()
- }
- }
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.navigateToLogin.collect {
- if (it) {
- navigateToLoginFragment()
- }
- }
- }
- }
-
- return binding.root
- }
-
- private fun bindUiStateNormal() {
- binding.buttonConnect.isEnabled = true
- binding.progressCircular.isVisible = false
- }
-
- private fun bindUiStateError(uiState: AddServerViewModel.UiState.Error) {
- binding.buttonConnect.isEnabled = true
- binding.progressCircular.isVisible = false
- binding.editTextServerAddress.error = uiState.message
- }
-
- private fun bindUiStateLoading() {
- binding.buttonConnect.isEnabled = false
- binding.progressCircular.isVisible = true
- binding.editTextServerAddress.error = null
- }
-
- private fun connectToServer() {
- val serverAddress = binding.editTextServerAddress.text.toString()
- viewModel.checkServer(serverAddress.removeSuffix("/"))
- }
-
- private fun navigateToLoginFragment() {
- findNavController().navigate(TvAddServerFragmentDirections.actionAddServerFragmentToLoginFragment())
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/TvLoginFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/tv/ui/TvLoginFragment.kt
deleted file mode 100644
index f83e81f1..00000000
--- a/app/src/main/java/dev/jdtech/jellyfin/tv/ui/TvLoginFragment.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-package dev.jdtech.jellyfin.tv.ui
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.inputmethod.EditorInfo
-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 dagger.hilt.android.AndroidEntryPoint
-import dev.jdtech.jellyfin.databinding.TvLoginFragmentBinding
-import dev.jdtech.jellyfin.viewmodels.LoginViewModel
-import kotlinx.coroutines.launch
-import timber.log.Timber
-
-@AndroidEntryPoint
-class TvLoginFragment : Fragment() {
-
- private lateinit var binding: TvLoginFragmentBinding
- private val viewModel: LoginViewModel by viewModels()
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- binding = TvLoginFragmentBinding.inflate(inflater)
-
- binding.editTextPassword.setOnEditorActionListener { _, actionId, _ ->
- return@setOnEditorActionListener when (actionId) {
- EditorInfo.IME_ACTION_GO -> {
- login()
- true
- }
- else -> false
- }
- }
-
- binding.buttonLogin.setOnClickListener {
- login()
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.uiState.collect { uiState ->
- Timber.d("$uiState")
- when(uiState) {
- is LoginViewModel.UiState.Normal -> bindUiStateNormal()
- is LoginViewModel.UiState.Error -> bindUiStateError(uiState)
- is LoginViewModel.UiState.Loading -> bindUiStateLoading()
- }
- }
- }
- }
-
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.navigateToMain.collect {
- if (it) {
- navigateToMainActivity()
- }
- }
- }
- }
-
- return binding.root
- }
-
- private fun bindUiStateNormal() {
- binding.buttonLogin.isEnabled = true
- binding.progressCircular.isVisible = false
- }
-
- private fun bindUiStateError(uiState: LoginViewModel.UiState.Error) {
- binding.buttonLogin.isEnabled = true
- binding.progressCircular.isVisible = false
- binding.editTextUsername.error = uiState.message
- }
-
- private fun bindUiStateLoading() {
- binding.buttonLogin.isEnabled = false
- binding.progressCircular.isVisible = true
- binding.editTextUsername.error = null
- }
-
- private fun login() {
- val username = binding.editTextUsername.text.toString()
- val password = binding.editTextPassword.text.toString()
- viewModel.login(username, password)
- }
-
- private fun navigateToMainActivity() {
- findNavController().navigate(TvLoginFragmentDirections.actionLoginFragmentToNavigationHome())
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt
index 99f195d5..0008ba7d 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt
@@ -10,11 +10,8 @@ import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
+import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import org.jellyfin.sdk.discovery.RecommendedServerInfo
import org.jellyfin.sdk.discovery.RecommendedServerInfoScore
import org.jellyfin.sdk.discovery.RecommendedServerIssue
@@ -68,7 +65,6 @@ constructor(
RecommendedServerInfoScore.OK
)
- val greatServers = mutableListOf()
val goodServers = mutableListOf()
val okServers = mutableListOf()
@@ -76,9 +72,6 @@ constructor(
.onCompletion {
if (serverFound) return@onCompletion
when {
- greatServers.isNotEmpty() -> {
- connectToServer(greatServers.first())
- }
goodServers.isNotEmpty() -> {
val issuesString = createIssuesString(goodServers.first())
Toast.makeText(
@@ -109,6 +102,8 @@ constructor(
RecommendedServerInfoScore.BAD -> Unit
}
}
+ } catch (e: CancellationException) {
+
} catch (e: Exception) {
_uiState.emit(
UiState.Error(
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt
index 991ca534..14e17af4 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt
@@ -38,18 +38,25 @@ class HomeViewModel @Inject internal constructor(
}
init {
- loadData(updateCapabilities = true)
+ viewModelScope.launch {
+ try {
+ repository.postCapabilities()
+ } catch (e: Exception) {
+ }
+ }
}
- fun refreshData() = loadData(updateCapabilities = false)
-
- private fun loadData(updateCapabilities: Boolean) {
+ fun loadData(includeLibraries: Boolean = false) {
viewModelScope.launch {
_uiState.emit(UiState.Loading)
try {
- if (updateCapabilities) repository.postCapabilities()
+ val items = mutableListOf()
- val updated = loadDynamicItems() + loadViews()
+ if (includeLibraries) {
+ items.add(loadLibraries())
+ }
+
+ val updated = items + loadDynamicItems() + loadViews()
withContext(Dispatchers.Default) {
syncPlaybackProgress(downloadDatabase, repository)
@@ -61,6 +68,19 @@ class HomeViewModel @Inject internal constructor(
}
}
+ private suspend fun loadLibraries(): HomeItem {
+ val items = repository.getItems()
+ val collections =
+ items.filter { collection -> CollectionType.unsupportedCollections.none { it.type == collection.collectionType } }
+ return HomeItem.Libraries(
+ HomeSection(
+ UUID.fromString("38f5ca96-9e4b-4c0e-a8e4-02225ed07e02"),
+ application.resources.getString(R.string.libraries),
+ collections
+ )
+ )
+ }
+
private suspend fun loadDynamicItems(): List {
val resumeItems = repository.getResumeItems()
val nextUpItems = repository.getNextUp()
diff --git a/app/src/main/res/drawable-television/button_accent_background.xml b/app/src/main/res/drawable-television/button_accent_background.xml
new file mode 100644
index 00000000..7cb3b3f7
--- /dev/null
+++ b/app/src/main/res/drawable-television/button_accent_background.xml
@@ -0,0 +1,16 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-television/button_setup_background.xml b/app/src/main/res/drawable-television/button_setup_background.xml
new file mode 100644
index 00000000..5079712d
--- /dev/null
+++ b/app/src/main/res/drawable-television/button_setup_background.xml
@@ -0,0 +1,16 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/focus_border.xml b/app/src/main/res/drawable/focus_border.xml
new file mode 100644
index 00000000..b2dc9917
--- /dev/null
+++ b/app/src/main/res/drawable/focus_border.xml
@@ -0,0 +1,18 @@
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml
index 0bf84f11..17da1f55 100644
--- a/app/src/main/res/drawable/ic_play.xml
+++ b/app/src/main/res/drawable/ic_play.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:viewportHeight="24">
-
+ android:layout_height="match_parent">
+ app:navGraph="@navigation/app_navigation" />
\ No newline at end of file
diff --git a/app/src/main/res/layout-television/base_item.xml b/app/src/main/res/layout-television/base_item.xml
new file mode 100644
index 00000000..21d15548
--- /dev/null
+++ b/app/src/main/res/layout-television/base_item.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-television/browse_support_fragment_title_view.xml b/app/src/main/res/layout-television/browse_support_fragment_title_view.xml
index e7abcde5..8cd301fc 100644
--- a/app/src/main/res/layout-television/browse_support_fragment_title_view.xml
+++ b/app/src/main/res/layout-television/browse_support_fragment_title_view.xml
@@ -1,38 +1,34 @@
-
+ tools:ignore="MissingDefaultResource">
+ android:src="@drawable/ic_settings"
+ app:layout_constraintEnd_toStartOf="@+id/clock"
+ app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="12:00" />
\ No newline at end of file
diff --git a/app/src/main/res/layout-television/collection_item.xml b/app/src/main/res/layout-television/collection_item.xml
new file mode 100644
index 00000000..df6a573b
--- /dev/null
+++ b/app/src/main/res/layout-television/collection_item.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-television/tv_add_server_fragment.xml b/app/src/main/res/layout-television/fragment_add_server.xml
similarity index 90%
rename from app/src/main/res/layout-television/tv_add_server_fragment.xml
rename to app/src/main/res/layout-television/fragment_add_server.xml
index b4863eeb..81cb1302 100644
--- a/app/src/main/res/layout-television/tv_add_server_fragment.xml
+++ b/app/src/main/res/layout-television/fragment_add_server.xml
@@ -1,14 +1,15 @@
-
-
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ tools:context=".fragments.AddServerFragment">
+ android:layout_height="wrap_content">
@@ -76,4 +78,4 @@
-
+
diff --git a/app/src/main/res/layout-television/fragment_library.xml b/app/src/main/res/layout-television/fragment_library.xml
new file mode 100644
index 00000000..89133813
--- /dev/null
+++ b/app/src/main/res/layout-television/fragment_library.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-television/tv_login_fragment.xml b/app/src/main/res/layout-television/fragment_login.xml
similarity index 92%
rename from app/src/main/res/layout-television/tv_login_fragment.xml
rename to app/src/main/res/layout-television/fragment_login.xml
index f8474f31..e3c115c7 100644
--- a/app/src/main/res/layout-television/tv_login_fragment.xml
+++ b/app/src/main/res/layout-television/fragment_login.xml
@@ -1,14 +1,15 @@
-
-
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fillViewport="true"
+ tools:context=".fragments.LoginFragment">
+ android:layout_height="wrap_content">
+
-
+
diff --git a/app/src/main/res/layout-television/home_episode_item.xml b/app/src/main/res/layout-television/home_episode_item.xml
new file mode 100644
index 00000000..e67188e3
--- /dev/null
+++ b/app/src/main/res/layout-television/home_episode_item.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-television/media_detail_fragment.xml b/app/src/main/res/layout-television/media_detail_fragment.xml
index 57ec35e0..bc100652 100644
--- a/app/src/main/res/layout-television/media_detail_fragment.xml
+++ b/app/src/main/res/layout-television/media_detail_fragment.xml
@@ -19,27 +19,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
-
-
@@ -47,6 +34,7 @@
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"
@@ -59,189 +47,209 @@
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" />
-
+ app:layout_constraintTop_toTopOf="@id/poster">
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+ app:layout_constraintTop_toBottomOf="@id/main_info">
-
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/seasons_layout">
-
+
-
+
+
+
diff --git a/app/src/main/res/layout-television/person_item.xml b/app/src/main/res/layout-television/person_item.xml
new file mode 100644
index 00000000..c5491990
--- /dev/null
+++ b/app/src/main/res/layout-television/person_item.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-w600dp-television/activity_main.xml b/app/src/main/res/layout-w600dp-television/activity_main.xml
new file mode 120000
index 00000000..48cb2e0b
--- /dev/null
+++ b/app/src/main/res/layout-w600dp-television/activity_main.xml
@@ -0,0 +1 @@
+../layout-television/activity_main.xml
\ No newline at end of file
diff --git a/app/src/main/res/layout-w600dp/activity_main.xml b/app/src/main/res/layout-w600dp/activity_main.xml
new file mode 100644
index 00000000..2847a342
--- /dev/null
+++ b/app/src/main/res/layout-w600dp/activity_main.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-w600dp/activity_main_app.xml b/app/src/main/res/layout-w600dp/activity_main_app.xml
deleted file mode 100644
index 82714c02..00000000
--- a/app/src/main/res/layout-w600dp/activity_main_app.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..9911d987
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main_app.xml b/app/src/main/res/layout/activity_main_app.xml
deleted file mode 100644
index ab84762f..00000000
--- a/app/src/main/res/layout/activity_main_app.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/home_episode_item.xml b/app/src/main/res/layout/home_episode_item.xml
index 90c936c7..adb58f43 100644
--- a/app/src/main/res/layout/home_episode_item.xml
+++ b/app/src/main/res/layout/home_episode_item.xml
@@ -21,7 +21,7 @@
@@ -44,7 +44,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
- android:maxLines="2"
+ android:maxLines="1"
android:text="@{person.role}"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
tools:text="Alita" />
diff --git a/app/src/main/res/navigation/app_navigation.xml b/app/src/main/res/navigation/app_navigation.xml
index 4c390690..2323af3b 100644
--- a/app/src/main/res/navigation/app_navigation.xml
+++ b/app/src/main/res/navigation/app_navigation.xml
@@ -28,7 +28,7 @@
android:id="@+id/action_navigation_home_to_episodeBottomSheetFragment"
app:destination="@id/episodeBottomSheetFragment" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values-television/dimens.xml b/app/src/main/res/values-television/dimens.xml
index e0bc502a..5492eb53 100644
--- a/app/src/main/res/values-television/dimens.xml
+++ b/app/src/main/res/values-television/dimens.xml
@@ -11,4 +11,6 @@
48dp
16sp
+ - 6
+
\ No newline at end of file
diff --git a/app/src/main/res/values-television/themes.xml b/app/src/main/res/values-television/themes.xml
index 862c7243..8b60622e 100644
--- a/app/src/main/res/values-television/themes.xml
+++ b/app/src/main/res/values-television/themes.xml
@@ -1,6 +1,6 @@
-