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) [![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) [![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/) [![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/) </div><div align="center" style="width:100%; display:flex; justify-content:space-between;">
[<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/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/tgload.png" alt="Get it on GitHub" height="80">](https://t.me/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> </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) <div style="width:100%; display:flex; justify-content:space-between;">
[<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)
## 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 || </div>
| - | - |
| User Accounts | ✅ |
| Subscriptions | ✅ |
| User Playlists | ✅ |
| Channel Playlists | ✅ |
| Search Filters | ✅ |
| SponsorBlock | ✅ |
| Subtitles | ✅ |
| Comments | ✅ |
## ⭐ 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! 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 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. 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"> <a href="https://hosted.weblate.org/projects/libretube/#languages">
<img src="https://hosted.weblate.org/widgets/libretube/-/287x66-grey.png" alt="Translation status" /> <img src="https://hosted.weblate.org/widgets/libretube/-/287x66-grey.png" alt="Translation status" />
</a> </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) [![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` **BTC:** `bc1q0hk2smc74ej8fxupfrp05wk867e54e2zztnxfc`
**XMR:** `44txdmy4E5bDzMYQJh1ZSoHbrp1sWfpGa2FYg26L2ya8EaRejPsh42yVrYhepW9P4YWvrqmTZvms35z5FDgqy1xcVewk18d` **XMR:** `44txdmy4E5bDzMYQJh1ZSoHbrp1sWfpGa2FYg26L2ya8EaRejPsh42yVrYhepW9P4YWvrqmTZvms35z5FDgqy1xcVewk18d`
## Mirrors (read-only) ## 🪞 Mirrors (read-only)
<a href="https://gitlab.com/libretube/LibreTube">GitLab</a></p> <a href="https://gitlab.com/libretube/LibreTube">GitLab</a></p>
<a href="https://notabug.org/LibreTube/LibreTube">NotABug</a></p> <a href="https://notabug.org/LibreTube/LibreTube">NotABug</a></p>

View File

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

View File

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

View File

@ -1,8 +1,23 @@
package com.github.libretube 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" 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 WEBSITE_URL = "https://libre-tube.github.io/"
const val AUTHORS_URL = "https://github.com/libre-tube/LibreTube/graphs/contributors" const val DONATE_URL = "https://github.com/libre-tube/LibreTube#donate"
const val DONATE_URL = "https://libre-tube.github.io/#donate" const val GITHUB_URL = "https://github.com/libre-tube/LibreTube"
const val CONTRIBUTING_URL = "https://github.com/libre-tube/LibreTube#donate"
const val PIPED_GITHUB_URL = "https://github.com/TeamPiped/Piped" 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.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import android.view.WindowInsetsController import android.view.WindowInsetsController
import android.view.WindowManager import android.view.WindowManager
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.setupWithNavController 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.PlayerFragment
import com.github.libretube.fragments.isFullScreen import com.github.libretube.fragments.isFullScreen
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.services.ClosingService import com.github.libretube.services.ClosingService
import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.CronetHelper import com.github.libretube.util.CronetHelper
import com.github.libretube.util.LocaleHelper import com.github.libretube.util.LocaleHelper
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper 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.color.DynamicColors
import com.google.android.material.elevation.SurfaceColors
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
val TAG = "MainActivity" val TAG = "MainActivity"
lateinit var bottomNavigationView: BottomNavigationView lateinit var binding: ActivityMainBinding
private lateinit var toolbar: Toolbar
lateinit var navController: NavController lateinit var navController: NavController
private var startFragmentId = R.id.homeFragment
override fun onCreate(savedInstanceState: Bundle?) { 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) super.onCreate(savedInstanceState)
// start service that gets called on closure // start service that gets called on closure
@ -61,74 +74,89 @@ class MainActivity : AppCompatActivity() {
RetrofitInstance.url = RetrofitInstance.url =
PreferenceHelper.getString(this, "selectInstance", "https://pipedapi.kavin.rocks/")!! PreferenceHelper.getString(this, "selectInstance", "https://pipedapi.kavin.rocks/")!!
// set auth instance
ThemeHelper.updateTheme(this) RetrofitInstance.authUrl =
LocaleHelper.updateLanguage(this) 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 // show noInternet Activity if no internet available on app startup
if (!isNetworkAvailable(this)) { if (!ConnectionHelper.isNetworkAvailable(this)) {
setContentView(R.layout.activity_nointernet) val noInternetIntent = Intent(this, NoInternetActivity::class.java)
findViewById<Button>(R.id.retry_button).setOnClickListener { startActivity(noInternetIntent)
recreate()
}
findViewById<ImageView>(R.id.noInternet_settingsImageView).setOnClickListener {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
} else { } else {
setContentView(R.layout.activity_main) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
bottomNavigationView = findViewById(R.id.bottomNav)
navController = findNavController(R.id.fragment) 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 // hide the trending page if enabled
val hideTrendingPage = PreferenceHelper.getBoolean(this, "hide_trending_page", false) 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 // save start tab fragment id
when (PreferenceHelper.getString(this, "default_tab", "home")) { startFragmentId = when (PreferenceHelper.getString(this, "default_tab", "home")) {
"home" -> navController.navigate(R.id.home2) "home" -> R.id.homeFragment
"subscriptions" -> navController.navigate(R.id.subscriptions) "subscriptions" -> R.id.subscriptionsFragment
"library" -> navController.navigate(R.id.library) "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) { when (it.itemId) {
R.id.home2 -> { R.id.homeFragment -> {
navController.backQueue.clear() navController.navigate(R.id.homeFragment)
navController.navigate(R.id.home2)
} }
R.id.subscriptions -> { R.id.subscriptionsFragment -> {
// navController.backQueue.clear() navController.navigate(R.id.subscriptionsFragment)
navController.navigate(R.id.subscriptions)
} }
R.id.library -> { R.id.libraryFragment -> {
// navController.backQueue.clear() navController.navigate(R.id.libraryFragment)
navController.navigate(R.id.library)
} }
} }
false false
} }
toolbar = findViewById(R.id.toolbar) /**
val typedValue = TypedValue() * don't remove this line
this.theme.resolveAttribute(R.attr.colorPrimary, typedValue, true) * this prevents reselected items at the bottomNav to be duplicated in the backstack
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data)) */
val appName = HtmlCompat.fromHtml( binding.bottomNav.setOnItemReselectedListener {}
"Libre<span style='color:$hexColor';>Tube</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
toolbar.title = appName
toolbar.setNavigationOnClickListener { binding.toolbar.title = ThemeHelper.getStyledAppName(this)
binding.toolbar.setNavigationOnClickListener {
// settings activity stuff // settings activity stuff
val intent = Intent(this, SettingsActivity::class.java) val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent) startActivity(intent)
} }
toolbar.setOnMenuItemClickListener { binding.toolbar.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.action_search -> { R.id.action_search -> {
navController.navigate(R.id.searchFragment) 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() { override fun onStart() {
super.onStart() super.onStart()
val intentData: Uri? = intent?.data val intentData: Uri? = intent?.data
@ -183,7 +189,7 @@ class MainActivity : AppCompatActivity() {
channel = channel!!.replace("/c/", "") channel = channel!!.replace("/c/", "")
channel = channel.replace("/user/", "") channel = channel.replace("/user/", "")
val bundle = bundleOf("channel_id" to channel) 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")) { } else if (data.path!!.contains("/playlist")) {
Log.i(TAG, "URI Type: Playlist") Log.i(TAG, "URI Type: Playlist")
var playlist = data.query!! var playlist = data.query!!
@ -266,34 +272,37 @@ class MainActivity : AppCompatActivity() {
} }
override fun onBackPressed() { override fun onBackPressed() {
try { if (binding.mainMotionLayout.progress == 0F) {
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
try { try {
navController.popBackStack() minimizePlayer()
moveTaskToBack(true)
} catch (e: Exception) { } 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) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
val orientation = newConfig.orientation val orientation = newConfig.orientation
@ -362,10 +371,6 @@ fun Fragment.hideKeyboard() {
view?.let { activity?.hideKeyboard(it) } view?.let { activity?.hideKeyboard(it) }
} }
fun Activity.hideKeyboard() {
hideKeyboard(currentFocus ?: View(this))
}
fun Context.hideKeyboard(view: View) { fun Context.hideKeyboard(view: View) {
val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) 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.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
@ -6,6 +6,7 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.github.libretube.R
import com.github.libretube.util.ThemeHelper import com.github.libretube.util.ThemeHelper
class RouterActivity : AppCompatActivity() { class RouterActivity : AppCompatActivity() {

View File

@ -1,13 +1,12 @@
package com.github.libretube package com.github.libretube.activities
import android.app.NotificationManager import android.app.NotificationManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat 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.preferences.MainSettings
import com.github.libretube.util.ThemeHelper import com.github.libretube.util.ThemeHelper
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
@ -17,24 +16,27 @@ var requireMainActivityRestart = false
class SettingsActivity : AppCompatActivity() { class SettingsActivity : AppCompatActivity() {
val TAG = "SettingsActivity" val TAG = "SettingsActivity"
lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
DynamicColors.applyToActivityIfAvailable(this) DynamicColors.applyToActivityIfAvailable(this)
ThemeHelper.updateTheme(this) ThemeHelper.updateTheme(this)
// makes the preference dialogs use material dialogs // makes the preference dialogs use material dialogs
setTheme(R.style.MaterialAlertDialog) setTheme(R.style.MaterialAlertDialog)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
overridePendingTransition(50, 50) overridePendingTransition(50, 50)
} }
val view = this.findViewById<View>(android.R.id.content) binding.root.alpha = 0F
view.alpha = 0F binding.root.animate().alpha(1F).duration = 300
view.animate().alpha(1F).duration = 300
setContentView(R.layout.activity_settings) setContentView(binding.root)
val backButton = view.findViewById<ImageButton>(R.id.back_imageButton) binding.backImageButton.setOnClickListener {
backButton.setOnClickListener {
onBackPressed() onBackPressed()
} }
@ -66,8 +68,11 @@ class SettingsActivity : AppCompatActivity() {
.beginTransaction() .beginTransaction()
.replace(R.id.settings, MainSettings()) .replace(R.id.settings, MainSettings())
.commit() .commit()
val topBarTextView = findViewById<TextView>(R.id.topBar_textView) changeTopBarText(getString(R.string.settings))
topBarTextView?.text = 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.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.databinding.VideoChannelRowBinding
import com.github.libretube.dialogs.VideoOptionsDialog import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.obj.StreamItem import com.github.libretube.obj.StreamItem
@ -22,6 +20,7 @@ class ChannelAdapter(
private val childFragmentManager: FragmentManager private val childFragmentManager: FragmentManager
) : ) :
RecyclerView.Adapter<ChannelViewHolder>() { RecyclerView.Adapter<ChannelViewHolder>() {
override fun getItemCount(): Int { override fun getItemCount(): Int {
return videoFeed.size return videoFeed.size
} }
@ -33,43 +32,41 @@ class ChannelAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelViewHolder {
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.video_channel_row, parent, false) val binding = VideoChannelRowBinding.inflate(layoutInflater, parent, false)
return ChannelViewHolder(cell) return ChannelViewHolder(binding)
} }
override fun onBindViewHolder(holder: ChannelViewHolder, position: Int) { override fun onBindViewHolder(holder: ChannelViewHolder, position: Int) {
val trending = videoFeed[position] val trending = videoFeed[position]
holder.v.findViewById<TextView>(R.id.channel_description).text = trending.title holder.binding.apply {
holder.v.findViewById<TextView>(R.id.channel_views).text = channelDescription.text = trending.title
trending.views.formatShort() + "" + channelViews.text =
DateUtils.getRelativeTimeSpanString(trending.uploaded!!) trending.views.formatShort() + "" +
holder.v.findViewById<TextView>(R.id.channel_duration).text = DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
DateUtils.formatElapsedTime(trending.duration!!) channelDuration.text =
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.channel_thumbnail) DateUtils.formatElapsedTime(trending.duration!!)
Picasso.get().load(trending.thumbnail).into(thumbnailImage) Picasso.get().load(trending.thumbnail).into(channelThumbnail)
holder.v.setOnClickListener { root.setOnClickListener {
var bundle = Bundle() var bundle = Bundle()
bundle.putString("videoId", trending.url!!.replace("/watch?v=", "")) bundle.putString("videoId", trending.url!!.replace("/watch?v=", ""))
var frag = PlayerFragment() var frag = PlayerFragment()
frag.arguments = bundle frag.arguments = bundle
val activity = holder.v.context as AppCompatActivity val activity = root.context as AppCompatActivity
activity.supportFragmentManager.beginTransaction() activity.supportFragmentManager.beginTransaction()
.remove(PlayerFragment()) .remove(PlayerFragment())
.commit() .commit()
activity.supportFragmentManager.beginTransaction() activity.supportFragmentManager.beginTransaction()
.replace(R.id.container, frag) .replace(R.id.container, frag)
.commitNow() .commitNow()
} }
holder.v.setOnLongClickListener { root.setOnLongClickListener {
val videoId = trending.url!!.replace("/watch?v=", "") val videoId = trending.url!!.replace("/watch?v=", "")
VideoOptionsDialog(videoId, holder.v.context) VideoOptionsDialog(videoId, root.context)
.show(childFragmentManager, VideoOptionsDialog.TAG) .show(childFragmentManager, VideoOptionsDialog.TAG)
true true
}
} }
} }
} }
class ChannelViewHolder(val v: View) : RecyclerView.ViewHolder(v) { class ChannelViewHolder(val binding: VideoChannelRowBinding) : RecyclerView.ViewHolder(binding.root)
init {
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,16 +3,16 @@ package com.github.libretube.adapters
import android.os.Bundle import android.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.MainActivity
import com.github.libretube.R 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.PlaylistOptionsDialog
import com.github.libretube.dialogs.VideoOptionsDialog import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.fragments.PlayerFragment import com.github.libretube.fragments.PlayerFragment
@ -27,7 +27,7 @@ class SearchAdapter(
RecyclerView.Adapter<SearchViewHolder>() { RecyclerView.Adapter<SearchViewHolder>() {
fun updateItems(newItems: List<SearchItem>) { fun updateItems(newItems: List<SearchItem>) {
var searchItemsSize = searchItems.size val searchItemsSize = searchItems.size
searchItems.addAll(newItems) searchItems.addAll(newItems)
notifyItemRangeInserted(searchItemsSize, newItems.size) notifyItemRangeInserted(searchItemsSize, newItems.size)
} }
@ -37,19 +37,32 @@ class SearchAdapter(
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
val layout = when (viewType) { val layoutInflater = LayoutInflater.from(parent.context)
0 -> R.layout.video_search_row
1 -> R.layout.channel_search_row return when (viewType) {
2 -> R.layout.playlist_search_row 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") 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) { 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 { override fun getItemViewType(position: Int): Int {
@ -60,117 +73,110 @@ class SearchAdapter(
else -> 3 else -> 3
} }
} }
}
class SearchViewHolder( private fun bindWatch(item: SearchItem, binding: VideoSearchRowBinding) {
private val v: View, binding.apply {
private val childFragmentManager: FragmentManager Picasso.get().load(item.thumbnail).fit().centerCrop().into(searchThumbnail)
) : RecyclerView.ViewHolder(v) { if (item.duration != -1L) {
searchThumbnailDuration.text = DateUtils.formatElapsedTime(item.duration!!)
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"
} else { } 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) { private fun bindChannel(item: SearchItem, binding: ChannelSearchRowBinding) {
val channelImage = v.findViewById<ImageView>(R.id.search_channel_image) binding.apply {
Picasso.get().load(item.thumbnail).fit().centerCrop().into(channelImage) Picasso.get().load(item.thumbnail).fit().centerCrop().into(searchChannelImage)
val channelName = v.findViewById<TextView>(R.id.search_channel_name) searchChannelName.text = item.name
channelName.text = item.name searchViews.text = root.context.getString(
val channelViews = v.findViewById<TextView>(R.id.search_views) R.string.subscribers,
channelViews.text = v.context.getString( item.subscribers.formatShort()
R.string.subscribers, ) + "" + root.context.getString(R.string.videoCount, item.videos.toString())
item.subscribers.formatShort() root.setOnClickListener {
) + "" + v.context.getString(R.string.videoCount, item.videos.toString()) val activity = root.context as MainActivity
v.setOnClickListener { val bundle = bundleOf("channel_id" to item.url)
val activity = v.context as MainActivity activity.navController.navigate(R.id.channelFragment, bundle)
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
} }
} }
fun bind(searchItem: SearchItem) { private fun bindPlaylist(item: SearchItem, binding: PlaylistSearchRowBinding) {
when { binding.apply {
searchItem.url!!.startsWith("/watch", false) -> bindWatch(searchItem) Picasso.get().load(item.thumbnail).fit().centerCrop().into(searchThumbnail)
searchItem.url!!.startsWith("/channel", false) -> bindChannel(searchItem) if (item.videos?.toInt() != -1) searchPlaylistNumber.text = item.videos.toString()
searchItem.url!!.startsWith("/playlist", false) -> bindPlaylist(searchItem) searchDescription.text = item.name
else -> { 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.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView 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.fragments.SearchFragment
import com.github.libretube.util.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
class SearchHistoryAdapter( class SearchHistoryAdapter(
private val context: Context, private val context: Context,
@ -26,28 +23,28 @@ class SearchHistoryAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHistoryViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHistoryViewHolder {
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.searchhistory_row, parent, false) val binding = SearchhistoryRowBinding.inflate(layoutInflater, parent, false)
return SearchHistoryViewHolder(cell) return SearchHistoryViewHolder(binding)
} }
override fun onBindViewHolder(holder: SearchHistoryViewHolder, position: Int) { override fun onBindViewHolder(holder: SearchHistoryViewHolder, position: Int) {
val history = historyList[position] 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 { deleteHistory.setOnClickListener {
historyList = historyList - history historyList = historyList - history
PreferenceHelper.saveHistory(context, historyList) PreferenceHelper.saveHistory(context, historyList)
notifyDataSetChanged() notifyDataSetChanged()
} }
holder.v.setOnClickListener { root.setOnClickListener {
editText.setText(history) editText.setText(history)
searchFragment.fetchSearch(history) searchFragment.fetchSearch(history)
}
} }
} }
} }
class SearchHistoryViewHolder(val v: View) : RecyclerView.ViewHolder(v) { class SearchHistoryViewHolder(val binding: SearchhistoryRowBinding) :
init { RecyclerView.ViewHolder(binding.root)
}
}

View File

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

View File

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

View File

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

View File

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

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.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.databinding.DialogAddtoplaylistBinding
import com.github.libretube.obj.PlaylistId 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.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class AddtoPlaylistDialog : DialogFragment() { class AddtoPlaylistDialog : DialogFragment() {
private val TAG = "AddToPlaylistDialog" private val TAG = "AddToPlaylistDialog"
private lateinit var binding: DialogAddtoplaylistBinding
private lateinit var videoId: String private lateinit var videoId: String
private lateinit var token: String private lateinit var token: String
private lateinit var spinner: Spinner
private lateinit var button: Button
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
videoId = arguments?.getString("videoId")!! videoId = arguments?.getString("videoId")!!
val builder = MaterialAlertDialogBuilder(it) val builder = MaterialAlertDialogBuilder(it)
// Get the layout inflater // Get the layout inflater
val inflater = requireActivity().layoutInflater binding = DialogAddtoplaylistBinding.inflate(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
builder.setView(view) token = PreferenceHelper.getToken(requireContext())
if (token != "") fetchPlaylists()
binding.title.text = ThemeHelper.getStyledAppName(requireContext())
builder.setView(binding.root)
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
@ -59,7 +47,7 @@ class AddtoPlaylistDialog : DialogFragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.playlists(token) RetrofitInstance.authApi.playlists(token)
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") Log.e(TAG, "IOException, you might not have internet connection")
@ -80,10 +68,12 @@ class AddtoPlaylistDialog : DialogFragment() {
arrayAdapter.setDropDownViewResource( arrayAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item android.R.layout.simple_spinner_dropdown_item
) )
spinner.adapter = arrayAdapter binding.playlistsSpinner.adapter = arrayAdapter
runOnUiThread { runOnUiThread {
button.setOnClickListener { binding.addToPlaylist.setOnClickListener {
addToPlaylist(response[spinner.selectedItemPosition].id!!) addToPlaylist(
response[binding.playlistsSpinner.selectedItemPosition].id!!
)
} }
} }
} else { } else {
@ -97,7 +87,7 @@ class AddtoPlaylistDialog : DialogFragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.addToPlaylist(token, PlaylistId(playlistId, videoId)) RetrofitInstance.authApi.addToPlaylist(token, PlaylistId(playlistId, videoId))
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") 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.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.util.Log 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 android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.R 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.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.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class CreatePlaylistDialog : DialogFragment() { class CreatePlaylistDialog : DialogFragment() {
val TAG = "CreatePlaylistDialog" val TAG = "CreatePlaylistDialog"
private var token: String = "" private var token: String = ""
private lateinit var binding: DialogCreatePlaylistBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
val builder = MaterialAlertDialogBuilder(it) val builder = MaterialAlertDialogBuilder(it)
val inflater = requireActivity().layoutInflater binding = DialogCreatePlaylistBinding.inflate(layoutInflater)
val view: View = inflater.inflate(R.layout.dialog_create_playlist, null)
val typedValue = TypedValue() binding.title.text = ThemeHelper.getStyledAppName(requireContext())
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
val cancelBtn = view.findViewById<Button>(R.id.cancel_button) binding.cancelButton.setOnClickListener {
cancelBtn.setOnClickListener {
dismiss() dismiss()
} }
token = PreferenceHelper.getToken(requireContext()) token = PreferenceHelper.getToken(requireContext())
val playlistName = view.findViewById<TextInputEditText>(R.id.playlist_name) binding.createNewPlaylist.setOnClickListener {
val createPlaylistBtn = view.findViewById<Button>(R.id.create_new_playlist)
createPlaylistBtn.setOnClickListener {
// avoid creating the same playlist multiple times by spamming the button // avoid creating the same playlist multiple times by spamming the button
createPlaylistBtn.setOnClickListener(null) binding.createNewPlaylist.setOnClickListener(null)
val listName = playlistName.text.toString() val listName = binding.playlistName.text.toString()
if (listName != "") { if (listName != "") {
createPlaylist(listName) createPlaylist(listName)
} else { } else {
@ -60,7 +46,7 @@ class CreatePlaylistDialog : DialogFragment() {
} }
} }
builder.setView(view) builder.setView(binding.root)
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
@ -69,7 +55,7 @@ class CreatePlaylistDialog : DialogFragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.createPlaylist(token, Playlists(name = name)) RetrofitInstance.authApi.createPlaylist(token, Playlists(name = name))
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") Log.e(TAG, "IOException, you might not have internet connection")
@ -88,7 +74,7 @@ class CreatePlaylistDialog : DialogFragment() {
} }
// refresh the playlists in the library // refresh the playlists in the library
try { try {
val parent = parentFragment as Library val parent = parentFragment as LibraryFragment
parent.fetchPlaylists() parent.fetchPlaylists()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())

View File

@ -2,45 +2,34 @@ package com.github.libretube.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle 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 android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.databinding.DialogCustomInstanceBinding
import com.github.libretube.obj.CustomInstance 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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import java.net.URL import java.net.URL
class CustomInstanceDialog : DialogFragment() { class CustomInstanceDialog : DialogFragment() {
val TAG = "CustomInstanceDialog" val TAG = "CustomInstanceDialog"
private lateinit var binding: DialogCustomInstanceBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
val builder = MaterialAlertDialogBuilder(it) val builder = MaterialAlertDialogBuilder(it)
val inflater = requireActivity().layoutInflater binding = DialogCustomInstanceBinding.inflate(layoutInflater)
val view: View = inflater.inflate(R.layout.dialog_custom_instance, null)
val instanceNameEditText = view.findViewById<TextInputEditText>(R.id.instanceName) binding.cancel.setOnClickListener {
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 {
dismiss() dismiss()
} }
addInstanceButton.setOnClickListener { binding.addInstance.setOnClickListener {
val customInstance = CustomInstance() val customInstance = CustomInstance()
customInstance.name = instanceNameEditText.text.toString() customInstance.name = binding.instanceName.text.toString()
customInstance.apiUrl = instanceApiUrlEditText.text.toString() customInstance.apiUrl = binding.instanceApiUrl.text.toString()
customInstance.frontendUrl = instanceFrontendUrlEditText.text.toString() customInstance.frontendUrl = binding.instanceFrontendUrl.text.toString()
if ( if (
customInstance.name != "" && customInstance.name != "" &&
@ -73,16 +62,9 @@ class CustomInstanceDialog : DialogFragment() {
} }
} }
val typedValue = TypedValue() binding.title.text = ThemeHelper.getStyledAppName(requireContext())
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() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }

View File

@ -3,54 +3,42 @@ package com.github.libretube.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.util.Log 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 android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.R 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.obj.DeleteUserRequest
import com.github.libretube.requireMainActivityRestart import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
class DeleteAccountDialog : DialogFragment() { class DeleteAccountDialog : DialogFragment() {
private val TAG = "DeleteAccountDialog" private val TAG = "DeleteAccountDialog"
lateinit var username: EditText private lateinit var binding: DialogDeleteAccountBinding
lateinit var password: EditText
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
val builder = MaterialAlertDialogBuilder(it) val builder = MaterialAlertDialogBuilder(it)
val inflater = requireActivity().layoutInflater binding = DialogDeleteAccountBinding.inflate(layoutInflater)
val view = inflater.inflate(R.layout.dialog_delete_account, null)
view.findViewById<Button>(R.id.cancel_button).setOnClickListener { binding.cancelButton.setOnClickListener {
dialog?.dismiss() dialog?.dismiss()
} }
password = view.findViewById(R.id.delete_password) binding.deleteAccountConfirm.setOnClickListener {
view.findViewById<Button>(R.id.delete_account_confirm).setOnClickListener { if (binding.deletePassword.text.toString() != "") {
if (password.text.toString() != "") { deleteAccount(binding.deletePassword.text.toString())
deleteAccount(password.text.toString())
} else { } else {
Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
} }
} }
val typedValue = TypedValue() binding.title.text = ThemeHelper.getStyledAppName(requireContext())
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() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
@ -61,7 +49,7 @@ class DeleteAccountDialog : DialogFragment() {
val token = PreferenceHelper.getToken(requireContext()) val token = PreferenceHelper.getToken(requireContext())
try { try {
RetrofitInstance.api.deleteAccount(token, DeleteUserRequest(password)) RetrofitInstance.authApi.deleteAccount(token, DeleteUserRequest(password))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show() 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.Bundle
import android.os.Environment import android.os.Environment
import android.util.Log import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button import android.widget.Toast
import android.widget.Spinner
import android.widget.TextView
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.github.libretube.MainActivity import androidx.lifecycle.lifecycleScope
import com.github.libretube.R 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.obj.Streams
import com.github.libretube.services.DownloadService 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 com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException
import java.io.IOException
class DownloadDialog : DialogFragment() { class DownloadDialog : DialogFragment() {
private val TAG = "DownloadDialog" private val TAG = "DownloadDialog"
private lateinit var binding: DialogDownloadBinding
private lateinit var streams: Streams private lateinit var streams: Streams
private lateinit var videoId: String private lateinit var videoId: String
@ -32,14 +34,13 @@ class DownloadDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
streams = arguments?.getParcelable("streams")!!
videoId = arguments?.getString("video_id")!! videoId = arguments?.getString("video_id")!!
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
val builder = MaterialAlertDialogBuilder(it) val builder = MaterialAlertDialogBuilder(it)
// Get the layout inflater binding = DialogDownloadBinding.inflate(layoutInflater)
val inflater = requireActivity().layoutInflater
var view: View = inflater.inflate(R.layout.dialog_download, null) fetchStreams()
// request storage permissions if not granted yet // request storage permissions if not granted yet
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@ -75,78 +76,89 @@ class DownloadDialog : DialogFragment() {
} }
} }
var vidName = arrayListOf<String>() binding.title.text = ThemeHelper.getStyledAppName(requireContext())
var vidUrl = arrayListOf<String>()
// add empty selection builder.setView(binding.root)
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.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: 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.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.util.Log 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 android.widget.Toast
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.libretube.R 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.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.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class LoginDialog : DialogFragment() { class LoginDialog : DialogFragment() {
private val TAG = "LoginDialog" private val TAG = "LoginDialog"
lateinit var username: EditText private lateinit var binding: DialogLoginBinding
lateinit var password: EditText
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
val builder = MaterialAlertDialogBuilder(it) val builder = MaterialAlertDialogBuilder(it)
// Get the layout inflater // Get the layout inflater
val inflater = requireActivity().layoutInflater binding = DialogLoginBinding.inflate(layoutInflater)
val token = PreferenceHelper.getToken(requireContext())
var view: View binding.login.setOnClickListener {
Log.e("dafaq", token!!) if (binding.username.text.toString() != "" && binding.password.text.toString() != "") {
if (token != "") { val login =
val user = PreferenceHelper.getUsername(requireContext()) Login(binding.username.text.toString(), binding.password.text.toString())
view = inflater.inflate(R.layout.dialog_logout, null) login(login)
view.findViewById<TextView>(R.id.user).text = } else {
view.findViewById<TextView>(R.id.user).text.toString() + " (" + user + ")" Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
view.findViewById<Button>(R.id.logout).setOnClickListener {
Toast.makeText(context, R.string.loggedout, Toast.LENGTH_SHORT).show()
PreferenceHelper.setToken(requireContext(), "")
dialog?.dismiss()
} }
} else { }
view = inflater.inflate(R.layout.dialog_login, null) binding.register.setOnClickListener {
username = view.findViewById(R.id.username) if (
password = view.findViewById(R.id.password) binding.username.text.toString() != "" &&
view.findViewById<Button>(R.id.login).setOnClickListener { binding.password.text.toString() != ""
if (username.text.toString() != "" && password.text.toString() != "") { ) {
val login = Login(username.text.toString(), password.text.toString()) val login = Login(
login(login) binding.username.text.toString(),
} else { binding.password.text.toString()
Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show() )
} register(login)
} } else {
view.findViewById<Button>(R.id.register).setOnClickListener { Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
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()
}
} }
} }
val typedValue = TypedValue() binding.title.text = ThemeHelper.getStyledAppName(requireContext())
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() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
@ -82,7 +62,7 @@ class LoginDialog : DialogFragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.login(login) RetrofitInstance.authApi.login(login)
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") 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() Toast.makeText(context, R.string.loggedIn, Toast.LENGTH_SHORT).show()
PreferenceHelper.setToken(requireContext(), response.token!!) PreferenceHelper.setToken(requireContext(), response.token!!)
PreferenceHelper.setUsername(requireContext(), login.username!!) PreferenceHelper.setUsername(requireContext(), login.username!!)
requireMainActivityRestart = true
dialog?.dismiss() dialog?.dismiss()
activity?.recreate()
} }
} }
} }
@ -113,7 +95,7 @@ class LoginDialog : DialogFragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.register(login) RetrofitInstance.authApi.register(login)
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") 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 androidx.fragment.app.DialogFragment
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.obj.PlaylistId 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.RetrofitInstance
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -20,16 +20,23 @@ import java.io.IOException
class PlaylistOptionsDialog( class PlaylistOptionsDialog(
private val playlistId: String, private val playlistId: String,
private val isOwner: Boolean,
context: Context context: Context
) : DialogFragment() { ) : DialogFragment() {
val TAG = "PlaylistOptionsDialog" val TAG = "PlaylistOptionsDialog"
private val optionsList = listOf( private var optionsList = listOf(
context.getString(R.string.clonePlaylist), context.getString(R.string.clonePlaylist),
context.getString(R.string.share) context.getString(R.string.share)
) )
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
if (isOwner) {
optionsList = optionsList +
context?.getString(R.string.deletePlaylist)!! -
context?.getString(R.string.clonePlaylist)!!
}
val dialog = MaterialAlertDialogBuilder(requireContext()) val dialog = MaterialAlertDialogBuilder(requireContext())
.setNegativeButton(R.string.cancel) { dialog, _ -> .setNegativeButton(R.string.cancel) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
@ -41,12 +48,12 @@ class PlaylistOptionsDialog(
optionsList optionsList
) )
) { _, which -> ) { _, which ->
when (which) { when (optionsList[which]) {
// Clone the playlist to the users Piped account // Clone the playlist to the users Piped account
0 -> { context?.getString(R.string.clonePlaylist) -> {
val token = PreferenceHelper.getToken(requireContext()) val token = PreferenceHelper.getToken(requireContext())
if (token != "") { if (token != "") {
importPlaylist(token!!, playlistId) importPlaylist(token, playlistId)
} else { } else {
Toast.makeText( Toast.makeText(
context, context,
@ -56,11 +63,15 @@ class PlaylistOptionsDialog(
} }
} }
// share the playlist // share the playlist
1 -> { context?.getString(R.string.share) -> {
val shareDialog = ShareDialog(playlistId, true) val shareDialog = ShareDialog(playlistId, true)
// using parentFragmentManager is important here // using parentFragmentManager, childFragmentManager doesn't work here
shareDialog.show(parentFragmentManager, "ShareDialog") shareDialog.show(parentFragmentManager, "ShareDialog")
} }
context?.getString(R.string.deletePlaylist) -> {
val token = PreferenceHelper.getToken(requireContext())
deletePlaylist(playlistId, token)
}
} }
} }
return dialog.show() return dialog.show()
@ -70,7 +81,7 @@ class PlaylistOptionsDialog(
fun run() { fun run() {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val response = try { val response = try {
RetrofitInstance.api.importPlaylist(token, PlaylistId(playlistId)) RetrofitInstance.authApi.importPlaylist(token, PlaylistId(playlistId))
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
return@launch return@launch
@ -82,4 +93,22 @@ class PlaylistOptionsDialog(
} }
run() 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 android.os.Bundle
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.util.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
class ShareDialog( class ShareDialog(

View File

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

View File

@ -6,18 +6,14 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ScrollView
import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.adapters.ChannelAdapter import com.github.libretube.adapters.ChannelAdapter
import com.github.libretube.databinding.FragmentChannelBinding
import com.github.libretube.obj.Subscribe 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.RetrofitInstance
import com.github.libretube.util.formatShort import com.github.libretube.util.formatShort
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
@ -26,19 +22,19 @@ import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class ChannelFragment : Fragment() { class ChannelFragment : Fragment() {
private var channel_id: String? = null
private val TAG = "ChannelFragment" private val TAG = "ChannelFragment"
private lateinit var binding: FragmentChannelBinding
private var channelId: String? = null
var nextPage: String? = null var nextPage: String? = null
var channelAdapter: ChannelAdapter? = null private var channelAdapter: ChannelAdapter? = null
var isLoading = true private var isLoading = true
var isSubscribed: Boolean = false private var isSubscribed: Boolean = false
private var refreshLayout: SwipeRefreshLayout? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
channel_id = it.getString("channel_id") channelId = it.getString("channel_id")
} }
} }
@ -46,43 +42,39 @@ class ChannelFragment : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
// Inflate the layout for this fragment binding = FragmentChannelBinding.inflate(layoutInflater, container, false)
return inflater.inflate(R.layout.fragment_channel, container, false) return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
channel_id = channel_id!!.replace("/channel/", "") channelId = channelId!!.replace("/channel/", "")
view.findViewById<TextView>(R.id.channel_name).text = channel_id binding.channelName.text = channelId
val recyclerView = view.findViewById<RecyclerView>(R.id.channel_recView) binding.channelRecView.layoutManager = LinearLayoutManager(context)
recyclerView.layoutManager = LinearLayoutManager(context)
refreshLayout = view.findViewById(R.id.channel_refresh)
val refreshChannel = { val refreshChannel = {
refreshLayout?.isRefreshing = true binding.channelRefresh.isRefreshing = true
fetchChannel(view) fetchChannel()
val subButton = view.findViewById<MaterialButton>(R.id.channel_subscribe)
if (PreferenceHelper.getToken(requireContext()) != "") { if (PreferenceHelper.getToken(requireContext()) != "") {
isSubscribed(subButton) isSubscribed(binding.channelSubscribe)
} }
} }
refreshChannel() refreshChannel()
refreshLayout?.setOnRefreshListener { binding.channelRefresh.setOnRefreshListener {
refreshChannel() refreshChannel()
} }
val scrollView = view.findViewById<ScrollView>(R.id.channel_scrollView) binding.channelScrollView.viewTreeObserver
scrollView.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom if (binding.channelScrollView.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY) == (binding.channelScrollView.height + binding.channelScrollView.scrollY)
) { ) {
// scroll view is at bottom // scroll view is at bottom
if (nextPage != null && !isLoading) { if (nextPage != null && !isLoading) {
isLoading = true isLoading = true
refreshLayout?.isRefreshing = true binding.channelRefresh.isRefreshing = true
fetchNextPage() fetchNextPage()
} }
} }
@ -95,8 +87,8 @@ class ChannelFragment : Fragment() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val token = PreferenceHelper.getToken(requireContext()) val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.isSubscribed( RetrofitInstance.authApi.isSubscribed(
channel_id!!, channelId!!,
token token
) )
} catch (e: IOException) { } catch (e: IOException) {
@ -135,9 +127,9 @@ class ChannelFragment : Fragment() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val token = PreferenceHelper.getToken(requireContext()) val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.subscribe( RetrofitInstance.authApi.subscribe(
token, token,
Subscribe(channel_id) Subscribe(channelId)
) )
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
@ -158,9 +150,9 @@ class ChannelFragment : Fragment() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val token = PreferenceHelper.getToken(requireContext()) val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.unsubscribe( RetrofitInstance.authApi.unsubscribe(
token, token,
Subscribe(channel_id) Subscribe(channelId)
) )
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
@ -176,55 +168,52 @@ class ChannelFragment : Fragment() {
run() run()
} }
private fun fetchChannel(view: View) { private fun fetchChannel() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getChannel(channel_id!!) RetrofitInstance.api.getChannel(channelId!!)
} catch (e: IOException) { } catch (e: IOException) {
refreshLayout?.isRefreshing = false binding.channelRefresh.isRefreshing = false
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
refreshLayout?.isRefreshing = false binding.channelRefresh.isRefreshing = false
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated return@launchWhenCreated
} }
nextPage = response.nextpage nextPage = response.nextpage
isLoading = false isLoading = false
refreshLayout?.isRefreshing = false binding.channelRefresh.isRefreshing = false
runOnUiThread { runOnUiThread {
view.findViewById<ScrollView>(R.id.channel_scrollView).visibility = View.VISIBLE binding.channelScrollView.visibility = View.VISIBLE
val channelName = view.findViewById<TextView>(R.id.channel_name) binding.channelName.text = response.name
channelName.text = response.name
if (response.verified) { if (response.verified) {
channelName.setCompoundDrawablesWithIntrinsicBounds( binding.channelName.setCompoundDrawablesWithIntrinsicBounds(
0, 0,
0, 0,
R.drawable.ic_verified, R.drawable.ic_verified,
0 0
) )
} }
view.findViewById<TextView>(R.id.channel_subs).text = resources.getString( binding.channelSubs.text = resources.getString(
R.string.subscribers, R.string.subscribers,
response.subscriberCount.formatShort() response.subscriberCount.formatShort()
) )
val channelDescription = view.findViewById<TextView>(R.id.channel_description)
if (response.description?.trim() == "") { if (response.description?.trim() == "") {
channelDescription.visibility = View.GONE binding.channelDescription.visibility = View.GONE
} else { } 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(binding.channelBanner)
Picasso.get().load(response.bannerUrl).into(bannerImage) Picasso.get().load(response.avatarUrl).into(binding.channelImage)
Picasso.get().load(response.avatarUrl).into(channelImage)
channelAdapter = ChannelAdapter( channelAdapter = ChannelAdapter(
response.relatedStreams!!.toMutableList(), response.relatedStreams!!.toMutableList(),
childFragmentManager childFragmentManager
) )
view.findViewById<RecyclerView>(R.id.channel_recView).adapter = channelAdapter binding.channelRecView.adapter = channelAdapter
} }
} }
} }
@ -235,21 +224,21 @@ class ChannelFragment : Fragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getChannelNextPage(channel_id!!, nextPage!!) RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage!!)
} catch (e: IOException) { } catch (e: IOException) {
refreshLayout?.isRefreshing = false binding.channelRefresh.isRefreshing = false
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
refreshLayout?.isRefreshing = false binding.channelRefresh.isRefreshing = false
Log.e(TAG, "HttpException, unexpected response," + e.response()) Log.e(TAG, "HttpException, unexpected response," + e.response())
return@launchWhenCreated return@launchWhenCreated
} }
nextPage = response.nextpage nextPage = response.nextpage
channelAdapter?.updateItems(response.relatedStreams!!) channelAdapter?.updateItems(response.relatedStreams!!)
isLoading = false isLoading = false
refreshLayout?.isRefreshing = false binding.channelRefresh.isRefreshing = false
} }
} }
run() run()
@ -260,13 +249,4 @@ class ChannelFragment : Fragment() {
if (!isAdded) return // Fragment not attached to an Activity if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action) 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.adapters.TrendingAdapter 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 com.github.libretube.util.RetrofitInstance
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class Home : Fragment() { class HomeFragment : Fragment() {
private val TAG = "HomeFragment" private val TAG = "HomeFragment"
private var refreshLayout: SwipeRefreshLayout? = null private lateinit var binding: FragmentHomeBinding
private lateinit var region: String
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
@ -33,36 +33,41 @@ class Home : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
// Inflate the layout for this fragment binding = FragmentHomeBinding.inflate(layoutInflater, container, false)
return inflater.inflate(R.layout.fragment_home, container, false) return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<RecyclerView>(R.id.recview)
val grid = PreferenceHelper.getString( val grid = PreferenceHelper.getString(
requireContext(), requireContext(),
"grid", "grid",
resources.getInteger(R.integer.grid_items).toString() resources.getInteger(R.integer.grid_items).toString()
)!! )!!
recyclerView.layoutManager = GridLayoutManager(view.context, grid.toInt())
val progressbar = view.findViewById<ProgressBar>(R.id.progressBar) val regionPref = PreferenceHelper.getString(requireContext(), "region", "sys")!!
fetchJson(progressbar, recyclerView)
refreshLayout = view.findViewById(R.id.home_refresh) // get the system default country if auto region selected
refreshLayout?.isEnabled = true region = if (regionPref == "sys") {
refreshLayout?.setOnRefreshListener { LocaleHelper
Log.d(TAG, "hmm") .getDetectedCountry(requireContext(), "UK")
fetchJson(progressbar, recyclerView) .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() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val region = PreferenceHelper.getString(requireContext(), "region", "US") RetrofitInstance.api.getTrending(region)
RetrofitInstance.api.getTrending(region!!)
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") 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() Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} finally { } finally {
refreshLayout?.isRefreshing = false binding.homeRefresh.isRefreshing = false
} }
runOnUiThread { runOnUiThread {
progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
recyclerView.adapter = TrendingAdapter(response, childFragmentManager) binding.recview.adapter = TrendingAdapter(response, childFragmentManager)
} }
} }
} }
@ -89,11 +94,4 @@ class Home : Fragment() {
if (!isAdded) return // Fragment not attached to an Activity if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action) 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.adapters.PlaylistsAdapter import com.github.libretube.adapters.PlaylistsAdapter
import com.github.libretube.databinding.FragmentLibraryBinding
import com.github.libretube.dialogs.CreatePlaylistDialog 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.github.libretube.util.RetrofitInstance
import com.google.android.material.floatingactionbutton.FloatingActionButton
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class Library : Fragment() { class LibraryFragment : Fragment() {
private val TAG = "LibraryFragment" private val TAG = "LibraryFragment"
lateinit var token: String lateinit var token: String
private lateinit var playlistRecyclerView: RecyclerView private lateinit var binding: FragmentLibraryBinding
private lateinit var refreshLayout: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -39,51 +35,59 @@ class Library : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
// Inflate the layout for this fragment binding = FragmentLibraryBinding.inflate(layoutInflater, container, false)
return inflater.inflate(R.layout.fragment_library, container, false) return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
playlistRecyclerView = view.findViewById(R.id.playlist_recView) binding.playlistRecView.layoutManager = LinearLayoutManager(view.context)
playlistRecyclerView.layoutManager = LinearLayoutManager(view.context)
token = PreferenceHelper.getToken(requireContext()) 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 != "") { if (token != "") {
view.findViewById<ImageView>(R.id.boogh2).visibility = View.GONE binding.boogh.visibility = View.GONE
view.findViewById<TextView>(R.id.textLike2).visibility = View.GONE binding.textLike.visibility = View.GONE
fetchPlaylists() fetchPlaylists()
refreshLayout.isEnabled = true binding.playlistRefresh.isEnabled = true
refreshLayout.setOnRefreshListener { binding.playlistRefresh.setOnRefreshListener {
fetchPlaylists() fetchPlaylists()
} }
val createPlaylistButton = view.findViewById<FloatingActionButton>(R.id.create_playlist) binding.createPlaylist.setOnClickListener {
createPlaylistButton.setOnClickListener {
val newFragment = CreatePlaylistDialog() val newFragment = CreatePlaylistDialog()
newFragment.show(childFragmentManager, "Create Playlist") newFragment.show(childFragmentManager, "Create Playlist")
} }
} else { } else {
refreshLayout.isEnabled = false binding.playlistRefresh.isEnabled = false
view.findViewById<FloatingActionButton>(R.id.create_playlist).visibility = View.GONE binding.createPlaylist.visibility = View.GONE
} }
} }
override fun onResume() { override fun onResume() {
// optimize CreatePlaylistFab bottom margin if miniPlayer active // optimize CreatePlaylistFab bottom margin if miniPlayer active
val createPlaylistButton = view?.findViewById<FloatingActionButton>(R.id.create_playlist) val layoutParams = binding.createPlaylist.layoutParams as ViewGroup.MarginLayoutParams
val layoutParams = createPlaylistButton?.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.bottomMargin = if (isMiniPlayerVisible) 180 else 64 layoutParams.bottomMargin = if (isMiniPlayerVisible) 180 else 64
createPlaylistButton?.layoutParams = layoutParams binding.createPlaylist.layoutParams = layoutParams
super.onResume() super.onResume()
} }
fun fetchPlaylists() { fun fetchPlaylists() {
fun run() { fun run() {
refreshLayout.isRefreshing = true binding.playlistRefresh.isRefreshing = true
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.playlists(token) RetrofitInstance.authApi.playlists(token)
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG, "IOException, you might not have internet connection") 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() Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} finally { } finally {
refreshLayout.isRefreshing = false binding.playlistRefresh.isRefreshing = false
} }
if (response.isNotEmpty()) { if (response.isNotEmpty()) {
runOnUiThread { runOnUiThread {
view?.findViewById<ImageView>(R.id.boogh2)?.visibility = View.GONE binding.boogh.visibility = View.GONE
view?.findViewById<TextView>(R.id.textLike2)?.visibility = View.GONE binding.textLike.visibility = View.GONE
} }
val playlistsAdapter = PlaylistsAdapter( val playlistsAdapter = PlaylistsAdapter(
response.toMutableList(), response.toMutableList(),
requireActivity() requireActivity()
) )
playlistRecyclerView.adapter = playlistsAdapter binding.playlistRecView.adapter = playlistsAdapter
} else { } else {
runOnUiThread { runOnUiThread {
view?.findViewById<ImageView>(R.id.boogh2).apply { binding.boogh.apply {
this?.visibility = View.VISIBLE visibility = View.VISIBLE
this?.setImageResource(R.drawable.ic_list) setImageResource(R.drawable.ic_list)
} }
view?.findViewById<TextView>(R.id.textLike2).apply { binding.textLike.apply {
this?.visibility = View.VISIBLE visibility = View.VISIBLE
this?.text = getString(R.string.emptyList) text = getString(R.string.emptyList)
} }
} }
} }

View File

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

View File

@ -5,27 +5,27 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.ScrollView
import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.adapters.PlaylistAdapter 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 com.github.libretube.util.RetrofitInstance
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class PlaylistFragment : Fragment() { class PlaylistFragment : Fragment() {
private val TAG = "PlaylistFragment" private val TAG = "PlaylistFragment"
private lateinit var binding: FragmentPlaylistBinding
private var playlistId: String? = null private var playlistId: String? = null
var nextPage: String? = null var nextPage: String? = null
var playlistAdapter: PlaylistAdapter? = null private var playlistAdapter: PlaylistAdapter? = null
var isLoading = true private var isLoading = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
@ -37,24 +37,22 @@ class PlaylistFragment : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
// Inflate the layout for this fragment binding = FragmentPlaylistBinding.inflate(layoutInflater, container, false)
return inflater.inflate(R.layout.fragment_playlist, container, false) return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
playlistId = playlistId!!.replace("/playlist?list=", "") playlistId = playlistId!!.replace("/playlist?list=", "")
val recyclerView = view.findViewById<RecyclerView>(R.id.playlist_recView) binding.playlistRecView.layoutManager = LinearLayoutManager(context)
recyclerView.layoutManager = LinearLayoutManager(context)
val progressBar = view.findViewById<ProgressBar>(R.id.playlist_progress) binding.playlistProgress.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE fetchPlaylist()
fetchPlaylist(view)
} }
private fun fetchPlaylist(view: View) { private fun fetchPlaylist() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
@ -70,16 +68,24 @@ class PlaylistFragment : Fragment() {
nextPage = response.nextpage nextPage = response.nextpage
isLoading = false isLoading = false
runOnUiThread { runOnUiThread {
view.findViewById<ProgressBar>(R.id.playlist_progress).visibility = View.GONE binding.playlistProgress.visibility = View.GONE
view.findViewById<TextView>(R.id.playlist_name).text = response.name binding.playlistName.text = response.name
view.findViewById<TextView>(R.id.playlist_uploader).text = response.uploader binding.playlistUploader.text = response.uploader
view.findViewById<TextView>(R.id.playlist_totVideos).text = binding.playlistTotVideos.text =
getString(R.string.videoCount, response.videos.toString()) getString(R.string.videoCount, response.videos.toString())
val user = PreferenceHelper.getUsername(requireContext()) val user = PreferenceHelper.getUsername(requireContext())
var isOwner = false // check whether the user owns the playlist
if (response.uploaderUrl == null && response.uploader.equals(user, true)) { val isOwner = response.uploaderUrl == null &&
isOwner = true response.uploader.equals(user, true)
// show playlist options
binding.optionsMenu.setOnClickListener {
val optionsDialog =
PlaylistOptionsDialog(playlistId!!, isOwner, requireContext())
optionsDialog.show(childFragmentManager, "PlaylistOptionsDialog")
} }
playlistAdapter = PlaylistAdapter( playlistAdapter = PlaylistAdapter(
response.relatedStreams!!.toMutableList(), response.relatedStreams!!.toMutableList(),
playlistId!!, playlistId!!,
@ -87,12 +93,11 @@ class PlaylistFragment : Fragment() {
requireActivity(), requireActivity(),
childFragmentManager childFragmentManager
) )
view.findViewById<RecyclerView>(R.id.playlist_recView).adapter = playlistAdapter binding.playlistRecView.adapter = playlistAdapter
val scrollView = view.findViewById<ScrollView>(R.id.playlist_scrollview) binding.playlistScrollview.viewTreeObserver
scrollView.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom if (binding.playlistScrollview.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY) == (binding.playlistScrollview.height + binding.playlistScrollview.scrollY)
) { ) {
// scroll view is at bottom // scroll view is at bottom
if (nextPage != null && !isLoading) { 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.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView.GONE import android.widget.TextView.GONE
import android.widget.TextView.OnEditorActionListener import android.widget.TextView.OnEditorActionListener
import android.widget.TextView.VISIBLE import android.widget.TextView.VISIBLE
@ -20,28 +19,26 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.activities.hideKeyboard
import com.github.libretube.adapters.SearchAdapter import com.github.libretube.adapters.SearchAdapter
import com.github.libretube.adapters.SearchHistoryAdapter import com.github.libretube.adapters.SearchHistoryAdapter
import com.github.libretube.adapters.SearchSuggestionsAdapter import com.github.libretube.adapters.SearchSuggestionsAdapter
import com.github.libretube.hideKeyboard import com.github.libretube.databinding.FragmentSearchBinding
import com.github.libretube.util.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
private val TAG = "SearchFragment" private val TAG = "SearchFragment"
private lateinit var binding: FragmentSearchBinding
private var selectedFilter = 0 private var selectedFilter = 0
private var apiSearchFilter = "all" private var apiSearchFilter = "all"
private var nextPage: String? = null 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 searchAdapter: SearchAdapter? = null
private var isLoading: Boolean = true private var isLoading: Boolean = true
private var isFetchingSearch: Boolean = false private var isFetchingSearch: Boolean = false
@ -56,27 +53,21 @@ class SearchFragment : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
// Inflate the layout for this fragment binding = FragmentSearchBinding.inflate(layoutInflater, container, false)
return inflater.inflate(R.layout.fragment_search, container, false) return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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 var tempSelectedItem = 0
clearSearchButton.setOnClickListener { binding.clearSearchImageView.setOnClickListener {
autoTextView.text.clear() binding.autoCompleteTextView.text.clear()
} }
filterImageView.setOnClickListener { binding.filterMenuImageView.setOnClickListener {
val filterOptions = arrayOf( val filterOptions = arrayOf(
getString(R.string.all), getString(R.string.all),
getString(R.string.videos), getString(R.string.videos),
@ -108,7 +99,7 @@ class SearchFragment : Fragment() {
7 -> "music_playlists" 7 -> "music_playlists"
else -> "all" else -> "all"
} }
fetchSearch(autoTextView.text.toString()) fetchSearch(binding.autoCompleteTextView.text.toString())
} }
.setNegativeButton(getString(R.string.cancel), null) .setNegativeButton(getString(R.string.cancel), null)
.create() .create()
@ -116,16 +107,16 @@ class SearchFragment : Fragment() {
} }
// show search history // show search history
historyRecView.layoutManager = LinearLayoutManager(view.context) binding.historyRecycler.layoutManager = LinearLayoutManager(view.context)
showHistory() showHistory()
searchRecView.layoutManager = GridLayoutManager(view.context, 1) binding.searchRecycler.layoutManager = GridLayoutManager(view.context, 1)
autoTextView.requestFocus() binding.autoCompleteTextView.requestFocus()
val imm = val imm =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 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( override fun beforeTextChanged(
s: CharSequence?, s: CharSequence?,
start: Int, start: Int,
@ -136,18 +127,15 @@ class SearchFragment : Fragment() {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (s!! != "") { if (s!! != "") {
searchRecView.adapter = null binding.searchRecycler.adapter = null
searchRecView.viewTreeObserver binding.searchRecycler.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (!searchRecView.canScrollVertically(1)) { if (!binding.searchRecycler.canScrollVertically(1)) {
fetchNextSearchItems(autoTextView.text.toString()) fetchNextSearchItems(binding.autoCompleteTextView.text.toString())
} }
} }
fetchSuggestions(s.toString(), binding.autoCompleteTextView)
GlobalScope.launch {
fetchSuggestions(s.toString(), autoTextView)
}
} }
} }
@ -157,13 +145,13 @@ class SearchFragment : Fragment() {
} }
} }
}) })
autoTextView.setOnEditorActionListener( binding.autoCompleteTextView.setOnEditorActionListener(
OnEditorActionListener { _, actionId, _ -> OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) { if (actionId == EditorInfo.IME_ACTION_SEARCH) {
hideKeyboard() hideKeyboard()
searchRecView.visibility = VISIBLE binding.searchRecycler.visibility = VISIBLE
historyRecView.visibility = GONE binding.historyRecycler.visibility = GONE
fetchSearch(autoTextView.text.toString()) fetchSearch(binding.autoCompleteTextView.text.toString())
return@OnEditorActionListener true return@OnEditorActionListener true
} }
false false
@ -174,8 +162,8 @@ class SearchFragment : Fragment() {
private fun fetchSuggestions(query: String, autoTextView: EditText) { private fun fetchSuggestions(query: String, autoTextView: EditText) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
searchRecView.visibility = GONE binding.searchRecycler.visibility = GONE
historyRecView.visibility = VISIBLE binding.historyRecycler.visibility = VISIBLE
val response = try { val response = try {
RetrofitInstance.api.getSuggestions(query) RetrofitInstance.api.getSuggestions(query)
} catch (e: IOException) { } catch (e: IOException) {
@ -188,13 +176,16 @@ class SearchFragment : Fragment() {
} }
val suggestionsAdapter = val suggestionsAdapter =
SearchSuggestionsAdapter(response, autoTextView, this@SearchFragment) SearchSuggestionsAdapter(response, autoTextView, this@SearchFragment)
historyRecView.adapter = suggestionsAdapter binding.historyRecycler.adapter = suggestionsAdapter
} }
} }
if (!isFetchingSearch) run() if (!isFetchingSearch) run()
} }
fun fetchSearch(query: String) { fun fetchSearch(query: String) {
runOnUiThread {
binding.historyRecycler.visibility = GONE
}
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
isFetchingSearch = true isFetchingSearch = true
hideKeyboard() hideKeyboard()
@ -211,10 +202,9 @@ class SearchFragment : Fragment() {
nextPage = response.nextpage nextPage = response.nextpage
if (response.items!!.isNotEmpty()) { if (response.items!!.isNotEmpty()) {
runOnUiThread { runOnUiThread {
historyRecView.visibility = GONE binding.searchRecycler.visibility = VISIBLE
searchRecView.visibility = VISIBLE
searchAdapter = SearchAdapter(response.items, childFragmentManager) searchAdapter = SearchAdapter(response.items, childFragmentManager)
searchRecView.adapter = searchAdapter binding.searchRecycler.adapter = searchAdapter
} }
} }
addToHistory(query) addToHistory(query)
@ -265,12 +255,17 @@ class SearchFragment : Fragment() {
} }
private fun showHistory() { private fun showHistory() {
searchRecView.visibility = GONE binding.searchRecycler.visibility = GONE
val historyList = PreferenceHelper.getHistory(requireContext()) val historyList = PreferenceHelper.getHistory(requireContext())
if (historyList.isNotEmpty()) { if (historyList.isNotEmpty()) {
historyRecView.adapter = binding.historyRecycler.adapter =
SearchHistoryAdapter(requireContext(), historyList, autoTextView, this) SearchHistoryAdapter(
historyRecView.visibility = VISIBLE 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -17,21 +13,23 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.adapters.SubscriptionAdapter import com.github.libretube.adapters.SubscriptionAdapter
import com.github.libretube.adapters.SubscriptionChannelAdapter 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 com.github.libretube.util.RetrofitInstance
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
class Subscriptions : Fragment() { class SubscriptionsFragment : Fragment() {
val TAG = "SubFragment" val TAG = "SubFragment"
private lateinit var binding: FragmentSubscriptionsBinding
lateinit var token: String lateinit var token: String
var isLoaded = false private var isLoaded = false
private var subscriptionAdapter: SubscriptionAdapter? = null private var subscriptionAdapter: SubscriptionAdapter? = null
private var refreshLayout: SwipeRefreshLayout? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
@ -42,88 +40,76 @@ class Subscriptions : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
// Inflate the layout for this fragment binding = FragmentSubscriptionsBinding.inflate(layoutInflater, container, false)
return inflater.inflate(R.layout.fragment_subscriptions, container, false) return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
token = PreferenceHelper.getToken(requireContext()) token = PreferenceHelper.getToken(requireContext())
refreshLayout = view.findViewById(R.id.sub_refresh)
if (token != "") { if (token != "") {
view.findViewById<RelativeLayout>(R.id.loginOrRegister).visibility = View.GONE binding.loginOrRegister.visibility = View.GONE
refreshLayout?.isEnabled = true binding.subRefresh.isEnabled = true
var progressBar = view.findViewById<ProgressBar>(R.id.sub_progress) binding.subProgress.visibility = View.VISIBLE
progressBar.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( val grid = PreferenceHelper.getString(
requireContext(), requireContext(),
"grid", "grid",
resources.getInteger(R.integer.grid_items).toString() resources.getInteger(R.integer.grid_items).toString()
)!! )!!
feedRecView.layoutManager = GridLayoutManager(view.context, grid.toInt()) binding.subFeed.layoutManager = GridLayoutManager(view.context, grid.toInt())
fetchFeed(feedRecView, progressBar, view) fetchFeed(binding.subFeed, binding.subProgress)
refreshLayout?.setOnRefreshListener { binding.subRefresh.setOnRefreshListener {
fetchChannels(channelRecView) fetchChannels(binding.subChannels)
fetchFeed(feedRecView, progressBar, view) fetchFeed(binding.subFeed, binding.subProgress)
} }
var toggleSubs = view.findViewById<RelativeLayout>(R.id.toggle_subs) binding.toggleSubs.visibility = View.VISIBLE
val arrowImageView = view.findViewById<ImageView>(R.id.toggle)
toggleSubs.visibility = View.VISIBLE
var loadedSubbedChannels = false var loadedSubbedChannels = false
toggleSubs.setOnClickListener { binding.toggleSubs.setOnClickListener {
arrowImageView.animate().rotationBy(180F).setDuration(100).start() binding.toggle.animate().rotationBy(180F).setDuration(100).start()
if (!channelRecView.isVisible) { if (!binding.subChannels.isVisible) {
if (!loadedSubbedChannels) { if (!loadedSubbedChannels) {
channelRecView?.layoutManager = LinearLayoutManager(context) binding.subChannels.layoutManager = LinearLayoutManager(context)
fetchChannels(channelRecView) fetchChannels(binding.subChannels)
loadedSubbedChannels = true loadedSubbedChannels = true
} }
channelRecView.visibility = View.VISIBLE binding.subChannels.visibility = View.VISIBLE
feedRecView.visibility = View.GONE binding.subFeed.visibility = View.GONE
} else { } else {
channelRecView.visibility = View.GONE binding.subChannels.visibility = View.GONE
feedRecView.visibility = View.VISIBLE binding.subFeed.visibility = View.VISIBLE
// toggle button
val image = view.findViewById<ImageView>(R.id.toggle)
image.clearAnimation()
} }
} }
val scrollView = view.findViewById<ScrollView>(R.id.scrollview_sub) binding.scrollviewSub.viewTreeObserver
scrollView.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom if (binding.scrollviewSub.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY) == (binding.scrollviewSub.height + binding.scrollviewSub.scrollY)
) { ) {
// scroll view is at bottom // scroll view is at bottom
if (isLoaded) { if (isLoaded) {
refreshLayout?.isRefreshing = true binding.subRefresh.isRefreshing = true
subscriptionAdapter?.updateItems() subscriptionAdapter?.updateItems()
refreshLayout?.isRefreshing = false binding.subRefresh.isRefreshing = false
} }
} }
} }
} else { } 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() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getFeed(token) RetrofitInstance.authApi.getFeed(token)
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
Log.e(TAG, "IOException, you might not have internet connection") Log.e(TAG, "IOException, you might not have internet connection")
@ -132,7 +118,7 @@ class Subscriptions : Fragment() {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated return@launchWhenCreated
} finally { } finally {
refreshLayout?.isRefreshing = false binding.subRefresh.isRefreshing = false
} }
if (response.isNotEmpty()) { if (response.isNotEmpty()) {
subscriptionAdapter = SubscriptionAdapter(response, childFragmentManager) subscriptionAdapter = SubscriptionAdapter(response, childFragmentManager)
@ -140,16 +126,15 @@ class Subscriptions : Fragment() {
subscriptionAdapter?.updateItems() subscriptionAdapter?.updateItems()
} else { } else {
runOnUiThread { runOnUiThread {
with(view.findViewById<ImageView>(R.id.boogh)) { with(binding.boogh) {
visibility = View.VISIBLE visibility = View.VISIBLE
setImageResource(R.drawable.ic_list) setImageResource(R.drawable.ic_list)
} }
with(view.findViewById<TextView>(R.id.textLike)) { with(binding.textLike) {
visibility = View.VISIBLE visibility = View.VISIBLE
text = getString(R.string.emptyList) text = getString(R.string.emptyList)
} }
view.findViewById<RelativeLayout>(R.id.loginOrRegister) binding.loginOrRegister.visibility = View.VISIBLE
.visibility = View.VISIBLE
} }
} }
progressBar.visibility = View.GONE progressBar.visibility = View.GONE
@ -163,7 +148,7 @@ class Subscriptions : Fragment() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.subscriptions(token) RetrofitInstance.authApi.subscriptions(token)
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, e.toString()) Log.e(TAG, e.toString())
Log.e(TAG, "IOException, you might not have internet connection") Log.e(TAG, "IOException, you might not have internet connection")
@ -172,7 +157,7 @@ class Subscriptions : Fragment() {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated return@launchWhenCreated
} finally { } finally {
refreshLayout?.isRefreshing = false binding.subRefresh.isRefreshing = false
} }
if (response.isNotEmpty()) { if (response.isNotEmpty()) {
channelRecView.adapter = SubscriptionChannelAdapter(response.toMutableList()) channelRecView.adapter = SubscriptionChannelAdapter(response.toMutableList())
@ -184,13 +169,6 @@ class Subscriptions : Fragment() {
run() 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) { private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return this ?: return
if (!isAdded) return // Fragment not attached to an Activity 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 package com.github.libretube.obj
import android.os.Parcel
import android.os.Parcelable
import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@ -28,68 +26,9 @@ data class Streams(
val livestream: Boolean?, val livestream: Boolean?,
val proxyUrl: String?, val proxyUrl: String?,
val chapters: List<ChapterSegment>? 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( constructor() : this(
"", "", "", "", "", "", "", "", "", "", null, -1, -1, -1, -1, emptyList(), emptyList(), "", "", "", "", "", "", "", "", "", "", null, -1, -1, -1, -1, emptyList(), emptyList(),
emptyList(), emptyList(), null, "", 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.fragment.app.Fragment 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.DONATE_URL
import com.github.libretube.GITHUB_URL
import com.github.libretube.PIPED_GITHUB_URL import com.github.libretube.PIPED_GITHUB_URL
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.WEBSITE_URL 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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
class AboutFragment : Fragment() { class AboutFragment : Fragment() {
private lateinit var binding: FragmentAboutBinding
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
return inflater.inflate(R.layout.fragment_about, container, false) binding = FragmentAboutBinding.inflate(layoutInflater)
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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) val settingsActivity = activity as SettingsActivity
website.setOnClickListener { settingsActivity.changeTopBarText(getString(R.string.about))
binding.website.setOnClickListener {
openLinkFromHref(WEBSITE_URL) openLinkFromHref(WEBSITE_URL)
} }
val authors = view.findViewById<LinearLayout>(R.id.authors) binding.website.setOnLongClickListener {
authors.setOnClickListener { val text = context?.getString(R.string.website_summary)!!
openLinkFromHref(AUTHORS_URL) showSnackBar(text)
true
} }
val piped = view.findViewById<LinearLayout>(R.id.piped)
piped.setOnClickListener { binding.piped.setOnClickListener {
openLinkFromHref(PIPED_GITHUB_URL) openLinkFromHref(PIPED_GITHUB_URL)
} }
val donate = view.findViewById<LinearLayout>(R.id.donate) binding.piped.setOnLongClickListener {
donate.setOnClickListener { val text = context?.getString(R.string.piped_summary)!!
showSnackBar(text)
true
}
binding.donate.setOnClickListener {
openLinkFromHref(DONATE_URL) openLinkFromHref(DONATE_URL)
} }
val contributing = view.findViewById<LinearLayout>(R.id.contributing) binding.donate.setOnLongClickListener {
contributing.setOnClickListener { val text = context?.getString(R.string.donate_summary)!!
openLinkFromHref(CONTRIBUTING_URL) 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!!) binding.github.setOnClickListener {
.setPositiveButton(getString(R.string.okay)) { _, _ -> } openLinkFromHref(GITHUB_URL)
.setMessage(licenseHtml) }
.create() binding.github.setOnLongClickListener {
.show() 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) val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent) 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 package com.github.libretube.preferences
import android.os.Bundle import android.os.Bundle
import android.widget.TextView
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.requireMainActivityRestart import com.github.libretube.activities.SettingsActivity
import com.github.libretube.util.PreferenceHelper import com.github.libretube.activities.requireMainActivityRestart
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
class AdvancedSettings : PreferenceFragmentCompat() { class AdvancedSettings : PreferenceFragmentCompat() {
@ -15,15 +14,24 @@ class AdvancedSettings : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.advanced_settings, rootKey) setPreferencesFromResource(R.xml.advanced_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView) val settingsActivity = activity as SettingsActivity
topBarTextView?.text = getString(R.string.advanced) settingsActivity.changeTopBarText(getString(R.string.advanced))
// clear search history
val clearHistory = findPreference<Preference>("clear_history") val clearHistory = findPreference<Preference>("clear_history")
clearHistory?.setOnPreferenceClickListener { clearHistory?.setOnPreferenceClickListener {
PreferenceHelper.removePreference(requireContext(), "search_history") PreferenceHelper.removePreference(requireContext(), "search_history")
true 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") val resetSettings = findPreference<Preference>("reset_settings")
resetSettings?.setOnPreferenceClickListener { resetSettings?.setOnPreferenceClickListener {
showResetDialog() showResetDialog()

View File

@ -1,32 +1,33 @@
package com.github.libretube.preferences package com.github.libretube.preferences
import android.os.Bundle import android.os.Bundle
import android.widget.TextView
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import com.github.libretube.R 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.github.libretube.util.ThemeHelper
import com.google.android.material.color.DynamicColors
class AppearanceSettings : PreferenceFragmentCompat() { class AppearanceSettings : PreferenceFragmentCompat() {
private val TAG = "AppearanceSettings" private val TAG = "AppearanceSettings"
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.appearance_settings, rootKey) setPreferencesFromResource(R.xml.appearance_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView) val settingsActivity = activity as SettingsActivity
topBarTextView?.text = getString(R.string.appearance) settingsActivity.changeTopBarText(getString(R.string.appearance))
val themeToggle = findPreference<ListPreference>("theme_togglee") val themeToggle = findPreference<ListPreference>("theme_togglee")
themeToggle?.setOnPreferenceChangeListener { _, _ -> themeToggle?.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true requireMainActivityRestart = true
ThemeHelper.restartMainActivity(requireContext()) activity?.recreate()
true true
} }
val accentColor = findPreference<Preference>("accent_color") val accentColor = findPreference<ListPreference>("accent_color")
accentColor?.setOnPreferenceChangeListener { _, _ -> updateAccentColorValues(accentColor!!)
accentColor.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true requireMainActivityRestart = true
activity?.recreate() activity?.recreate()
true true
@ -50,4 +51,17 @@ class AppearanceSettings : PreferenceFragmentCompat() {
true 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.Manifest
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
@ -18,12 +18,14 @@ import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.github.libretube.R 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.CustomInstanceDialog
import com.github.libretube.dialogs.DeleteAccountDialog import com.github.libretube.dialogs.DeleteAccountDialog
import com.github.libretube.dialogs.LoginDialog import com.github.libretube.dialogs.LoginDialog
import com.github.libretube.requireMainActivityRestart import com.github.libretube.dialogs.LogoutDialog
import com.github.libretube.util.PreferenceHelper
import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.RetrofitInstance
import org.json.JSONObject import org.json.JSONObject
import org.json.JSONTokener import org.json.JSONTokener
@ -110,20 +112,49 @@ class InstanceSettings : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.instance_settings, rootKey) setPreferencesFromResource(R.xml.instance_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView) val settingsActivity = activity as SettingsActivity
topBarTextView?.text = getString(R.string.instance) settingsActivity.changeTopBarText(getString(R.string.instance))
val instance = findPreference<ListPreference>("selectInstance") val instance = findPreference<ListPreference>("selectInstance")
// fetchInstance() // fetchInstance()
initCustomInstances() initCustomInstances(instance!!)
instance?.setOnPreferenceChangeListener { _, newValue -> instance.setOnPreferenceChangeListener { _, newValue ->
requireMainActivityRestart = true requireMainActivityRestart = true
RetrofitInstance.url = newValue.toString() 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() RetrofitInstance.lazyMgr.reset()
logout() logout()
true 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") val customInstance = findPreference<Preference>("customInstance")
customInstance?.setOnPreferenceClickListener { customInstance?.setOnPreferenceClickListener {
val newFragment = CustomInstanceDialog() val newFragment = CustomInstanceDialog()
@ -134,15 +165,23 @@ class InstanceSettings : PreferenceFragmentCompat() {
val clearCustomInstances = findPreference<Preference>("clearCustomInstances") val clearCustomInstances = findPreference<Preference>("clearCustomInstances")
clearCustomInstances?.setOnPreferenceClickListener { clearCustomInstances?.setOnPreferenceClickListener {
PreferenceHelper.removePreference(requireContext(), "customInstances") PreferenceHelper.removePreference(requireContext(), "customInstances")
activity?.recreate() val intent = Intent(context, SettingsActivity::class.java)
startActivity(intent)
true true
} }
val login = findPreference<Preference>("login_register") val login = findPreference<Preference>("login_register")
val token = PreferenceHelper.getToken(requireContext())
if (token != "") login?.setTitle(R.string.logout)
login?.setOnPreferenceClickListener { login?.setOnPreferenceClickListener {
requireMainActivityRestart = true if (token == "") {
val newFragment = LoginDialog() val newFragment = LoginDialog()
newFragment.show(childFragmentManager, "Login") newFragment.show(childFragmentManager, "Login")
} else {
val newFragment = LogoutDialog()
newFragment.show(childFragmentManager, "Logout")
}
true true
} }
@ -160,58 +199,12 @@ class InstanceSettings : PreferenceFragmentCompat() {
val importFromYt = findPreference<Preference>("import_from_yt") val importFromYt = findPreference<Preference>("import_from_yt")
importFromYt?.setOnPreferenceClickListener { importFromYt?.setOnPreferenceClickListener {
val token = PreferenceHelper.getToken(requireContext()) importSubscriptions()
// 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()
}
}
true true
} }
} }
private fun initCustomInstances() { private fun initCustomInstances(instancePref: ListPreference) {
val customInstances = PreferenceHelper.getCustomInstances(requireContext()) val customInstances = PreferenceHelper.getCustomInstances(requireContext())
var instanceNames = resources.getStringArray(R.array.instances) var instanceNames = resources.getStringArray(R.array.instances)
@ -222,10 +215,9 @@ class InstanceSettings : PreferenceFragmentCompat() {
} }
// add custom instances to the list preference // add custom instances to the list preference
val instance = findPreference<ListPreference>("selectInstance") instancePref.entries = instanceNames
instance?.entries = instanceNames instancePref.entryValues = instanceValues
instance?.entryValues = instanceValues instancePref.summaryProvider =
instance?.summaryProvider =
Preference.SummaryProvider<ListPreference> { preference -> Preference.SummaryProvider<ListPreference> { preference ->
val text = preference.entry val text = preference.entry
if (TextUtils.isEmpty(text)) { if (TextUtils.isEmpty(text)) {
@ -238,6 +230,7 @@ class InstanceSettings : PreferenceFragmentCompat() {
private fun logout() { private fun logout() {
PreferenceHelper.setToken(requireContext(), "") PreferenceHelper.setToken(requireContext(), "")
Toast.makeText(context, getString(R.string.loggedout), Toast.LENGTH_SHORT).show()
} }
private fun fetchInstance() { private fun fetchInstance() {
@ -289,12 +282,62 @@ class InstanceSettings : PreferenceFragmentCompat() {
activity?.runOnUiThread(action) 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>) { private fun subscribe(channels: List<String>) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val token = PreferenceHelper.getToken(requireContext()) val token = PreferenceHelper.getToken(requireContext())
RetrofitInstance.api.importSubscriptions( RetrofitInstance.authApi.importSubscriptions(
false, false,
token, token,
channels channels

View File

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

View File

@ -1,9 +1,9 @@
package com.github.libretube.preferences package com.github.libretube.preferences
import android.os.Bundle import android.os.Bundle
import android.widget.TextView
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.activities.SettingsActivity
class PlayerSettings : PreferenceFragmentCompat() { class PlayerSettings : PreferenceFragmentCompat() {
val TAG = "PlayerSettings" val TAG = "PlayerSettings"
@ -11,7 +11,7 @@ class PlayerSettings : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.player_settings, rootKey) setPreferencesFromResource(R.xml.player_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView) val settingsActivity = activity as SettingsActivity
topBarTextView?.text = getString(R.string.player) 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.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.github.libretube.obj.CustomInstance 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.common.reflect.TypeToken
import com.google.gson.Gson import com.google.gson.Gson
import java.lang.reflect.Type import java.lang.reflect.Type
object PreferenceHelper { object PreferenceHelper {
private val TAG = "PreferenceHelper"
fun setString(context: Context, key: String?, value: String?) { fun setString(context: Context, key: String?, value: String?) {
val editor = getDefaultSharedPreferencesEditor(context) val editor = getDefaultSharedPreferencesEditor(context)
editor.putString(key, value) editor.putString(key, value)
@ -122,6 +127,97 @@ object PreferenceHelper {
editor.putStringSet("search_history", set).apply() 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 { private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context) return PreferenceManager.getDefaultSharedPreferences(context)
} }

View File

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

View File

@ -19,7 +19,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.FFmpegKit
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.util.PreferenceHelper import com.github.libretube.preferences.PreferenceHelper
import java.io.File import java.io.File
var IS_DOWNLOAD_RUNNING = false 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.content.Context
import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.MediaSessionCompat
import com.github.libretube.obj.Streams import com.github.libretube.obj.Streams
import com.github.libretube.util.DescriptionAdapter import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.RetrofitInstance
import com.google.android.exoplayer2.C import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.ui.PlayerNotificationManager
@ -42,7 +43,7 @@ class BackgroundMode {
/** /**
* The [PlayerNotificationManager] to load the [mediaSession] content on it. * 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] * The [AudioAttributes] handle the audio focus of the [player]
@ -63,9 +64,45 @@ class BackgroundMode {
.setAudioAttributes(audioAttributes, true) .setAudioAttributes(audioAttributes, true)
.build() .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) 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. * Initializes the [playerNotification] attached to the [player] and shows it.
*/ */
@ -82,10 +119,12 @@ class BackgroundMode {
) )
) )
.build() .build()
playerNotification.apply { playerNotification?.apply {
setPlayer(player) setPlayer(player)
setUsePreviousAction(false)
setUseNextAction(false) setUseNextAction(false)
setUsePreviousAction(false)
setUseStopAction(true)
setColorized(true)
setMediaSessionToken(mediaSession.sessionToken) setMediaSessionToken(mediaSession.sessionToken)
} }
} }
@ -110,7 +149,11 @@ class BackgroundMode {
/** /**
* Gets the video data and prepares the [player]. * 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 { runBlocking {
val job = launch { val job = launch {
response = RetrofitInstance.api.getStreams(videoId) 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.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory 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.Player
import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.ui.PlayerNotificationManager
import java.net.URL import java.net.URL
@ -83,8 +83,8 @@ class DescriptionAdapter(
return try { return try {
val resizedBitmap = Bitmap.createScaledBitmap( val resizedBitmap = Bitmap.createScaledBitmap(
bitmap, bitmap,
1080, bitmap.width,
1080, bitmap.width,
false false
) )
resizedBitmap resizedBitmap

View File

@ -2,25 +2,28 @@ package com.github.libretube.util
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.telephony.TelephonyManager
import com.github.libretube.preferences.PreferenceHelper
import java.util.* import java.util.*
object LocaleHelper { object LocaleHelper {
fun updateLanguage(context: Context) { fun updateLanguage(context: Context) {
val languageName = PreferenceHelper.getString(context, "language", "en") val languageName = PreferenceHelper.getString(context, "language", "sys")
if (languageName != "") { if (languageName == "sys") updateLocaleConf(context, Locale.getDefault())
setLanguage(context, languageName!!) 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) { private fun updateLocaleConf(context: Context, locale: Locale) {
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()
}
// Change API Language // Change API Language
Locale.setDefault(locale) Locale.setDefault(locale)
@ -35,4 +38,51 @@ object LocaleHelper {
} }
res.updateConfiguration(conf, dm) 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 java.util.*
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
class ResettableLazyManager { class ResettableLazyManager {
// we synchronize to make sure the timing of a reset() call and new inits do not collide // 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) { fun register(managed: Resettable) {
synchronized(managedDelegates) { synchronized(managedDelegates) {
@ -38,7 +38,7 @@ class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()
lazyHolder = makeInitBlock() lazyHolder = makeInitBlock()
} }
fun makeInitBlock(): Lazy<PROPTYPE> { private fun makeInitBlock(): Lazy<PROPTYPE> {
return lazy { return lazy {
manager.register(this) manager.register(this)
init() init()

View File

@ -1,12 +1,11 @@
package com.github.libretube.util package com.github.libretube.util
import com.github.libretube.resettableLazy
import com.github.libretube.resettableManager
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory import retrofit2.converter.jackson.JacksonConverterFactory
object RetrofitInstance { object RetrofitInstance {
lateinit var url: String lateinit var url: String
lateinit var authUrl: String
val lazyMgr = resettableManager() val lazyMgr = resettableManager()
val api: PipedApi by resettableLazy(lazyMgr) { val api: PipedApi by resettableLazy(lazyMgr) {
Retrofit.Builder() Retrofit.Builder()
@ -15,4 +14,11 @@ object RetrofitInstance {
.build() .build()
.create(PipedApi::class.java) .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.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.text.Spanned
import android.util.TypedValue
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.text.HtmlCompat
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.preferences.PreferenceHelper
object ThemeHelper { object ThemeHelper {
@ -18,7 +22,7 @@ object ThemeHelper {
private fun updateAccentColor(context: Context) { private fun updateAccentColor(context: Context) {
when (PreferenceHelper.getString(context, "accent_color", "purple")) { 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) "red" -> context.setTheme(R.style.Theme_Red)
"blue" -> context.setTheme(R.style.Theme_Blue) "blue" -> context.setTheme(R.style.Theme_Blue)
"yellow" -> context.setTheme(R.style.Theme_Yellow) "yellow" -> context.setTheme(R.style.Theme_Yellow)
@ -69,4 +73,19 @@ object ThemeHelper {
intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent) 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.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.google.android.exoplayer2.ui.StyledPlayerView import com.google.android.exoplayer2.ui.StyledPlayerView
internal class CustomExoPlayerView( internal class CustomExoPlayerView(
context: Context, context: Context,
attributeSet: AttributeSet? = null attributeSet: AttributeSet? = null
) : StyledPlayerView(context, attributeSet) { ) : StyledPlayerView(context, attributeSet) {
val binding: ExoStyledPlayerControlViewBinding = ExoStyledPlayerControlViewBinding.bind(this)
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean { 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.content.Context
import android.util.AttributeSet 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.content.Context
import android.graphics.Rect 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" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="@android:color/white" android:tint="?android:attr/colorControlNormal"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <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 <ImageView
android:id="@+id/noInternet_settingsImageView" android:id="@+id/noInternet_settingsImageView"
android:layout_width="30dp" android:layout_width="33dp"
android:layout_height="30dp" android:layout_height="33dp"
android:layout_margin="16dp" android:layout_margin="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="3dp"
android:src="@drawable/ic_settings" android:src="@drawable/ic_settings"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginLeft="10dp" android:layout_marginStart="10dp"
android:layout_toEndOf="@id/subscription_channel_image" android:layout_toEndOf="@id/subscription_channel_image"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:text="Channel Name" android:text="Channel Name"
android:textSize="16dp" /> android:textSize="16sp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/subscription_subscribe" android:id="@+id/subscription_subscribe"
@ -35,7 +35,7 @@
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:backgroundTint="?attr/colorOnPrimary" android:backgroundTint="?attr/colorOnPrimary"
android:text="@string/unsubscribe" android:text="@string/unsubscribe"
android:textColor="?android:attr/textColorPrimary" android:textColor="@android:color/white"
android:textSize="11dp" android:textSize="11sp"
app:cornerRadius="20dp" /> app:cornerRadius="20dp" />
</RelativeLayout> </RelativeLayout>

View File

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

View File

@ -8,12 +8,16 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="16dp" android:layout_marginBottom="16dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -106,8 +110,7 @@
android:id="@+id/replies_recView" android:id="@+id/replies_recView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp" android:background="@null"
android:layout_marginBottom="10dp"
android:nestedScrollingEnabled="false" /> android:nestedScrollingEnabled="false" />
</LinearLayout> </LinearLayout>

View File

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

View File

@ -1,26 +1,7 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright 2020 The Android Open Source Project <?xml version="1.0" encoding="utf-8"?>
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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android" <merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> 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 <View
android:id="@id/exo_controls_background" android:id="@id/exo_controls_background"
android:layout_width="0dp" android:layout_width="0dp"
@ -28,7 +9,7 @@
android:background="@color/exo_black_opacity_60" /> android:background="@color/exo_black_opacity_60" />
<LinearLayout <LinearLayout
android:id="@+id/top_bar" android:id="@+id/exo_top_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_bottom_bar_height" android:layout_height="@dimen/exo_styled_bottom_bar_height"
android:layout_gravity="top" android:layout_gravity="top"
@ -49,11 +30,10 @@
android:id="@+id/close_imageButton" android:id="@+id/close_imageButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginRight="10dp" android:layout_marginRight="5dp"
android:background="#00FFFFFF" android:background="#00FFFFFF"
android:padding="@dimen/exo_icon_padding" android:padding="@dimen/exo_icon_padding"
android:src="@drawable/ic_close" android:src="@drawable/ic_close"
android:visibility="gone"
app:tint="@android:color/white" /> app:tint="@android:color/white" />
<ImageButton <ImageButton
@ -110,7 +90,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:padding="@dimen/exo_icon_padding" android:padding="@dimen/exo_icon_padding"
android:text="HLS" android:text="@string/hls"
android:textColor="#FFFFFF" /> android:textColor="#FFFFFF" />
<ImageView <ImageView
@ -132,7 +112,6 @@
android:layout_height="@dimen/exo_styled_bottom_bar_height" android:layout_height="@dimen/exo_styled_bottom_bar_height"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top" android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
android:background="@color/exo_bottom_bar_background"
android:layoutDirection="ltr"> android:layoutDirection="ltr">
<LinearLayout <LinearLayout
@ -182,7 +161,8 @@
<ImageButton <ImageButton
android:id="@+id/fullscreen" android:id="@+id/fullscreen"
style="@style/ExoStyledControls.Button.Bottom.FullScreen" style="@style/ExoStyledControls.Button.Bottom.FullScreen"
android:src="@drawable/ic_fullscreen" /> android:src="@drawable/ic_fullscreen"
app:tint="@android:color/white" />
</LinearLayout> </LinearLayout>
@ -190,7 +170,6 @@
android:id="@+id/progress_bar" android:id="@+id/progress_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="15dp" android:layout_height="15dp"
android:layout_above="@id/exo_basic_controls"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginRight="10dp" android:layout_marginRight="10dp"
@ -200,7 +179,6 @@
android:id="@id/exo_progress" android:id="@id/exo_progress"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="15dp" android:layout_height="15dp"
android:background="@color/exo_bottom_bar_background"
app:bar_height="2dp" app:bar_height="2dp"
app:played_color="?attr/colorOnSecondary" app:played_color="?attr/colorOnSecondary"
app:scrubber_color="?attr/colorOnPrimary" app:scrubber_color="?attr/colorOnPrimary"
@ -233,25 +211,11 @@
android:gravity="center" android:gravity="center"
android:padding="@dimen/exo_styled_controls_padding"> 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 <ImageButton
android:id="@id/exo_play_pause" android:id="@id/exo_play_pause"
style="@style/ExoStyledControls.Button.Center.PlayPause" style="@style/ExoStyledControls.Button.Center.PlayPause"
android:scaleX="1.1" android:background="?android:selectableItemBackgroundBorderless"
android:scaleY="1.1" /> app:tint="@android:color/white" />
<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" />
</LinearLayout> </LinearLayout>

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/playerMotionLayout" android:id="@+id/playerMotionLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layoutDescription="@xml/player_scene" app:layoutDescription="@xml/player_scene"
tools:context=".PlayerFragment"> tools:context=".fragments.PlayerFragment">
<ScrollView <ScrollView
android:id="@+id/player_scrollView" android:id="@+id/player_scrollView"
@ -193,13 +193,13 @@
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="25dp" android:layout_height="25dp"
android:padding="2dp" android:padding="2dp"
android:src="@drawable/ic_vlc" /> android:src="@drawable/ic_player" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="VLC" /> android:text="@string/open" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@ -281,7 +281,10 @@
style="@style/Widget.Material3.CardView.Elevated" style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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"> app:cardCornerRadius="18dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -319,14 +322,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/comments_toggle" android:layout_below="@id/comments_toggle"
android:layout_marginLeft="20dp" android:layout_marginTop="10dp"
android:layout_marginTop="20dp" android:layout_marginBottom="10dp"
android:layout_marginRight="20dp"
android:nestedScrollingEnabled="false" android:nestedScrollingEnabled="false"
android:visibility="gone" /> android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/player_recView" android:id="@+id/related_rec_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/comments_recView" android:layout_below="@id/comments_recView"
@ -347,21 +349,59 @@
android:layout_height="0dp" android:layout_height="0dp"
android:background="?attr/colorSurface" android:background="?attr/colorSurface"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.35" app:layout_constraintHeight_percent="0.3"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.github.libretube.util.CustomExoPlayerView <com.github.libretube.views.CustomExoPlayerView
android:id="@+id/player" android:id="@+id/player"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:background="#000000" android:background="?attr/colorSurface"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="@id/main_container" app:layout_constraintBottom_toBottomOf="@id/main_container"
app:layout_constraintStart_toStartOf="@id/main_container" app:layout_constraintStart_toStartOf="@id/main_container"
app:layout_constraintTop_toTopOf="@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 <ImageView
android:id="@+id/close_imageView" android:id="@+id/close_imageView"
@ -402,4 +442,4 @@
app:layout_constraintStart_toEndOf="@+id/player" app:layout_constraintStart_toEndOf="@+id/player"
app:layout_constraintTop_toTopOf="@+id/play_imageView" /> 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:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<TextView <LinearLayout
android:id="@+id/playlist_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp" android:orientation="horizontal"
android:text="" android:padding="8dp">
android:textSize="24sp"
android:textStyle="bold" /> <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 <TextView
android:id="@+id/playlist_uploader" android:id="@+id/playlist_uploader"

View File

@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragments.Subscriptions"> tools:context=".fragments.SubscriptionsFragment">
<ProgressBar <ProgressBar
android:id="@+id/sub_progress" android:id="@+id/sub_progress"
@ -33,13 +33,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/boogh" android:layout_below="@id/boogh"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginHorizontal="10dp"
android:gravity="center" android:gravity="center"
android:text="@string/please_login" android:text="@string/please_login"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
</RelativeLayout> </RelativeLayout>
<com.github.libretube.util.CustomSwipeToRefresh <com.github.libretube.views.CustomSwipeToRefresh
android:id="@+id/sub_refresh" android:id="@+id/sub_refresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -101,5 +102,5 @@
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</com.github.libretube.util.CustomSwipeToRefresh> </com.github.libretube.views.CustomSwipeToRefresh>
</RelativeLayout> </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" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:background="?android:attr/selectableItemBackground">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

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