mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-27 23:40:33 +05:30
570 lines
20 KiB
Kotlin
570 lines
20 KiB
Kotlin
package com.github.libretube.activities
|
|
|
|
import android.content.Intent
|
|
import android.content.pm.ActivityInfo
|
|
import android.content.res.Configuration
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.os.Handler
|
|
import android.os.Looper
|
|
import android.util.Log
|
|
import android.view.Menu
|
|
import android.view.MenuItem
|
|
import android.view.View
|
|
import android.view.WindowInsets
|
|
import android.view.WindowInsetsController
|
|
import android.view.WindowManager
|
|
import android.widget.LinearLayout
|
|
import android.widget.Toast
|
|
import androidx.activity.OnBackPressedCallback
|
|
import androidx.appcompat.widget.SearchView
|
|
import androidx.constraintlayout.motion.widget.MotionLayout
|
|
import androidx.constraintlayout.widget.ConstraintLayout
|
|
import androidx.core.os.bundleOf
|
|
import androidx.lifecycle.ViewModelProvider
|
|
import androidx.navigation.NavController
|
|
import androidx.navigation.findNavController
|
|
import androidx.navigation.ui.setupWithNavController
|
|
import com.github.libretube.R
|
|
import com.github.libretube.constants.PreferenceKeys
|
|
import com.github.libretube.databinding.ActivityMainBinding
|
|
import com.github.libretube.dialogs.ErrorDialog
|
|
import com.github.libretube.extensions.BaseActivity
|
|
import com.github.libretube.extensions.TAG
|
|
import com.github.libretube.fragments.PlayerFragment
|
|
import com.github.libretube.models.PlayerViewModel
|
|
import com.github.libretube.models.SearchViewModel
|
|
import com.github.libretube.services.ClosingService
|
|
import com.github.libretube.util.NetworkHelper
|
|
import com.github.libretube.util.PreferenceHelper
|
|
import com.github.libretube.util.ThemeHelper
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
import com.google.android.material.elevation.SurfaceColors
|
|
import com.google.android.material.navigation.NavigationBarView
|
|
|
|
class MainActivity : BaseActivity() {
|
|
|
|
lateinit var binding: ActivityMainBinding
|
|
|
|
lateinit var navController: NavController
|
|
private var startFragmentId = R.id.homeFragment
|
|
var autoRotationEnabled = false
|
|
|
|
lateinit var searchView: SearchView
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
|
|
autoRotationEnabled = PreferenceHelper.getBoolean(PreferenceKeys.AUTO_ROTATION, false)
|
|
|
|
// enable auto rotation if turned on
|
|
requestedOrientation = if (autoRotationEnabled) {
|
|
ActivityInfo.SCREEN_ORIENTATION_USER
|
|
} else {
|
|
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
|
}
|
|
|
|
// start service that gets called on closure
|
|
try {
|
|
startService(Intent(this, ClosingService::class.java))
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
}
|
|
|
|
// show noInternet Activity if no internet available on app startup
|
|
if (!NetworkHelper.isNetworkAvailable(this)) {
|
|
val noInternetIntent = Intent(this, NoInternetActivity::class.java)
|
|
startActivity(noInternetIntent)
|
|
finish()
|
|
return
|
|
}
|
|
|
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
|
setContentView(binding.root)
|
|
|
|
// set the action bar for the activity
|
|
setSupportActionBar(binding.toolbar)
|
|
|
|
navController = findNavController(R.id.fragment)
|
|
binding.bottomNav.setupWithNavController(navController)
|
|
|
|
// gets the surface color of the bottom navigation view
|
|
val color = SurfaceColors.getColorForElevation(this, 10F)
|
|
|
|
// sets the navigation bar color to the previously calculated color
|
|
window.navigationBarColor = color
|
|
|
|
// hide the trending page if enabled
|
|
val hideTrendingPage =
|
|
PreferenceHelper.getBoolean(PreferenceKeys.HIDE_TRENDING_PAGE, false)
|
|
if (hideTrendingPage) {
|
|
binding.bottomNav.menu.findItem(R.id.homeFragment).isVisible =
|
|
false
|
|
}
|
|
|
|
// save start tab fragment id
|
|
startFragmentId =
|
|
when (PreferenceHelper.getString(PreferenceKeys.DEFAULT_TAB, "home")) {
|
|
"home" -> R.id.homeFragment
|
|
"subscriptions" -> R.id.subscriptionsFragment
|
|
"library" -> R.id.libraryFragment
|
|
else -> R.id.homeFragment
|
|
}
|
|
|
|
// set default tab as start fragment
|
|
navController.graph.setStartDestination(startFragmentId)
|
|
|
|
// navigate to the default fragment
|
|
navController.navigate(startFragmentId)
|
|
|
|
val labelVisibilityMode = when (
|
|
PreferenceHelper.getString(PreferenceKeys.LABEL_VISIBILITY, "always")
|
|
) {
|
|
"always" -> NavigationBarView.LABEL_VISIBILITY_LABELED
|
|
"selected" -> NavigationBarView.LABEL_VISIBILITY_SELECTED
|
|
"never" -> NavigationBarView.LABEL_VISIBILITY_UNLABELED
|
|
else -> NavigationBarView.LABEL_VISIBILITY_AUTO
|
|
}
|
|
binding.bottomNav.labelVisibilityMode = labelVisibilityMode
|
|
|
|
binding.bottomNav.setOnApplyWindowInsetsListener(null)
|
|
|
|
binding.bottomNav.setOnItemSelectedListener {
|
|
// clear backstack if it's the start fragment
|
|
if (startFragmentId == it.itemId) navController.backQueue.clear()
|
|
// set menu item on click listeners
|
|
removeSearchFocus()
|
|
when (it.itemId) {
|
|
R.id.homeFragment -> {
|
|
navController.navigate(R.id.homeFragment)
|
|
}
|
|
R.id.subscriptionsFragment -> {
|
|
navController.navigate(R.id.subscriptionsFragment)
|
|
}
|
|
R.id.libraryFragment -> {
|
|
navController.navigate(R.id.libraryFragment)
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
binding.toolbar.title = ThemeHelper.getStyledAppName(this)
|
|
|
|
/**
|
|
* handle error logs
|
|
*/
|
|
val log = PreferenceHelper.getErrorLog()
|
|
if (log != "") ErrorDialog().show(supportFragmentManager, null)
|
|
|
|
setupBreakReminder()
|
|
|
|
// new way of handling back presses
|
|
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
|
|
override fun handleOnBackPressed() {
|
|
navController.popBackStack(R.id.searchFragment, false)
|
|
|
|
if (binding.mainMotionLayout.progress == 0F) {
|
|
try {
|
|
minimizePlayer()
|
|
return
|
|
} catch (e: Exception) {
|
|
// current fragment isn't the player fragment
|
|
}
|
|
}
|
|
|
|
if (navController.currentDestination?.id == startFragmentId) {
|
|
moveTaskToBack(true)
|
|
} else {
|
|
navController.popBackStack()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Show a break reminder when watched too long
|
|
*/
|
|
private fun setupBreakReminder() {
|
|
if (!PreferenceHelper.getBoolean(
|
|
PreferenceKeys.BREAK_REMINDER_TOGGLE,
|
|
false
|
|
)
|
|
) {
|
|
return
|
|
}
|
|
val breakReminderPref = PreferenceHelper.getString(
|
|
PreferenceKeys.BREAK_REMINDER,
|
|
"0"
|
|
)
|
|
if (!breakReminderPref.all { Character.isDigit(it) } ||
|
|
breakReminderPref == "" || breakReminderPref == "0"
|
|
) {
|
|
return
|
|
}
|
|
Handler(Looper.getMainLooper()).postDelayed(
|
|
{
|
|
try {
|
|
MaterialAlertDialogBuilder(this)
|
|
.setTitle(getString(R.string.share_with_time))
|
|
.setMessage(
|
|
getString(
|
|
R.string.already_spent_time,
|
|
breakReminderPref
|
|
)
|
|
)
|
|
.setPositiveButton(R.string.okay, null)
|
|
.show()
|
|
} catch (e: Exception) {
|
|
kotlin.runCatching {
|
|
Toast.makeText(this, R.string.take_a_break, Toast.LENGTH_LONG).show()
|
|
}
|
|
}
|
|
},
|
|
breakReminderPref.toLong() * 60 * 1000
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Remove the focus of the search view in the toolbar
|
|
*/
|
|
private fun removeSearchFocus() {
|
|
searchView.setQuery("", false)
|
|
searchView.clearFocus()
|
|
searchView.onActionViewCollapsed()
|
|
}
|
|
|
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
// Inflate the menu; this adds items to the action bar if it is present.
|
|
menuInflater.inflate(R.menu.action_bar, menu)
|
|
|
|
// stuff for the search in the topBar
|
|
val searchItem = menu.findItem(R.id.action_search)
|
|
searchView = searchItem.actionView as SearchView
|
|
|
|
val searchViewModel = ViewModelProvider(this)[SearchViewModel::class.java]
|
|
|
|
searchView.setOnSearchClickListener {
|
|
if (navController.currentDestination?.id != R.id.searchResultFragment) {
|
|
searchViewModel.setQuery(null)
|
|
navController.navigate(R.id.searchFragment)
|
|
}
|
|
}
|
|
|
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
|
val bundle = Bundle()
|
|
bundle.putString("query", query)
|
|
navController.navigate(R.id.searchResultFragment, bundle)
|
|
searchViewModel.setQuery("")
|
|
return true
|
|
}
|
|
|
|
override fun onQueryTextChange(newText: String?): Boolean {
|
|
if (navController.currentDestination?.id != R.id.searchFragment) {
|
|
val bundle = Bundle()
|
|
bundle.putString("query", newText)
|
|
navController.navigate(R.id.searchFragment, bundle)
|
|
} else {
|
|
searchViewModel.setQuery(newText)
|
|
}
|
|
return true
|
|
}
|
|
})
|
|
|
|
searchItem.setOnActionExpandListener(
|
|
object : MenuItem.OnActionExpandListener {
|
|
override fun onMenuItemActionExpand(p0: MenuItem): Boolean {
|
|
return true
|
|
}
|
|
|
|
override fun onMenuItemActionCollapse(p0: MenuItem): Boolean {
|
|
val currentFragmentId = navController.currentDestination?.id
|
|
if (currentFragmentId == R.id.searchFragment || currentFragmentId == R.id.searchResultFragment) {
|
|
navController.popBackStack()
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
)
|
|
return super.onCreateOptionsMenu(menu)
|
|
}
|
|
|
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
// Handle action bar item clicks here. The action bar will
|
|
// automatically handle clicks on the Home/Up button, so long
|
|
// as you specify a parent activity in AndroidManifest.xml.
|
|
return when (item.itemId) {
|
|
R.id.action_settings -> {
|
|
val settingsIntent = Intent(this, SettingsActivity::class.java)
|
|
startActivity(settingsIntent)
|
|
true
|
|
}
|
|
R.id.action_about -> {
|
|
val aboutIntent = Intent(this, AboutActivity::class.java)
|
|
startActivity(aboutIntent)
|
|
true
|
|
}
|
|
R.id.action_community -> {
|
|
val communityIntent = Intent(this, CommunityActivity::class.java)
|
|
startActivity(communityIntent)
|
|
true
|
|
}
|
|
else -> super.onOptionsItemSelected(item)
|
|
}
|
|
}
|
|
|
|
override fun onStart() {
|
|
super.onStart()
|
|
val intentData: Uri? = intent?.data
|
|
// check whether an URI got submitted over the intent data
|
|
if (intentData != null && intentData.host != null && intentData.path != null) {
|
|
Log.d(TAG(), "intentData: ${intentData.host} ${intentData.path} ")
|
|
// load the URI of the submitted link (e.g. video)
|
|
loadIntentData(intentData)
|
|
}
|
|
}
|
|
|
|
private fun loadIntentData(data: Uri) {
|
|
if (data.path!!.contains("/channel/")
|
|
) {
|
|
val channelId = data.path!!
|
|
.replace("/channel/", "")
|
|
|
|
loadChannel(channelId = channelId)
|
|
} else if (
|
|
data.path!!.contains("/c/") ||
|
|
data.path!!.contains("/user/")
|
|
) {
|
|
val channelName = data.path!!
|
|
.replace("/c/", "")
|
|
.replace("/user/", "")
|
|
|
|
loadChannel(channelName = channelName)
|
|
} else if (
|
|
data.path!!.contains("/playlist")
|
|
) {
|
|
var playlistId = data.query!!
|
|
if (playlistId.contains("&")) {
|
|
for (v in playlistId.split("&")) {
|
|
if (v.contains("list=")) {
|
|
playlistId = v.replace("list=", "")
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
playlistId = playlistId.replace("list=", "")
|
|
}
|
|
|
|
loadPlaylist(playlistId)
|
|
} else if (
|
|
data.path!!.contains("/shorts/") ||
|
|
data.path!!.contains("/embed/") ||
|
|
data.path!!.contains("/v/")
|
|
) {
|
|
val videoId = data.path!!
|
|
.replace("/shorts/", "")
|
|
.replace("/v/", "")
|
|
.replace("/embed/", "")
|
|
|
|
loadVideo(videoId, data.query)
|
|
} else if (data.path!!.contains("/watch") && data.query != null) {
|
|
var videoId = data.query!!
|
|
|
|
if (videoId.contains("&")) {
|
|
val watches = videoId.split("&")
|
|
for (v in watches) {
|
|
if (v.contains("v=")) {
|
|
videoId = v.replace("v=", "")
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
videoId = videoId
|
|
.replace("v=", "")
|
|
}
|
|
|
|
loadVideo(videoId, data.query)
|
|
} else {
|
|
val videoId = data.path!!.replace("/", "")
|
|
|
|
loadVideo(videoId, data.query)
|
|
}
|
|
}
|
|
|
|
private fun loadVideo(videoId: String, query: String?) {
|
|
Log.i(TAG(), "URI type: Video")
|
|
|
|
val bundle = Bundle()
|
|
Log.e(TAG(), videoId)
|
|
|
|
// for time stamped links
|
|
if (query != null && query.contains("t=")) {
|
|
val timeStamp = query.toString().split("t=")[1]
|
|
bundle.putLong("timeStamp", timeStamp.toLong())
|
|
}
|
|
|
|
bundle.putString("videoId", videoId)
|
|
val frag = PlayerFragment()
|
|
frag.arguments = bundle
|
|
|
|
supportFragmentManager.beginTransaction()
|
|
.remove(PlayerFragment())
|
|
.commit()
|
|
supportFragmentManager.beginTransaction()
|
|
.replace(R.id.container, frag)
|
|
.commitNow()
|
|
Handler(Looper.getMainLooper()).postDelayed({
|
|
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
|
|
motionLayout.transitionToEnd()
|
|
motionLayout.transitionToStart()
|
|
}, 100)
|
|
}
|
|
|
|
private fun loadChannel(
|
|
channelId: String? = null,
|
|
channelName: String? = null
|
|
) {
|
|
Log.i(TAG(), "Uri Type: Channel")
|
|
|
|
val bundle = if (channelId != null) {
|
|
bundleOf("channel_id" to channelId)
|
|
} else {
|
|
bundleOf("channel_name" to channelName)
|
|
}
|
|
navController.navigate(R.id.channelFragment, bundle)
|
|
}
|
|
|
|
private fun loadPlaylist(playlistId: String) {
|
|
Log.i(TAG(), "Uri Type: Playlist")
|
|
|
|
val bundle = bundleOf("playlist_id" to playlistId)
|
|
navController.navigate(R.id.playlistFragment, bundle)
|
|
}
|
|
|
|
private fun minimizePlayer() {
|
|
binding.mainMotionLayout.transitionToEnd()
|
|
findViewById<ConstraintLayout>(R.id.main_container).isClickable = false
|
|
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
|
|
// set the animation duration
|
|
motionLayout.setTransitionDuration(250)
|
|
motionLayout.transitionToEnd()
|
|
with(motionLayout) {
|
|
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
|
|
enableTransition(R.id.yt_transition, true)
|
|
}
|
|
findViewById<LinearLayout>(R.id.linLayout).visibility = View.VISIBLE
|
|
val playerViewModel = ViewModelProvider(this)[PlayerViewModel::class.java]
|
|
playerViewModel.isFullscreen.value = false
|
|
requestedOrientation = if (autoRotationEnabled) {
|
|
ActivityInfo.SCREEN_ORIENTATION_USER
|
|
} else {
|
|
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
|
}
|
|
}
|
|
|
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
super.onConfigurationChanged(newConfig)
|
|
val orientation = newConfig.orientation
|
|
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
|
println("Portrait")
|
|
unsetFullscreen()
|
|
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
println("Landscape")
|
|
setFullscreen()
|
|
}
|
|
}
|
|
|
|
private fun setFullscreen() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
window.attributes.layoutInDisplayCutoutMode =
|
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
window.setDecorFitsSystemWindows(false)
|
|
window.insetsController?.apply {
|
|
hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
|
systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
}
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
window.decorView.systemUiVisibility = (
|
|
View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
or View.SYSTEM_UI_FLAG_IMMERSIVE
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
)
|
|
}
|
|
|
|
window.setFlags(
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
|
)
|
|
}
|
|
|
|
private fun unsetFullscreen() {
|
|
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
|
|
|
showSystemBars()
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
window.attributes.layoutInDisplayCutoutMode =
|
|
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
window.setDecorFitsSystemWindows(true)
|
|
window.insetsController?.apply {
|
|
show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
systemBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT
|
|
}
|
|
}
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
window.decorView.systemUiVisibility =
|
|
(View.SYSTEM_UI_FLAG_VISIBLE or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* hide the status bar
|
|
*/
|
|
fun hideSystemBars() {
|
|
if (resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE) return
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
window.insetsController?.hide(WindowInsets.Type.statusBars())
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
window.setFlags(
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* show the status bar
|
|
*/
|
|
private fun showSystemBars() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
window.insetsController?.show(WindowInsets.Type.statusBars())
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
window.clearFlags(
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
|
)
|
|
}
|
|
}
|
|
|
|
override fun onUserLeaveHint() {
|
|
super.onUserLeaveHint()
|
|
supportFragmentManager.fragments.forEach { fragment ->
|
|
(fragment as? PlayerFragment)?.onUserLeaveHint()
|
|
}
|
|
}
|
|
}
|