Merge branch 'libre-tube:master' into master

This commit is contained in:
XelXen 2022-07-09 13:19:56 +05:30 committed by GitHub
commit ef6cbeb591
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
136 changed files with 3815 additions and 2286 deletions

View File

@ -6,60 +6,70 @@
[![Telegram](https://libre-tube.github.io/assets/tg-widget.svg)](https://t.me/libretube)
[![Twitter](https://libre-tube.github.io/assets/tw-widget.svg)](https://twitter.com/libretube)
[![Reddit](https://libre-tube.github.io/assets/rd-widget.svg)](https://www.reddit.com/r/Libretube/)
[<img src="https://libre-tube.github.io/assets/fdrload.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/en/packages/com.github.libretube/)
[<img src="https://libre-tube.github.io/assets/izzyload.png" alt="Get it on IzzyOnDroid" height="80">](https://apt.izzysoft.de/fdroid/index/apk/com.github.libretube)
[<img src="https://libre-tube.github.io/assets/ghload.png" alt="Get it on GitHub" height="80">](https://github.com/libre-tube/LibreTube/releases/latest)
[<img src="https://libre-tube.github.io/assets/tgload.png" alt="Get it on GitHub" height="80">](https://t.me/LibreTube)
</div><div align="center" style="width:100%; display:flex; justify-content:space-between;">
[<img src="https://libre-tube.github.io/assets/fdrload.png" alt="Get it on F-Droid" width="30%">](https://f-droid.org/en/packages/com.github.libretube/)
[<img src="https://libre-tube.github.io/assets/izzyload.png" alt="Get it on IzzyOnDroid" width="30%">](https://apt.izzysoft.de/fdroid/index/apk/com.github.libretube)<br/>
[<img src="https://libre-tube.github.io/assets/ghload.png" alt="Get it on GitHub" width="30%">](https://github.com/libre-tube/LibreTube/releases/latest)
[<img src="https://libre-tube.github.io/assets/tgload.png" alt="Get it on GitHub" width="30%">](https://t.me/LibreTube)
</div>
## Screenshots
## 📱 Screenshots
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png)
<div style="width:100%; display:flex; justify-content:space-between;">
## Features
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png" width=30% alt="Home">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_1.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png" width=30% alt="Search">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_2.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png" width=30% alt="Player">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_3.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png" width=30% alt="Channel">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_4.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png" width=30% alt="Settings">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_5.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png" width=30% alt="Subscriptions">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_6.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png" width=30% alt="Subscriptions List">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_7.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png" width=30% alt="Library">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png" width=30% alt="Playlist">](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png)
| Feature ||
| - | - |
| User Accounts | ✅ |
| Subscriptions | ✅ |
| User Playlists | ✅ |
| Channel Playlists | ✅ |
| Search Filters | ✅ |
| SponsorBlock | ✅ |
| Subtitles | ✅ |
| Comments | ✅ |
</div>
## ⭐ Features
| Feature | |
| ----------------- | --- |
| User Accounts | ✅ |
| Subscriptions | ✅ |
| User Playlists | ✅ |
| Channel Playlists | ✅ |
| Search Filters | ✅ |
| SponsorBlock | ✅ |
| Subtitles | ✅ |
| Comments | ✅ |
## 😇 Contributing
## Contributing
Whether you have ideas, translations, design changes, code cleaning, or real heavy code changes, help is always welcome.The more is done the better it gets!
If creating a pull request, please make sure to format your code (preferred ktlint) before.
If opening an issue without following the issue template, we will ignore the issue and force close it.
WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.
> **⚠️ WARNING: This is a beta version, therefore you may encounter bugs. If you do, open an issue via our github repository.**
### 📝 Translation
### Translation
<a href="https://hosted.weblate.org/projects/libretube/#languages">
<img src="https://hosted.weblate.org/widgets/libretube/-/287x66-grey.png" alt="Translation status" />
</a>
### Donate
### 💰 Donate
[![Support us on Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dlibretubeteam%26type%3Dpatrons&style=for-the-badge)](https://patreon.com/libretubeteam)
**BTC:** `bc1q0hk2smc74ej8fxupfrp05wk867e54e2zztnxfc`
**XMR:** `44txdmy4E5bDzMYQJh1ZSoHbrp1sWfpGa2FYg26L2ya8EaRejPsh42yVrYhepW9P4YWvrqmTZvms35z5FDgqy1xcVewk18d`
## Mirrors (read-only)
## 🪞 Mirrors (read-only)
<a href="https://gitlab.com/libretube/LibreTube">GitLab</a></p>
<a href="https://notabug.org/LibreTube/LibreTube">NotABug</a></p>

View File

@ -10,13 +10,17 @@ android {
applicationId 'com.github.libretube'
minSdk 21
targetSdk 31
versionCode 12
versionName '0.3.2'
versionCode 13
versionName '0.3.3'
multiDexEnabled true
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
resValue "string", "app_name", "LibreTube"
}
buildFeatures {
viewBinding true
}
buildTypes {
release {
minifyEnabled true

View File

@ -22,15 +22,17 @@
android:supportsRtl="true"
android:theme="@style/Theme.Purple">
<activity
android:name=".util.Player"
android:name=".activities.Player"
android:configChanges="orientation|screenSize"
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
android:exported="false" />
<activity
android:name=".SettingsActivity"
android:name=".activities.NoInternetActivity"
android:label="@string/noInternet" />
<activity
android:name=".activities.SettingsActivity"
android:label="@string/settings" />
<activity
android:name=".MainActivity"
android:name=".activities.MainActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:exported="true"
android:hardwareAccelerated="true"
@ -53,7 +55,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_gradient_round"
android:supportsPictureInPicture="true"
android:targetActivity=".MainActivity">
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -70,7 +72,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_fire_round"
android:supportsPictureInPicture="true"
android:targetActivity=".MainActivity">
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -87,7 +89,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_flame_round"
android:supportsPictureInPicture="true"
android:targetActivity=".MainActivity">
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -104,7 +106,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_shaped_round"
android:supportsPictureInPicture="true"
android:targetActivity=".MainActivity">
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -121,7 +123,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_torch_round"
android:supportsPictureInPicture="true"
android:targetActivity=".MainActivity">
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -138,7 +140,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_legacy_round"
android:supportsPictureInPicture="true"
android:targetActivity=".MainActivity">
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -155,7 +157,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_bird_round"
android:supportsPictureInPicture="true"
android:targetActivity=".MainActivity">
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -163,7 +165,7 @@
</activity-alias>
<activity
android:name=".RouterActivity"
android:name=".activities.RouterActivity"
android:exported="true"
android:launchMode="singleInstance">
<intent-filter>

View File

@ -1,8 +1,23 @@
package com.github.libretube
/**
* API link for the update checker
*/
const val GITHUB_API_URL = "https://api.github.com/repos/libre-tube/LibreTube/releases/latest"
/**
* Links for the about fragment
*/
const val WEBSITE_URL = "https://libre-tube.github.io/"
const val AUTHORS_URL = "https://github.com/libre-tube/LibreTube/graphs/contributors"
const val DONATE_URL = "https://libre-tube.github.io/#donate"
const val CONTRIBUTING_URL = "https://github.com/libre-tube/LibreTube#donate"
const val DONATE_URL = "https://github.com/libre-tube/LibreTube#donate"
const val GITHUB_URL = "https://github.com/libre-tube/LibreTube"
const val PIPED_GITHUB_URL = "https://github.com/TeamPiped/Piped"
/**
* Social media links for the community fragment
*/
const val TELEGRAM_URL = "https://t.me/libretube"
const val MATRIX_URL = "https://matrix.to/#/#LibreTube:matrix.org"
const val DISCORD_URL = "https://discord.com/invite/Qc34xCj2GV"
const val REDDIT_URL = "https://www.reddit.com/r/Libretube/"
const val TWITTER_URL = "https://twitter.com/libretube"

View File

@ -1,57 +1,70 @@
package com.github.libretube
package com.github.libretube.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
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.util.TypedValue
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.ui.setupWithNavController
import com.github.libretube.R
import com.github.libretube.databinding.ActivityMainBinding
import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.fragments.isFullScreen
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.services.ClosingService
import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.CronetHelper
import com.github.libretube.util.LocaleHelper
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.color.DynamicColors
import com.google.android.material.elevation.SurfaceColors
class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
lateinit var bottomNavigationView: BottomNavigationView
private lateinit var toolbar: Toolbar
lateinit var binding: ActivityMainBinding
lateinit var navController: NavController
private var startFragmentId = R.id.homeFragment
override fun onCreate(savedInstanceState: Bundle?) {
DynamicColors.applyToActivityIfAvailable(this)
/**
* apply dynamic colors if enabled
*/
val materialColorsEnabled = PreferenceHelper
.getString(this, "accent_color", "purple") == "my"
if (materialColorsEnabled) {
// apply dynamic colors to the current activity
DynamicColors.applyToActivityIfAvailable(this)
// apply dynamic colors to the all other activities
DynamicColors.applyToActivitiesIfAvailable(application)
}
// set the theme
ThemeHelper.updateTheme(this)
// set the language
LocaleHelper.updateLanguage(this)
super.onCreate(savedInstanceState)
// start service that gets called on closure
@ -61,74 +74,89 @@ class MainActivity : AppCompatActivity() {
RetrofitInstance.url =
PreferenceHelper.getString(this, "selectInstance", "https://pipedapi.kavin.rocks/")!!
ThemeHelper.updateTheme(this)
LocaleHelper.updateLanguage(this)
// set auth instance
RetrofitInstance.authUrl =
if (PreferenceHelper.getBoolean(this, "auth_instance_toggle", false)) {
PreferenceHelper.getString(
this,
"selectAuthInstance",
"https://pipedapi.kavin.rocks/"
)!!
} else {
RetrofitInstance.url
}
// show noInternet Activity if no internet available on app startup
if (!isNetworkAvailable(this)) {
setContentView(R.layout.activity_nointernet)
findViewById<Button>(R.id.retry_button).setOnClickListener {
recreate()
}
findViewById<ImageView>(R.id.noInternet_settingsImageView).setOnClickListener {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
if (!ConnectionHelper.isNetworkAvailable(this)) {
val noInternetIntent = Intent(this, NoInternetActivity::class.java)
startActivity(noInternetIntent)
} else {
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
bottomNavigationView = findViewById(R.id.bottomNav)
navController = findNavController(R.id.fragment)
bottomNavigationView.setupWithNavController(navController)
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(this, "hide_trending_page", false)
if (hideTrendingPage) bottomNavigationView.menu.findItem(R.id.home2).isVisible = false
if (hideTrendingPage) binding.bottomNav.menu.findItem(R.id.homeFragment).isVisible =
false
// navigate to the default start tab
when (PreferenceHelper.getString(this, "default_tab", "home")) {
"home" -> navController.navigate(R.id.home2)
"subscriptions" -> navController.navigate(R.id.subscriptions)
"library" -> navController.navigate(R.id.library)
// save start tab fragment id
startFragmentId = when (PreferenceHelper.getString(this, "default_tab", "home")) {
"home" -> R.id.homeFragment
"subscriptions" -> R.id.subscriptionsFragment
"library" -> R.id.libraryFragment
else -> R.id.homeFragment
}
bottomNavigationView.setOnItemSelectedListener {
// set default tab as start fragment
navController.graph.setStartDestination(startFragmentId)
// navigate to the default fragment
navController.navigate(startFragmentId)
binding.bottomNav.setOnItemSelectedListener {
// clear backstack if it's the start fragment
if (startFragmentId == it.itemId) navController.backQueue.clear()
// set menu item on click listeners
when (it.itemId) {
R.id.home2 -> {
navController.backQueue.clear()
navController.navigate(R.id.home2)
R.id.homeFragment -> {
navController.navigate(R.id.homeFragment)
}
R.id.subscriptions -> {
// navController.backQueue.clear()
navController.navigate(R.id.subscriptions)
R.id.subscriptionsFragment -> {
navController.navigate(R.id.subscriptionsFragment)
}
R.id.library -> {
// navController.backQueue.clear()
navController.navigate(R.id.library)
R.id.libraryFragment -> {
navController.navigate(R.id.libraryFragment)
}
}
false
}
toolbar = findViewById(R.id.toolbar)
val typedValue = TypedValue()
this.theme.resolveAttribute(R.attr.colorPrimary, typedValue, true)
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data))
val appName = HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
toolbar.title = appName
/**
* don't remove this line
* this prevents reselected items at the bottomNav to be duplicated in the backstack
*/
binding.bottomNav.setOnItemReselectedListener {}
toolbar.setNavigationOnClickListener {
binding.toolbar.title = ThemeHelper.getStyledAppName(this)
binding.toolbar.setNavigationOnClickListener {
// settings activity stuff
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
toolbar.setOnMenuItemClickListener {
binding.toolbar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_search -> {
navController.navigate(R.id.searchFragment)
@ -139,28 +167,6 @@ class MainActivity : AppCompatActivity() {
}
}
private fun isNetworkAvailable(context: Context): Boolean {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val nw = connectivityManager.activeNetwork ?: return false
val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
return when {
// WiFi
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
// Mobile
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
// Ethernet
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
// Bluetooth
actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
else -> false
}
} else {
return connectivityManager.activeNetworkInfo?.isConnected ?: false
}
}
override fun onStart() {
super.onStart()
val intentData: Uri? = intent?.data
@ -183,7 +189,7 @@ class MainActivity : AppCompatActivity() {
channel = channel!!.replace("/c/", "")
channel = channel.replace("/user/", "")
val bundle = bundleOf("channel_id" to channel)
navController.navigate(R.id.channel, bundle)
navController.navigate(R.id.channelFragment, bundle)
} else if (data.path!!.contains("/playlist")) {
Log.i(TAG, "URI Type: Playlist")
var playlist = data.query!!
@ -266,34 +272,37 @@ class MainActivity : AppCompatActivity() {
}
override fun onBackPressed() {
try {
val mainMotionLayout = findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
findViewById<ConstraintLayout>(R.id.main_container).isClickable = false
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
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
isFullScreen = false
} else {
navController.popBackStack()
}
} catch (e: Exception) {
// try catch to prevent nointernet activity to crash
if (binding.mainMotionLayout.progress == 0F) {
try {
navController.popBackStack()
moveTaskToBack(true)
minimizePlayer()
} catch (e: Exception) {
super.onBackPressed()
if (navController.currentDestination?.id == startFragmentId) {
super.onBackPressed()
} else {
navController.popBackStack()
}
}
} else if (navController.currentDestination?.id == startFragmentId) {
super.onBackPressed()
} else {
navController.popBackStack()
}
}
private fun minimizePlayer() {
binding.mainMotionLayout.transitionToEnd()
findViewById<ConstraintLayout>(R.id.main_container).isClickable = false
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
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
isFullScreen = false
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val orientation = newConfig.orientation
@ -362,10 +371,6 @@ fun Fragment.hideKeyboard() {
view?.let { activity?.hideKeyboard(it) }
}
fun Activity.hideKeyboard() {
hideKeyboard(currentFocus ?: View(this))
}
fun Context.hideKeyboard(view: View) {
val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)

View File

@ -0,0 +1,51 @@
package com.github.libretube.activities
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.github.libretube.R
import com.github.libretube.databinding.ActivityNointernetBinding
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.ThemeHelper
import com.google.android.material.color.DynamicColors
import com.google.android.material.snackbar.Snackbar
class NoInternetActivity : AppCompatActivity() {
private lateinit var binding: ActivityNointernetBinding
override fun onCreate(savedInstanceState: Bundle?) {
/**
* apply dynamic colors if enabled
*/
val materialColorsEnabled = PreferenceHelper
.getString(this, "accent_color", "purple") == "my"
if (materialColorsEnabled) {
DynamicColors.applyToActivityIfAvailable(this)
}
super.onCreate(savedInstanceState)
binding = ActivityNointernetBinding.inflate(layoutInflater)
// retry button
binding.retryButton.setOnClickListener {
if (ConnectionHelper.isNetworkAvailable(this)) {
ThemeHelper.restartMainActivity(this)
} else {
val snackBar = Snackbar
.make(binding.root, R.string.turnInternetOn, Snackbar.LENGTH_LONG)
snackBar.show()
}
}
binding.noInternetSettingsImageView.setOnClickListener {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
setContentView(binding.root)
}
override fun onBackPressed() {
finishAffinity()
super.onBackPressed()
}
}

View File

@ -1,4 +1,4 @@
package com.github.libretube
package com.github.libretube.activities
import android.content.Intent
import android.content.pm.PackageManager
@ -6,6 +6,7 @@ import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.github.libretube.R
import com.github.libretube.util.ThemeHelper
class RouterActivity : AppCompatActivity() {

View File

@ -1,13 +1,12 @@
package com.github.libretube
package com.github.libretube.activities
import android.app.NotificationManager
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.github.libretube.R
import com.github.libretube.databinding.ActivitySettingsBinding
import com.github.libretube.preferences.MainSettings
import com.github.libretube.util.ThemeHelper
import com.google.android.material.color.DynamicColors
@ -17,24 +16,27 @@ var requireMainActivityRestart = false
class SettingsActivity : AppCompatActivity() {
val TAG = "SettingsActivity"
lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
DynamicColors.applyToActivityIfAvailable(this)
ThemeHelper.updateTheme(this)
// makes the preference dialogs use material dialogs
setTheme(R.style.MaterialAlertDialog)
super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
overridePendingTransition(50, 50)
}
val view = this.findViewById<View>(android.R.id.content)
view.alpha = 0F
view.animate().alpha(1F).duration = 300
binding.root.alpha = 0F
binding.root.animate().alpha(1F).duration = 300
setContentView(R.layout.activity_settings)
setContentView(binding.root)
val backButton = view.findViewById<ImageButton>(R.id.back_imageButton)
backButton.setOnClickListener {
binding.backImageButton.setOnClickListener {
onBackPressed()
}
@ -66,8 +68,11 @@ class SettingsActivity : AppCompatActivity() {
.beginTransaction()
.replace(R.id.settings, MainSettings())
.commit()
val topBarTextView = findViewById<TextView>(R.id.topBar_textView)
topBarTextView?.text = getString(R.string.settings)
changeTopBarText(getString(R.string.settings))
}
}
fun changeTopBarText(text: String) {
if (this::binding.isInitialized) binding.topBarTextView.text = text
}
}

View File

@ -3,14 +3,12 @@ package com.github.libretube.adapters
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.databinding.VideoChannelRowBinding
import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.obj.StreamItem
@ -22,6 +20,7 @@ class ChannelAdapter(
private val childFragmentManager: FragmentManager
) :
RecyclerView.Adapter<ChannelViewHolder>() {
override fun getItemCount(): Int {
return videoFeed.size
}
@ -33,43 +32,41 @@ class ChannelAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.video_channel_row, parent, false)
return ChannelViewHolder(cell)
val binding = VideoChannelRowBinding.inflate(layoutInflater, parent, false)
return ChannelViewHolder(binding)
}
override fun onBindViewHolder(holder: ChannelViewHolder, position: Int) {
val trending = videoFeed[position]
holder.v.findViewById<TextView>(R.id.channel_description).text = trending.title
holder.v.findViewById<TextView>(R.id.channel_views).text =
trending.views.formatShort() + "" +
DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
holder.v.findViewById<TextView>(R.id.channel_duration).text =
DateUtils.formatElapsedTime(trending.duration!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.channel_thumbnail)
Picasso.get().load(trending.thumbnail).into(thumbnailImage)
holder.v.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", trending.url!!.replace("/watch?v=", ""))
var frag = PlayerFragment()
frag.arguments = bundle
val activity = holder.v.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
holder.v.setOnLongClickListener {
val videoId = trending.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, holder.v.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
holder.binding.apply {
channelDescription.text = trending.title
channelViews.text =
trending.views.formatShort() + "" +
DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
channelDuration.text =
DateUtils.formatElapsedTime(trending.duration!!)
Picasso.get().load(trending.thumbnail).into(channelThumbnail)
root.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", trending.url!!.replace("/watch?v=", ""))
var frag = PlayerFragment()
frag.arguments = bundle
val activity = root.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
root.setOnLongClickListener {
val videoId = trending.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, root.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
}
}
}
class ChannelViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class ChannelViewHolder(val binding: VideoChannelRowBinding) : RecyclerView.ViewHolder(binding.root)

View File

@ -1,12 +1,9 @@
package com.github.libretube.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.databinding.ChapterColumnBinding
import com.github.libretube.obj.ChapterSegment
import com.google.android.exoplayer2.ExoPlayer
import com.squareup.picasso.Picasso
@ -19,21 +16,20 @@ class ChaptersAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChaptersViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.chapter_column, parent, false)
return ChaptersViewHolder(cell)
val binding = ChapterColumnBinding.inflate(layoutInflater, parent, false)
return ChaptersViewHolder(binding)
}
override fun onBindViewHolder(holder: ChaptersViewHolder, position: Int) {
val chapter = chapters[position]
val chapterImage = holder.v.findViewById<ImageView>(R.id.chapter_image)
Picasso.get().load(chapter.image).fit().centerCrop().into(chapterImage)
holder.binding.apply {
Picasso.get().load(chapter.image).fit().centerCrop().into(chapterImage)
chapterTitle.text = chapter.title
val chapterTitle = holder.v.findViewById<TextView>(R.id.chapter_title)
chapterTitle.text = chapter.title
holder.v.setOnClickListener {
val chapterStart = chapter.start!!.toLong() * 1000 // s -> ms
exoPlayer.seekTo(chapterStart)
root.setOnClickListener {
val chapterStart = chapter.start!!.toLong() * 1000 // s -> ms
exoPlayer.seekTo(chapterStart)
}
}
}
@ -42,7 +38,4 @@ class ChaptersAdapter(
}
}
class ChaptersViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class ChaptersViewHolder(val binding: ChapterColumnBinding) : RecyclerView.ViewHolder(binding.root)

View File

@ -1,19 +1,17 @@
package com.github.libretube.adapters
import android.annotation.SuppressLint
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.CommentsRowBinding
import com.github.libretube.obj.Comment
import com.github.libretube.obj.CommentsPage
import com.github.libretube.util.RetrofitInstance
@ -29,8 +27,8 @@ class CommentsAdapter(
private val videoId: String,
private val comments: MutableList<Comment>
) : RecyclerView.Adapter<CommentsViewHolder>() {
private val TAG = "CommentsAdapter"
private var isLoading = false
private var nextpage = ""
private var repliesPage = CommentsPage()
@ -42,59 +40,61 @@ class CommentsAdapter(
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentsViewHolder {
val commentsView =
LayoutInflater.from(parent.context).inflate(R.layout.comments_row, parent, false)
return CommentsViewHolder(commentsView)
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CommentsRowBinding.inflate(layoutInflater, parent, false)
return CommentsViewHolder(binding)
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: CommentsViewHolder, position: Int) {
holder.v.findViewById<TextView>(R.id.comment_infos).text =
comments[position].author.toString() +
"" + comments[position].commentedTime.toString()
holder.v.findViewById<TextView>(R.id.comment_text).text =
comments[position].commentText.toString()
val channelImage = holder.v.findViewById<ImageView>(R.id.commentor_image)
Picasso.get().load(comments[position].thumbnail).fit().centerCrop().into(channelImage)
holder.v.findViewById<TextView>(R.id.likes_textView).text =
comments[position].likeCount?.toLong().formatShort()
if (comments[position].verified == true) {
holder.v.findViewById<ImageView>(R.id.verified_imageView).visibility = View.VISIBLE
}
if (comments[position].pinned == true) {
holder.v.findViewById<ImageView>(R.id.pinned_imageView).visibility = View.VISIBLE
}
if (comments[position].hearted == true) {
holder.v.findViewById<ImageView>(R.id.hearted_imageView).visibility = View.VISIBLE
}
channelImage.setOnClickListener {
val activity = holder.v.context as MainActivity
val bundle = bundleOf("channel_id" to comments[position].commentorUrl)
activity.navController.navigate(R.id.channel, bundle)
try {
val mainMotionLayout = activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
}
} catch (e: Exception) {
val comment = comments[position]
holder.binding.apply {
commentInfos.text =
comment.author.toString() +
"" + comment.commentedTime.toString()
commentText.text =
comment.commentText.toString()
Picasso.get().load(comment.thumbnail).fit().centerCrop().into(commentorImage)
likesTextView.text =
comment.likeCount?.toLong().formatShort()
if (comment.verified == true) {
verifiedImageView.visibility = View.VISIBLE
}
}
val repliesRecView = holder.v.findViewById<RecyclerView>(R.id.replies_recView)
repliesRecView.layoutManager = LinearLayoutManager(holder.v.context)
val repliesAdapter = RepliesAdapter(CommentsPage().comments)
repliesRecView.adapter = repliesAdapter
holder.v.setOnClickListener {
if (repliesAdapter.itemCount == 0) {
if (comments[position].repliesPage != null) {
nextpage = comments[position].repliesPage!!
fetchReplies(nextpage, repliesAdapter)
} else {
Toast.makeText(holder.v.context, R.string.no_replies, Toast.LENGTH_SHORT).show()
if (comment.pinned == true) {
pinnedImageView.visibility = View.VISIBLE
}
if (comment.hearted == true) {
heartedImageView.visibility = View.VISIBLE
}
commentorImage.setOnClickListener {
val activity = root.context as MainActivity
val bundle = bundleOf("channel_id" to comment.commentorUrl)
activity.navController.navigate(R.id.channelFragment, bundle)
try {
val mainMotionLayout =
activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout)
.transitionToEnd()
}
} catch (e: Exception) {
}
}
repliesRecView.layoutManager = LinearLayoutManager(root.context)
val repliesAdapter = RepliesAdapter(CommentsPage().comments)
repliesRecView.adapter = repliesAdapter
root.setOnClickListener {
if (repliesAdapter.itemCount == 0) {
if (comment.repliesPage != null) {
nextpage = comment.repliesPage
fetchReplies(nextpage, repliesAdapter)
} else {
Toast.makeText(root.context, R.string.no_replies, Toast.LENGTH_SHORT)
.show()
}
} else {
repliesAdapter.clear()
}
// repliesAdapter.updateItems(repliesPage.comments)
} else {
repliesAdapter.clear()
}
}
}
@ -103,19 +103,18 @@ class CommentsAdapter(
return comments.size
}
private fun fetchReplies(nextpage: String, repliesAdapter: RepliesAdapter) {
private fun fetchReplies(nextPage: String, repliesAdapter: RepliesAdapter) {
CoroutineScope(Dispatchers.Main).launch {
if (!isLoading) {
isLoading = true
try {
repliesPage = RetrofitInstance.api.getCommentsNextPage(videoId, nextpage)
repliesPage = RetrofitInstance.api.getCommentsNextPage(videoId, nextPage)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response," + e.response())
}
// nextpage = if (repliesPage.nextpage!! != null) repliesPage.nextpage!! else ""
repliesAdapter.updateItems(repliesPage.comments)
isLoading = false
}
@ -123,7 +122,4 @@ class CommentsAdapter(
}
}
class CommentsViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class CommentsViewHolder(val binding: CommentsRowBinding) : RecyclerView.ViewHolder(binding.root)

View File

@ -7,17 +7,16 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.databinding.PlaylistRowBinding
import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.obj.PlaylistId
import com.github.libretube.obj.StreamItem
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope
@ -34,6 +33,7 @@ class PlaylistAdapter(
private val childFragmentManager: FragmentManager
) : RecyclerView.Adapter<PlaylistViewHolder>() {
private val TAG = "PlaylistAdapter"
override fun getItemCount(): Int {
return videoFeed.size
}
@ -45,45 +45,44 @@ class PlaylistAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.playlist_row, parent, false)
return PlaylistViewHolder(cell)
val binding = PlaylistRowBinding.inflate(layoutInflater, parent, false)
return PlaylistViewHolder(binding)
}
override fun onBindViewHolder(holder: PlaylistViewHolder, position: Int) {
val streamItem = videoFeed[position]
holder.v.findViewById<TextView>(R.id.playlist_title).text = streamItem.title
holder.v.findViewById<TextView>(R.id.playlist_description).text = streamItem.uploaderName
holder.v.findViewById<TextView>(R.id.playlist_duration).text =
DateUtils.formatElapsedTime(streamItem.duration!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.playlist_thumbnail)
Picasso.get().load(streamItem.thumbnail).into(thumbnailImage)
holder.v.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", streamItem.url!!.replace("/watch?v=", ""))
bundle.putString("playlistId", playlistId)
var frag = PlayerFragment()
frag.arguments = bundle
val activity = holder.v.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
holder.v.setOnLongClickListener {
val videoId = streamItem.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, holder.v.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
holder.binding.apply {
playlistTitle.text = streamItem.title
playlistDescription.text = streamItem.uploaderName
playlistDuration.text = DateUtils.formatElapsedTime(streamItem.duration!!)
Picasso.get().load(streamItem.thumbnail).into(playlistThumbnail)
root.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", streamItem.url!!.replace("/watch?v=", ""))
bundle.putString("playlistId", playlistId)
var frag = PlayerFragment()
frag.arguments = bundle
val activity = root.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
root.setOnLongClickListener {
val videoId = streamItem.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, root.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
if (isOwner) {
val delete = holder.v.findViewById<ImageView>(R.id.delete_playlist)
delete.visibility = View.VISIBLE
delete.setOnClickListener {
val token = PreferenceHelper.getToken(holder.v.context)
removeFromPlaylist(token, position)
if (isOwner) {
deletePlaylist.visibility = View.VISIBLE
deletePlaylist.setOnClickListener {
val token = PreferenceHelper.getToken(root.context)
removeFromPlaylist(token, position)
}
}
}
}
@ -92,7 +91,7 @@ class PlaylistAdapter(
fun run() {
CoroutineScope(Dispatchers.IO).launch {
val response = try {
RetrofitInstance.api.removeFromPlaylist(
RetrofitInstance.authApi.removeFromPlaylist(
token,
PlaylistId(playlistId = playlistId, index = position)
)
@ -111,10 +110,6 @@ class PlaylistAdapter(
videoFeed.removeAt(position)
// FIXME: This needs to run on UI thread?
activity.runOnUiThread { notifyDataSetChanged() }
/*if(playlists.isEmpty()){
view.findViewById<ImageView>(R.id.boogh2).visibility=View.VISIBLE
}*/
}
} catch (e: Exception) {
Log.e(TAG, e.toString())
@ -125,7 +120,4 @@ class PlaylistAdapter(
}
}
class PlaylistViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class PlaylistViewHolder(val binding: PlaylistRowBinding) : RecyclerView.ViewHolder(binding.root)

View File

@ -3,21 +3,20 @@ package com.github.libretube.adapters
import android.app.Activity
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.PlaylistsRowBinding
import com.github.libretube.obj.PlaylistId
import com.github.libretube.obj.Playlists
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.squareup.picasso.Picasso
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
@ -27,6 +26,7 @@ class PlaylistsAdapter(
private val activity: Activity
) : RecyclerView.Adapter<PlaylistsViewHolder>() {
val TAG = "PlaylistsAdapter"
override fun getItemCount(): Int {
return playlists.size
}
@ -38,45 +38,46 @@ class PlaylistsAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistsViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.playlists_row, parent, false)
return PlaylistsViewHolder(cell)
val binding = PlaylistsRowBinding.inflate(layoutInflater, parent, false)
return PlaylistsViewHolder(binding)
}
override fun onBindViewHolder(holder: PlaylistsViewHolder, position: Int) {
val playlist = playlists[position]
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.playlist_thumbnail)
Picasso.get().load(playlist.thumbnail).into(thumbnailImage)
// set imageview drawable as empty playlist if imageview empty
if (thumbnailImage.drawable == null) {
thumbnailImage.setImageResource(R.drawable.ic_empty_playlist)
thumbnailImage.setBackgroundColor(R.attr.colorSurface)
}
holder.v.findViewById<TextView>(R.id.playlist_title).text = playlist.name
holder.v.findViewById<ImageView>(R.id.delete_playlist).setOnClickListener {
val builder = MaterialAlertDialogBuilder(holder.v.context)
builder.setTitle(R.string.deletePlaylist)
builder.setMessage(R.string.areYouSure)
builder.setPositiveButton(R.string.yes) { _, _ ->
val token = PreferenceHelper.getToken(holder.v.context)
deletePlaylist(playlist.id!!, token, position)
holder.binding.apply {
Picasso.get().load(playlist.thumbnail).into(playlistThumbnail)
// set imageview drawable as empty playlist if imageview empty
if (playlistThumbnail.drawable == null) {
playlistThumbnail.setImageResource(R.drawable.ic_empty_playlist)
playlistThumbnail.setBackgroundColor(R.attr.colorSurface)
}
builder.setNegativeButton(R.string.cancel) { _, _ ->
playlistTitle.text = playlist.name
deletePlaylist.setOnClickListener {
val builder = MaterialAlertDialogBuilder(root.context)
builder.setTitle(R.string.deletePlaylist)
builder.setMessage(R.string.areYouSure)
builder.setPositiveButton(R.string.yes) { _, _ ->
val token = PreferenceHelper.getToken(root.context)
deletePlaylist(playlist.id!!, token, position)
}
builder.setNegativeButton(R.string.cancel) { _, _ ->
}
builder.show()
}
root.setOnClickListener {
// playlists clicked
val activity = root.context as MainActivity
val bundle = bundleOf("playlist_id" to playlist.id)
activity.navController.navigate(R.id.playlistFragment, bundle)
}
builder.show()
}
holder.v.setOnClickListener {
// playlists clicked
val activity = holder.v.context as MainActivity
val bundle = bundleOf("playlist_id" to playlist.id)
activity.navController.navigate(R.id.playlistFragment, bundle)
}
}
private fun deletePlaylist(id: String, token: String, position: Int) {
fun run() {
GlobalScope.launch {
CoroutineScope(Dispatchers.IO).launch {
val response = try {
RetrofitInstance.api.deletePlaylist(token, PlaylistId(id))
RetrofitInstance.authApi.deletePlaylist(token, PlaylistId(id))
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
@ -84,7 +85,6 @@ class PlaylistsAdapter(
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
return@launch
} finally {
}
try {
if (response.message == "ok") {
@ -102,7 +102,4 @@ class PlaylistsAdapter(
}
}
class PlaylistsViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class PlaylistsViewHolder(val binding: PlaylistsRowBinding) : RecyclerView.ViewHolder(binding.root)

View File

@ -3,13 +3,12 @@ package com.github.libretube.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.RepliesRowBinding
import com.github.libretube.obj.Comment
import com.github.libretube.util.formatShort
import com.squareup.picasso.Picasso
@ -17,10 +16,7 @@ import com.squareup.picasso.Picasso
class RepliesAdapter(
private val replies: MutableList<Comment>
) : RecyclerView.Adapter<RepliesViewHolder>() {
private val TAG = "RepliesAdapter"
private var isLoading = false
private var nextPage = ""
fun clear() {
val size: Int = replies.size
@ -35,41 +31,45 @@ class RepliesAdapter(
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RepliesViewHolder {
var repliesView =
LayoutInflater.from(parent.context).inflate(R.layout.replies_row, parent, false)
return RepliesViewHolder(repliesView)
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RepliesRowBinding.inflate(layoutInflater, parent, false)
return RepliesViewHolder(binding)
}
override fun onBindViewHolder(holder: RepliesViewHolder, position: Int) {
holder.v.findViewById<TextView>(R.id.comment_infos).text =
replies[position].author.toString() +
"" + replies[position].commentedTime.toString()
holder.v.findViewById<TextView>(R.id.comment_text).text =
replies[position].commentText.toString()
val channelImage = holder.v.findViewById<ImageView>(R.id.commentor_image)
Picasso.get().load(replies[position].thumbnail).fit().centerCrop().into(channelImage)
holder.v.findViewById<TextView>(R.id.likes_textView).text =
replies[position].likeCount?.toLong().formatShort()
if (replies[position].verified == true) {
holder.v.findViewById<ImageView>(R.id.verified_imageView).visibility = View.VISIBLE
}
if (replies[position].pinned == true) {
holder.v.findViewById<ImageView>(R.id.pinned_imageView).visibility = View.VISIBLE
}
if (replies[position].hearted == true) {
holder.v.findViewById<ImageView>(R.id.hearted_imageView).visibility = View.VISIBLE
}
channelImage.setOnClickListener {
val activity = holder.v.context as MainActivity
val bundle = bundleOf("channel_id" to replies[position].commentorUrl)
activity.navController.navigate(R.id.channel, bundle)
try {
val mainMotionLayout = activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
holder.binding.apply {
val reply = replies[position]
commentInfos.text =
reply.author.toString() +
"" + reply.commentedTime.toString()
commentText.text =
reply.commentText.toString()
Picasso.get().load(reply.thumbnail).fit().centerCrop().into(commentorImage)
likesTextView.text =
reply.likeCount?.toLong().formatShort()
if (reply.verified == true) {
verifiedImageView.visibility = View.VISIBLE
}
if (reply.pinned == true) {
pinnedImageView.visibility = View.VISIBLE
}
if (reply.hearted == true) {
heartedImageView.visibility = View.VISIBLE
}
commentorImage.setOnClickListener {
val activity = root.context as MainActivity
val bundle = bundleOf("channel_id" to reply.commentorUrl)
activity.navController.navigate(R.id.channelFragment, bundle)
try {
val mainMotionLayout =
activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout)
.transitionToEnd()
}
} catch (e: Exception) {
}
} catch (e: Exception) {
}
}
}
@ -79,7 +79,4 @@ class RepliesAdapter(
}
}
class RepliesViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class RepliesViewHolder(val binding: RepliesRowBinding) : RecyclerView.ViewHolder(binding.root)

View File

@ -3,16 +3,16 @@ package com.github.libretube.adapters
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.ChannelSearchRowBinding
import com.github.libretube.databinding.PlaylistSearchRowBinding
import com.github.libretube.databinding.VideoSearchRowBinding
import com.github.libretube.dialogs.PlaylistOptionsDialog
import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment
@ -27,7 +27,7 @@ class SearchAdapter(
RecyclerView.Adapter<SearchViewHolder>() {
fun updateItems(newItems: List<SearchItem>) {
var searchItemsSize = searchItems.size
val searchItemsSize = searchItems.size
searchItems.addAll(newItems)
notifyItemRangeInserted(searchItemsSize, newItems.size)
}
@ -37,19 +37,32 @@ class SearchAdapter(
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
val layout = when (viewType) {
0 -> R.layout.video_search_row
1 -> R.layout.channel_search_row
2 -> R.layout.playlist_search_row
val layoutInflater = LayoutInflater.from(parent.context)
return when (viewType) {
0 -> SearchViewHolder(
VideoSearchRowBinding.inflate(layoutInflater, parent, false)
)
1 -> SearchViewHolder(
ChannelSearchRowBinding.inflate(layoutInflater, parent, false)
)
2 -> SearchViewHolder(
PlaylistSearchRowBinding.inflate(layoutInflater, parent, false)
)
else -> throw IllegalArgumentException("Invalid type")
}
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(layout, parent, false)
return SearchViewHolder(cell, childFragmentManager)
}
override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
holder.bind(searchItems[position])
val searchItem = searchItems[position]
val videoRowBinding = holder.videoRowBinding
val channelRowBinding = holder.channelRowBinding
val playlistRowBinding = holder.playlistRowBinding
if (videoRowBinding != null) bindWatch(searchItem, videoRowBinding)
else if (channelRowBinding != null) bindChannel(searchItem, channelRowBinding)
else if (playlistRowBinding != null) bindPlaylist(searchItem, playlistRowBinding)
}
override fun getItemViewType(position: Int): Int {
@ -60,117 +73,110 @@ class SearchAdapter(
else -> 3
}
}
}
class SearchViewHolder(
private val v: View,
private val childFragmentManager: FragmentManager
) : RecyclerView.ViewHolder(v) {
private fun bindWatch(item: SearchItem) {
val thumbnailImage = v.findViewById<ImageView>(R.id.search_thumbnail)
Picasso.get().load(item.thumbnail).fit().centerCrop().into(thumbnailImage)
val thumbnailDuration = v.findViewById<TextView>(R.id.search_thumbnail_duration)
if (item.duration != -1L) {
thumbnailDuration.text = DateUtils.formatElapsedTime(item.duration!!)
} else {
thumbnailDuration.text = v.context.getString(R.string.live)
thumbnailDuration.setBackgroundColor(R.attr.colorPrimaryDark)
}
val channelImage = v.findViewById<ImageView>(R.id.search_channel_image)
Picasso.get().load(item.uploaderAvatar).fit().centerCrop().into(channelImage)
val title = v.findViewById<TextView>(R.id.search_description)
title.text = item.title
val views = v.findViewById<TextView>(R.id.search_views)
val viewsString = if (item.views?.toInt() != -1) item.views.formatShort() else ""
val uploadDate = if (item.uploadedDate != null) item.uploadedDate else ""
views.text =
if (viewsString != "" && uploadDate != "") {
"$viewsString$uploadDate"
private fun bindWatch(item: SearchItem, binding: VideoSearchRowBinding) {
binding.apply {
Picasso.get().load(item.thumbnail).fit().centerCrop().into(searchThumbnail)
if (item.duration != -1L) {
searchThumbnailDuration.text = DateUtils.formatElapsedTime(item.duration!!)
} else {
viewsString + uploadDate
searchThumbnailDuration.text = root.context.getString(R.string.live)
searchThumbnailDuration.setBackgroundColor(R.attr.colorPrimaryDark)
}
Picasso.get().load(item.uploaderAvatar).fit().centerCrop().into(searchChannelImage)
searchDescription.text = item.title
val viewsString = if (item.views?.toInt() != -1) item.views.formatShort() else ""
val uploadDate = if (item.uploadedDate != null) item.uploadedDate else ""
searchViews.text =
if (viewsString != "" && uploadDate != "") {
"$viewsString$uploadDate"
} else {
viewsString + uploadDate
}
searchChannelName.text = item.uploaderName
root.setOnClickListener {
val bundle = Bundle()
bundle.putString("videoId", item.url!!.replace("/watch?v=", ""))
val frag = PlayerFragment()
frag.arguments = bundle
val activity = root.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
root.setOnLongClickListener {
val videoId = item.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, root.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
searchChannelImage.setOnClickListener {
val activity = root.context as MainActivity
val bundle = bundleOf("channel_id" to item.uploaderUrl)
activity.navController.navigate(R.id.channelFragment, bundle)
}
val channelName = v.findViewById<TextView>(R.id.search_channel_name)
channelName.text = item.uploaderName
v.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", item.url!!.replace("/watch?v=", ""))
var frag = PlayerFragment()
frag.arguments = bundle
val activity = v.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
v.setOnLongClickListener {
val videoId = item.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, v.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
channelImage.setOnClickListener {
val activity = v.context as MainActivity
val bundle = bundleOf("channel_id" to item.uploaderUrl)
activity.navController.navigate(R.id.channel, bundle)
}
}
private fun bindChannel(item: SearchItem) {
val channelImage = v.findViewById<ImageView>(R.id.search_channel_image)
Picasso.get().load(item.thumbnail).fit().centerCrop().into(channelImage)
val channelName = v.findViewById<TextView>(R.id.search_channel_name)
channelName.text = item.name
val channelViews = v.findViewById<TextView>(R.id.search_views)
channelViews.text = v.context.getString(
R.string.subscribers,
item.subscribers.formatShort()
) + "" + v.context.getString(R.string.videoCount, item.videos.toString())
v.setOnClickListener {
val activity = v.context as MainActivity
val bundle = bundleOf("channel_id" to item.url)
activity.navController.navigate(R.id.channel, bundle)
}
// todo sub button
}
private fun bindPlaylist(item: SearchItem) {
val playlistImage = v.findViewById<ImageView>(R.id.search_thumbnail)
Picasso.get().load(item.thumbnail).fit().centerCrop().into(playlistImage)
val playlistNumber = v.findViewById<TextView>(R.id.search_playlist_number)
if (item.videos?.toInt() != -1) playlistNumber.text = item.videos.toString()
val playlistName = v.findViewById<TextView>(R.id.search_description)
playlistName.text = item.name
val playlistChannelName = v.findViewById<TextView>(R.id.search_name)
playlistChannelName.text = item.uploaderName
val playlistVideosNumber = v.findViewById<TextView>(R.id.search_playlist_videos)
if (item.videos?.toInt() != -1) {
playlistVideosNumber.text =
v.context.getString(R.string.videoCount, item.videos.toString())
}
v.setOnClickListener {
// playlist clicked
val activity = v.context as MainActivity
val bundle = bundleOf("playlist_id" to item.url)
activity.navController.navigate(R.id.playlistFragment, bundle)
}
v.setOnLongClickListener {
val playlistId = item.url!!.replace("/playlist?list=", "")
PlaylistOptionsDialog(playlistId, v.context)
.show(childFragmentManager, "PlaylistOptionsDialog")
true
private fun bindChannel(item: SearchItem, binding: ChannelSearchRowBinding) {
binding.apply {
Picasso.get().load(item.thumbnail).fit().centerCrop().into(searchChannelImage)
searchChannelName.text = item.name
searchViews.text = root.context.getString(
R.string.subscribers,
item.subscribers.formatShort()
) + "" + root.context.getString(R.string.videoCount, item.videos.toString())
root.setOnClickListener {
val activity = root.context as MainActivity
val bundle = bundleOf("channel_id" to item.url)
activity.navController.navigate(R.id.channelFragment, bundle)
}
}
}
fun bind(searchItem: SearchItem) {
when {
searchItem.url!!.startsWith("/watch", false) -> bindWatch(searchItem)
searchItem.url!!.startsWith("/channel", false) -> bindChannel(searchItem)
searchItem.url!!.startsWith("/playlist", false) -> bindPlaylist(searchItem)
else -> {
private fun bindPlaylist(item: SearchItem, binding: PlaylistSearchRowBinding) {
binding.apply {
Picasso.get().load(item.thumbnail).fit().centerCrop().into(searchThumbnail)
if (item.videos?.toInt() != -1) searchPlaylistNumber.text = item.videos.toString()
searchDescription.text = item.name
searchName.text = item.uploaderName
if (item.videos?.toInt() != -1) {
searchPlaylistNumber.text =
root.context.getString(R.string.videoCount, item.videos.toString())
}
root.setOnClickListener {
// playlist clicked
val activity = root.context as MainActivity
val bundle = bundleOf("playlist_id" to item.url)
activity.navController.navigate(R.id.playlistFragment, bundle)
}
root.setOnLongClickListener {
val playlistId = item.url!!.replace("/playlist?list=", "")
PlaylistOptionsDialog(playlistId, false, root.context)
.show(childFragmentManager, "PlaylistOptionsDialog")
true
}
}
}
}
class SearchViewHolder : RecyclerView.ViewHolder {
var videoRowBinding: VideoSearchRowBinding? = null
var channelRowBinding: ChannelSearchRowBinding? = null
var playlistRowBinding: PlaylistSearchRowBinding? = null
constructor(binding: VideoSearchRowBinding) : super(binding.root) {
videoRowBinding = binding
}
constructor(binding: ChannelSearchRowBinding) : super(binding.root) {
channelRowBinding = binding
}
constructor(binding: PlaylistSearchRowBinding) : super(binding.root) {
playlistRowBinding = binding
}
}

View File

@ -2,15 +2,12 @@ package com.github.libretube.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.databinding.SearchhistoryRowBinding
import com.github.libretube.fragments.SearchFragment
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
class SearchHistoryAdapter(
private val context: Context,
@ -26,28 +23,28 @@ class SearchHistoryAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHistoryViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.searchhistory_row, parent, false)
return SearchHistoryViewHolder(cell)
val binding = SearchhistoryRowBinding.inflate(layoutInflater, parent, false)
return SearchHistoryViewHolder(binding)
}
override fun onBindViewHolder(holder: SearchHistoryViewHolder, position: Int) {
val history = historyList[position]
holder.v.findViewById<TextView>(R.id.history_text).text = history
holder.binding.apply {
historyText.text = history
holder.v.findViewById<ImageView>(R.id.delete_history).setOnClickListener {
historyList = historyList - history
PreferenceHelper.saveHistory(context, historyList)
notifyDataSetChanged()
}
deleteHistory.setOnClickListener {
historyList = historyList - history
PreferenceHelper.saveHistory(context, historyList)
notifyDataSetChanged()
}
holder.v.setOnClickListener {
editText.setText(history)
searchFragment.fetchSearch(history)
root.setOnClickListener {
editText.setText(history)
searchFragment.fetchSearch(history)
}
}
}
}
class SearchHistoryViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class SearchHistoryViewHolder(val binding: SearchhistoryRowBinding) :
RecyclerView.ViewHolder(binding.root)

View File

@ -1,12 +1,10 @@
package com.github.libretube.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.databinding.SearchsuggestionRowBinding
import com.github.libretube.fragments.SearchFragment
class SearchSuggestionsAdapter(
@ -16,28 +14,29 @@ class SearchSuggestionsAdapter(
) :
RecyclerView.Adapter<SearchSuggestionsViewHolder>() {
private val TAG = "SearchSuggestionsAdapter"
override fun getItemCount(): Int {
return suggestionsList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchSuggestionsViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.searchsuggestion_row, parent, false)
return SearchSuggestionsViewHolder(cell)
val binding = SearchsuggestionRowBinding.inflate(layoutInflater, parent, false)
return SearchSuggestionsViewHolder(binding)
}
override fun onBindViewHolder(holder: SearchSuggestionsViewHolder, position: Int) {
val suggestion = suggestionsList[position]
val suggestionTextView = holder.v.findViewById<TextView>(R.id.suggestion_text)
suggestionTextView.text = suggestion
holder.v.setOnClickListener {
editText.setText(suggestion)
searchFragment.fetchSearch(editText.text.toString())
holder.binding.apply {
suggestionText.text = suggestion
root.setOnClickListener {
editText.setText(suggestion)
searchFragment.fetchSearch(editText.text.toString())
}
}
}
}
class SearchSuggestionsViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class SearchSuggestionsViewHolder(val binding: SearchsuggestionRowBinding) :
RecyclerView.ViewHolder(binding.root)

View File

@ -3,17 +3,15 @@ package com.github.libretube.adapters
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.TrendingRowBinding
import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.obj.StreamItem
@ -23,16 +21,15 @@ import com.squareup.picasso.Picasso
class SubscriptionAdapter(
private val videoFeed: List<StreamItem>,
private val childFragmentManager: FragmentManager
) :
RecyclerView.Adapter<SubscriptionViewHolder>() {
// private var limitedVideoFeed: MutableList<String> = [""].toMutableList()
) : RecyclerView.Adapter<SubscriptionViewHolder>() {
private val TAG = "SubscriptionAdapter"
var i = 0
override fun getItemCount(): Int {
return i
}
fun updateItems() {
// limitedVideoFeed.add("")
i += 10
if (i > videoFeed.size) {
i = videoFeed.size
@ -42,64 +39,63 @@ class SubscriptionAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.trending_row, parent, false)
return SubscriptionViewHolder(cell)
val binding = TrendingRowBinding.inflate(layoutInflater, parent, false)
return SubscriptionViewHolder(binding)
}
override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) {
val trending = videoFeed[position]
holder.v.findViewById<TextView>(R.id.textView_title).text = trending.title
holder.v.findViewById<TextView>(R.id.textView_channel).text =
trending.uploaderName + "" +
trending.views.formatShort() + "" +
DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.thumbnail)
val thumbnailDuration = holder.v.findViewById<TextView>(R.id.thumbnail_duration)
if (trending.duration != -1L) {
thumbnailDuration.text = DateUtils.formatElapsedTime(trending.duration!!)
} else {
thumbnailDuration.text = holder.v.context.getString(R.string.live)
thumbnailDuration.setBackgroundColor(R.attr.colorPrimaryDark)
}
val channelImage = holder.v.findViewById<ImageView>(R.id.channel_image)
channelImage.setOnClickListener {
val activity = holder.v.context as MainActivity
val bundle = bundleOf("channel_id" to trending.uploaderUrl)
activity.navController.navigate(R.id.channel, bundle)
try {
val mainMotionLayout = activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
}
} catch (e: Exception) {
holder.binding.apply {
textViewTitle.text = trending.title
textViewChannel.text =
trending.uploaderName + "" +
trending.views.formatShort() + "" +
DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
if (trending.duration != -1L) {
thumbnailDuration.text = DateUtils.formatElapsedTime(trending.duration!!)
} else {
thumbnailDuration.text = root.context.getString(R.string.live)
thumbnailDuration.setBackgroundColor(R.attr.colorPrimaryDark)
}
channelImage.setOnClickListener {
val activity = root.context as MainActivity
val bundle = bundleOf("channel_id" to trending.uploaderUrl)
activity.navController.navigate(R.id.channelFragment, bundle)
try {
val mainMotionLayout =
activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout)
.transitionToEnd()
}
} catch (e: Exception) {
}
}
Picasso.get().load(trending.thumbnail).into(thumbnail)
Picasso.get().load(trending.uploaderAvatar).into(channelImage)
root.setOnClickListener {
val bundle = Bundle()
bundle.putString("videoId", trending.url!!.replace("/watch?v=", ""))
val frag = PlayerFragment()
frag.arguments = bundle
val activity = root.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
root.setOnLongClickListener {
val videoId = trending.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, root.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
}
Picasso.get().load(trending.thumbnail).into(thumbnailImage)
Picasso.get().load(trending.uploaderAvatar).into(channelImage)
holder.v.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", trending.url!!.replace("/watch?v=", ""))
var frag = PlayerFragment()
frag.arguments = bundle
val activity = holder.v.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
holder.v.setOnLongClickListener {
val videoId = trending.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, holder.v.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
}
}
class SubscriptionViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class SubscriptionViewHolder(val binding: TrendingRowBinding) :
RecyclerView.ViewHolder(binding.root)

View File

@ -3,17 +3,15 @@ package com.github.libretube.adapters
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.ChannelSubscriptionRowBinding
import com.github.libretube.obj.Subscribe
import com.github.libretube.obj.Subscription
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope
@ -25,8 +23,10 @@ import java.io.IOException
class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscription>) :
RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
val TAG = "SubChannelAdapter"
private var subscribed = true
private var isLoading = false
override fun getItemCount(): Int {
return subscriptions.size
}
@ -34,34 +34,32 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
SubscriptionChannelViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.channel_subscription_row, parent, false)
return SubscriptionChannelViewHolder(cell)
val binding = ChannelSubscriptionRowBinding.inflate(layoutInflater, parent, false)
return SubscriptionChannelViewHolder(binding)
}
override fun onBindViewHolder(holder: SubscriptionChannelViewHolder, position: Int) {
val subscription = subscriptions[position]
holder.v.findViewById<TextView>(R.id.subscription_channel_name).text = subscription.name
val avatar = holder.v.findViewById<ImageView>(R.id.subscription_channel_image)
Picasso.get().load(subscription.avatar).into(avatar)
holder.v.setOnClickListener {
val activity = holder.v.context as MainActivity
val bundle = bundleOf("channel_id" to subscription.url)
activity.navController.navigate(R.id.channel, bundle)
}
val subscribeBtn = holder.v
.findViewById<com.google.android.material.button.MaterialButton>(
R.id.subscription_subscribe
)
subscribeBtn.setOnClickListener {
if (!isLoading) {
isLoading = true
val channelId = subscription.url?.replace("/channel/", "")!!
if (subscribed) {
unsubscribe(holder.v.context, channelId)
subscribeBtn.text = holder.v.context.getString(R.string.subscribe)
} else {
subscribe(holder.v.context, channelId)
subscribeBtn.text = holder.v.context.getString(R.string.unsubscribe)
holder.binding.apply {
subscriptionChannelName.text = subscription.name
Picasso.get().load(subscription.avatar).into(subscriptionChannelImage)
root.setOnClickListener {
val activity = root.context as MainActivity
val bundle = bundleOf("channel_id" to subscription.url)
activity.navController.navigate(R.id.channelFragment, bundle)
}
subscriptionSubscribe.setOnClickListener {
if (!isLoading) {
isLoading = true
val channelId = subscription.url?.replace("/channel/", "")!!
if (subscribed) {
unsubscribe(root.context, channelId)
subscriptionSubscribe.text = root.context.getString(R.string.subscribe)
} else {
subscribe(root.context, channelId)
subscriptionSubscribe.text =
root.context.getString(R.string.unsubscribe)
}
}
}
}
@ -72,7 +70,7 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
CoroutineScope(Dispatchers.IO).launch {
val response = try {
val token = PreferenceHelper.getToken(context)
RetrofitInstance.api.subscribe(
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
@ -94,7 +92,7 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
CoroutineScope(Dispatchers.IO).launch {
val response = try {
val token = PreferenceHelper.getToken(context)
RetrofitInstance.api.unsubscribe(
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channelId)
)
@ -112,7 +110,5 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
}
}
class SubscriptionChannelViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class SubscriptionChannelViewHolder(val binding: ChannelSubscriptionRowBinding) :
RecyclerView.ViewHolder(binding.root)

View File

@ -3,17 +3,15 @@ package com.github.libretube.adapters
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.TrendingRowBinding
import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.obj.StreamItem
@ -24,77 +22,75 @@ class TrendingAdapter(
private val videoFeed: List<StreamItem>,
private val childFragmentManager: FragmentManager
) : RecyclerView.Adapter<TrendingViewHolder>() {
private val TAG = "TrendingAdapter"
override fun getItemCount(): Int {
return videoFeed.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrendingViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.trending_row, parent, false)
return TrendingViewHolder(cell)
val binding = TrendingRowBinding.inflate(layoutInflater, parent, false)
return TrendingViewHolder(binding)
}
override fun onBindViewHolder(holder: TrendingViewHolder, position: Int) {
val trending = videoFeed[position]
holder.v.findViewById<TextView>(R.id.textView_title).text = trending.title
holder.v.findViewById<TextView>(R.id.textView_channel).text =
trending.uploaderName + "" +
trending.views.formatShort() + "" +
DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.thumbnail)
val thumbnailDuration = holder.v.findViewById<TextView>(R.id.thumbnail_duration)
if (trending.duration != -1L) {
thumbnailDuration.text = DateUtils.formatElapsedTime(trending.duration!!)
} else {
thumbnailDuration.text = holder.v.context.getString(R.string.live)
thumbnailDuration.setBackgroundColor(R.attr.colorPrimaryDark)
}
val channelImage = holder.v.findViewById<ImageView>(R.id.channel_image)
channelImage.setOnClickListener {
val activity = holder.v.context as MainActivity
val bundle = bundleOf("channel_id" to trending.uploaderUrl)
activity.navController.navigate(R.id.channel, bundle)
try {
val mainMotionLayout = activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
holder.binding.apply {
textViewTitle.text = trending.title
textViewChannel.text =
trending.uploaderName + "" +
trending.views.formatShort() + "" +
DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
if (trending.duration != -1L) {
thumbnailDuration.text = DateUtils.formatElapsedTime(trending.duration!!)
} else {
thumbnailDuration.text = root.context.getString(R.string.live)
thumbnailDuration.setBackgroundColor(R.attr.colorPrimaryDark)
}
channelImage.setOnClickListener {
val activity = root.context as MainActivity
val bundle = bundleOf("channel_id" to trending.uploaderUrl)
activity.navController.navigate(R.id.channelFragment, bundle)
try {
val mainMotionLayout =
activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout)
.transitionToEnd()
}
} catch (e: Exception) {
}
} catch (e: Exception) {
}
if (trending.thumbnail!!.isNotEmpty()) {
Picasso.get().load(trending.thumbnail).into(thumbnail)
}
if (trending.uploaderAvatar!!.isNotEmpty()) {
Picasso.get().load(trending.uploaderAvatar).into(channelImage)
}
root.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", trending.url!!.replace("/watch?v=", ""))
var frag = PlayerFragment()
frag.arguments = bundle
val activity = root.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
root.setOnLongClickListener {
val videoId = trending.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, root.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
}
if (trending.thumbnail!!.isEmpty()) {
} else {
Picasso.get().load(trending.thumbnail).into(thumbnailImage)
}
if (trending.uploaderAvatar!!.isEmpty()) {
} else {
Picasso.get().load(trending.uploaderAvatar).into(channelImage)
}
holder.v.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", trending.url!!.replace("/watch?v=", ""))
var frag = PlayerFragment()
frag.arguments = bundle
val activity = holder.v.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
holder.v.setOnLongClickListener {
val videoId = trending.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, holder.v.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
}
}
class TrendingViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init {
}
}
class TrendingViewHolder(val binding: TrendingRowBinding) : RecyclerView.ViewHolder(binding.root)

View File

@ -0,0 +1,92 @@
package com.github.libretube.adapters
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.WatchHistoryRowBinding
import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.obj.WatchHistoryItem
import com.squareup.picasso.Picasso
class WatchHistoryAdapter(
private val watchHistory: MutableList<WatchHistoryItem>,
private val childFragmentManager: FragmentManager
) :
RecyclerView.Adapter<WatchHistoryViewHolder>() {
private val TAG = "WatchHistoryAdapter"
fun clear() {
val size = watchHistory.size
watchHistory.clear()
notifyItemRangeRemoved(0, size)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WatchHistoryViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = WatchHistoryRowBinding.inflate(layoutInflater, parent, false)
return WatchHistoryViewHolder(binding)
}
override fun onBindViewHolder(holder: WatchHistoryViewHolder, position: Int) {
val video = watchHistory[position]
holder.binding.apply {
videoTitle.text = video.title
channelName.text = video.uploader
uploadDate.text = video.uploadDate
thumbnailDuration.text = DateUtils.formatElapsedTime(video.duration?.toLong()!!)
Picasso.get().load(video.thumbnailUrl).into(thumbnail)
Picasso.get().load(video.uploaderAvatar).into(channelImage)
channelImage.setOnClickListener {
val activity = root.context as MainActivity
val bundle = bundleOf("channel_id" to video.uploaderUrl)
activity.navController.navigate(R.id.channelFragment, bundle)
try {
val mainMotionLayout =
activity.findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout)
.transitionToEnd()
}
} catch (e: Exception) {
}
}
root.setOnClickListener {
var bundle = Bundle()
bundle.putString("videoId", video.videoId)
var frag = PlayerFragment()
frag.arguments = bundle
val activity = root.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
}
root.setOnLongClickListener {
VideoOptionsDialog(video.videoId!!, root.context)
.show(childFragmentManager, VideoOptionsDialog.TAG)
true
}
}
}
override fun getItemCount(): Int {
return watchHistory.size
}
}
class WatchHistoryViewHolder(val binding: WatchHistoryRowBinding) :
RecyclerView.ViewHolder(binding.root)

View File

@ -3,54 +3,42 @@ package com.github.libretube.dialogs
import android.app.Dialog
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.databinding.DialogAddtoplaylistBinding
import com.github.libretube.obj.PlaylistId
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException
import java.io.IOException
class AddtoPlaylistDialog : DialogFragment() {
private val TAG = "AddToPlaylistDialog"
private lateinit var binding: DialogAddtoplaylistBinding
private lateinit var videoId: String
private lateinit var token: String
private lateinit var spinner: Spinner
private lateinit var button: Button
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
videoId = arguments?.getString("videoId")!!
val builder = MaterialAlertDialogBuilder(it)
// Get the layout inflater
val inflater = requireActivity().layoutInflater
token = PreferenceHelper.getToken(requireContext())
var view: View = inflater.inflate(R.layout.dialog_addtoplaylist, null)
spinner = view.findViewById(R.id.playlists_spinner)
button = view.findViewById(R.id.addToPlaylist)
if (token != "") {
fetchPlaylists()
}
val typedValue = TypedValue()
this.requireActivity().theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true)
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data))
val appName = HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
view.findViewById<TextView>(R.id.title).text = appName
binding = DialogAddtoplaylistBinding.inflate(layoutInflater)
builder.setView(view)
token = PreferenceHelper.getToken(requireContext())
if (token != "") fetchPlaylists()
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
builder.setView(binding.root)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
@ -59,7 +47,7 @@ class AddtoPlaylistDialog : DialogFragment() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.playlists(token)
RetrofitInstance.authApi.playlists(token)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
@ -80,10 +68,12 @@ class AddtoPlaylistDialog : DialogFragment() {
arrayAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item
)
spinner.adapter = arrayAdapter
binding.playlistsSpinner.adapter = arrayAdapter
runOnUiThread {
button.setOnClickListener {
addToPlaylist(response[spinner.selectedItemPosition].id!!)
binding.addToPlaylist.setOnClickListener {
addToPlaylist(
response[binding.playlistsSpinner.selectedItemPosition].id!!
)
}
}
} else {
@ -97,7 +87,7 @@ class AddtoPlaylistDialog : DialogFragment() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.addToPlaylist(token, PlaylistId(playlistId, videoId))
RetrofitInstance.authApi.addToPlaylist(token, PlaylistId(playlistId, videoId))
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")

View File

@ -3,56 +3,42 @@ package com.github.libretube.dialogs
import android.app.Dialog
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.fragments.Library
import com.github.libretube.databinding.DialogCreatePlaylistBinding
import com.github.libretube.fragments.LibraryFragment
import com.github.libretube.obj.Playlists
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import retrofit2.HttpException
import java.io.IOException
class CreatePlaylistDialog : DialogFragment() {
val TAG = "CreatePlaylistDialog"
private var token: String = ""
private lateinit var binding: DialogCreatePlaylistBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = MaterialAlertDialogBuilder(it)
val inflater = requireActivity().layoutInflater
val view: View = inflater.inflate(R.layout.dialog_create_playlist, null)
binding = DialogCreatePlaylistBinding.inflate(layoutInflater)
val typedValue = TypedValue()
this.requireActivity().theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true)
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data))
val appName = HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
view.findViewById<TextView>(R.id.title).text = appName
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
val cancelBtn = view.findViewById<Button>(R.id.cancel_button)
cancelBtn.setOnClickListener {
binding.cancelButton.setOnClickListener {
dismiss()
}
token = PreferenceHelper.getToken(requireContext())
val playlistName = view.findViewById<TextInputEditText>(R.id.playlist_name)
val createPlaylistBtn = view.findViewById<Button>(R.id.create_new_playlist)
createPlaylistBtn.setOnClickListener {
binding.createNewPlaylist.setOnClickListener {
// avoid creating the same playlist multiple times by spamming the button
createPlaylistBtn.setOnClickListener(null)
val listName = playlistName.text.toString()
binding.createNewPlaylist.setOnClickListener(null)
val listName = binding.playlistName.text.toString()
if (listName != "") {
createPlaylist(listName)
} else {
@ -60,7 +46,7 @@ class CreatePlaylistDialog : DialogFragment() {
}
}
builder.setView(view)
builder.setView(binding.root)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
@ -69,7 +55,7 @@ class CreatePlaylistDialog : DialogFragment() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.createPlaylist(token, Playlists(name = name))
RetrofitInstance.authApi.createPlaylist(token, Playlists(name = name))
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
@ -88,7 +74,7 @@ class CreatePlaylistDialog : DialogFragment() {
}
// refresh the playlists in the library
try {
val parent = parentFragment as Library
val parent = parentFragment as LibraryFragment
parent.fetchPlaylists()
} catch (e: Exception) {
Log.e(TAG, e.toString())

View File

@ -2,45 +2,34 @@ package com.github.libretube.dialogs
import android.app.Dialog
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import com.github.libretube.R
import com.github.libretube.databinding.DialogCustomInstanceBinding
import com.github.libretube.obj.CustomInstance
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import java.net.URL
class CustomInstanceDialog : DialogFragment() {
val TAG = "CustomInstanceDialog"
private lateinit var binding: DialogCustomInstanceBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = MaterialAlertDialogBuilder(it)
val inflater = requireActivity().layoutInflater
val view: View = inflater.inflate(R.layout.dialog_custom_instance, null)
binding = DialogCustomInstanceBinding.inflate(layoutInflater)
val instanceNameEditText = view.findViewById<TextInputEditText>(R.id.instanceName)
val instanceApiUrlEditText = view.findViewById<TextInputEditText>(R.id.instanceApiUrl)
val instanceFrontendUrlEditText = view
.findViewById<TextInputEditText>(R.id.instanceFrontendUrl)
val addInstanceButton = view.findViewById<Button>(R.id.addInstance)
val cancelButton = view.findViewById<Button>(R.id.cancel)
cancelButton.setOnClickListener {
binding.cancel.setOnClickListener {
dismiss()
}
addInstanceButton.setOnClickListener {
binding.addInstance.setOnClickListener {
val customInstance = CustomInstance()
customInstance.name = instanceNameEditText.text.toString()
customInstance.apiUrl = instanceApiUrlEditText.text.toString()
customInstance.frontendUrl = instanceFrontendUrlEditText.text.toString()
customInstance.name = binding.instanceName.text.toString()
customInstance.apiUrl = binding.instanceApiUrl.text.toString()
customInstance.frontendUrl = binding.instanceFrontendUrl.text.toString()
if (
customInstance.name != "" &&
@ -73,16 +62,9 @@ class CustomInstanceDialog : DialogFragment() {
}
}
val typedValue = TypedValue()
this.requireActivity().theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true)
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data))
val appName = HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
view.findViewById<TextView>(R.id.title).text = appName
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
builder.setView(view)
builder.setView(binding.root)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}

View File

@ -3,54 +3,42 @@ package com.github.libretube.dialogs
import android.app.Dialog
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.activities.requireMainActivityRestart
import com.github.libretube.databinding.DialogDeleteAccountBinding
import com.github.libretube.obj.DeleteUserRequest
import com.github.libretube.requireMainActivityRestart
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class DeleteAccountDialog : DialogFragment() {
private val TAG = "DeleteAccountDialog"
lateinit var username: EditText
lateinit var password: EditText
private lateinit var binding: DialogDeleteAccountBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = MaterialAlertDialogBuilder(it)
val inflater = requireActivity().layoutInflater
val view = inflater.inflate(R.layout.dialog_delete_account, null)
binding = DialogDeleteAccountBinding.inflate(layoutInflater)
view.findViewById<Button>(R.id.cancel_button).setOnClickListener {
binding.cancelButton.setOnClickListener {
dialog?.dismiss()
}
password = view.findViewById(R.id.delete_password)
view.findViewById<Button>(R.id.delete_account_confirm).setOnClickListener {
if (password.text.toString() != "") {
deleteAccount(password.text.toString())
binding.deleteAccountConfirm.setOnClickListener {
if (binding.deletePassword.text.toString() != "") {
deleteAccount(binding.deletePassword.text.toString())
} else {
Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
}
}
val typedValue = TypedValue()
this.requireActivity().theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true)
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data))
val appName = HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
view.findViewById<TextView>(R.id.title).text = appName
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
builder.setView(view)
builder.setView(binding.root)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
@ -61,7 +49,7 @@ class DeleteAccountDialog : DialogFragment() {
val token = PreferenceHelper.getToken(requireContext())
try {
RetrofitInstance.api.deleteAccount(token, DeleteUserRequest(password))
RetrofitInstance.authApi.deleteAccount(token, DeleteUserRequest(password))
} catch (e: Exception) {
Log.e(TAG, e.toString())
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()

View File

@ -8,23 +8,25 @@ import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import com.github.libretube.MainActivity
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.databinding.DialogDownloadBinding
import com.github.libretube.obj.Streams
import com.github.libretube.services.DownloadService
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException
import java.io.IOException
class DownloadDialog : DialogFragment() {
private val TAG = "DownloadDialog"
private lateinit var binding: DialogDownloadBinding
private lateinit var streams: Streams
private lateinit var videoId: String
@ -32,14 +34,13 @@ class DownloadDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
streams = arguments?.getParcelable("streams")!!
videoId = arguments?.getString("video_id")!!
val mainActivity = activity as MainActivity
val builder = MaterialAlertDialogBuilder(it)
// Get the layout inflater
val inflater = requireActivity().layoutInflater
var view: View = inflater.inflate(R.layout.dialog_download, null)
binding = DialogDownloadBinding.inflate(layoutInflater)
fetchStreams()
// request storage permissions if not granted yet
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@ -75,78 +76,89 @@ class DownloadDialog : DialogFragment() {
}
}
var vidName = arrayListOf<String>()
var vidUrl = arrayListOf<String>()
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
// add empty selection
vidName.add(getString(R.string.no_video))
vidUrl.add("")
// add all available video streams
for (vid in streams.videoStreams!!) {
val name = vid.quality + " " + vid.format
vidName.add(name)
vidUrl.add(vid.url!!)
}
var audioName = arrayListOf<String>()
var audioUrl = arrayListOf<String>()
// add empty selection
audioName.add(getString(R.string.no_audio))
audioUrl.add("")
// add all available audio streams
for (audio in streams.audioStreams!!) {
val name = audio.quality + " " + audio.format
audioName.add(name)
audioUrl.add(audio.url!!)
}
val videoSpinner = view.findViewById<Spinner>(R.id.video_spinner)
val videoArrayAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_item,
vidName
)
videoArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
videoSpinner.adapter = videoArrayAdapter
videoSpinner.setSelection(1)
val audioSpinner = view.findViewById<Spinner>(R.id.audio_spinner)
val audioArrayAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_item,
audioName
)
audioArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
audioSpinner.adapter = audioArrayAdapter
audioSpinner.setSelection(1)
view.findViewById<Button>(R.id.download).setOnClickListener {
val selectedAudioUrl = audioUrl[audioSpinner.selectedItemPosition]
val selectedVideoUrl = vidUrl[videoSpinner.selectedItemPosition]
val intent = Intent(context, DownloadService::class.java)
intent.putExtra("videoId", videoId)
intent.putExtra("videoUrl", selectedVideoUrl)
intent.putExtra("audioUrl", selectedAudioUrl)
intent.putExtra("duration", duration)
context?.startService(intent)
dismiss()
}
val typedValue = TypedValue()
this.requireActivity().theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true)
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data))
val appName = HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
view.findViewById<TextView>(R.id.title).text = appName
builder.setView(view)
builder.setView(binding.root)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
private fun fetchStreams() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getStreams(videoId!!)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
}
initDownloadOptions(response)
}
}
private fun initDownloadOptions(streams: Streams) {
var vidName = arrayListOf<String>()
var vidUrl = arrayListOf<String>()
// add empty selection
vidName.add(getString(R.string.no_video))
vidUrl.add("")
// add all available video streams
for (vid in streams.videoStreams!!) {
val name = vid.quality + " " + vid.format
vidName.add(name)
vidUrl.add(vid.url!!)
}
var audioName = arrayListOf<String>()
var audioUrl = arrayListOf<String>()
// add empty selection
audioName.add(getString(R.string.no_audio))
audioUrl.add("")
// add all available audio streams
for (audio in streams.audioStreams!!) {
val name = audio.quality + " " + audio.format
audioName.add(name)
audioUrl.add(audio.url!!)
}
val videoArrayAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_item,
vidName
)
videoArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.videoSpinner.adapter = videoArrayAdapter
binding.videoSpinner.setSelection(1)
val audioArrayAdapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_item,
audioName
)
audioArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.audioSpinner.adapter = audioArrayAdapter
binding.audioSpinner.setSelection(1)
binding.download.setOnClickListener {
val selectedAudioUrl = audioUrl[binding.audioSpinner.selectedItemPosition]
val selectedVideoUrl = vidUrl[binding.videoSpinner.selectedItemPosition]
val intent = Intent(context, DownloadService::class.java)
intent.putExtra("videoId", videoId)
intent.putExtra("videoUrl", selectedVideoUrl)
intent.putExtra("audioUrl", selectedAudioUrl)
intent.putExtra("duration", duration)
context?.startService(intent)
dismiss()
}
}
}

View File

@ -3,77 +3,57 @@ package com.github.libretube.dialogs
import android.app.Dialog
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.activities.requireMainActivityRestart
import com.github.libretube.databinding.DialogLoginBinding
import com.github.libretube.obj.Login
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException
import java.io.IOException
class LoginDialog : DialogFragment() {
private val TAG = "LoginDialog"
lateinit var username: EditText
lateinit var password: EditText
private lateinit var binding: DialogLoginBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = MaterialAlertDialogBuilder(it)
// Get the layout inflater
val inflater = requireActivity().layoutInflater
val token = PreferenceHelper.getToken(requireContext())
var view: View
Log.e("dafaq", token!!)
if (token != "") {
val user = PreferenceHelper.getUsername(requireContext())
view = inflater.inflate(R.layout.dialog_logout, null)
view.findViewById<TextView>(R.id.user).text =
view.findViewById<TextView>(R.id.user).text.toString() + " (" + user + ")"
view.findViewById<Button>(R.id.logout).setOnClickListener {
Toast.makeText(context, R.string.loggedout, Toast.LENGTH_SHORT).show()
PreferenceHelper.setToken(requireContext(), "")
dialog?.dismiss()
binding = DialogLoginBinding.inflate(layoutInflater)
binding.login.setOnClickListener {
if (binding.username.text.toString() != "" && binding.password.text.toString() != "") {
val login =
Login(binding.username.text.toString(), binding.password.text.toString())
login(login)
} else {
Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
}
} else {
view = inflater.inflate(R.layout.dialog_login, null)
username = view.findViewById(R.id.username)
password = view.findViewById(R.id.password)
view.findViewById<Button>(R.id.login).setOnClickListener {
if (username.text.toString() != "" && password.text.toString() != "") {
val login = Login(username.text.toString(), password.text.toString())
login(login)
} else {
Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
}
}
view.findViewById<Button>(R.id.register).setOnClickListener {
if (username.text.toString() != "" && password.text.toString() != "") {
val login = Login(username.text.toString(), password.text.toString())
register(login)
} else {
Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
}
}
binding.register.setOnClickListener {
if (
binding.username.text.toString() != "" &&
binding.password.text.toString() != ""
) {
val login = Login(
binding.username.text.toString(),
binding.password.text.toString()
)
register(login)
} else {
Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
}
}
val typedValue = TypedValue()
this.requireActivity().theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true)
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data))
val appName = HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
view.findViewById<TextView>(R.id.title).text = appName
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
builder.setView(view)
builder.setView(binding.root)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
@ -82,7 +62,7 @@ class LoginDialog : DialogFragment() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.login(login)
RetrofitInstance.authApi.login(login)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
@ -102,7 +82,9 @@ class LoginDialog : DialogFragment() {
Toast.makeText(context, R.string.loggedIn, Toast.LENGTH_SHORT).show()
PreferenceHelper.setToken(requireContext(), response.token!!)
PreferenceHelper.setUsername(requireContext(), login.username!!)
requireMainActivityRestart = true
dialog?.dismiss()
activity?.recreate()
}
}
}
@ -113,7 +95,7 @@ class LoginDialog : DialogFragment() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.register(login)
RetrofitInstance.authApi.register(login)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")

View File

@ -0,0 +1,41 @@
package com.github.libretube.dialogs
import android.app.Dialog
import android.os.Bundle
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import com.github.libretube.R
import com.github.libretube.activities.requireMainActivityRestart
import com.github.libretube.databinding.DialogLogoutBinding
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class LogoutDialog : DialogFragment() {
private val TAG = "LogoutDialog"
private lateinit var binding: DialogLogoutBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = MaterialAlertDialogBuilder(it)
binding = DialogLogoutBinding.inflate(layoutInflater)
val user = PreferenceHelper.getUsername(requireContext())
binding.user.text =
binding.user.text.toString() + " (" + user + ")"
binding.logout.setOnClickListener {
requireMainActivityRestart = true
Toast.makeText(context, R.string.loggedout, Toast.LENGTH_SHORT).show()
PreferenceHelper.setToken(requireContext(), "")
dialog?.dismiss()
activity?.recreate()
}
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
builder.setView(binding.root)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}

View File

@ -9,7 +9,7 @@ import android.widget.Toast
import androidx.fragment.app.DialogFragment
import com.github.libretube.R
import com.github.libretube.obj.PlaylistId
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
@ -20,16 +20,23 @@ import java.io.IOException
class PlaylistOptionsDialog(
private val playlistId: String,
private val isOwner: Boolean,
context: Context
) : DialogFragment() {
val TAG = "PlaylistOptionsDialog"
private val optionsList = listOf(
private var optionsList = listOf(
context.getString(R.string.clonePlaylist),
context.getString(R.string.share)
)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
if (isOwner) {
optionsList = optionsList +
context?.getString(R.string.deletePlaylist)!! -
context?.getString(R.string.clonePlaylist)!!
}
val dialog = MaterialAlertDialogBuilder(requireContext())
.setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
@ -41,12 +48,12 @@ class PlaylistOptionsDialog(
optionsList
)
) { _, which ->
when (which) {
when (optionsList[which]) {
// Clone the playlist to the users Piped account
0 -> {
context?.getString(R.string.clonePlaylist) -> {
val token = PreferenceHelper.getToken(requireContext())
if (token != "") {
importPlaylist(token!!, playlistId)
importPlaylist(token, playlistId)
} else {
Toast.makeText(
context,
@ -56,11 +63,15 @@ class PlaylistOptionsDialog(
}
}
// share the playlist
1 -> {
context?.getString(R.string.share) -> {
val shareDialog = ShareDialog(playlistId, true)
// using parentFragmentManager is important here
// using parentFragmentManager, childFragmentManager doesn't work here
shareDialog.show(parentFragmentManager, "ShareDialog")
}
context?.getString(R.string.deletePlaylist) -> {
val token = PreferenceHelper.getToken(requireContext())
deletePlaylist(playlistId, token)
}
}
}
return dialog.show()
@ -70,7 +81,7 @@ class PlaylistOptionsDialog(
fun run() {
CoroutineScope(Dispatchers.IO).launch {
val response = try {
RetrofitInstance.api.importPlaylist(token, PlaylistId(playlistId))
RetrofitInstance.authApi.importPlaylist(token, PlaylistId(playlistId))
} catch (e: IOException) {
println(e)
return@launch
@ -82,4 +93,22 @@ class PlaylistOptionsDialog(
}
run()
}
private fun deletePlaylist(id: String, token: String) {
fun run() {
CoroutineScope(Dispatchers.IO).launch {
val response = try {
RetrofitInstance.authApi.deletePlaylist(token, PlaylistId(id))
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launch
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
return@launch
}
}
}
run()
}
}

View File

@ -5,7 +5,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.github.libretube.R
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class ShareDialog(

View File

@ -6,9 +6,9 @@ import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import com.github.libretube.BackgroundMode
import com.github.libretube.R
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.BackgroundMode
import com.google.android.material.dialog.MaterialAlertDialogBuilder
/**
@ -43,14 +43,14 @@ class VideoOptionsDialog(private val videoId: String, context: Context) : Dialog
) { _, which ->
// For now, this checks the position of the option with the position that is in the
// list. I don't like it, but we will do like this for now.
when (which) {
when (optionsList[which]) {
// This for example will be the "Background mode" option
0 -> {
context?.getString(R.string.playOnBackground) -> {
BackgroundMode.getInstance()
.playOnBackgroundMode(requireContext(), videoId, 0)
.playOnBackgroundMode(requireContext(), videoId)
}
// Add Video to Playlist Dialog
1 -> {
context?.getString(R.string.addToPlaylist) -> {
val token = PreferenceHelper.getToken(requireContext())
if (token != "") {
val newFragment = AddtoPlaylistDialog()
@ -62,7 +62,7 @@ class VideoOptionsDialog(private val videoId: String, context: Context) : Dialog
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
2 -> {
context?.getString(R.string.share) -> {
val shareDialog = ShareDialog(videoId, false)
// using parentFragmentManager is important here
shareDialog.show(parentFragmentManager, "ShareDialog")

View File

@ -6,18 +6,14 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ScrollView
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.R
import com.github.libretube.adapters.ChannelAdapter
import com.github.libretube.databinding.FragmentChannelBinding
import com.github.libretube.obj.Subscribe
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.formatShort
import com.google.android.material.button.MaterialButton
@ -26,19 +22,19 @@ import retrofit2.HttpException
import java.io.IOException
class ChannelFragment : Fragment() {
private var channel_id: String? = null
private val TAG = "ChannelFragment"
private lateinit var binding: FragmentChannelBinding
private var channelId: String? = null
var nextPage: String? = null
var channelAdapter: ChannelAdapter? = null
var isLoading = true
var isSubscribed: Boolean = false
private var refreshLayout: SwipeRefreshLayout? = null
private var channelAdapter: ChannelAdapter? = null
private var isLoading = true
private var isSubscribed: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
channel_id = it.getString("channel_id")
channelId = it.getString("channel_id")
}
}
@ -46,43 +42,39 @@ class ChannelFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_channel, container, false)
): View {
binding = FragmentChannelBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
channel_id = channel_id!!.replace("/channel/", "")
view.findViewById<TextView>(R.id.channel_name).text = channel_id
val recyclerView = view.findViewById<RecyclerView>(R.id.channel_recView)
recyclerView.layoutManager = LinearLayoutManager(context)
refreshLayout = view.findViewById(R.id.channel_refresh)
channelId = channelId!!.replace("/channel/", "")
binding.channelName.text = channelId
binding.channelRecView.layoutManager = LinearLayoutManager(context)
val refreshChannel = {
refreshLayout?.isRefreshing = true
fetchChannel(view)
val subButton = view.findViewById<MaterialButton>(R.id.channel_subscribe)
binding.channelRefresh.isRefreshing = true
fetchChannel()
if (PreferenceHelper.getToken(requireContext()) != "") {
isSubscribed(subButton)
isSubscribed(binding.channelSubscribe)
}
}
refreshChannel()
refreshLayout?.setOnRefreshListener {
binding.channelRefresh.setOnRefreshListener {
refreshChannel()
}
val scrollView = view.findViewById<ScrollView>(R.id.channel_scrollView)
scrollView.viewTreeObserver
binding.channelScrollView.viewTreeObserver
.addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY)
if (binding.channelScrollView.getChildAt(0).bottom
== (binding.channelScrollView.height + binding.channelScrollView.scrollY)
) {
// scroll view is at bottom
if (nextPage != null && !isLoading) {
isLoading = true
refreshLayout?.isRefreshing = true
binding.channelRefresh.isRefreshing = true
fetchNextPage()
}
}
@ -95,8 +87,8 @@ class ChannelFragment : Fragment() {
lifecycleScope.launchWhenCreated {
val response = try {
val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.isSubscribed(
channel_id!!,
RetrofitInstance.authApi.isSubscribed(
channelId!!,
token
)
} catch (e: IOException) {
@ -135,9 +127,9 @@ class ChannelFragment : Fragment() {
lifecycleScope.launchWhenCreated {
val response = try {
val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.subscribe(
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channel_id)
Subscribe(channelId)
)
} catch (e: IOException) {
println(e)
@ -158,9 +150,9 @@ class ChannelFragment : Fragment() {
lifecycleScope.launchWhenCreated {
val response = try {
val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.unsubscribe(
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channel_id)
Subscribe(channelId)
)
} catch (e: IOException) {
println(e)
@ -176,55 +168,52 @@ class ChannelFragment : Fragment() {
run()
}
private fun fetchChannel(view: View) {
private fun fetchChannel() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getChannel(channel_id!!)
RetrofitInstance.api.getChannel(channelId!!)
} catch (e: IOException) {
refreshLayout?.isRefreshing = false
binding.channelRefresh.isRefreshing = false
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
refreshLayout?.isRefreshing = false
binding.channelRefresh.isRefreshing = false
Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated
}
nextPage = response.nextpage
isLoading = false
refreshLayout?.isRefreshing = false
binding.channelRefresh.isRefreshing = false
runOnUiThread {
view.findViewById<ScrollView>(R.id.channel_scrollView).visibility = View.VISIBLE
val channelName = view.findViewById<TextView>(R.id.channel_name)
channelName.text = response.name
binding.channelScrollView.visibility = View.VISIBLE
binding.channelName.text = response.name
if (response.verified) {
channelName.setCompoundDrawablesWithIntrinsicBounds(
binding.channelName.setCompoundDrawablesWithIntrinsicBounds(
0,
0,
R.drawable.ic_verified,
0
)
}
view.findViewById<TextView>(R.id.channel_subs).text = resources.getString(
binding.channelSubs.text = resources.getString(
R.string.subscribers,
response.subscriberCount.formatShort()
)
val channelDescription = view.findViewById<TextView>(R.id.channel_description)
if (response.description?.trim() == "") {
channelDescription.visibility = View.GONE
binding.channelDescription.visibility = View.GONE
} else {
channelDescription.text = response.description?.trim()
binding.channelDescription.text = response.description?.trim()
}
val bannerImage = view.findViewById<ImageView>(R.id.channel_banner)
val channelImage = view.findViewById<ImageView>(R.id.channel_image)
Picasso.get().load(response.bannerUrl).into(bannerImage)
Picasso.get().load(response.avatarUrl).into(channelImage)
Picasso.get().load(response.bannerUrl).into(binding.channelBanner)
Picasso.get().load(response.avatarUrl).into(binding.channelImage)
channelAdapter = ChannelAdapter(
response.relatedStreams!!.toMutableList(),
childFragmentManager
)
view.findViewById<RecyclerView>(R.id.channel_recView).adapter = channelAdapter
binding.channelRecView.adapter = channelAdapter
}
}
}
@ -235,21 +224,21 @@ class ChannelFragment : Fragment() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getChannelNextPage(channel_id!!, nextPage!!)
RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage!!)
} catch (e: IOException) {
refreshLayout?.isRefreshing = false
binding.channelRefresh.isRefreshing = false
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
refreshLayout?.isRefreshing = false
binding.channelRefresh.isRefreshing = false
Log.e(TAG, "HttpException, unexpected response," + e.response())
return@launchWhenCreated
}
nextPage = response.nextpage
channelAdapter?.updateItems(response.relatedStreams!!)
isLoading = false
refreshLayout?.isRefreshing = false
binding.channelRefresh.isRefreshing = false
}
}
run()
@ -260,13 +249,4 @@ class ChannelFragment : Fragment() {
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
override fun onDestroyView() {
val scrollView = view?.findViewById<ScrollView>(R.id.channel_scrollView)
scrollView?.viewTreeObserver?.removeOnScrollChangedListener {
}
channelAdapter = null
view?.findViewById<RecyclerView>(R.id.channel_recView)?.adapter = null
super.onDestroyView()
}
}

View File

@ -5,24 +5,24 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.R
import com.github.libretube.adapters.TrendingAdapter
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.databinding.FragmentHomeBinding
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.LocaleHelper
import com.github.libretube.util.RetrofitInstance
import retrofit2.HttpException
import java.io.IOException
class Home : Fragment() {
class HomeFragment : Fragment() {
private val TAG = "HomeFragment"
private var refreshLayout: SwipeRefreshLayout? = null
private lateinit var binding: FragmentHomeBinding
private lateinit var region: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
@ -33,36 +33,41 @@ class Home : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
): View {
binding = FragmentHomeBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<RecyclerView>(R.id.recview)
val grid = PreferenceHelper.getString(
requireContext(),
"grid",
resources.getInteger(R.integer.grid_items).toString()
)!!
recyclerView.layoutManager = GridLayoutManager(view.context, grid.toInt())
val progressbar = view.findViewById<ProgressBar>(R.id.progressBar)
fetchJson(progressbar, recyclerView)
refreshLayout = view.findViewById(R.id.home_refresh)
refreshLayout?.isEnabled = true
refreshLayout?.setOnRefreshListener {
Log.d(TAG, "hmm")
fetchJson(progressbar, recyclerView)
val regionPref = PreferenceHelper.getString(requireContext(), "region", "sys")!!
// get the system default country if auto region selected
region = if (regionPref == "sys") {
LocaleHelper
.getDetectedCountry(requireContext(), "UK")
.uppercase()
} else regionPref
binding.recview.layoutManager = GridLayoutManager(view.context, grid.toInt())
fetchJson()
binding.homeRefresh.isEnabled = true
binding.homeRefresh.setOnRefreshListener {
fetchJson()
}
}
private fun fetchJson(progressBar: ProgressBar, recyclerView: RecyclerView) {
private fun fetchJson() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
val region = PreferenceHelper.getString(requireContext(), "region", "US")
RetrofitInstance.api.getTrending(region!!)
RetrofitInstance.api.getTrending(region)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
@ -73,11 +78,11 @@ class Home : Fragment() {
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
} finally {
refreshLayout?.isRefreshing = false
binding.homeRefresh.isRefreshing = false
}
runOnUiThread {
progressBar.visibility = View.GONE
recyclerView.adapter = TrendingAdapter(response, childFragmentManager)
binding.progressBar.visibility = View.GONE
binding.recview.adapter = TrendingAdapter(response, childFragmentManager)
}
}
}
@ -89,11 +94,4 @@ class Home : Fragment() {
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
override fun onDestroyView() {
view?.findViewById<RecyclerView>(R.id.recview)?.adapter = null
refreshLayout = null
Log.e(TAG, "destroyview")
super.onDestroyView()
}
}

View File

@ -5,29 +5,25 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.R
import com.github.libretube.adapters.PlaylistsAdapter
import com.github.libretube.databinding.FragmentLibraryBinding
import com.github.libretube.dialogs.CreatePlaylistDialog
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.google.android.material.floatingactionbutton.FloatingActionButton
import retrofit2.HttpException
import java.io.IOException
class Library : Fragment() {
class LibraryFragment : Fragment() {
private val TAG = "LibraryFragment"
lateinit var token: String
private lateinit var playlistRecyclerView: RecyclerView
private lateinit var refreshLayout: SwipeRefreshLayout
private lateinit var binding: FragmentLibraryBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -39,51 +35,59 @@ class Library : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_library, container, false)
): View {
binding = FragmentLibraryBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
playlistRecyclerView = view.findViewById(R.id.playlist_recView)
playlistRecyclerView.layoutManager = LinearLayoutManager(view.context)
binding.playlistRecView.layoutManager = LinearLayoutManager(view.context)
token = PreferenceHelper.getToken(requireContext())
refreshLayout = view.findViewById(R.id.playlist_refresh)
// hide watch history button of history disabled
val watchHistoryEnabled =
PreferenceHelper.getBoolean(requireContext(), "watch_history_toggle", true)
if (!watchHistoryEnabled) {
binding.showWatchHistory.visibility = View.GONE
} else {
binding.showWatchHistory.setOnClickListener {
findNavController().navigate(R.id.watchHistoryFragment)
}
}
if (token != "") {
view.findViewById<ImageView>(R.id.boogh2).visibility = View.GONE
view.findViewById<TextView>(R.id.textLike2).visibility = View.GONE
binding.boogh.visibility = View.GONE
binding.textLike.visibility = View.GONE
fetchPlaylists()
refreshLayout.isEnabled = true
refreshLayout.setOnRefreshListener {
binding.playlistRefresh.isEnabled = true
binding.playlistRefresh.setOnRefreshListener {
fetchPlaylists()
}
val createPlaylistButton = view.findViewById<FloatingActionButton>(R.id.create_playlist)
createPlaylistButton.setOnClickListener {
binding.createPlaylist.setOnClickListener {
val newFragment = CreatePlaylistDialog()
newFragment.show(childFragmentManager, "Create Playlist")
}
} else {
refreshLayout.isEnabled = false
view.findViewById<FloatingActionButton>(R.id.create_playlist).visibility = View.GONE
binding.playlistRefresh.isEnabled = false
binding.createPlaylist.visibility = View.GONE
}
}
override fun onResume() {
// optimize CreatePlaylistFab bottom margin if miniPlayer active
val createPlaylistButton = view?.findViewById<FloatingActionButton>(R.id.create_playlist)
val layoutParams = createPlaylistButton?.layoutParams as ViewGroup.MarginLayoutParams
val layoutParams = binding.createPlaylist.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.bottomMargin = if (isMiniPlayerVisible) 180 else 64
createPlaylistButton?.layoutParams = layoutParams
binding.createPlaylist.layoutParams = layoutParams
super.onResume()
}
fun fetchPlaylists() {
fun run() {
refreshLayout.isRefreshing = true
binding.playlistRefresh.isRefreshing = true
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.playlists(token)
RetrofitInstance.authApi.playlists(token)
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
@ -94,27 +98,27 @@ class Library : Fragment() {
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
} finally {
refreshLayout.isRefreshing = false
binding.playlistRefresh.isRefreshing = false
}
if (response.isNotEmpty()) {
runOnUiThread {
view?.findViewById<ImageView>(R.id.boogh2)?.visibility = View.GONE
view?.findViewById<TextView>(R.id.textLike2)?.visibility = View.GONE
binding.boogh.visibility = View.GONE
binding.textLike.visibility = View.GONE
}
val playlistsAdapter = PlaylistsAdapter(
response.toMutableList(),
requireActivity()
)
playlistRecyclerView.adapter = playlistsAdapter
binding.playlistRecView.adapter = playlistsAdapter
} else {
runOnUiThread {
view?.findViewById<ImageView>(R.id.boogh2).apply {
this?.visibility = View.VISIBLE
this?.setImageResource(R.drawable.ic_list)
binding.boogh.apply {
visibility = View.VISIBLE
setImageResource(R.drawable.ic_list)
}
view?.findViewById<TextView>(R.id.textLike2).apply {
this?.visibility = View.VISIBLE
this?.text = getString(R.string.emptyList)
binding.textLike.apply {
visibility = View.VISIBLE
text = getString(R.string.emptyList)
}
}
}

View File

@ -11,6 +11,8 @@ import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.support.v4.media.session.MediaSessionCompat
import android.text.Html
@ -19,14 +21,6 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintLayout
@ -37,16 +31,17 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.activities.MainActivity
import com.github.libretube.activities.hideKeyboard
import com.github.libretube.adapters.ChaptersAdapter
import com.github.libretube.adapters.CommentsAdapter
import com.github.libretube.adapters.TrendingAdapter
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.github.libretube.databinding.FragmentPlayerBinding
import com.github.libretube.dialogs.AddtoPlaylistDialog
import com.github.libretube.dialogs.DownloadDialog
import com.github.libretube.dialogs.ShareDialog
import com.github.libretube.hideKeyboard
import com.github.libretube.obj.ChapterSegment
import com.github.libretube.obj.PipedStream
import com.github.libretube.obj.Playlist
@ -56,12 +51,13 @@ import com.github.libretube.obj.SponsorBlockPrefs
import com.github.libretube.obj.StreamItem
import com.github.libretube.obj.Streams
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.services.IS_DOWNLOAD_RUNNING
import com.github.libretube.util.CronetHelper
import com.github.libretube.util.DescriptionAdapter
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.formatShort
import com.github.libretube.views.DoubleClickListener
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.ExoPlayer
@ -79,12 +75,13 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.ui.TimeBar
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSource
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.util.RepeatModeUtil
import com.google.android.exoplayer2.video.VideoSize
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope
@ -102,6 +99,9 @@ var isMiniPlayerVisible = false
class PlayerFragment : Fragment() {
private val TAG = "PlayerFragment"
private lateinit var binding: FragmentPlayerBinding
private lateinit var playerBinding: ExoStyledPlayerControlViewBinding
private var videoId: String? = null
private var playlistId: String? = null
private var sId: Int = 0
@ -114,14 +114,11 @@ class PlayerFragment : Fragment() {
private var isSubscribed: Boolean = false
private lateinit var relatedRecView: RecyclerView
private lateinit var commentsRecView: RecyclerView
private var commentsAdapter: CommentsAdapter? = null
private var commentsLoaded: Boolean? = false
private var nextPage: String? = null
private var isLoading = true
private lateinit var exoPlayerView: StyledPlayerView
private lateinit var motionLayout: MotionLayout
private lateinit var exoPlayer: ExoPlayer
private lateinit var segmentData: Segments
private var relatedStreamsEnabled = true
@ -133,8 +130,6 @@ class PlayerFragment : Fragment() {
private var isPlayerLocked: Boolean = false
private lateinit var relDownloadVideo: LinearLayout
private lateinit var mediaSession: MediaSessionCompat
private lateinit var mediaSessionConnector: MediaSessionConnector
private lateinit var playerNotification: PlayerNotificationManager
@ -156,9 +151,11 @@ class PlayerFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
): View {
binding = FragmentPlayerBinding.inflate(layoutInflater, container, false)
playerBinding = binding.player.binding
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_player, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -172,17 +169,14 @@ class PlayerFragment : Fragment() {
}
private fun initializeTransitionLayout(view: View) {
val playerDescription = view.findViewById<TextView>(R.id.player_description)
videoId = videoId!!.replace("/watch?v=", "")
relDownloadVideo = view.findViewById(R.id.relPlayer_download)
val mainActivity = activity as MainActivity
mainActivity.findViewById<FrameLayout>(R.id.container).visibility = View.VISIBLE
val playerMotionLayout = view.findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout = playerMotionLayout
exoPlayerView = view.findViewById(R.id.player)
view.findViewById<TextView>(R.id.player_description).text = videoId
playerMotionLayout.addTransitionListener(object : MotionLayout.TransitionListener {
val mainActivity = activity as MainActivity
mainActivity.binding.container.visibility = View.VISIBLE
exoPlayerView = binding.player
binding.playerMotionLayout.addTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionStarted(
motionLayout: MotionLayout?,
startId: Int,
@ -198,7 +192,7 @@ class PlayerFragment : Fragment() {
) {
val mainActivity = activity as MainActivity
val mainMotionLayout =
mainActivity.findViewById<MotionLayout>(R.id.mainMotionLayout)
mainActivity.binding.mainMotionLayout
mainMotionLayout.progress = abs(progress)
exoPlayerView.hideController()
eId = endId
@ -209,7 +203,7 @@ class PlayerFragment : Fragment() {
println(currentId)
val mainActivity = activity as MainActivity
val mainMotionLayout =
mainActivity.findViewById<MotionLayout>(R.id.mainMotionLayout)
mainActivity.binding.mainMotionLayout
if (currentId == eId) {
isMiniPlayerVisible = true
exoPlayerView.useController = false
@ -222,7 +216,7 @@ class PlayerFragment : Fragment() {
}
override fun onTransitionTrigger(
motionLayout: MotionLayout?,
MotionLayout: MotionLayout?,
triggerId: Int,
positive: Boolean,
progress: Float
@ -230,93 +224,65 @@ class PlayerFragment : Fragment() {
}
})
playerMotionLayout.progress = 1.toFloat()
playerMotionLayout.transitionToStart()
binding.playerMotionLayout.progress = 1.toFloat()
binding.playerMotionLayout.transitionToStart()
view.findViewById<ImageView>(R.id.close_imageView).setOnClickListener {
binding.closeImageView.setOnClickListener {
isMiniPlayerVisible = false
motionLayout.transitionToEnd()
binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
mainActivity.supportFragmentManager.beginTransaction()
.remove(this)
.commit()
}
view.findViewById<ImageButton>(R.id.close_imageButton).setOnClickListener {
playerBinding.closeImageButton.setOnClickListener {
isMiniPlayerVisible = false
motionLayout.transitionToEnd()
binding.playerMotionLayout.transitionToEnd()
val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
mainActivity.supportFragmentManager.beginTransaction()
.remove(this)
.commit()
}
val playImageView = view.findViewById<ImageView>(R.id.play_imageView)
playImageView.setOnClickListener {
binding.playImageView.setOnClickListener {
paused = if (paused) {
playImageView.setImageResource(R.drawable.ic_pause)
binding.playImageView.setImageResource(R.drawable.ic_pause)
exoPlayer.play()
false
} else {
playImageView.setImageResource(R.drawable.ic_play)
binding.playImageView.setImageResource(R.drawable.ic_play)
exoPlayer.pause()
true
}
}
// video description and chapters toggle
val descLinLayout = view.findViewById<LinearLayout>(R.id.desc_linLayout)
view.findViewById<RelativeLayout>(R.id.player_title_layout).setOnClickListener {
val arrowImageView = view.findViewById<ImageView>(R.id.player_description_arrow)
arrowImageView.animate().rotationBy(180F).setDuration(250).start()
descLinLayout.visibility = if (descLinLayout.isVisible) View.GONE else View.VISIBLE
binding.playerTitleLayout.setOnClickListener {
binding.playerDescriptionArrow.animate().rotationBy(180F).setDuration(250).start()
binding.descLinLayout.visibility =
if (binding.descLinLayout.isVisible) View.GONE else View.VISIBLE
}
view.findViewById<MaterialCardView>(R.id.comments_toggle)
.setOnClickListener {
toggleComments()
}
val fullScreenButton = view.findViewById<ImageButton>(R.id.fullscreen)
val exoTitle = view.findViewById<TextView>(R.id.exo_title)
val mainContainer = view.findViewById<ConstraintLayout>(R.id.main_container)
val linLayout = view.findViewById<LinearLayout>(R.id.linLayout)
binding.commentsToggle.setOnClickListener {
toggleComments()
}
// FullScreen button trigger
fullScreenButton.setOnClickListener {
playerBinding.fullscreen.setOnClickListener {
// hide player controller
exoPlayerView.hideController()
if (!isFullScreen) {
with(motionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, -1)
enableTransition(R.id.yt_transition, false)
}
mainContainer.isClickable = true
linLayout.visibility = View.GONE
fullScreenButton.setImageResource(R.drawable.ic_fullscreen_exit)
exoTitle.visibility = View.VISIBLE
val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
// go to fullscreen mode
setFullscreen()
} else {
with(motionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
enableTransition(R.id.yt_transition, true)
}
mainContainer.isClickable = false
linLayout.visibility = View.VISIBLE
fullScreenButton.setImageResource(R.drawable.ic_fullscreen)
exoTitle.visibility = View.INVISIBLE
val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
// exit fullscreen mode
unsetFullscreen()
}
isFullScreen = !isFullScreen
}
// switching between original aspect ratio (black bars) and zoomed to fill device screen
view.findViewById<ImageButton>(R.id.aspect_ratio_button).setOnClickListener {
playerBinding.aspectRatioButton.setOnClickListener {
if (isZoomed) {
exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
isZoomed = false
@ -327,13 +293,12 @@ class PlayerFragment : Fragment() {
}
// lock and unlock the player
val lockPlayerButton = view.findViewById<ImageButton>(R.id.lock_player)
lockPlayerButton.setOnClickListener {
playerBinding.lockPlayer.setOnClickListener {
// change the locked/unlocked icon
if (!isPlayerLocked) {
lockPlayerButton.setImageResource(R.drawable.ic_locked)
playerBinding.lockPlayer.setImageResource(R.drawable.ic_locked)
} else {
lockPlayerButton.setImageResource(R.drawable.ic_unlocked)
playerBinding.lockPlayer.setImageResource(R.drawable.ic_unlocked)
}
// show/hide all the controls
@ -343,32 +308,88 @@ class PlayerFragment : Fragment() {
isPlayerLocked = !isPlayerLocked
}
val scrollView = view.findViewById<ScrollView>(R.id.player_scrollView)
scrollView.viewTreeObserver
binding.playerScrollView.viewTreeObserver
.addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY) &&
if (binding.playerScrollView.getChildAt(0).bottom
== (binding.playerScrollView.height + binding.playerScrollView.scrollY) &&
nextPage != null
) {
fetchNextComments()
}
}
commentsRecView = view.findViewById(R.id.comments_recView)
commentsRecView.layoutManager = LinearLayoutManager(view.context)
binding.commentsRecView.layoutManager = LinearLayoutManager(view.context)
binding.commentsRecView.setItemViewCacheSize(20)
commentsRecView.setItemViewCacheSize(20)
relatedRecView = view.findViewById(R.id.player_recView)
relatedRecView.layoutManager =
binding.relatedRecView.layoutManager =
GridLayoutManager(view.context, resources.getInteger(R.integer.grid_items))
}
private fun setFullscreen() {
with(binding.playerMotionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, -1)
enableTransition(R.id.yt_transition, false)
}
binding.mainContainer.isClickable = true
binding.linLayout.visibility = View.GONE
playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen_exit)
playerBinding.exoTitle.visibility = View.VISIBLE
val mainActivity = activity as MainActivity
val fullscreenOrientationPref = PreferenceHelper
.getString(requireContext(), "fullscreen_orientation", "ratio")
val scaleFactor = 1.3F
playerBinding.exoPlayPause.scaleX = scaleFactor
playerBinding.exoPlayPause.scaleY = scaleFactor
val orientation = when (fullscreenOrientationPref) {
"ratio" -> {
val videoSize = exoPlayer.videoSize
// probably a youtube shorts video
Log.e(TAG, videoSize.height.toString() + " " + videoSize.width.toString())
if (videoSize.height > videoSize.width) ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
// a video with normal aspect ratio
else ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
}
"auto" -> ActivityInfo.SCREEN_ORIENTATION_USER
"landscape" -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
"portrait" -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
}
mainActivity.requestedOrientation = orientation
isFullScreen = true
}
private fun unsetFullscreen() {
// leave fullscreen mode
with(binding.playerMotionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
enableTransition(R.id.yt_transition, true)
}
binding.mainContainer.isClickable = false
binding.linLayout.visibility = View.VISIBLE
playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen)
playerBinding.exoTitle.visibility = View.INVISIBLE
val scaleFactor = 1F
playerBinding.exoPlayPause.scaleX = scaleFactor
playerBinding.exoPlayPause.scaleY = scaleFactor
val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
isFullScreen = false
}
private fun toggleComments() {
commentsRecView.visibility =
if (commentsRecView.isVisible) View.GONE else View.VISIBLE
relatedRecView.visibility =
if (relatedRecView.isVisible) View.GONE else View.VISIBLE
binding.commentsRecView.visibility =
if (binding.commentsRecView.isVisible) View.GONE else View.VISIBLE
binding.relatedRecView.visibility =
if (binding.relatedRecView.isVisible) View.GONE else View.VISIBLE
if (!commentsLoaded!!) fetchComments()
}
@ -386,10 +407,7 @@ class PlayerFragment : Fragment() {
// pause player if screen off and setting enabled
if (
this::exoPlayer.isInitialized &&
exoPlayer != null &&
!isScreenOn &&
pausePlayerOnScreenOffEnabled
this::exoPlayer.isInitialized && !isScreenOn && pausePlayerOnScreenOffEnabled
) {
exoPlayer.pause()
}
@ -399,6 +417,7 @@ class PlayerFragment : Fragment() {
override fun onDestroy() {
super.onDestroy()
try {
saveWatchPosition()
mediaSession.isActive = false
mediaSession.release()
mediaSessionConnector.setPlayer(null)
@ -412,6 +431,25 @@ class PlayerFragment : Fragment() {
}
}
// save the watch position if video isn't finished and option enabled
private fun saveWatchPosition() {
val watchPositionsEnabled = PreferenceHelper.getBoolean(
requireContext(),
"watch_positions_toggle",
true
)
if (watchPositionsEnabled && exoPlayer.currentPosition != exoPlayer.duration) {
PreferenceHelper.saveWatchPosition(
requireContext(),
videoId!!,
exoPlayer.currentPosition
)
} else if (watchPositionsEnabled) {
// delete watch position if video has ended
PreferenceHelper.removeWatchPosition(requireContext(), videoId!!)
}
}
private fun checkForSegments() {
if (!exoPlayer.isPlaying || !sponsorBlockPrefs.sponsorBlockEnabled) return
@ -462,17 +500,12 @@ class PlayerFragment : Fragment() {
relatedStreams = response.relatedStreams
runOnUiThread {
if (response.chapters != null) initializeChapters(response.chapters)
// set media sources for the player
setResolutionAndSubtitles(view, response)
exoPlayer.prepare()
prepareExoPlayerView()
initializePlayerView(view, response)
// support for time stamped links
if (arguments?.getLong("timeStamp") != null) {
val position = arguments?.getLong("timeStamp")!! * 1000
exoPlayer.seekTo(position)
}
seekToWatchPosition()
exoPlayer.prepare()
exoPlayer.play()
exoPlayerView.useController = true
initializePlayerNotification(requireContext())
@ -481,12 +514,32 @@ class PlayerFragment : Fragment() {
if (!relatedStreamsEnabled) toggleComments()
// prepare for autoplay
initAutoPlay()
val watchHistoryEnabled =
PreferenceHelper.getBoolean(requireContext(), "Watch_history_toggle", true)
if (watchHistoryEnabled) {
PreferenceHelper.addToWatchHistory(requireContext(), videoId!!, response)
}
}
}
}
run()
}
private fun seekToWatchPosition() {
// seek to saved watch position if available
val watchPositions = PreferenceHelper.getWatchPositions(requireContext())
var position: Long? = null
watchPositions.forEach {
if (it.videoId == videoId) position = it.position
}
// support for time stamped links
val timeStamp: Long? = arguments?.getLong("timeStamp")
if (timeStamp != null && timeStamp != 0L) {
position = timeStamp * 1000
}
if (position != null) exoPlayer.seekTo(position!!)
}
// the function is working recursively
private fun initAutoPlay() {
// save related streams for autoplay
@ -535,7 +588,7 @@ class PlayerFragment : Fragment() {
// if it's not a playlist then use the next related video
} else if (relatedStreams != null && relatedStreams!!.isNotEmpty()) {
// save next video from related streams for autoplay
nextStreamId = relatedStreams!![0].url!!.replace("/watch?v=", "")!!
nextStreamId = relatedStreams!![0].url!!.replace("/watch?v=", "")
}
}
}
@ -638,20 +691,25 @@ class PlayerFragment : Fragment() {
}
private fun initializePlayerView(view: View, response: Streams) {
view.findViewById<TextView>(R.id.player_views_info).text =
binding.playerViewsInfo.text =
context?.getString(R.string.views, response.views.formatShort()) +
"" + response.uploadDate
view.findViewById<TextView>(R.id.textLike).text = response.likes.formatShort()
view.findViewById<TextView>(R.id.textDislike).text = response.dislikes.formatShort()
val channelImage = view.findViewById<ImageView>(R.id.player_channelImage)
Picasso.get().load(response.uploaderAvatar).into(channelImage)
view.findViewById<TextView>(R.id.player_channelName).text = response.uploader
binding.textLike.text = response.likes.formatShort()
binding.textDislike.text = response.dislikes.formatShort()
Picasso.get().load(response.uploaderAvatar).into(binding.playerChannelImage)
binding.playerChannelName.text = response.uploader
view.findViewById<TextView>(R.id.title_textView).text = response.title
view.findViewById<TextView>(R.id.player_title).text = response.title
view.findViewById<TextView>(R.id.player_description).text = response.description
binding.titleTextView.text = response.title
binding.playerTitle.text = response.title
binding.playerDescription.text = response.description
view.findViewById<TextView>(R.id.exo_title).text = response.title
playerBinding.exoTitle.text = response.title
enableSeekbarPreview()
enableDoubleTapToSeek()
// init the chapters recyclerview
if (response.chapters != null) initializeChapters(response.chapters)
// Listener for play and pause icon change
exoPlayer.addListener(object : Player.Listener {
@ -664,6 +722,22 @@ class PlayerFragment : Fragment() {
}
}
override fun onVideoSizeChanged(
videoSize: VideoSize
) {
// Set new width/height of view
// height or width must be cast to float as int/int will give 0
// Redraw the player container with the new layout height
val params = binding.player.layoutParams
params.height = videoSize.height / videoSize.width * params.width
binding.player.layoutParams = params
binding.player.requestLayout()
(binding.mainContainer.layoutParams as ConstraintLayout.LayoutParams).apply {
matchConstraintPercentHeight = (videoSize.height / videoSize.width).toFloat()
}
}
@Deprecated(message = "Deprecated", level = DeprecationLevel.HIDDEN)
override fun onPlayerStateChanged(
playWhenReady: Boolean,
@ -690,38 +764,34 @@ class PlayerFragment : Fragment() {
if (playWhenReady && playbackState == Player.STATE_READY) {
// media actually playing
transitioning = false
view.findViewById<ImageView>(R.id.play_imageView)
.setImageResource(R.drawable.ic_pause)
binding.playImageView.setImageResource(R.drawable.ic_pause)
} else if (playWhenReady) {
// might be idle (plays after prepare()),
// buffering (plays when data available)
// or ended (plays when seek away from end)
view.findViewById<ImageView>(R.id.play_imageView)
.setImageResource(R.drawable.ic_play)
binding.playImageView.setImageResource(R.drawable.ic_play)
} else {
// player paused in any state
view.findViewById<ImageView>(R.id.play_imageView)
.setImageResource(R.drawable.ic_play)
binding.playImageView.setImageResource(R.drawable.ic_play)
}
}
})
// share button
view.findViewById<LinearLayout>(R.id.relPlayer_share).setOnClickListener {
binding.relPlayerShare.setOnClickListener {
val shareDialog = ShareDialog(videoId!!, false)
shareDialog.show(childFragmentManager, "ShareDialog")
}
// check if livestream
if (response.duration!! > 0) {
// download clicked
relDownloadVideo.setOnClickListener {
binding.relPlayerDownload.setOnClickListener {
if (!IS_DOWNLOAD_RUNNING) {
val newFragment = DownloadDialog()
val bundle = Bundle()
bundle.putString("video_id", videoId)
bundle.putParcelable("streams", response)
newFragment.arguments = bundle
newFragment.show(childFragmentManager, "Download")
newFragment.show(childFragmentManager, "DownloadDialog")
} else {
Toast.makeText(context, R.string.dlisinprogress, Toast.LENGTH_SHORT)
.show()
@ -732,7 +802,7 @@ class PlayerFragment : Fragment() {
}
if (response.hls != null) {
view.findViewById<LinearLayout>(R.id.relPlayer_vlc).setOnClickListener {
binding.relPlayerVlc.setOnClickListener {
// start an intent with video as mimetype using the hls stream
val uri: Uri = Uri.parse(response.hls)
val intent = Intent()
@ -749,14 +819,14 @@ class PlayerFragment : Fragment() {
}
if (relatedStreamsEnabled) {
// only show related streams if enabled
relatedRecView.adapter = TrendingAdapter(
binding.relatedRecView.adapter = TrendingAdapter(
response.relatedStreams!!,
childFragmentManager
)
}
// set video description
val description = response.description!!
view.findViewById<TextView>(R.id.player_description).text =
binding.playerDescription.text =
// detect whether the description is html formatted
if (description.contains("<") && description.contains(">")) {
if (SDK_INT >= Build.VERSION_CODES.N) {
@ -769,19 +839,18 @@ class PlayerFragment : Fragment() {
description
}
view.findViewById<RelativeLayout>(R.id.player_channel).setOnClickListener {
binding.playerChannel.setOnClickListener {
val activity = view.context as MainActivity
val bundle = bundleOf("channel_id" to response.uploaderUrl)
activity.navController.navigate(R.id.channel, bundle)
activity.findViewById<MotionLayout>(R.id.mainMotionLayout).transitionToEnd()
view.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
activity.navController.navigate(R.id.channelFragment, bundle)
activity.binding.mainMotionLayout.transitionToEnd()
binding.playerMotionLayout.transitionToEnd()
}
val token = PreferenceHelper.getToken(requireContext())
if (token != "") {
val channelId = response.uploaderUrl?.replace("/channel/", "")
val subButton = view.findViewById<MaterialButton>(R.id.player_subscribe)
isSubscribed(subButton, channelId!!)
view.findViewById<LinearLayout>(R.id.save).setOnClickListener {
isSubscribed(binding.playerSubscribe, channelId!!)
binding.save.setOnClickListener {
val newFragment = AddtoPlaylistDialog()
val bundle = Bundle()
bundle.putString("videoId", videoId)
@ -791,14 +860,81 @@ class PlayerFragment : Fragment() {
}
}
private fun initializeChapters(chapters: List<ChapterSegment>) {
val chaptersRecView = view?.findViewById<RecyclerView>(R.id.chapters_recView)
private fun enableDoubleTapToSeek() {
val seekIncrement =
PreferenceHelper.getString(requireContext(), "seek_increment", "5")?.toLong()!! * 1000
// enable rewind button
binding.rewindFL.setOnClickListener(
DoubleClickListener(
callback = object : DoubleClickListener.Callback {
override fun doubleClicked() {
binding.rewindBTN.visibility = View.VISIBLE
exoPlayer.seekTo(exoPlayer.currentPosition - seekIncrement)
Handler(Looper.getMainLooper()).postDelayed({
binding.rewindBTN.visibility = View.INVISIBLE
}, 700)
}
override fun singleClicked() {
toggleController()
}
}
)
)
// enable fast forward button
binding.forwardFL.setOnClickListener(
DoubleClickListener(
callback = object : DoubleClickListener.Callback {
override fun doubleClicked() {
binding.forwardBTN.visibility = View.VISIBLE
exoPlayer.seekTo(exoPlayer.currentPosition + seekIncrement)
Handler(Looper.getMainLooper()).postDelayed({
binding.forwardBTN.visibility = View.INVISIBLE
}, 700)
}
override fun singleClicked() {
toggleController()
}
}
)
)
}
// toggle the visibility of the player controller
private fun toggleController() {
if (exoPlayerView.isControllerFullyVisible) exoPlayerView.hideController()
else exoPlayerView.showController()
}
// enable seek bar preview
private fun enableSeekbarPreview() {
playerBinding.exoProgress.addListener(object : TimeBar.OnScrubListener {
override fun onScrubStart(timeBar: TimeBar, position: Long) {
exoPlayer.pause()
}
override fun onScrubMove(timeBar: TimeBar, position: Long) {
exoPlayer.seekTo(position)
}
override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) {
exoPlayer.play()
Handler(Looper.getMainLooper()).postDelayed({
exoPlayerView.hideController()
}, 200)
}
})
}
private fun initializeChapters(chapters: List<ChapterSegment>) {
if (chapters.isNotEmpty()) {
chaptersRecView?.layoutManager =
binding.chaptersRecView.layoutManager =
LinearLayoutManager(this.context, LinearLayoutManager.HORIZONTAL, false)
chaptersRecView?.adapter = ChaptersAdapter(chapters, exoPlayer)
chaptersRecView?.visibility = View.VISIBLE
binding.chaptersRecView.adapter = ChaptersAdapter(chapters, exoPlayer)
binding.chaptersRecView.visibility = View.VISIBLE
}
}
@ -816,7 +952,7 @@ class PlayerFragment : Fragment() {
val videoSource: MediaSource =
DefaultMediaSourceFactory(dataSourceFactory)
.createMediaSource(videoItem)
var audioSource: MediaSource =
val audioSource: MediaSource =
ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(fromUri(audioUrl))
val mergeSource: MediaSource =
@ -829,15 +965,12 @@ class PlayerFragment : Fragment() {
PreferenceHelper.getString(requireContext(), "player_video_format", "WEBM")
val defres = PreferenceHelper.getString(requireContext(), "default_res", "")!!
val qualityText = view.findViewById<TextView>(R.id.quality_text)
val qualitySelect = view.findViewById<LinearLayout>(R.id.quality_linLayout)
var videosNameArray: Array<CharSequence> = arrayOf()
var videosUrlArray: Array<Uri> = arrayOf()
// append hls to list if available
if (response.hls != null) {
videosNameArray += "HLS"
videosNameArray += getString(R.string.hls)
videosUrlArray += response.hls.toUri()
}
@ -871,7 +1004,7 @@ class PlayerFragment : Fragment() {
val videoUri = videosUrlArray[index]
val audioUrl = getMostBitRate(response.audioStreams!!)
setMediaSource(subtitle, videoUri, audioUrl)
qualityText.text = videosNameArray[index]
playerBinding.qualityText.text = videosNameArray[index]
return@lit
} else if (response.hls != null) {
val mediaItem: MediaItem = MediaItem.Builder()
@ -902,11 +1035,11 @@ class PlayerFragment : Fragment() {
val videoUri = videosUrlArray[0]
val audioUrl = getMostBitRate(response.audioStreams!!)
setMediaSource(subtitle, videoUri, audioUrl)
qualityText.text = videosNameArray[0]
playerBinding.qualityText.text = videosNameArray[0]
}
}
qualitySelect.setOnClickListener {
playerBinding.qualityLinLayout.setOnClickListener {
// Dialog for quality selection
val builder: MaterialAlertDialogBuilder? = activity?.let {
MaterialAlertDialogBuilder(it)
@ -918,7 +1051,7 @@ class PlayerFragment : Fragment() {
) { _, which ->
whichQuality = which
if (
videosNameArray[which] == "HLS" ||
videosNameArray[which] == getString(R.string.hls) ||
videosNameArray[which] == "LBRY HLS"
) {
// no need to merge sources if using hls
@ -933,7 +1066,7 @@ class PlayerFragment : Fragment() {
setMediaSource(subtitle, videoUri, audioUrl)
}
exoPlayer.seekTo(lastPosition)
qualityText.text = videosNameArray[which]
playerBinding.qualityText.text = videosNameArray[which]
}
val dialog = builder.create()
dialog.show()
@ -969,7 +1102,7 @@ class PlayerFragment : Fragment() {
// cache the last three minutes
.setBackBuffer(1000 * 60 * 3, true)
.setBufferDurationsMs(
DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
1000 * 10, // exo default is 50s
bufferingGoal,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
@ -1006,21 +1139,19 @@ class PlayerFragment : Fragment() {
playerNotification.apply {
setPlayer(exoPlayer)
setUseNextAction(false)
setUsePreviousAction(false)
setUseStopAction(true)
setMediaSessionToken(mediaSession.sessionToken)
}
}
private fun lockPlayer(isLocked: Boolean) {
val visibility = if (isLocked) View.VISIBLE else View.INVISIBLE
exoPlayerView.findViewById<LinearLayout>(R.id.exo_top_bar_right).visibility = visibility
exoPlayerView.findViewById<ImageButton>(R.id.exo_play_pause).visibility = visibility
exoPlayerView.findViewById<Button>(R.id.exo_ffwd_with_amount).visibility = visibility
exoPlayerView.findViewById<Button>(R.id.exo_rew_with_amount).visibility = visibility
exoPlayerView.findViewById<FrameLayout>(R.id.exo_bottom_bar).visibility = visibility
exoPlayerView.findViewById<TextView>(R.id.exo_title).visibility =
if (isLocked && isFullScreen) View.VISIBLE else View.INVISIBLE
val visibility = if (isLocked) View.VISIBLE else View.GONE
playerBinding.exoTopBarRight.visibility = visibility
playerBinding.exoPlayPause.visibility = visibility
playerBinding.exoBottomBar.visibility = visibility
playerBinding.closeImageButton.visibility = visibility
playerBinding.exoTitle.visibility = visibility
}
private fun isSubscribed(button: MaterialButton, channel_id: String) {
@ -1029,7 +1160,7 @@ class PlayerFragment : Fragment() {
lifecycleScope.launchWhenCreated {
val response = try {
val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.isSubscribed(
RetrofitInstance.authApi.isSubscribed(
channel_id,
token
)
@ -1069,7 +1200,7 @@ class PlayerFragment : Fragment() {
lifecycleScope.launchWhenCreated {
val response = try {
val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.subscribe(
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channel_id)
)
@ -1092,7 +1223,7 @@ class PlayerFragment : Fragment() {
lifecycleScope.launchWhenCreated {
val response = try {
val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.unsubscribe(
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channel_id)
)
@ -1143,7 +1274,7 @@ class PlayerFragment : Fragment() {
return@launchWhenCreated
}
commentsAdapter = CommentsAdapter(videoId!!, commentsResponse.comments)
commentsRecView.adapter = commentsAdapter
binding.commentsRecView.adapter = commentsAdapter
nextPage = commentsResponse.nextpage
commentsLoaded = true
isLoading = false
@ -1176,37 +1307,35 @@ class PlayerFragment : Fragment() {
if (isInPictureInPictureMode) {
exoPlayerView.hideController()
exoPlayerView.useController = false
with(motionLayout) {
binding.linLayout.visibility = View.GONE
with(binding.playerMotionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, -1)
enableTransition(R.id.yt_transition, false)
}
view?.findViewById<ConstraintLayout>(R.id.main_container)?.isClickable = true
view?.findViewById<LinearLayout>(R.id.top_bar)?.visibility = View.GONE
binding.mainContainer.isClickable = true
val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
isFullScreen = false
} else {
with(motionLayout) {
with(binding.playerMotionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
enableTransition(R.id.yt_transition, true)
}
exoPlayerView.showController()
exoPlayerView.useController = true
view?.findViewById<ConstraintLayout>(R.id.main_container)?.isClickable = false
view?.findViewById<LinearLayout>(R.id.top_bar)?.visibility = View.VISIBLE
binding.linLayout.visibility = View.VISIBLE
binding.mainContainer.isClickable = false
}
}
fun onUserLeaveHint() {
val bounds = Rect()
val scrollView = view?.findViewById<ScrollView>(R.id.player_scrollView)
scrollView?.getHitRect(bounds)
binding.playerScrollView.getHitRect(bounds)
if (SDK_INT >= Build.VERSION_CODES.O &&
exoPlayer.isPlaying && (
scrollView?.getLocalVisibleRect(bounds) == true ||
isFullScreen
)
exoPlayer.isPlaying && (binding.playerScrollView.getLocalVisibleRect(bounds) || isFullScreen)
) {
activity?.enterPictureInPictureMode(updatePipParams())
}

View File

@ -5,27 +5,27 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.ScrollView
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.adapters.PlaylistAdapter
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.databinding.FragmentPlaylistBinding
import com.github.libretube.dialogs.PlaylistOptionsDialog
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import retrofit2.HttpException
import java.io.IOException
class PlaylistFragment : Fragment() {
private val TAG = "PlaylistFragment"
private lateinit var binding: FragmentPlaylistBinding
private var playlistId: String? = null
var nextPage: String? = null
var playlistAdapter: PlaylistAdapter? = null
var isLoading = true
private var playlistAdapter: PlaylistAdapter? = null
private var isLoading = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
@ -37,24 +37,22 @@ class PlaylistFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_playlist, container, false)
): View {
binding = FragmentPlaylistBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
playlistId = playlistId!!.replace("/playlist?list=", "")
val recyclerView = view.findViewById<RecyclerView>(R.id.playlist_recView)
recyclerView.layoutManager = LinearLayoutManager(context)
binding.playlistRecView.layoutManager = LinearLayoutManager(context)
val progressBar = view.findViewById<ProgressBar>(R.id.playlist_progress)
progressBar.visibility = View.VISIBLE
fetchPlaylist(view)
binding.playlistProgress.visibility = View.VISIBLE
fetchPlaylist()
}
private fun fetchPlaylist(view: View) {
private fun fetchPlaylist() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
@ -70,16 +68,24 @@ class PlaylistFragment : Fragment() {
nextPage = response.nextpage
isLoading = false
runOnUiThread {
view.findViewById<ProgressBar>(R.id.playlist_progress).visibility = View.GONE
view.findViewById<TextView>(R.id.playlist_name).text = response.name
view.findViewById<TextView>(R.id.playlist_uploader).text = response.uploader
view.findViewById<TextView>(R.id.playlist_totVideos).text =
binding.playlistProgress.visibility = View.GONE
binding.playlistName.text = response.name
binding.playlistUploader.text = response.uploader
binding.playlistTotVideos.text =
getString(R.string.videoCount, response.videos.toString())
val user = PreferenceHelper.getUsername(requireContext())
var isOwner = false
if (response.uploaderUrl == null && response.uploader.equals(user, true)) {
isOwner = true
// check whether the user owns the playlist
val isOwner = response.uploaderUrl == null &&
response.uploader.equals(user, true)
// show playlist options
binding.optionsMenu.setOnClickListener {
val optionsDialog =
PlaylistOptionsDialog(playlistId!!, isOwner, requireContext())
optionsDialog.show(childFragmentManager, "PlaylistOptionsDialog")
}
playlistAdapter = PlaylistAdapter(
response.relatedStreams!!.toMutableList(),
playlistId!!,
@ -87,12 +93,11 @@ class PlaylistFragment : Fragment() {
requireActivity(),
childFragmentManager
)
view.findViewById<RecyclerView>(R.id.playlist_recView).adapter = playlistAdapter
val scrollView = view.findViewById<ScrollView>(R.id.playlist_scrollview)
scrollView.viewTreeObserver
binding.playlistRecView.adapter = playlistAdapter
binding.playlistScrollview.viewTreeObserver
.addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY)
if (binding.playlistScrollview.getChildAt(0).bottom
== (binding.playlistScrollview.height + binding.playlistScrollview.scrollY)
) {
// scroll view is at bottom
if (nextPage != null && !isLoading) {

View File

@ -12,7 +12,6 @@ import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView.GONE
import android.widget.TextView.OnEditorActionListener
import android.widget.TextView.VISIBLE
@ -20,28 +19,26 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.activities.hideKeyboard
import com.github.libretube.adapters.SearchAdapter
import com.github.libretube.adapters.SearchHistoryAdapter
import com.github.libretube.adapters.SearchSuggestionsAdapter
import com.github.libretube.hideKeyboard
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.databinding.FragmentSearchBinding
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
class SearchFragment : Fragment() {
private val TAG = "SearchFragment"
private lateinit var binding: FragmentSearchBinding
private var selectedFilter = 0
private var apiSearchFilter = "all"
private var nextPage: String? = null
private lateinit var searchRecView: RecyclerView
private lateinit var historyRecView: RecyclerView
private lateinit var autoTextView: EditText
private var searchAdapter: SearchAdapter? = null
private var isLoading: Boolean = true
private var isFetchingSearch: Boolean = false
@ -56,27 +53,21 @@ class SearchFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_search, container, false)
): View {
binding = FragmentSearchBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
searchRecView = view.findViewById(R.id.search_recycler)
historyRecView = view.findViewById(R.id.history_recycler)
autoTextView = view.findViewById(R.id.autoCompleteTextView)
val clearSearchButton = view.findViewById<ImageView>(R.id.clearSearch_imageView)
val filterImageView = view.findViewById<ImageView>(R.id.filterMenu_imageView)
var tempSelectedItem = 0
clearSearchButton.setOnClickListener {
autoTextView.text.clear()
binding.clearSearchImageView.setOnClickListener {
binding.autoCompleteTextView.text.clear()
}
filterImageView.setOnClickListener {
binding.filterMenuImageView.setOnClickListener {
val filterOptions = arrayOf(
getString(R.string.all),
getString(R.string.videos),
@ -108,7 +99,7 @@ class SearchFragment : Fragment() {
7 -> "music_playlists"
else -> "all"
}
fetchSearch(autoTextView.text.toString())
fetchSearch(binding.autoCompleteTextView.text.toString())
}
.setNegativeButton(getString(R.string.cancel), null)
.create()
@ -116,16 +107,16 @@ class SearchFragment : Fragment() {
}
// show search history
historyRecView.layoutManager = LinearLayoutManager(view.context)
binding.historyRecycler.layoutManager = LinearLayoutManager(view.context)
showHistory()
searchRecView.layoutManager = GridLayoutManager(view.context, 1)
autoTextView.requestFocus()
binding.searchRecycler.layoutManager = GridLayoutManager(view.context, 1)
binding.autoCompleteTextView.requestFocus()
val imm =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(autoTextView, InputMethodManager.SHOW_IMPLICIT)
imm.showSoftInput(binding.autoCompleteTextView, InputMethodManager.SHOW_IMPLICIT)
autoTextView.addTextChangedListener(object : TextWatcher {
binding.autoCompleteTextView.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
@ -136,18 +127,15 @@ class SearchFragment : Fragment() {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (s!! != "") {
searchRecView.adapter = null
binding.searchRecycler.adapter = null
searchRecView.viewTreeObserver
binding.searchRecycler.viewTreeObserver
.addOnScrollChangedListener {
if (!searchRecView.canScrollVertically(1)) {
fetchNextSearchItems(autoTextView.text.toString())
if (!binding.searchRecycler.canScrollVertically(1)) {
fetchNextSearchItems(binding.autoCompleteTextView.text.toString())
}
}
GlobalScope.launch {
fetchSuggestions(s.toString(), autoTextView)
}
fetchSuggestions(s.toString(), binding.autoCompleteTextView)
}
}
@ -157,13 +145,13 @@ class SearchFragment : Fragment() {
}
}
})
autoTextView.setOnEditorActionListener(
binding.autoCompleteTextView.setOnEditorActionListener(
OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
hideKeyboard()
searchRecView.visibility = VISIBLE
historyRecView.visibility = GONE
fetchSearch(autoTextView.text.toString())
binding.searchRecycler.visibility = VISIBLE
binding.historyRecycler.visibility = GONE
fetchSearch(binding.autoCompleteTextView.text.toString())
return@OnEditorActionListener true
}
false
@ -174,8 +162,8 @@ class SearchFragment : Fragment() {
private fun fetchSuggestions(query: String, autoTextView: EditText) {
fun run() {
lifecycleScope.launchWhenCreated {
searchRecView.visibility = GONE
historyRecView.visibility = VISIBLE
binding.searchRecycler.visibility = GONE
binding.historyRecycler.visibility = VISIBLE
val response = try {
RetrofitInstance.api.getSuggestions(query)
} catch (e: IOException) {
@ -188,13 +176,16 @@ class SearchFragment : Fragment() {
}
val suggestionsAdapter =
SearchSuggestionsAdapter(response, autoTextView, this@SearchFragment)
historyRecView.adapter = suggestionsAdapter
binding.historyRecycler.adapter = suggestionsAdapter
}
}
if (!isFetchingSearch) run()
}
fun fetchSearch(query: String) {
runOnUiThread {
binding.historyRecycler.visibility = GONE
}
lifecycleScope.launchWhenCreated {
isFetchingSearch = true
hideKeyboard()
@ -211,10 +202,9 @@ class SearchFragment : Fragment() {
nextPage = response.nextpage
if (response.items!!.isNotEmpty()) {
runOnUiThread {
historyRecView.visibility = GONE
searchRecView.visibility = VISIBLE
binding.searchRecycler.visibility = VISIBLE
searchAdapter = SearchAdapter(response.items, childFragmentManager)
searchRecView.adapter = searchAdapter
binding.searchRecycler.adapter = searchAdapter
}
}
addToHistory(query)
@ -265,12 +255,17 @@ class SearchFragment : Fragment() {
}
private fun showHistory() {
searchRecView.visibility = GONE
binding.searchRecycler.visibility = GONE
val historyList = PreferenceHelper.getHistory(requireContext())
if (historyList.isNotEmpty()) {
historyRecView.adapter =
SearchHistoryAdapter(requireContext(), historyList, autoTextView, this)
historyRecView.visibility = VISIBLE
binding.historyRecycler.adapter =
SearchHistoryAdapter(
requireContext(),
historyList,
binding.autoCompleteTextView,
this
)
binding.historyRecycler.visibility = VISIBLE
}
}

View File

@ -5,11 +5,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
@ -17,21 +13,23 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.R
import com.github.libretube.adapters.SubscriptionAdapter
import com.github.libretube.adapters.SubscriptionChannelAdapter
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.databinding.FragmentSubscriptionsBinding
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import retrofit2.HttpException
import java.io.IOException
class Subscriptions : Fragment() {
class SubscriptionsFragment : Fragment() {
val TAG = "SubFragment"
private lateinit var binding: FragmentSubscriptionsBinding
lateinit var token: String
var isLoaded = false
private var isLoaded = false
private var subscriptionAdapter: SubscriptionAdapter? = null
private var refreshLayout: SwipeRefreshLayout? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
@ -42,88 +40,76 @@ class Subscriptions : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_subscriptions, container, false)
): View {
binding = FragmentSubscriptionsBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
token = PreferenceHelper.getToken(requireContext())
refreshLayout = view.findViewById(R.id.sub_refresh)
if (token != "") {
view.findViewById<RelativeLayout>(R.id.loginOrRegister).visibility = View.GONE
refreshLayout?.isEnabled = true
binding.loginOrRegister.visibility = View.GONE
binding.subRefresh.isEnabled = true
var progressBar = view.findViewById<ProgressBar>(R.id.sub_progress)
progressBar.visibility = View.VISIBLE
binding.subProgress.visibility = View.VISIBLE
var channelRecView = view.findViewById<RecyclerView>(R.id.sub_channels)
var feedRecView = view.findViewById<RecyclerView>(R.id.sub_feed)
val grid = PreferenceHelper.getString(
requireContext(),
"grid",
resources.getInteger(R.integer.grid_items).toString()
)!!
feedRecView.layoutManager = GridLayoutManager(view.context, grid.toInt())
fetchFeed(feedRecView, progressBar, view)
binding.subFeed.layoutManager = GridLayoutManager(view.context, grid.toInt())
fetchFeed(binding.subFeed, binding.subProgress)
refreshLayout?.setOnRefreshListener {
fetchChannels(channelRecView)
fetchFeed(feedRecView, progressBar, view)
binding.subRefresh.setOnRefreshListener {
fetchChannels(binding.subChannels)
fetchFeed(binding.subFeed, binding.subProgress)
}
var toggleSubs = view.findViewById<RelativeLayout>(R.id.toggle_subs)
val arrowImageView = view.findViewById<ImageView>(R.id.toggle)
toggleSubs.visibility = View.VISIBLE
binding.toggleSubs.visibility = View.VISIBLE
var loadedSubbedChannels = false
toggleSubs.setOnClickListener {
arrowImageView.animate().rotationBy(180F).setDuration(100).start()
if (!channelRecView.isVisible) {
binding.toggleSubs.setOnClickListener {
binding.toggle.animate().rotationBy(180F).setDuration(100).start()
if (!binding.subChannels.isVisible) {
if (!loadedSubbedChannels) {
channelRecView?.layoutManager = LinearLayoutManager(context)
fetchChannels(channelRecView)
binding.subChannels.layoutManager = LinearLayoutManager(context)
fetchChannels(binding.subChannels)
loadedSubbedChannels = true
}
channelRecView.visibility = View.VISIBLE
feedRecView.visibility = View.GONE
binding.subChannels.visibility = View.VISIBLE
binding.subFeed.visibility = View.GONE
} else {
channelRecView.visibility = View.GONE
feedRecView.visibility = View.VISIBLE
// toggle button
val image = view.findViewById<ImageView>(R.id.toggle)
image.clearAnimation()
binding.subChannels.visibility = View.GONE
binding.subFeed.visibility = View.VISIBLE
}
}
val scrollView = view.findViewById<ScrollView>(R.id.scrollview_sub)
scrollView.viewTreeObserver
binding.scrollviewSub.viewTreeObserver
.addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY)
if (binding.scrollviewSub.getChildAt(0).bottom
== (binding.scrollviewSub.height + binding.scrollviewSub.scrollY)
) {
// scroll view is at bottom
if (isLoaded) {
refreshLayout?.isRefreshing = true
binding.subRefresh.isRefreshing = true
subscriptionAdapter?.updateItems()
refreshLayout?.isRefreshing = false
binding.subRefresh.isRefreshing = false
}
}
}
} else {
refreshLayout?.isEnabled = false
binding.subRefresh.isEnabled = false
}
}
private fun fetchFeed(feedRecView: RecyclerView, progressBar: ProgressBar, view: View) {
private fun fetchFeed(feedRecView: RecyclerView, progressBar: ProgressBar) {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getFeed(token)
RetrofitInstance.authApi.getFeed(token)
} catch (e: IOException) {
Log.e(TAG, e.toString())
Log.e(TAG, "IOException, you might not have internet connection")
@ -132,7 +118,7 @@ class Subscriptions : Fragment() {
Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated
} finally {
refreshLayout?.isRefreshing = false
binding.subRefresh.isRefreshing = false
}
if (response.isNotEmpty()) {
subscriptionAdapter = SubscriptionAdapter(response, childFragmentManager)
@ -140,16 +126,15 @@ class Subscriptions : Fragment() {
subscriptionAdapter?.updateItems()
} else {
runOnUiThread {
with(view.findViewById<ImageView>(R.id.boogh)) {
with(binding.boogh) {
visibility = View.VISIBLE
setImageResource(R.drawable.ic_list)
}
with(view.findViewById<TextView>(R.id.textLike)) {
with(binding.textLike) {
visibility = View.VISIBLE
text = getString(R.string.emptyList)
}
view.findViewById<RelativeLayout>(R.id.loginOrRegister)
.visibility = View.VISIBLE
binding.loginOrRegister.visibility = View.VISIBLE
}
}
progressBar.visibility = View.GONE
@ -163,7 +148,7 @@ class Subscriptions : Fragment() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.subscriptions(token)
RetrofitInstance.authApi.subscriptions(token)
} catch (e: IOException) {
Log.e(TAG, e.toString())
Log.e(TAG, "IOException, you might not have internet connection")
@ -172,7 +157,7 @@ class Subscriptions : Fragment() {
Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated
} finally {
refreshLayout?.isRefreshing = false
binding.subRefresh.isRefreshing = false
}
if (response.isNotEmpty()) {
channelRecView.adapter = SubscriptionChannelAdapter(response.toMutableList())
@ -184,13 +169,6 @@ class Subscriptions : Fragment() {
run()
}
override fun onDestroy() {
Log.e(TAG, "Destroyed")
super.onDestroy()
subscriptionAdapter = null
view?.findViewById<RecyclerView>(R.id.sub_feed)?.adapter = null
}
private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return
if (!isAdded) return // Fragment not attached to an Activity

View File

@ -0,0 +1,45 @@
package com.github.libretube.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.adapters.WatchHistoryAdapter
import com.github.libretube.databinding.FragmentWatchHistoryBinding
import com.github.libretube.preferences.PreferenceHelper
class WatchHistoryFragment : Fragment() {
private val TAG = "WatchHistoryFragment"
private lateinit var binding: FragmentWatchHistoryBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentWatchHistoryBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val watchHistory = PreferenceHelper.getWatchHistory(requireContext())
val watchHistoryAdapter = WatchHistoryAdapter(watchHistory, childFragmentManager)
binding.watchHistoryRecView.adapter = watchHistoryAdapter
binding.clearHistory.setOnClickListener {
PreferenceHelper.removePreference(requireContext(), "watch_history")
watchHistoryAdapter.clear()
}
// reverse order
val linearLayoutManager = LinearLayoutManager(view.context)
linearLayoutManager.reverseLayout = true
linearLayoutManager.stackFromEnd = true
binding.watchHistoryRecView.layoutManager = linearLayoutManager
}
}

View File

@ -1,7 +1,5 @@
package com.github.libretube.obj
import android.os.Parcel
import android.os.Parcelable
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
@ -28,68 +26,9 @@ data class Streams(
val livestream: Boolean?,
val proxyUrl: String?,
val chapters: List<ChapterSegment>?
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Long::class.java.classLoader) as? Long,
parcel.readValue(Long::class.java.classLoader) as? Long,
parcel.readValue(Int::class.java.classLoader) as? Long,
TODO("audioStreams"),
TODO("videoStreams"),
TODO("relatedStreams"),
TODO("subtitles"),
parcel.readValue(Boolean::class.java.classLoader) as? Boolean,
parcel.readString(),
TODO("chapters")
)
) {
constructor() : this(
"", "", "", "", "", "", "", "", "", "", null, -1, -1, -1, -1, emptyList(), emptyList(),
emptyList(), emptyList(), null, "", emptyList()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(title)
parcel.writeString(description)
parcel.writeString(uploadDate)
parcel.writeString(uploader)
parcel.writeString(uploaderUrl)
parcel.writeString(uploaderAvatar)
parcel.writeString(thumbnailUrl)
parcel.writeString(hls)
parcel.writeString(dash)
parcel.writeString(lbryId)
parcel.writeValue(uploaderVerified)
parcel.writeValue(duration)
parcel.writeValue(views)
parcel.writeValue(likes)
parcel.writeValue(dislikes)
parcel.writeValue(livestream)
parcel.writeString(proxyUrl)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Streams> {
override fun createFromParcel(parcel: Parcel): Streams {
return Streams(parcel)
}
override fun newArray(size: Int): Array<Streams?> {
return arrayOfNulls(size)
}
}
}

View File

@ -0,0 +1,12 @@
package com.github.libretube.obj
data class WatchHistoryItem(
val videoId: String?,
val title: String?,
val uploadDate: String?,
val uploader: String?,
val uploaderUrl: String?,
val uploaderAvatar: String?,
val thumbnailUrl: String?,
val duration: Int?
)

View File

@ -0,0 +1,6 @@
package com.github.libretube.obj
data class WatchPosition(
val videoId: String,
val position: Long
)

View File

@ -8,68 +8,79 @@ import android.text.Html
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.github.libretube.AUTHORS_URL
import com.github.libretube.CONTRIBUTING_URL
import com.github.libretube.DONATE_URL
import com.github.libretube.GITHUB_URL
import com.github.libretube.PIPED_GITHUB_URL
import com.github.libretube.R
import com.github.libretube.WEBSITE_URL
import com.github.libretube.activities.SettingsActivity
import com.github.libretube.databinding.FragmentAboutBinding
import com.github.libretube.util.ThemeHelper.getThemeColor
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
class AboutFragment : Fragment() {
private lateinit var binding: FragmentAboutBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_about, container, false)
): View {
binding = FragmentAboutBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val topBarText = activity?.findViewById<TextView>(R.id.topBar_textView)
topBarText?.text = getString(R.string.about)
val website = view.findViewById<LinearLayout>(R.id.website)
website.setOnClickListener {
val settingsActivity = activity as SettingsActivity
settingsActivity.changeTopBarText(getString(R.string.about))
binding.website.setOnClickListener {
openLinkFromHref(WEBSITE_URL)
}
val authors = view.findViewById<LinearLayout>(R.id.authors)
authors.setOnClickListener {
openLinkFromHref(AUTHORS_URL)
binding.website.setOnLongClickListener {
val text = context?.getString(R.string.website_summary)!!
showSnackBar(text)
true
}
val piped = view.findViewById<LinearLayout>(R.id.piped)
piped.setOnClickListener {
binding.piped.setOnClickListener {
openLinkFromHref(PIPED_GITHUB_URL)
}
val donate = view.findViewById<LinearLayout>(R.id.donate)
donate.setOnClickListener {
binding.piped.setOnLongClickListener {
val text = context?.getString(R.string.piped_summary)!!
showSnackBar(text)
true
}
binding.donate.setOnClickListener {
openLinkFromHref(DONATE_URL)
}
val contributing = view.findViewById<LinearLayout>(R.id.contributing)
contributing.setOnClickListener {
openLinkFromHref(CONTRIBUTING_URL)
binding.donate.setOnLongClickListener {
val text = context?.getString(R.string.donate_summary)!!
showSnackBar(text)
true
}
val license = view.findViewById<LinearLayout>(R.id.license)
license.setOnClickListener {
val licenseString = view.context.assets
.open("gpl3.html").bufferedReader().use {
it.readText()
}
val licenseHtml = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(licenseString, 1)
} else {
Html.fromHtml(licenseString)
}
MaterialAlertDialogBuilder(view.context!!)
.setPositiveButton(getString(R.string.okay)) { _, _ -> }
.setMessage(licenseHtml)
.create()
.show()
binding.github.setOnClickListener {
openLinkFromHref(GITHUB_URL)
}
binding.github.setOnLongClickListener {
val text = context?.getString(R.string.contributing_summary)!!
showSnackBar(text)
true
}
binding.license.setOnClickListener {
showLicense()
}
binding.license.setOnLongClickListener {
val text = context?.getString(R.string.license_summary)!!
showSnackBar(text)
true
}
}
@ -78,4 +89,40 @@ class AboutFragment : Fragment() {
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent)
}
private fun showSnackBar(text: String) {
val snackBar = Snackbar
.make(binding.root, text, Snackbar.LENGTH_LONG)
// set snackBar color
snackBar.setBackgroundTint(getThemeColor(requireContext(), R.attr.colorSurface))
snackBar.setTextColor(getThemeColor(requireContext(), R.attr.colorPrimary))
// prevent the text from being partially hidden
snackBar.setTextMaxLines(3)
snackBar.show()
}
private fun showLicense() {
val assets = view?.context?.assets
val licenseString = assets
?.open("gpl3.html")
?.bufferedReader()
.use {
it?.readText()
}
val licenseHtml = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(licenseString.toString(), 1)
} else {
Html.fromHtml(licenseString.toString())
}
MaterialAlertDialogBuilder(requireContext())
.setPositiveButton(getString(R.string.okay)) { _, _ -> }
.setMessage(licenseHtml)
.create()
.show()
}
}

View File

@ -1,12 +1,11 @@
package com.github.libretube.preferences
import android.os.Bundle
import android.widget.TextView
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.R
import com.github.libretube.requireMainActivityRestart
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.activities.SettingsActivity
import com.github.libretube.activities.requireMainActivityRestart
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class AdvancedSettings : PreferenceFragmentCompat() {
@ -15,15 +14,24 @@ class AdvancedSettings : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.advanced_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView)
topBarTextView?.text = getString(R.string.advanced)
val settingsActivity = activity as SettingsActivity
settingsActivity.changeTopBarText(getString(R.string.advanced))
// clear search history
val clearHistory = findPreference<Preference>("clear_history")
clearHistory?.setOnPreferenceClickListener {
PreferenceHelper.removePreference(requireContext(), "search_history")
true
}
// clear watch history and positions
val clearWatchHistory = findPreference<Preference>("clear_watch_history")
clearWatchHistory?.setOnPreferenceClickListener {
PreferenceHelper.removePreference(requireContext(), "watch_history")
PreferenceHelper.removePreference(requireContext(), "watch_positions")
true
}
val resetSettings = findPreference<Preference>("reset_settings")
resetSettings?.setOnPreferenceClickListener {
showResetDialog()

View File

@ -1,32 +1,33 @@
package com.github.libretube.preferences
import android.os.Bundle
import android.widget.TextView
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.github.libretube.R
import com.github.libretube.requireMainActivityRestart
import com.github.libretube.activities.SettingsActivity
import com.github.libretube.activities.requireMainActivityRestart
import com.github.libretube.util.ThemeHelper
import com.google.android.material.color.DynamicColors
class AppearanceSettings : PreferenceFragmentCompat() {
private val TAG = "AppearanceSettings"
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.appearance_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView)
topBarTextView?.text = getString(R.string.appearance)
val settingsActivity = activity as SettingsActivity
settingsActivity.changeTopBarText(getString(R.string.appearance))
val themeToggle = findPreference<ListPreference>("theme_togglee")
themeToggle?.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true
ThemeHelper.restartMainActivity(requireContext())
activity?.recreate()
true
}
val accentColor = findPreference<Preference>("accent_color")
accentColor?.setOnPreferenceChangeListener { _, _ ->
val accentColor = findPreference<ListPreference>("accent_color")
updateAccentColorValues(accentColor!!)
accentColor.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true
activity?.recreate()
true
@ -50,4 +51,17 @@ class AppearanceSettings : PreferenceFragmentCompat() {
true
}
}
// remove material you from accent color option if not available
private fun updateAccentColorValues(pref: ListPreference) {
val dynamicColorsAvailable = DynamicColors.isDynamicColorAvailable()
if (!dynamicColorsAvailable) {
val entries = pref.entries.toMutableList()
entries -= entries[0]
pref.entries = entries.toTypedArray()
val values = pref.entryValues.toMutableList()
values -= values[0]
pref.entryValues = values.toTypedArray()
}
}
}

View File

@ -0,0 +1,63 @@
package com.github.libretube.preferences
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.github.libretube.DISCORD_URL
import com.github.libretube.MATRIX_URL
import com.github.libretube.R
import com.github.libretube.REDDIT_URL
import com.github.libretube.TELEGRAM_URL
import com.github.libretube.TWITTER_URL
import com.github.libretube.activities.SettingsActivity
import com.github.libretube.databinding.FragmentCommunityBinding
class CommunityFragment : Fragment() {
private lateinit var binding: FragmentCommunityBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCommunityBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val settingsActivity = activity as SettingsActivity
settingsActivity.changeTopBarText(getString(R.string.community))
binding.telegram.setOnClickListener {
openLinkFromHref(TELEGRAM_URL)
}
binding.matrix.setOnClickListener {
openLinkFromHref(MATRIX_URL)
}
binding.discord.setOnClickListener {
openLinkFromHref(DISCORD_URL)
}
binding.reddit.setOnClickListener {
openLinkFromHref(REDDIT_URL)
}
binding.twitter.setOnClickListener {
openLinkFromHref(TWITTER_URL)
}
}
private fun openLinkFromHref(link: String) {
val uri = Uri.parse(link)
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent)
}
}

View File

@ -2,13 +2,13 @@ package com.github.libretube.preferences
import android.Manifest
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
@ -18,12 +18,14 @@ import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.github.libretube.R
import com.github.libretube.activities.SettingsActivity
import com.github.libretube.activities.requireMainActivityRestart
import com.github.libretube.dialogs.CustomInstanceDialog
import com.github.libretube.dialogs.DeleteAccountDialog
import com.github.libretube.dialogs.LoginDialog
import com.github.libretube.requireMainActivityRestart
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.dialogs.LogoutDialog
import com.github.libretube.util.RetrofitInstance
import org.json.JSONObject
import org.json.JSONTokener
@ -110,20 +112,49 @@ class InstanceSettings : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.instance_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView)
topBarTextView?.text = getString(R.string.instance)
val settingsActivity = activity as SettingsActivity
settingsActivity.changeTopBarText(getString(R.string.instance))
val instance = findPreference<ListPreference>("selectInstance")
// fetchInstance()
initCustomInstances()
instance?.setOnPreferenceChangeListener { _, newValue ->
initCustomInstances(instance!!)
instance.setOnPreferenceChangeListener { _, newValue ->
requireMainActivityRestart = true
RetrofitInstance.url = newValue.toString()
if (!PreferenceHelper.getBoolean(requireContext(), "auth_instance_toggle", false)) {
RetrofitInstance.authUrl = newValue.toString()
logout()
}
RetrofitInstance.lazyMgr.reset()
true
}
val authInstance = findPreference<ListPreference>("selectAuthInstance")
initCustomInstances(authInstance!!)
// hide auth instance if option deselected
if (!PreferenceHelper.getBoolean(requireContext(), "auth_instance_toggle", false)) {
authInstance.isVisible = false
}
authInstance.setOnPreferenceChangeListener { _, newValue ->
requireMainActivityRestart = true
// save new auth url
RetrofitInstance.authUrl = newValue.toString()
RetrofitInstance.lazyMgr.reset()
logout()
true
}
val authInstanceToggle = findPreference<SwitchPreference>("auth_instance_toggle")
authInstanceToggle?.setOnPreferenceChangeListener { _, newValue ->
requireMainActivityRestart = true
authInstance.isVisible = newValue == true
logout()
// either use new auth url or the normal api url if auth instance disabled
RetrofitInstance.authUrl = if (newValue == false) RetrofitInstance.url
else authInstance.value
true
}
val customInstance = findPreference<Preference>("customInstance")
customInstance?.setOnPreferenceClickListener {
val newFragment = CustomInstanceDialog()
@ -134,15 +165,23 @@ class InstanceSettings : PreferenceFragmentCompat() {
val clearCustomInstances = findPreference<Preference>("clearCustomInstances")
clearCustomInstances?.setOnPreferenceClickListener {
PreferenceHelper.removePreference(requireContext(), "customInstances")
activity?.recreate()
val intent = Intent(context, SettingsActivity::class.java)
startActivity(intent)
true
}
val login = findPreference<Preference>("login_register")
val token = PreferenceHelper.getToken(requireContext())
if (token != "") login?.setTitle(R.string.logout)
login?.setOnPreferenceClickListener {
requireMainActivityRestart = true
val newFragment = LoginDialog()
newFragment.show(childFragmentManager, "Login")
if (token == "") {
val newFragment = LoginDialog()
newFragment.show(childFragmentManager, "Login")
} else {
val newFragment = LogoutDialog()
newFragment.show(childFragmentManager, "Logout")
}
true
}
@ -160,58 +199,12 @@ class InstanceSettings : PreferenceFragmentCompat() {
val importFromYt = findPreference<Preference>("import_from_yt")
importFromYt?.setOnPreferenceClickListener {
val token = PreferenceHelper.getToken(requireContext())
// check StorageAccess
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.d("myz", "" + Build.VERSION.SDK_INT)
if (ContextCompat.checkSelfPermission(
this.requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
),
1
) // permission request code is just an int
} else if (token != "") {
MainSettings.getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
} else {
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1
)
} else if (token != "") {
MainSettings.getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
importSubscriptions()
true
}
}
private fun initCustomInstances() {
private fun initCustomInstances(instancePref: ListPreference) {
val customInstances = PreferenceHelper.getCustomInstances(requireContext())
var instanceNames = resources.getStringArray(R.array.instances)
@ -222,10 +215,9 @@ class InstanceSettings : PreferenceFragmentCompat() {
}
// add custom instances to the list preference
val instance = findPreference<ListPreference>("selectInstance")
instance?.entries = instanceNames
instance?.entryValues = instanceValues
instance?.summaryProvider =
instancePref.entries = instanceNames
instancePref.entryValues = instanceValues
instancePref.summaryProvider =
Preference.SummaryProvider<ListPreference> { preference ->
val text = preference.entry
if (TextUtils.isEmpty(text)) {
@ -238,6 +230,7 @@ class InstanceSettings : PreferenceFragmentCompat() {
private fun logout() {
PreferenceHelper.setToken(requireContext(), "")
Toast.makeText(context, getString(R.string.loggedout), Toast.LENGTH_SHORT).show()
}
private fun fetchInstance() {
@ -289,12 +282,62 @@ class InstanceSettings : PreferenceFragmentCompat() {
activity?.runOnUiThread(action)
}
private fun importSubscriptions() {
val token = PreferenceHelper.getToken(requireContext())
// check StorageAccess
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.d("myz", "" + Build.VERSION.SDK_INT)
if (ContextCompat.checkSelfPermission(
this.requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
),
1
) // permission request code is just an int
} else if (token != "") {
MainSettings.getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
} else {
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1
)
} else if (token != "") {
MainSettings.getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
}
private fun subscribe(channels: List<String>) {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.importSubscriptions(
RetrofitInstance.authApi.importSubscriptions(
false,
token,
channels

View File

@ -8,8 +8,8 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.BuildConfig
import com.github.libretube.R
import com.github.libretube.isCurrentViewMainSettings
import com.github.libretube.requireMainActivityRestart
import com.github.libretube.activities.isCurrentViewMainSettings
import com.github.libretube.activities.requireMainActivityRestart
import com.github.libretube.util.ThemeHelper
import com.github.libretube.util.checkUpdate
@ -38,35 +38,35 @@ class MainSettings : PreferenceFragmentCompat() {
val instance = findPreference<Preference>("instance")
instance?.setOnPreferenceClickListener {
val newFragment = InstanceSettings()
navigateSettings(newFragment)
navigateToSettingsFragment(newFragment)
true
}
val appearance = findPreference<Preference>("appearance")
appearance?.setOnPreferenceClickListener {
val newFragment = AppearanceSettings()
navigateSettings(newFragment)
navigateToSettingsFragment(newFragment)
true
}
val sponsorBlock = findPreference<Preference>("sponsorblock")
sponsorBlock?.setOnPreferenceClickListener {
val newFragment = SponsorBlockSettings()
navigateSettings(newFragment)
navigateToSettingsFragment(newFragment)
true
}
val player = findPreference<Preference>("player")
player?.setOnPreferenceClickListener {
val newFragment = PlayerSettings()
navigateSettings(newFragment)
navigateToSettingsFragment(newFragment)
true
}
val advanced = findPreference<Preference>("advanced")
advanced?.setOnPreferenceClickListener {
val newFragment = AdvancedSettings()
navigateSettings(newFragment)
navigateToSettingsFragment(newFragment)
true
}
@ -80,12 +80,19 @@ class MainSettings : PreferenceFragmentCompat() {
val about = findPreference<Preference>("about")
about?.setOnPreferenceClickListener {
val newFragment = AboutFragment()
navigateSettings(newFragment)
navigateToSettingsFragment(newFragment)
true
}
val community = findPreference<Preference>("community")
community?.setOnPreferenceClickListener {
val newFragment = CommunityFragment()
navigateToSettingsFragment(newFragment)
true
}
}
private fun navigateSettings(newFragment: Fragment) {
private fun navigateToSettingsFragment(newFragment: Fragment) {
isCurrentViewMainSettings = false
parentFragmentManager.beginTransaction()
.replace(R.id.settings, newFragment)

View File

@ -1,9 +1,9 @@
package com.github.libretube.preferences
import android.os.Bundle
import android.widget.TextView
import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.R
import com.github.libretube.activities.SettingsActivity
class PlayerSettings : PreferenceFragmentCompat() {
val TAG = "PlayerSettings"
@ -11,7 +11,7 @@ class PlayerSettings : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.player_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView)
topBarTextView?.text = getString(R.string.player)
val settingsActivity = activity as SettingsActivity
settingsActivity.changeTopBarText(getString(R.string.audio_video))
}
}

View File

@ -1,14 +1,19 @@
package com.github.libretube.util
package com.github.libretube.preferences
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import com.github.libretube.obj.CustomInstance
import com.github.libretube.obj.Streams
import com.github.libretube.obj.WatchHistoryItem
import com.github.libretube.obj.WatchPosition
import com.google.common.reflect.TypeToken
import com.google.gson.Gson
import java.lang.reflect.Type
object PreferenceHelper {
private val TAG = "PreferenceHelper"
fun setString(context: Context, key: String?, value: String?) {
val editor = getDefaultSharedPreferencesEditor(context)
editor.putString(key, value)
@ -122,6 +127,97 @@ object PreferenceHelper {
editor.putStringSet("search_history", set).apply()
}
fun addToWatchHistory(context: Context, videoId: String, streams: Streams) {
val editor = getDefaultSharedPreferencesEditor(context)
val gson = Gson()
val watchHistoryItem = WatchHistoryItem(
videoId,
streams.title,
streams.uploadDate,
streams.uploader,
streams.uploaderUrl?.replace("/channel/", ""),
streams.uploaderAvatar,
streams.thumbnailUrl,
streams.duration
)
val watchHistory = getWatchHistory(context)
// delete entries that have the same videoId
var indexToRemove: Int? = null
watchHistory.forEachIndexed { index, item ->
if (item.videoId == videoId) indexToRemove = index
}
if (indexToRemove != null) watchHistory.removeAt(indexToRemove!!)
watchHistory += watchHistoryItem
val json = gson.toJson(watchHistory)
editor.putString("watch_history", json).apply()
}
fun getWatchHistory(context: Context): ArrayList<WatchHistoryItem> {
val settings = getDefaultSharedPreferences(context)
val gson = Gson()
val json: String = settings.getString("watch_history", "")!!
val type: Type = object : TypeToken<List<WatchHistoryItem?>?>() {}.type
return try {
gson.fromJson(json, type)
} catch (e: Exception) {
arrayListOf()
}
}
fun saveWatchPosition(context: Context, videoId: String, position: Long) {
val editor = getDefaultSharedPreferencesEditor(context)
val watchPositions = getWatchPositions(context)
val watchPositionItem = WatchPosition(videoId, position)
var indexToRemove: Int? = null
watchPositions.forEachIndexed { index, item ->
if (item.videoId == videoId) indexToRemove = index
}
if (indexToRemove != null) watchPositions.removeAt(indexToRemove!!)
watchPositions += watchPositionItem
val gson = Gson()
val json = gson.toJson(watchPositions)
editor.putString("watch_positions", json).commit()
}
fun removeWatchPosition(context: Context, videoId: String) {
val editor = getDefaultSharedPreferencesEditor(context)
val watchPositions = getWatchPositions(context)
var indexToRemove: Int? = null
watchPositions.forEachIndexed { index, item ->
if (item.videoId == videoId) indexToRemove = index
}
if (indexToRemove != null) watchPositions.removeAt(indexToRemove!!)
val gson = Gson()
val json = gson.toJson(watchPositions)
editor.putString("watch_positions", json).commit()
}
fun getWatchPositions(context: Context): ArrayList<WatchPosition> {
val settings = getDefaultSharedPreferences(context)
val gson = Gson()
val json: String = settings.getString("watch_positions", "")!!
val type: Type = object : TypeToken<List<WatchPosition?>?>() {}.type
return try {
gson.fromJson(json, type)
} catch (e: Exception) {
arrayListOf()
}
}
private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}

View File

@ -1,9 +1,9 @@
package com.github.libretube.preferences
import android.os.Bundle
import android.widget.TextView
import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.R
import com.github.libretube.activities.SettingsActivity
class SponsorBlockSettings : PreferenceFragmentCompat() {
private val TAG = "SponsorBlockSettings"
@ -11,7 +11,7 @@ class SponsorBlockSettings : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.sponsorblock_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView)
topBarTextView?.text = getString(R.string.sponsorblock)
val settingsActivity = activity as SettingsActivity
settingsActivity.changeTopBarText(getString(R.string.sponsorblock))
}
}

View File

@ -19,7 +19,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.arthenica.ffmpegkit.FFmpegKit
import com.github.libretube.R
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.preferences.PreferenceHelper
import java.io.File
var IS_DOWNLOAD_RUNNING = false

View File

@ -1,13 +1,14 @@
package com.github.libretube
package com.github.libretube.util
import android.app.NotificationManager
import android.content.Context
import android.support.v4.media.session.MediaSessionCompat
import com.github.libretube.obj.Streams
import com.github.libretube.util.DescriptionAdapter
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.preferences.PreferenceHelper
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ui.PlayerNotificationManager
@ -42,7 +43,7 @@ class BackgroundMode {
/**
* The [PlayerNotificationManager] to load the [mediaSession] content on it.
*/
private lateinit var playerNotification: PlayerNotificationManager
private var playerNotification: PlayerNotificationManager? = null
/**
* The [AudioAttributes] handle the audio focus of the [player]
@ -63,9 +64,45 @@ class BackgroundMode {
.setAudioAttributes(audioAttributes, true)
.build()
}
/**
* Listens for changed playbackStates (e.g. pause, end)
* Plays the next video when the current one ended
*/
player!!.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(@Player.State state: Int) {
val autoplay = PreferenceHelper.getBoolean(c, "autoplay", false)
if (state == Player.STATE_ENDED) {
if (autoplay) playNextVideo(c)
}
}
})
setMediaItem(c)
}
/**
* Plays the first related video to the current (used when the playback of the current video ended)
*/
private fun playNextVideo(c: Context) {
if (response!!.relatedStreams!!.isNotEmpty()) {
val videoId = response!!
.relatedStreams!![0].url!!
.replace("/watch?v=", "")
// destroy old player and its notification
playerNotification = null
player = null
// kill old notification
val notificationManager = c.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
notificationManager.cancel(1)
// play new video on background
playOnBackgroundMode(c, videoId)
}
}
/**
* Initializes the [playerNotification] attached to the [player] and shows it.
*/
@ -82,10 +119,12 @@ class BackgroundMode {
)
)
.build()
playerNotification.apply {
playerNotification?.apply {
setPlayer(player)
setUsePreviousAction(false)
setUseNextAction(false)
setUsePreviousAction(false)
setUseStopAction(true)
setColorized(true)
setMediaSessionToken(mediaSession.sessionToken)
}
}
@ -110,7 +149,11 @@ class BackgroundMode {
/**
* Gets the video data and prepares the [player].
*/
fun playOnBackgroundMode(c: Context, videoId: String, seekToPosition: Long) {
fun playOnBackgroundMode(
c: Context,
videoId: String,
seekToPosition: Long = 0
) {
runBlocking {
val job = launch {
response = RetrofitInstance.api.getStreams(videoId)

View File

@ -0,0 +1,32 @@
package com.github.libretube.util
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
object ConnectionHelper {
fun isNetworkAvailable(context: Context): Boolean {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val nw = connectivityManager.activeNetwork ?: return false
val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
return when {
// WiFi
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
// Mobile
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
// Ethernet
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
// Bluetooth
actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
// VPN
actNw.hasCapability(NetworkCapabilities.TRANSPORT_VPN) -> true
else -> false
}
} else {
return connectivityManager.activeNetworkInfo?.isConnected ?: false
}
}
}

View File

@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.github.libretube.MainActivity
import com.github.libretube.activities.MainActivity
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import java.net.URL
@ -83,8 +83,8 @@ class DescriptionAdapter(
return try {
val resizedBitmap = Bitmap.createScaledBitmap(
bitmap,
1080,
1080,
bitmap.width,
bitmap.width,
false
)
resizedBitmap

View File

@ -2,25 +2,28 @@ package com.github.libretube.util
import android.content.Context
import android.os.Build
import android.telephony.TelephonyManager
import com.github.libretube.preferences.PreferenceHelper
import java.util.*
object LocaleHelper {
fun updateLanguage(context: Context) {
val languageName = PreferenceHelper.getString(context, "language", "en")
if (languageName != "") {
setLanguage(context, languageName!!)
val languageName = PreferenceHelper.getString(context, "language", "sys")
if (languageName == "sys") updateLocaleConf(context, Locale.getDefault())
else if ("$languageName".length < 3) {
val locale = Locale(languageName.toString())
updateLocaleConf(context, locale)
} else if ("$languageName".length > 3) {
val locale = Locale(
languageName?.substring(0, 2).toString(),
languageName?.substring(4, 6).toString()
)
updateLocaleConf(context, locale)
}
}
private fun setLanguage(context: Context, languageName: String) {
val locale = if (languageName != "sys" && "$languageName".length < 3) {
Locale(languageName)
} else if ("$languageName".length > 3) {
Locale(languageName?.substring(0, 2), languageName?.substring(4, 6))
} else {
Locale.getDefault()
}
private fun updateLocaleConf(context: Context, locale: Locale) {
// Change API Language
Locale.setDefault(locale)
@ -35,4 +38,51 @@ object LocaleHelper {
}
res.updateConfiguration(conf, dm)
}
fun getDetectedCountry(context: Context, defaultCountryIsoCode: String): String {
detectSIMCountry(context)?.let {
return it
}
detectNetworkCountry(context)?.let {
return it
}
detectLocaleCountry(context)?.let {
return it
}
return defaultCountryIsoCode
}
private fun detectSIMCountry(context: Context): String? {
try {
val telephonyManager =
context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return telephonyManager.simCountryIso
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
private fun detectNetworkCountry(context: Context): String? {
try {
val telephonyManager =
context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
return telephonyManager.networkCountryIso
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
private fun detectLocaleCountry(context: Context): String? {
try {
return context.resources.configuration.locales[0].country
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
}

View File

@ -1,15 +0,0 @@
package com.github.libretube.util
import android.app.Activity
import android.os.Bundle
import com.github.libretube.R
import com.google.android.material.color.DynamicColors
class Player : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
DynamicColors.applyToActivityIfAvailable(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
}
}

View File

@ -1,11 +1,11 @@
package com.github.libretube
package com.github.libretube.util
import java.util.*
import kotlin.reflect.KProperty
class ResettableLazyManager {
// we synchronize to make sure the timing of a reset() call and new inits do not collide
val managedDelegates = LinkedList<Resettable>()
private val managedDelegates = LinkedList<Resettable>()
fun register(managed: Resettable) {
synchronized(managedDelegates) {
@ -38,7 +38,7 @@ class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()
lazyHolder = makeInitBlock()
}
fun makeInitBlock(): Lazy<PROPTYPE> {
private fun makeInitBlock(): Lazy<PROPTYPE> {
return lazy {
manager.register(this)
init()

View File

@ -1,12 +1,11 @@
package com.github.libretube.util
import com.github.libretube.resettableLazy
import com.github.libretube.resettableManager
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
object RetrofitInstance {
lateinit var url: String
lateinit var authUrl: String
val lazyMgr = resettableManager()
val api: PipedApi by resettableLazy(lazyMgr) {
Retrofit.Builder()
@ -15,4 +14,11 @@ object RetrofitInstance {
.build()
.create(PipedApi::class.java)
}
val authApi: PipedApi by resettableLazy(lazyMgr) {
Retrofit.Builder()
.baseUrl(authUrl)
.addConverterFactory(JacksonConverterFactory.create())
.build()
.create(PipedApi::class.java)
}
}

View File

@ -5,9 +5,13 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.text.Spanned
import android.util.TypedValue
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.text.HtmlCompat
import com.github.libretube.R
import com.github.libretube.preferences.PreferenceHelper
object ThemeHelper {
@ -18,7 +22,7 @@ object ThemeHelper {
private fun updateAccentColor(context: Context) {
when (PreferenceHelper.getString(context, "accent_color", "purple")) {
"my" -> context.setTheme(R.style.Theme_MY)
"my" -> context.setTheme(R.style.MaterialYou)
"red" -> context.setTheme(R.style.Theme_Red)
"blue" -> context.setTheme(R.style.Theme_Blue)
"yellow" -> context.setTheme(R.style.Theme_Yellow)
@ -69,4 +73,19 @@ object ThemeHelper {
intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent)
}
fun getThemeColor(context: Context, colorCode: Int): Int {
val value = TypedValue()
context.theme.resolveAttribute(colorCode, value, true)
return value.data
}
fun getStyledAppName(context: Context): Spanned {
val colorPrimary = getThemeColor(context, R.attr.colorPrimaryDark)
val hexColor = String.format("#%06X", (0xFFFFFF and colorPrimary))
return HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
}
}

View File

@ -1,15 +1,17 @@
package com.github.libretube.util
package com.github.libretube.views
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.google.android.exoplayer2.ui.StyledPlayerView
internal class CustomExoPlayerView(
context: Context,
attributeSet: AttributeSet? = null
) : StyledPlayerView(context, attributeSet) {
val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this)
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {

View File

@ -1,4 +1,4 @@
package com.github.libretube.util
package com.github.libretube.views
import android.content.Context
import android.util.AttributeSet

View File

@ -0,0 +1,49 @@
package com.github.libretube.views
import android.os.Handler
import android.os.Looper
import android.view.View
class DoubleClickListener(
private val doubleClickTimeLimitMills: Long = 300,
private val callback: Callback
) : View.OnClickListener {
private var lastClicked: Long = -1L
private var doubleClicked: Boolean = false
override fun onClick(v: View?) {
lastClicked = when {
lastClicked == -1L -> {
doubleClicked = false
System.currentTimeMillis()
}
isDoubleClicked() -> {
doubleClicked = true
callback.doubleClicked()
-1L
}
else -> {
Handler(Looper.getMainLooper()).postDelayed({
if (!doubleClicked) callback.singleClicked()
}, doubleClickTimeLimitMills)
System.currentTimeMillis()
}
}
}
private fun getTimeDiff(from: Long, to: Long): Long {
return to - from
}
private fun isDoubleClicked(): Boolean {
return getTimeDiff(
lastClicked,
System.currentTimeMillis()
) <= doubleClickTimeLimitMills
}
interface Callback {
fun doubleClicked()
fun singleClicked()
}
}

View File

@ -1,4 +1,4 @@
package com.github.libretube.util
package com.github.libretube.views
import android.content.Context
import android.graphics.Rect

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:fillColor="#FF000000"
android:pathData="M23.38,16.77l0.6,-0.6A5,5 0,0 0,24 9.1L18.71,3.84a5,5 0,0 0,-7.07 0L3.09,12.39a5,5 0,0 0,0 7.07l5.26,5.26a5,5 0,0 0,7.07 0l0.45,-0.45 2.1,2.2h3.44v3h3.69v1.63L28,34h6L34,27.45ZM14.82,10.18L9.37,15.64a1,1 0,0 1,-1.41 0l-0.4,-0.4a1,1 0,0 1,0 -1.41L13,8.36a1,1 0,0 1,1.41 0l0.4,0.4A1,1 0,0 1,14.82 10.18ZM32,32L28.86,32l-1.77,-1.76v-2.8L23.41,27.44v-3L18.8,24.44l-1.52,-1.61L22,18.18 32,28.28Z" />
<path
android:fillAlpha="0"
android:fillColor="#FF000000"
android:pathData="M0,0h36v36h-36z" />
</vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="M20,32C28.837,32 36,26.627 36,20C36,13.373 28.837,8 20,8C11.163,8 4,13.373 4,20C4,22.684 5.175,25.163 7.16,27.162C6.356,29.454 5.313,31.172 4.65,32.132C4.407,32.483 4.657,32.98 5.083,32.945C6.785,32.806 10.122,32.311 12.374,30.552C14.641,31.475 17.239,32 20,32Z" />
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="M22.784,33.834C31.403,32.793 38,26.996 38,20C38,19.463 37.961,18.933 37.886,18.412C41.553,20.1 44,23.136 44,26.6C44,28.748 43.06,30.73 41.472,32.329C42.068,34.028 42.828,35.333 43.358,36.126C43.595,36.481 43.342,36.978 42.917,36.937C41.504,36.802 39.011,36.377 37.301,35.042C35.487,35.781 33.409,36.2 31.2,36.2C27.978,36.2 25.034,35.307 22.784,33.834Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="m15.43,11.4c-0.054,0.633 -0.582,1.127 -1.224,1.127 -0.678,0 -1.229,-0.55 -1.229,-1.229s0.55,-1.229 1.228,-1.229c0.683,0.029 1.225,0.59 1.225,1.277 0,0.019 0,0.037 -0.001,0.056v-0.003zM9.826,10.07c-0.688,0.061 -1.223,0.634 -1.223,1.332s0.535,1.271 1.218,1.332h0.005c0.683,-0.029 1.225,-0.59 1.225,-1.277 0,-0.019 0,-0.037 -0.001,-0.056v0.003c0.001,-0.02 0.002,-0.043 0.002,-0.067 0,-0.685 -0.541,-1.243 -1.219,-1.269h-0.002zM22.5,2.472v21.528c-3.023,-2.672 -2.057,-1.787 -5.568,-5.052l0.636,2.22h-13.609c-1.359,-0.004 -2.46,-1.106 -2.46,-2.466 0,-0.002 0,-0.004 0,-0.006v-16.224c0,-0.002 0,-0.004 0,-0.006 0,-1.36 1.101,-2.462 2.459,-2.466h16.081c1.359,0.004 2.46,1.106 2.46,2.466v0.006zM19.08,13.848c-0.042,-2.559 -0.676,-4.96 -1.77,-7.086l0.042,0.09c-0.924,-0.731 -2.088,-1.195 -3.358,-1.259l-0.014,-0.001 -0.168,0.192c1.15,0.312 2.15,0.837 3.002,1.535l-0.014,-0.011c-1.399,-0.769 -3.066,-1.222 -4.839,-1.222 -1.493,0 -2.911,0.321 -4.189,0.898l0.064,-0.026c-0.444,0.204 -0.708,0.35 -0.708,0.35 0.884,-0.722 1.942,-1.266 3.1,-1.56l0.056,-0.012 -0.12,-0.144c-1.284,0.065 -2.448,0.529 -3.384,1.269l0.012,-0.009c-1.052,2.036 -1.686,4.437 -1.728,6.982v0.014c0.799,1.111 2.088,1.826 3.543,1.826 0.041,0 0.082,-0.001 0.123,-0.002h-0.006s0.444,-0.54 0.804,-0.996c-0.866,-0.223 -1.592,-0.727 -2.093,-1.406l-0.007,-0.01c0.176,0.124 0.468,0.284 0.49,0.3 1.209,0.672 2.652,1.067 4.188,1.067 1.191,0 2.326,-0.238 3.36,-0.668l-0.058,0.021c0.528,-0.202 0.982,-0.44 1.404,-0.723l-0.025,0.016c-0.526,0.703 -1.277,1.212 -2.144,1.423l-0.026,0.005c0.36,0.456 0.792,0.972 0.792,0.972 0.033,0.001 0.072,0.001 0.111,0.001 1.461,0 2.755,-0.714 3.552,-1.813l0.009,-0.013z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF000000"
android:pathData="M256,416c114.9,0 208,-93.1 208,-208S370.9,0 256,0 48,93.1 48,208s93.1,208 208,208zM233.8,97.4V80.6c0,-9.2 7.4,-16.6 16.6,-16.6h11.1c9.2,0 16.6,7.4 16.6,16.6v17c15.5,0.8 30.5,6.1 43,15.4 5.6,4.1 6.2,12.3 1.2,17.1L306,145.6c-3.8,3.7 -9.5,3.8 -14,1 -5.4,-3.4 -11.4,-5.1 -17.8,-5.1h-38.9c-9,0 -16.3,8.2 -16.3,18.3 0,8.2 5,15.5 12.1,17.6l62.3,18.7c25.7,7.7 43.7,32.4 43.7,60.1 0,34 -26.4,61.5 -59.1,62.4v16.8c0,9.2 -7.4,16.6 -16.6,16.6h-11.1c-9.2,0 -16.6,-7.4 -16.6,-16.6v-17c-15.5,-0.8 -30.5,-6.1 -43,-15.4 -5.6,-4.1 -6.2,-12.3 -1.2,-17.1l16.3,-15.5c3.8,-3.7 9.5,-3.8 14,-1 5.4,3.4 11.4,5.1 17.8,5.1h38.9c9,0 16.3,-8.2 16.3,-18.3 0,-8.2 -5,-15.5 -12.1,-17.6l-62.3,-18.7c-25.7,-7.7 -43.7,-32.4 -43.7,-60.1 0.1,-34 26.4,-61.5 59.1,-62.4zM480,352h-32.5c-19.6,26 -44.6,47.7 -73,64h63.8c5.3,0 9.6,3.6 9.6,8v16c0,4.4 -4.3,8 -9.6,8H73.6c-5.3,0 -9.6,-3.6 -9.6,-8v-16c0,-4.4 4.3,-8 9.6,-8h63.8c-28.4,-16.3 -53.3,-38 -73,-64H32c-17.7,0 -32,14.3 -32,32v96c0,17.7 14.3,32 32,32h448c17.7,0 32,-14.3 32,-32v-96c0,-17.7 -14.3,-32 -32,-32z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@android:color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z" />
</vector>

View File

@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@android:color/white"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:fillColor="#FF000000"
android:pathData="M16,0.396c-8.839,0 -16,7.167 -16,16 0,7.073 4.584,13.068 10.937,15.183 0.803,0.151 1.093,-0.344 1.093,-0.772 0,-0.38 -0.009,-1.385 -0.015,-2.719 -4.453,0.964 -5.391,-2.151 -5.391,-2.151 -0.729,-1.844 -1.781,-2.339 -1.781,-2.339 -1.448,-0.989 0.115,-0.968 0.115,-0.968 1.604,0.109 2.448,1.645 2.448,1.645 1.427,2.448 3.744,1.74 4.661,1.328 0.14,-1.031 0.557,-1.74 1.011,-2.135 -3.552,-0.401 -7.287,-1.776 -7.287,-7.907 0,-1.751 0.62,-3.177 1.645,-4.297 -0.177,-0.401 -0.719,-2.031 0.141,-4.235 0,0 1.339,-0.427 4.4,1.641 1.281,-0.355 2.641,-0.532 4,-0.541 1.36,0.009 2.719,0.187 4,0.541 3.043,-2.068 4.381,-1.641 4.381,-1.641 0.859,2.204 0.317,3.833 0.161,4.235 1.015,1.12 1.635,2.547 1.635,4.297 0,6.145 -3.74,7.5 -7.296,7.891 0.556,0.479 1.077,1.464 1.077,2.959 0,2.14 -0.02,3.864 -0.02,4.385 0,0.416 0.28,0.916 1.104,0.755 6.4,-2.093 10.979,-8.093 10.979,-15.156 0,-8.833 -7.161,-16 -16,-16z" />
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1280dp"
android:height="1280dp"
android:viewportWidth="1280"
android:viewportHeight="1280">
<path
android:fillColor="#00000000"
android:pathData="m863.3,639.7c0,-2.3 -1.2,-4.4 -3.1,-5.5L527.9,437.1c-5.8,-3.4 -13.1,0.8 -13.1,7.6v85c0,3.2 1.7,6.1 4.4,7.7l165.6,94.8c5.9,3.4 5.9,12 0,15.4l-165.6,94.8c-2.7,1.6 -4.4,4.5 -4.4,7.7v84.9c0,6.8 7.3,11.1 13.1,7.6L860.1,645.2c1.9,-1.2 3.1,-3.3 3.1,-5.5z"
android:strokeWidth="30"
android:strokeColor="#000000" />
<path
android:fillColor="#000000"
android:pathData="m582.8,634.5c4.2,2.4 4.2,8.5 0,10.9l-73.8,42.5c-4.1,2.4 -9.2,-0.6 -9.2,-5.4v-85.1c0,-4.8 5.1,-7.8 9.2,-5.4z"
android:strokeWidth="0"
android:strokeColor="#000000" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M0.632,0.55v22.9L2.28,23.45L2.28,24L0,24L0,0h2.28v0.55zM7.675,7.81v1.157h0.033c0.309,-0.443 0.683,-0.784 1.117,-1.024 0.433,-0.245 0.936,-0.365 1.5,-0.365 0.54,0 1.033,0.107 1.481,0.314 0.448,0.208 0.785,0.582 1.02,1.108 0.254,-0.374 0.6,-0.706 1.034,-0.992 0.434,-0.287 0.95,-0.43 1.546,-0.43 0.453,0 0.872,0.056 1.26,0.167 0.388,0.11 0.716,0.286 0.993,0.53 0.276,0.245 0.489,0.559 0.646,0.951 0.152,0.392 0.23,0.863 0.23,1.417v5.728h-2.349L16.186,11.52c0,-0.286 -0.01,-0.559 -0.032,-0.812a1.755,1.755 0,0 0,-0.18 -0.66,1.106 1.106,0 0,0 -0.438,-0.448c-0.194,-0.11 -0.457,-0.166 -0.785,-0.166 -0.332,0 -0.6,0.064 -0.803,0.189a1.38,1.38 0,0 0,-0.48 0.499,1.946 1.946,0 0,0 -0.231,0.696 5.56,5.56 0,0 0,-0.06 0.785v4.768h-2.35v-4.8c0,-0.254 -0.004,-0.503 -0.018,-0.752a2.074,2.074 0,0 0,-0.143 -0.688,1.052 1.052,0 0,0 -0.415,-0.503c-0.194,-0.125 -0.476,-0.19 -0.854,-0.19 -0.111,0 -0.259,0.024 -0.439,0.074 -0.18,0.051 -0.36,0.143 -0.53,0.282 -0.171,0.138 -0.319,0.337 -0.439,0.595 -0.12,0.259 -0.18,0.6 -0.18,1.02v4.966L5.46,16.375L5.46,7.81zM23.368,23.45L23.368,0.55L21.72,0.55L21.72,0L24,0v24h-2.28v-0.55z" />
</vector>

View File

@ -0,0 +1,93 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="1080"
android:viewportHeight="1080">
<path android:pathData="M308.4,156.8C308.4,156.8 315.7,151.4 328.7,144.1C336,141 345.1,137.7 355.5,134.8C366.4,132.6 378.6,130.8 391.1,129.9C403.9,129.9 416.9,130.7 429.1,132.3C441.2,134.7 452.4,137.9 461.8,141.2C480,149.7 491.4,156.8 491.4,156.8C491.4,156.8 491.4,168.5 491.4,189.1C491.4,199.3 491.4,211.8 491.4,226C491.4,240.3 491.4,256.4 491.4,274C491.4,291.6 491.4,310.6 491.4,330.8C491.4,340.8 491.4,351.2 491.4,361.7C491.4,372.3 491.4,383.1 491.4,394.1C491.4,405.1 491.4,416.3 491.4,427.6C491.4,438.9 491.4,450.3 491.4,461.8C491.4,473.4 491.4,485 491.4,496.7C491.4,508.3 491.4,520.1 491.4,531.8C491.4,543.5 491.4,555.2 491.4,566.9C491.4,578.5 491.4,590.2 491.4,601.7C491.4,613.3 491.4,624.7 491.4,636C491.4,647.3 491.4,658.5 491.4,669.5C491.4,680.4 491.4,691.2 491.4,701.8C491.4,712.4 491.4,722.7 491.4,732.8C491.4,752.9 491.4,772 491.4,789.5C491.4,807.1 491.4,823.2 491.4,837.5C491.4,851.8 491.4,864.3 491.4,874.5C491.4,895 491.4,906.7 491.4,906.7C491.4,906.7 480,906.7 462.8,906.7C454.2,906.7 444.2,906.7 433.5,906.7C422.8,906.7 411.3,906.7 399.9,906.7C388.5,906.7 377,906.7 366.3,906.7C355.6,906.7 345.6,906.7 337,906.7C319.9,906.7 308.4,906.7 308.4,906.7C308.4,906.7 308.4,895 308.4,874.5C308.4,864.3 308.4,851.8 308.4,837.5C308.4,823.2 308.4,807.1 308.4,789.5C308.4,772 308.4,752.9 308.4,732.8C308.4,722.7 308.4,712.4 308.4,701.8C308.4,691.2 308.4,680.4 308.4,669.5C308.4,658.5 308.4,647.3 308.4,636C308.4,624.7 308.4,613.3 308.4,601.7C308.4,590.2 308.4,578.5 308.4,566.9C308.4,555.2 308.4,543.5 308.4,531.8C308.4,520.1 308.4,508.3 308.4,496.7C308.4,485 308.4,473.4 308.4,461.8C308.4,450.3 308.4,438.9 308.4,427.6C308.4,416.3 308.4,405.1 308.4,394.1C308.4,383.1 308.4,372.3 308.4,361.7C308.4,351.2 308.4,340.8 308.4,330.8C308.4,310.6 308.4,291.6 308.4,274C308.4,256.4 308.4,240.3 308.4,226C308.4,211.8 308.4,199.3 308.4,189.1C308.4,168.5 308.4,156.8 308.4,156.8Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="522.5"
android:endY="481"
android:startX="138.3"
android:startY="555.7"
android:type="linear">
<item
android:color="#FF8B1010"
android:offset="0.2" />
<item
android:color="#FFF84330"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
<path android:pathData="M400,948C349.1,948 308,931.5 308,911C308,890.5 349.1,874 400,874C450.9,874 492,890.5 492,911C492,931.5 450.9,948 400,948Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="321.5"
android:endY="986.1"
android:startX="353.4"
android:startY="835.9"
android:type="linear">
<item
android:color="#FF493B32"
android:offset="0.2" />
<item
android:color="#FFDBCCC4"
android:offset="0.6" />
</gradient>
</aapt:attr>
</path>
<path android:pathData="M550.1,292.5C550.1,292.5 550.1,282.8 550,270.2C549.8,264.6 549.6,258.3 549.4,252C549.2,245.7 549,239 548.8,232.1C548.7,224.7 548.7,216.6 548.8,208.3C549.1,199 549.5,189.3 550,180.1C551.3,158.9 552.6,142.4 552.6,142.4C552.6,142.4 567.2,141.2 591.9,141C597.8,141.4 604.3,142 611.3,142.8C618.2,143.7 625.6,144.9 633.4,146.3C640.9,148.2 648.7,150.6 656.8,153.4C664.8,156.2 673.1,159.4 681.5,163.1C689.5,167.4 697.5,172.2 705.6,177.6C713.6,182.9 721.7,188.8 729.7,195.2C737.2,202 744.5,209.4 751.7,217.3C758.5,225.5 765.1,234.1 771.4,243.2C777.3,252.6 783,262.4 788.2,272.5C793,282.9 797.4,293.5 801.4,304.5C804.9,315.6 808,326.9 810.6,338.4C812.7,350 814.3,361.7 815.5,373.4C816,385.1 816.1,396.8 815.7,408.4C814.8,420 813.3,431.4 811.3,442.6C808.8,453.7 805.9,464.5 802.4,475C798.4,485.3 794,495.3 789.1,504.9C783.8,514.2 778,523.1 771.9,531.5C765.3,539.7 758.5,547.3 751.3,554.5C743.5,561.1 735.5,567.3 727.4,573C719.3,578.6 711.1,583.8 702.8,588.4C693.9,592.4 685,596 676.3,599.1C667.5,602.2 659,604.9 650.7,607.2C641.9,608.9 633.4,610.4 625.5,611.5C617.5,612.7 610,613.6 603.1,614.2C588.6,614.6 576.9,614.7 568.9,614.6C560.9,614.6 556.5,614.4 556.5,614.4C556.5,614.4 556.8,604.8 557.3,591.3C557.4,584.9 557.6,577.5 557.8,569.7C557.9,561.9 557.9,553.4 557.9,544.6C557.8,535.4 557.7,525.6 557.5,515.8C557.1,505.3 556.7,494.7 556.3,485C555.1,463.5 554.1,447.5 554.1,447.5C554.1,447.5 561,447.9 572.7,448C578.3,447.8 584.8,447.3 592,446.4C599,445.1 606.4,443.4 614,441.1C621.1,438.4 628.3,435 635.2,431C638.5,428.8 641.6,426.5 644.6,424C647.5,421.5 650.2,418.7 652.8,415.8C655.1,412.9 657.3,409.8 659.4,406.6C661.2,403.3 662.8,399.9 664.2,396.4C665.4,392.9 666.4,389.3 667.2,385.6C667.7,382 668,378.2 668.1,374.5C668,370.8 667.6,367 667,363.3C666.2,359.6 665.1,355.9 663.8,352.3C662.3,348.7 660.7,345.2 658.8,341.8C656.7,338.4 654.4,335.1 651.9,332C649.3,328.9 646.5,326 643.5,323.2C640.4,320.5 637.2,317.9 633.9,315.5C626.7,311 619.4,307.1 612,303.8C604.2,300.9 596.5,298.6 589.4,296.8C581.9,295.3 575.1,294.3 569.4,293.6C557.3,292.7 550.1,292.5 550.1,292.5Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="861.4"
android:endY="323.3"
android:startX="300.2"
android:startY="432.4"
android:type="linear">
<item
android:color="#FF8B1010"
android:offset="0.2" />
<item
android:color="#FFF84330"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
<path android:pathData="M556.4,294C533.9,294 515.7,259.9 515.7,217.8C515.7,175.6 533.9,141.5 556.4,141.5C578.8,141.5 597,175.6 597,217.8C597,259.9 578.8,294 556.4,294Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="504.2"
android:endY="372.5"
android:startX="553.3"
android:startY="63"
android:type="linear">
<item
android:color="#FF493B32"
android:offset="0.2" />
<item
android:color="#FFDBCCC4"
android:offset="0.6" />
</gradient>
</aapt:attr>
</path>
<path android:pathData="M557.9,615C535.4,615 517.3,577.5 517.3,531C517.3,484.5 535.4,447 557.9,447C580.4,447 598.5,484.5 598.5,531C598.5,577.5 580.4,615 557.9,615Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="503.3"
android:endY="701.5"
android:startX="557.3"
android:startY="360.5"
android:type="linear">
<item
android:color="#FF493B32"
android:offset="0.2" />
<item
android:color="#FFDBCCC4"
android:offset="0.6" />
</gradient>
</aapt:attr>
</path>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="244.86"
android:viewportHeight="244.86">
<path
android:fillColor="#FF000000"
android:pathData="M240.9,38.89c-2.43,-1.31 -5.39,-1.17 -7.69,0.35l-64.63,42.81V64.38c0,-14.48 -11.78,-26.25 -26.25,-26.25H26.25C11.78,38.13 0,49.91 0,64.38v116.09c0,14.47 11.78,26.25 26.25,26.25h116.09c14.47,0 26.25,-11.78 26.25,-26.25v-17.67l64.63,42.81c1.25,0.83 2.69,1.25 4.14,1.25c1.22,0 2.44,-0.3 3.55,-0.89c2.43,-1.31 3.95,-3.85 3.95,-6.61V45.5C244.86,42.74 243.34,40.2 240.9,38.89zM153.59,180.47c0,6.2 -5.05,11.25 -11.25,11.25H26.25c-6.2,0 -11.25,-5.05 -11.25,-11.25V64.38c0,-6.2 5.05,-11.25 11.25,-11.25h116.09c6.2,0 11.25,5.05 11.25,11.25v31.64v52.82V180.47zM229.86,185.39l-61.27,-40.58v-44.76l61.27,-40.58V185.39z"
android:strokeWidth="10"
android:strokeColor="#000000" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,0A12,12 0,0 0,0 12a12,12 0,0 0,12 12,12 12,0 0,0 12,-12A12,12 0,0 0,12 0zM17.01,4.744c0.688,0 1.25,0.561 1.25,1.249a1.25,1.25 0,0 1,-2.498 0.056l-2.597,-0.547 -0.8,3.747c1.824,0.07 3.48,0.632 4.674,1.488 0.308,-0.309 0.73,-0.491 1.207,-0.491 0.968,0 1.754,0.786 1.754,1.754 0,0.716 -0.435,1.333 -1.01,1.614a3.111,3.111 0,0 1,0.042 0.52c0,2.694 -3.13,4.87 -7.004,4.87 -3.874,0 -7.004,-2.176 -7.004,-4.87 0,-0.183 0.015,-0.366 0.043,-0.534A1.748,1.748 0,0 1,4.028 12c0,-0.968 0.786,-1.754 1.754,-1.754 0.463,0 0.898,0.196 1.207,0.49 1.207,-0.883 2.878,-1.43 4.744,-1.487l0.885,-4.182a0.342,0.342 0,0 1,0.14 -0.197,0.35 0.35,0 0,1 0.238,-0.042l2.906,0.617a1.214,1.214 0,0 1,1.108 -0.701zM9.25,12C8.561,12 8,12.562 8,13.25c0,0.687 0.561,1.248 1.25,1.248 0.687,0 1.248,-0.561 1.248,-1.249 0,-0.688 -0.561,-1.249 -1.249,-1.249zM14.75,12c-0.687,0 -1.248,0.561 -1.248,1.25 0,0.687 0.561,1.248 1.249,1.248 0.688,0 1.249,-0.561 1.249,-1.249 0,-0.687 -0.562,-1.249 -1.25,-1.249zM9.284,15.99a0.327,0.327 0,0 0,-0.231 0.094,0.33 0.33,0 0,0 0,0.463c0.842,0.842 2.484,0.913 2.961,0.913 0.477,0 2.105,-0.056 2.961,-0.913a0.361,0.361 0,0 0,0.029 -0.463,0.33 0.33,0 0,0 -0.464,0c-0.547,0.533 -1.684,0.73 -2.512,0.73 -0.828,0 -1.979,-0.196 -2.512,-0.73a0.326,0.326 0,0 0,-0.232 -0.095z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@android:color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="15"
android:viewportHeight="15">
<path
android:fillColor="#000000"
android:pathData="M14.993,1.582C15.022,1.407 14.957,1.23 14.821,1.116C14.685,1.003 14.499,0.97 14.332,1.029L0.332,6.029C0.143,6.096 0.013,6.27 0.001,6.47C-0.011,6.67 0.097,6.858 0.276,6.947L4.276,8.947C4.437,9.027 4.628,9.016 4.777,8.916L8.098,6.702L6.11,9.188C6.022,9.297 5.984,9.438 6.006,9.577C6.027,9.715 6.106,9.838 6.223,9.916L12.223,13.916C12.364,14.01 12.543,14.026 12.699,13.959C12.854,13.891 12.965,13.75 12.993,13.582L14.993,1.582Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="@android:color/black"
android:pathData="M9.5,13a1.5,1.5 0,1 1,-3 0,1.5 1.5,0 0,1 3,0zM9.5,8a1.5,1.5 0,1 1,-3 0,1.5 1.5,0 0,1 3,0zM9.5,3a1.5,1.5 0,1 1,-3 0,1.5 1.5,0 0,1 3,0z" />
</vector>

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M21.25,12C21.25,17.108 17.109,21.25 12,21.25C6.892,21.25 2.75,17.108 2.75,12C2.75,6.891 6.892,2.75 12,2.75C17.109,2.75 21.25,6.891 21.25,12Z"
android:strokeWidth="2.5"
android:strokeColor="#130F26"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M16.191,12.767L11.661,12.693V7.846"
android:strokeWidth="2.5"
android:strokeColor="#130F26"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="310"
android:viewportHeight="310">
<path
android:fillColor="#FF000000"
android:pathData="M302.97,57.39c-4.87,2.16 -9.88,3.98 -14.99,5.46c6.06,-6.85 10.68,-14.91 13.49,-23.73c0.63,-1.98 -0.02,-4.14 -1.65,-5.43c-1.62,-1.29 -3.88,-1.45 -5.66,-0.39c-10.86,6.44 -22.59,11.07 -34.88,13.78c-12.38,-12.1 -29.2,-18.98 -46.58,-18.98c-36.69,0 -66.55,29.85 -66.55,66.55c0,2.89 0.18,5.76 0.55,8.6C101.16,99.24 58.83,76.86 29.76,41.2c-1.04,-1.27 -2.63,-1.96 -4.27,-1.83c-1.63,0.13 -3.1,1.05 -3.93,2.47c-5.9,10.12 -9.01,21.69 -9.01,33.46c0,16.03 5.72,31.25 15.84,43.14c-3.08,-1.07 -6.06,-2.4 -8.91,-3.98c-1.53,-0.85 -3.39,-0.84 -4.91,0.03c-1.52,0.87 -2.47,2.47 -2.51,4.22c-0.01,0.29 -0.01,0.59 -0.01,0.89c0,23.93 12.88,45.48 32.58,57.23c-1.69,-0.17 -3.38,-0.41 -5.06,-0.74c-1.73,-0.33 -3.51,0.28 -4.68,1.6c-1.17,1.32 -1.56,3.16 -1.02,4.84c7.29,22.76 26.06,39.5 48.75,44.6c-18.82,11.79 -40.34,17.96 -62.93,17.96c-4.71,0 -9.45,-0.28 -14.1,-0.83c-2.31,-0.27 -4.51,1.09 -5.29,3.28c-0.79,2.19 0.05,4.64 2.01,5.89c29.02,18.61 62.58,28.44 97.05,28.44c67.75,0 110.14,-31.95 133.76,-58.75c29.46,-33.42 46.36,-77.66 46.36,-121.37c0,-1.83 -0.03,-3.67 -0.08,-5.51c11.62,-8.76 21.63,-19.35 29.77,-31.54c1.24,-1.85 1.1,-4.3 -0.33,-6C307.39,57.04 305.01,56.49 302.97,57.39z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#FF000000"
android:pathData="M395.6,69.8L325.8,0h-58.2l69.8,69.8H395.6zM23.3,0H0v69.8h93.1L23.3,0zM244.4,69.8L174.5,0h-58.2l69.8,69.8H244.4zM418.9,162.9h-93.1l69.8,-69.8h-58.2l-69.8,69.8h-93.1l69.8,-69.8h-58.2l-69.8,69.8H23.3l69.8,-69.8H0v372.4C0,491.1 20.9,512 46.5,512h418.9c25.7,0 46.5,-20.9 46.5,-46.5V93.1h-23.3L418.9,162.9zM186.2,442.2V232.7l186.2,104.7L186.2,442.2zM418.9,0l69.8,69.8H512V0H418.9z" />
</vector>

View File

@ -1,16 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="1000"
android:viewportHeight="1000">
<path
android:fillColor="@android:color/white"
android:pathData="M469.81,12.11c-5.14,0.79 -12.65,6.33 -17,11.86c-6.33,8.3 -23.33,56.15 -37.96,109.13c-5.14,17 77.1,28.47 134.84,18.98c42.31,-7.12 41.12,-4.75 25.7,-56.54c-19.38,-66.03 -25.7,-77.5 -43.1,-82.24C518.05,9.34 486.81,8.95 469.81,12.11z" />
<path
android:fillColor="@android:color/white"
android:pathData="M356.72,328.83c-1.19,1.58 -13.05,40.73 -26.89,87.39l-24.91,84.62l16.61,14.63c40.33,35.99 154.6,52.99 253.85,37.56c60.89,-9.09 110.71,-30.45 116.64,-49.82c1.98,-5.93 -42.7,-170.42 -47.85,-177.14c-0.79,-0.79 -3.56,0.4 -6.32,2.77c-3.16,2.37 -18.58,7.91 -34.79,12.65c-62.87,18.19 -178.33,13.84 -228.15,-7.91C365.42,329.62 357.51,327.25 356.72,328.83z" />
<path
android:fillColor="@android:color/white"
android:pathData="M185.51,628.94c-14.24,2.77 -19.77,12.65 -34.8,60.5C85.87,897.82 72.43,942.89 70.45,958.71c-3.95,34 -39.93,31.23 432.58,31.23c459.46,-0 428.23,1.98 428.23,-24.91c0,-6.72 -4.75,-27.29 -10.28,-45.87c-32.42,-107.95 -85.01,-274.81 -88.57,-280.74c-4.35,-8.31 -22.93,-12.26 -61.29,-12.26l-27.68,-0l1.19,24.12l1.58,24.12l-19.77,18.98c-35.19,33.61 -105.18,54.17 -200.47,58.12c-114.67,4.75 -219.06,-20.56 -257.41,-62.87c-13.44,-15.02 -13.84,-17 -12.65,-39.14l1.19,-23.33l-31.63,0.4C208.05,626.96 189.86,628.15 185.51,628.94z" />
</vector>

View File

@ -6,9 +6,11 @@
<ImageView
android:id="@+id/noInternet_settingsImageView"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_width="33dp"
android:layout_height="33dp"
android:layout_margin="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="3dp"
android:src="@drawable/ic_settings"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:background="@android:color/black"
tools:context=".util.Player">
<com.github.libretube.util.CustomExoPlayerView
android:id="@+id/fullscreen_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black" />
</RelativeLayout>

View File

@ -20,12 +20,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/subscription_channel_image"
android:ellipsize="end"
android:maxLines="1"
android:text="Channel Name"
android:textSize="16dp" />
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/subscription_subscribe"
@ -35,7 +35,7 @@
android:layout_alignParentEnd="true"
android:backgroundTint="?attr/colorOnPrimary"
android:text="@string/unsubscribe"
android:textColor="?android:attr/textColorPrimary"
android:textSize="11dp"
android:textColor="@android:color/white"
android:textSize="11sp"
app:cornerRadius="20dp" />
</RelativeLayout>

View File

@ -3,8 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:orientation="vertical">
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:paddingHorizontal="5dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/chapter_image"

View File

@ -8,12 +8,16 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_marginTop="10dp"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -106,8 +110,7 @@
android:id="@+id/replies_recView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginBottom="10dp"
android:background="@null"
android:nestedScrollingEnabled="false" />
</LinearLayout>

View File

@ -24,6 +24,7 @@
<Button
android:id="@+id/logout"
style="@style/CustomDialogButton"
android:layout_marginRight="16dp"
android:text="@string/logout" />
</LinearLayout>

View File

@ -1,26 +1,7 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- 0dp dimensions are used to prevent this view from influencing the size of
the parent view if it uses "wrap_content". It is expanded to occupy the
entirety of the parent in code, after the parent's size has been
determined. See: https://github.com/google/ExoPlayer/issues/8726.
-->
<View
android:id="@id/exo_controls_background"
android:layout_width="0dp"
@ -28,7 +9,7 @@
android:background="@color/exo_black_opacity_60" />
<LinearLayout
android:id="@+id/top_bar"
android:id="@+id/exo_top_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_bottom_bar_height"
android:layout_gravity="top"
@ -49,11 +30,10 @@
android:id="@+id/close_imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_marginRight="5dp"
android:background="#00FFFFFF"
android:padding="@dimen/exo_icon_padding"
android:src="@drawable/ic_close"
android:visibility="gone"
app:tint="@android:color/white" />
<ImageButton
@ -110,7 +90,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="@dimen/exo_icon_padding"
android:text="HLS"
android:text="@string/hls"
android:textColor="#FFFFFF" />
<ImageView
@ -132,7 +112,6 @@
android:layout_height="@dimen/exo_styled_bottom_bar_height"
android:layout_gravity="bottom"
android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
android:background="@color/exo_bottom_bar_background"
android:layoutDirection="ltr">
<LinearLayout
@ -182,7 +161,8 @@
<ImageButton
android:id="@+id/fullscreen"
style="@style/ExoStyledControls.Button.Bottom.FullScreen"
android:src="@drawable/ic_fullscreen" />
android:src="@drawable/ic_fullscreen"
app:tint="@android:color/white" />
</LinearLayout>
@ -190,7 +170,6 @@
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="15dp"
android:layout_above="@id/exo_basic_controls"
android:layout_gravity="bottom"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
@ -200,7 +179,6 @@
android:id="@id/exo_progress"
android:layout_width="match_parent"
android:layout_height="15dp"
android:background="@color/exo_bottom_bar_background"
app:bar_height="2dp"
app:played_color="?attr/colorOnSecondary"
app:scrubber_color="?attr/colorOnPrimary"
@ -233,25 +211,11 @@
android:gravity="center"
android:padding="@dimen/exo_styled_controls_padding">
<Button
android:id="@id/exo_rew_with_amount"
style="@style/ExoStyledControls.Button.Center.RewWithAmount"
android:layout_marginRight="25dp"
android:scaleX="1.1"
android:scaleY="1.1" />
<ImageButton
android:id="@id/exo_play_pause"
style="@style/ExoStyledControls.Button.Center.PlayPause"
android:scaleX="1.1"
android:scaleY="1.1" />
<Button
android:id="@id/exo_ffwd_with_amount"
style="@style/ExoStyledControls.Button.Center.FfwdWithAmount"
android:layout_marginLeft="25dp"
android:scaleX="1.1"
android:scaleY="1.1" />
android:background="?android:selectableItemBackgroundBorderless"
app:tint="@android:color/white" />
</LinearLayout>

View File

@ -1,5 +1,6 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -8,125 +9,113 @@
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
style="@style/roundedImageViewRounded"
android:layout_width="80dp"
android:layout_height="80dp"
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_gravity="center"
android:layout_marginTop="30dp"
android:src="@mipmap/ic_launcher" />
android:layout_marginTop="50dp"
android:src="@mipmap/ic_launcher_round"
app:shapeAppearanceOverlay="@style/CircleImageView" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_marginBottom="40dp"
android:text="LibreTube"
android:textSize="27sp"
android:textSize="24sp"
android:textStyle="bold" />
<LinearLayout
<com.google.android.material.card.MaterialCardView
android:id="@+id/website"
style="@style/AboutItem">
style="@style/AboutCard">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/website"
android:textStyle="bold" />
<LinearLayout style="@style/AboutItem">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/website_summary" />
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_region" />
</LinearLayout>
<TextView
style="@style/AboutTextView"
android:text="@string/website" />
<LinearLayout
android:id="@+id/contributing"
style="@style/AboutItem">
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/contributing"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/contributing_summary" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/github"
style="@style/AboutCard">
</LinearLayout>
<LinearLayout style="@style/AboutItem">
<LinearLayout
android:id="@+id/authors"
style="@style/AboutItem">
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_github" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/authors"
android:textStyle="bold" />
<TextView
style="@style/AboutTextView"
android:text="@string/github" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/authors_summary" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
<com.google.android.material.card.MaterialCardView
android:id="@+id/piped"
style="@style/AboutItem">
style="@style/AboutCard">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/piped"
android:textStyle="bold" />
<LinearLayout style="@style/AboutItem">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/piped_summary" />
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_piped" />
</LinearLayout>
<TextView
style="@style/AboutTextView"
android:text="@string/piped" />
<LinearLayout
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/donate"
style="@style/AboutItem">
style="@style/AboutCard">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/donate"
android:textStyle="bold" />
<LinearLayout style="@style/AboutItem">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/donate_summary" />
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_donate" />
</LinearLayout>
<TextView
style="@style/AboutTextView"
android:text="@string/donate" />
<LinearLayout
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/license"
style="@style/AboutItem">
style="@style/AboutCard">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/license"
android:textStyle="bold" />
<LinearLayout style="@style/AboutItem">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/license_summary" />
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_license" />
</LinearLayout>
<TextView
style="@style/AboutTextView"
android:text="@string/license" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<com.github.libretube.util.CustomSwipeToRefresh xmlns:android="http://schemas.android.com/apk/res/android"
<com.github.libretube.views.CustomSwipeToRefresh xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/channel_refresh"
android:layout_width="match_parent"
@ -66,7 +66,7 @@
android:id="@+id/channel_subs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_gravity="start"
android:text="@string/app_name"
android:textSize="12sp" />
@ -76,11 +76,10 @@
android:id="@+id/channel_subscribe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="center_horizontal"
android:backgroundTint="?attr/colorOnPrimary"
android:drawableLeft="@drawable/ic_bell_small"
android:drawableTint="?android:attr/textColorPrimary"
android:drawableStart="@drawable/ic_bell_small"
android:drawableTint="@android:color/white"
android:text="@string/subscribe"
android:textColor="?android:attr/textColorPrimary"
android:textSize="11sp" />
@ -113,4 +112,4 @@
</RelativeLayout>
</LinearLayout>
</ScrollView>
</com.github.libretube.util.CustomSwipeToRefresh>
</com.github.libretube.views.CustomSwipeToRefresh>

View File

@ -0,0 +1,122 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_gravity="center"
android:layout_marginTop="50dp"
android:src="@mipmap/ic_launcher_round"
app:shapeAppearanceOverlay="@style/CircleImageView" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_marginBottom="40dp"
android:text="@string/community"
android:textSize="24sp"
android:textStyle="bold" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/telegram"
style="@style/AboutCard">
<LinearLayout style="@style/AboutItem">
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_telegram" />
<TextView
style="@style/AboutTextView"
android:text="@string/telegram" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/matrix"
style="@style/AboutCard">
<LinearLayout style="@style/AboutItem">
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_matrix" />
<TextView
style="@style/AboutTextView"
android:text="@string/matrix" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/discord"
style="@style/AboutCard">
<LinearLayout style="@style/AboutItem">
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_discord" />
<TextView
style="@style/AboutTextView"
android:text="@string/discord" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/reddit"
style="@style/AboutCard">
<LinearLayout style="@style/AboutItem">
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_reddit" />
<TextView
style="@style/AboutTextView"
android:text="@string/reddit" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/twitter"
style="@style/AboutCard">
<LinearLayout style="@style/AboutItem">
<ImageView
style="@style/AboutImageView"
android:src="@drawable/ic_twitter" />
<TextView
style="@style/AboutTextView"
android:text="@string/twitter" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.Home">
tools:context=".fragments.HomeFragment">
<ProgressBar
android:id="@+id/progressBar"

View File

@ -4,17 +4,15 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.Library">
tools:context=".fragments.LibraryFragment">
<RelativeLayout
android:id="@+id/loginOrRegister2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true">
android:layout_height="match_parent">
<ImageView
android:id="@+id/boogh2"
android:id="@+id/boogh"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true"
@ -22,18 +20,19 @@
android:src="@drawable/ic_login" />
<TextView
android:id="@+id/textLike2"
android:id="@+id/text_like"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/boogh2"
android:layout_below="@id/boogh"
android:layout_centerHorizontal="true"
android:layout_marginHorizontal="10dp"
android:gravity="center"
android:text="@string/please_login"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
<com.github.libretube.util.CustomSwipeToRefresh
<com.github.libretube.views.CustomSwipeToRefresh
android:id="@+id/playlist_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -45,9 +44,35 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/showWatchHistory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:src="@drawable/ic_time_outlined" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:text="@string/watch_history"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -62,7 +87,7 @@
</RelativeLayout>
</LinearLayout>
</ScrollView>
</com.github.libretube.util.CustomSwipeToRefresh>
</com.github.libretube.views.CustomSwipeToRefresh>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/create_playlist"

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<com.github.libretube.util.SingleViewTouchableMotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.github.libretube.views.SingleViewTouchableMotionLayout 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/playerMotionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/player_scene"
tools:context=".PlayerFragment">
tools:context=".fragments.PlayerFragment">
<ScrollView
android:id="@+id/player_scrollView"
@ -193,13 +193,13 @@
android:layout_width="24dp"
android:layout_height="25dp"
android:padding="2dp"
android:src="@drawable/ic_vlc" />
android:src="@drawable/ic_player" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="VLC" />
android:text="@string/open" />
</LinearLayout>
<LinearLayout
@ -281,7 +281,10 @@
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="18dp">
<androidx.constraintlayout.widget.ConstraintLayout
@ -319,14 +322,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/comments_toggle"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:nestedScrollingEnabled="false"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/player_recView"
android:id="@+id/related_rec_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/comments_recView"
@ -347,21 +349,59 @@
android:layout_height="0dp"
android:background="?attr/colorSurface"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.35"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHeight_percent="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.github.libretube.util.CustomExoPlayerView
<com.github.libretube.views.CustomExoPlayerView
android:id="@+id/player"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#000000"
android:scaleType="centerCrop"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
app:layout_constraintBottom_toBottomOf="@id/main_container"
app:layout_constraintStart_toStartOf="@id/main_container"
app:layout_constraintTop_toTopOf="@id/main_container"
app:show_buffering="always" />
app:show_buffering="when_playing">
<FrameLayout
android:id="@+id/forwardFL"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|end"
android:layout_marginVertical="50dp">
<ImageButton
android:id="@+id/forwardBTN"
android:layout_width="150dp"
android:layout_height="match_parent"
android:background="?android:selectableItemBackgroundBorderless"
android:clickable="false"
android:src="@drawable/ic_forward"
android:visibility="invisible"
app:tint="@android:color/white" />
</FrameLayout>
<FrameLayout
android:id="@+id/rewindFL"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
android:layout_marginVertical="50dp">
<ImageButton
android:id="@+id/rewindBTN"
android:layout_width="150dp"
android:layout_height="match_parent"
android:background="?android:selectableItemBackgroundBorderless"
android:clickable="false"
android:src="@drawable/ic_rewind"
android:visibility="invisible"
app:tint="@android:color/white" />
</FrameLayout>
</com.github.libretube.views.CustomExoPlayerView>
<ImageView
android:id="@+id/close_imageView"
@ -402,4 +442,4 @@
app:layout_constraintStart_toEndOf="@+id/player"
app:layout_constraintTop_toTopOf="@+id/play_imageView" />
</com.github.libretube.util.SingleViewTouchableMotionLayout>
</com.github.libretube.views.SingleViewTouchableMotionLayout>

View File

@ -23,14 +23,30 @@
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/playlist_name"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text=""
android:textSize="24sp"
android:textStyle="bold" />
android:orientation="horizontal"
android:padding="8dp">
<TextView
android:id="@+id/playlist_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text=""
android:textSize="24sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/optionsMenu"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:layout_marginRight="10dp"
android:src="@drawable/ic_three_dots" />
</LinearLayout>
<TextView
android:id="@+id/playlist_uploader"

View File

@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.Subscriptions">
tools:context=".fragments.SubscriptionsFragment">
<ProgressBar
android:id="@+id/sub_progress"
@ -33,13 +33,14 @@
android:layout_height="wrap_content"
android:layout_below="@id/boogh"
android:layout_centerHorizontal="true"
android:layout_marginHorizontal="10dp"
android:gravity="center"
android:text="@string/please_login"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
<com.github.libretube.util.CustomSwipeToRefresh
<com.github.libretube.views.CustomSwipeToRefresh
android:id="@+id/sub_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -101,5 +102,5 @@
</RelativeLayout>
</LinearLayout>
</ScrollView>
</com.github.libretube.util.CustomSwipeToRefresh>
</com.github.libretube.views.CustomSwipeToRefresh>
</RelativeLayout>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:paddingHorizontal="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/watch_history"
android:textSize="16sp" />
<ImageView
android:id="@+id/clearHistory"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center"
android:layout_marginRight="5dp"
android:src="@drawable/ic_reset" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/watchHistoryRecView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</ScrollView>

View File

@ -2,8 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground">
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"

Some files were not shown because too many files have changed in this diff Show More