Merge pull request #253 from Bnyro/theming

Accent colors and OLED Mode
This commit is contained in:
Farbod 2022-05-20 16:44:57 +04:30 committed by GitHub
commit 45f87649f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1186 additions and 984 deletions

View File

@ -1,13 +1,11 @@
package com.github.libretube package com.github.libretube
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.Assert.*
/** /**
* Instrumented test, which will execute on an Android device. * Instrumented test, which will execute on an Android device.
* *

View File

@ -14,7 +14,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_libretube_round" android:roundIcon="@mipmap/ic_libretube_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.LibreTube" android:theme="@style/Theme.MY"
android:name=".myApp" android:name=".myApp"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:largeHeap="true" android:largeHeap="true"

View File

@ -12,7 +12,6 @@ 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.adapters.PlaylistsAdapter
import com.github.libretube.obj.PlaylistId import com.github.libretube.obj.PlaylistId
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException import retrofit2.HttpException
@ -29,14 +28,14 @@ class AddtoPlaylistDialog : DialogFragment() {
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; val inflater = requireActivity().layoutInflater
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
token = sharedPref?.getString("token","")!! token = sharedPref?.getString("token", "")!!
var view: View = inflater.inflate(R.layout.dialog_addtoplaylist, null) var view: View = inflater.inflate(R.layout.dialog_addtoplaylist, null)
spinner = view.findViewById(R.id.playlists_spinner) spinner = view.findViewById(R.id.playlists_spinner)
button = view.findViewById(R.id.addToPlaylist) button = view.findViewById(R.id.addToPlaylist)
if(token!=""){ if (token != "") {
fetchPlaylists() fetchPlaylists()
} }
val typedValue = TypedValue() val typedValue = TypedValue()
this.requireActivity().theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true) this.requireActivity().theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true)
@ -51,64 +50,61 @@ class AddtoPlaylistDialog : DialogFragment() {
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
private fun fetchPlaylists(){ private fun fetchPlaylists() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.playlists(token) RetrofitInstance.api.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")
Toast.makeText(context,R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
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
} }
if (response.isNotEmpty()){ if (response.isNotEmpty()) {
var names = emptyList<String>().toMutableList() var names = emptyList<String>().toMutableList()
for(playlist in response){ for (playlist in response) {
names.add(playlist.name!!) names.add(playlist.name!!)
} }
val arrayAdapter = ArrayAdapter(requireContext(),android.R.layout.simple_spinner_item,names) val arrayAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, names)
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = arrayAdapter spinner.adapter = arrayAdapter
runOnUiThread { runOnUiThread {
button.setOnClickListener { button.setOnClickListener {
addToPlaylist(response[spinner.selectedItemPosition].id!!) addToPlaylist(response[spinner.selectedItemPosition].id!!)
} }
} }
}else{ } else {
} }
} }
} }
run() run()
} }
private fun addToPlaylist(playlistId: String){ private fun addToPlaylist(playlistId: String) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.addToPlaylist(token, PlaylistId(playlistId, videoId)) RetrofitInstance.api.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")
Toast.makeText(context,R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
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
} }
if (response.message == "ok"){ if (response.message == "ok") {
Toast.makeText(context,R.string.success, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
dialog?.dismiss() dialog?.dismiss()
}else{ } else {
Toast.makeText(context,R.string.fail, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.fail, Toast.LENGTH_SHORT).show()
} }
} }
} }
run() run()

View File

@ -1,13 +1,10 @@
package com.github.libretube package com.github.libretube
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils.substring import android.text.TextUtils.substring
import android.util.Log import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -23,18 +20,17 @@ import com.github.libretube.adapters.ChannelAdapter
import com.github.libretube.obj.Subscribe import com.github.libretube.obj.Subscribe
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import retrofit2.HttpException
class ChannelFragment : Fragment() { class ChannelFragment : Fragment() {
private var channel_id: String? = null private var channel_id: String? = null
private val TAG = "ChannelFragment" private val TAG = "ChannelFragment"
var nextPage: String? =null var nextPage: String? = null
var channelAdapter: ChannelAdapter? = null var channelAdapter: ChannelAdapter? = null
var isLoading = true var isLoading = true
var isSubscribed: Boolean =false var isSubscribed: Boolean = false
private var refreshLayout: SwipeRefreshLayout? = null private var refreshLayout: SwipeRefreshLayout? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -45,20 +41,19 @@ class ChannelFragment : Fragment() {
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
// Inflate the layout for this fragment // Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_channel, container, false) return inflater.inflate(R.layout.fragment_channel, container, false)
} }
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/","") channel_id = channel_id!!.replace("/channel/", "")
view.findViewById<TextView>(R.id.channel_name).text=channel_id view.findViewById<TextView>(R.id.channel_name).text = channel_id
val recyclerView = view.findViewById<RecyclerView>(R.id.channel_recView) val recyclerView = view.findViewById<RecyclerView>(R.id.channel_recView)
recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.layoutManager = LinearLayoutManager(context)
refreshLayout = view.findViewById(R.id.channel_refresh) refreshLayout = view.findViewById(R.id.channel_refresh)
@ -68,7 +63,7 @@ class ChannelFragment : Fragment() {
fetchChannel(view) fetchChannel(view)
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
val subButton = view.findViewById<MaterialButton>(R.id.channel_subscribe) val subButton = view.findViewById<MaterialButton>(R.id.channel_subscribe)
if (sharedPref?.getString("token","") != "") { if (sharedPref?.getString("token", "") != "") {
isSubscribed(subButton) isSubscribed(subButton)
} }
} }
@ -81,28 +76,26 @@ class ChannelFragment : Fragment() {
scrollView.viewTreeObserver scrollView.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom if (scrollView.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY)) { == (scrollView.height + scrollView.scrollY)
//scroll view is at bottom ) {
if(nextPage!=null && !isLoading){ // scroll view is at bottom
isLoading=true if (nextPage != null && !isLoading) {
refreshLayout?.isRefreshing = true; isLoading = true
refreshLayout?.isRefreshing = true
fetchNextPage() fetchNextPage()
} }
} }
} }
} }
private fun isSubscribed(button: MaterialButton){ private fun isSubscribed(button: MaterialButton) {
@SuppressLint("ResourceAsColor") @SuppressLint("ResourceAsColor")
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
RetrofitInstance.api.isSubscribed(channel_id!!,sharedPref?.getString("token","")!!) RetrofitInstance.api.isSubscribed(channel_id!!, sharedPref?.getString("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")
return@launchWhenCreated return@launchWhenCreated
@ -112,33 +105,34 @@ class ChannelFragment : Fragment() {
} }
runOnUiThread { runOnUiThread {
if (response.subscribed==true){ if (response.subscribed == true) {
isSubscribed=true isSubscribed = true
button.text=getString(R.string.unsubscribe) button.text = getString(R.string.unsubscribe)
} }
if(response.subscribed!=null){ if (response.subscribed != null) {
button.setOnClickListener { button.setOnClickListener {
if(isSubscribed){ if (isSubscribed) {
unsubscribe() unsubscribe()
button.text=getString(R.string.subscribe) button.text = getString(R.string.subscribe)
}else{ } else {
subscribe() subscribe()
button.text=getString(R.string.unsubscribe) button.text = getString(R.string.unsubscribe)
}
} }
}} }
} }
} }
} }
run() run()
} }
private fun subscribe(){ private fun subscribe() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
RetrofitInstance.api.subscribe(sharedPref?.getString("token","")!!, Subscribe(channel_id)) RetrofitInstance.api.subscribe(sharedPref?.getString("token", "")!!, Subscribe(channel_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")
return@launchWhenCreated return@launchWhenCreated
@ -146,18 +140,18 @@ class ChannelFragment : Fragment() {
Log.e(TAG, "HttpException, unexpected response$e") Log.e(TAG, "HttpException, unexpected response$e")
return@launchWhenCreated return@launchWhenCreated
} }
isSubscribed=true isSubscribed = true
} }
} }
run() run()
} }
private fun unsubscribe(){ private fun unsubscribe() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
RetrofitInstance.api.unsubscribe(sharedPref?.getString("token","")!!, Subscribe(channel_id)) RetrofitInstance.api.unsubscribe(sharedPref?.getString("token", "")!!, Subscribe(channel_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")
return@launchWhenCreated return@launchWhenCreated
@ -165,37 +159,37 @@ class ChannelFragment : Fragment() {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
return@launchWhenCreated return@launchWhenCreated
} }
isSubscribed=false isSubscribed = false
} }
} }
run() run()
} }
private fun fetchChannel(view: View){ private fun fetchChannel(view: View) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getChannel(channel_id!!) RetrofitInstance.api.getChannel(channel_id!!)
}catch(e: IOException) { } catch (e: IOException) {
refreshLayout?.isRefreshing = false; refreshLayout?.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; refreshLayout?.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; refreshLayout?.isRefreshing = false
runOnUiThread { runOnUiThread {
view.findViewById<ScrollView>(R.id.channel_scrollView).visibility = View.VISIBLE view.findViewById<ScrollView>(R.id.channel_scrollView).visibility = View.VISIBLE
val channelName = view.findViewById<TextView>(R.id.channel_name) val channelName = view.findViewById<TextView>(R.id.channel_name)
channelName.text = if (response.name?.length!! > 18) response.name.toString().substring(0,16) + "..." else response.name channelName.text = if (response.name?.length!! > 18) response.name.toString().substring(0, 16) + "..." else response.name
val channelVerified = view.findViewById<ImageView>(R.id.channel_verified) val channelVerified = view.findViewById<ImageView>(R.id.channel_verified)
if (response.verified) channelVerified.visibility = View.VISIBLE if (response.verified) channelVerified.visibility = View.VISIBLE
view.findViewById<TextView>(R.id.channel_subs).text=resources.getString(R.string.subscribers, response.subscriberCount.formatShort()) view.findViewById<TextView>(R.id.channel_subs).text = resources.getString(R.string.subscribers, response.subscriberCount.formatShort())
val channelDescription = view.findViewById<TextView>(R.id.channel_description) val channelDescription = view.findViewById<TextView>(R.id.channel_description)
if (response.description?.trim() == "") channelDescription.visibility = View.GONE else channelDescription.text = response.description?.trim() if (response.description?.trim() == "") channelDescription.visibility = View.GONE else channelDescription.text = response.description?.trim()
val bannerImage = view.findViewById<ImageView>(R.id.channel_banner) val bannerImage = view.findViewById<ImageView>(R.id.channel_banner)
@ -204,18 +198,17 @@ class ChannelFragment : Fragment() {
Picasso.get().load(response.avatarUrl).into(channelImage) Picasso.get().load(response.avatarUrl).into(channelImage)
channelAdapter = ChannelAdapter(response.relatedStreams!!.toMutableList()) channelAdapter = ChannelAdapter(response.relatedStreams!!.toMutableList())
view.findViewById<RecyclerView>(R.id.channel_recView).adapter = channelAdapter view.findViewById<RecyclerView>(R.id.channel_recView).adapter = channelAdapter
} }
} }
} }
run() run()
} }
private fun fetchNextPage(){ private fun fetchNextPage() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getChannelNextPage(channel_id!!,nextPage!!) RetrofitInstance.api.getChannelNextPage(channel_id!!, nextPage!!)
} catch (e: IOException) { } catch (e: IOException) {
refreshLayout?.isRefreshing = false refreshLayout?.isRefreshing = false
println(e) println(e)
@ -223,12 +216,12 @@ class ChannelFragment : Fragment() {
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
refreshLayout?.isRefreshing = false refreshLayout?.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 refreshLayout?.isRefreshing = false
} }
} }
@ -244,8 +237,8 @@ class ChannelFragment : Fragment() {
val scrollView = view?.findViewById<ScrollView>(R.id.channel_scrollView) val scrollView = view?.findViewById<ScrollView>(R.id.channel_scrollView)
scrollView?.viewTreeObserver?.removeOnScrollChangedListener { scrollView?.viewTreeObserver?.removeOnScrollChangedListener {
} }
channelAdapter=null channelAdapter = null
view?.findViewById<RecyclerView>(R.id.channel_recView)?.adapter=null view?.findViewById<RecyclerView>(R.id.channel_recView)?.adapter = null
super.onDestroyView() super.onDestroyView()
} }
} }

View File

@ -39,11 +39,10 @@ class CreatePlaylistDialog : DialogFragment() {
val createPlaylistBtn = rootView.findViewById<Button>(R.id.create_new_playlist) val createPlaylistBtn = rootView.findViewById<Button>(R.id.create_new_playlist)
createPlaylistBtn.setOnClickListener { createPlaylistBtn.setOnClickListener {
var listName = playlistName.text.toString() var listName = playlistName.text.toString()
if(listName != "") { if (listName != "") {
setFragmentResult("key_parent", bundleOf("playlistName" to "$listName")) setFragmentResult("key_parent", bundleOf("playlistName" to "$listName"))
dismiss() dismiss()
} } else {
else {
Toast.makeText(context, R.string.emptyPlaylistName, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.emptyPlaylistName, Toast.LENGTH_LONG).show()
} }
} }

View File

@ -4,12 +4,11 @@ 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.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.ui.StyledPlayerControlView
import com.google.android.exoplayer2.ui.StyledPlayerView import com.google.android.exoplayer2.ui.StyledPlayerView
internal class CustomExoPlayerView( internal class CustomExoPlayerView(
context: Context, attributeSet: AttributeSet? = null context: Context,
attributeSet: AttributeSet? = null
) : StyledPlayerView(context, attributeSet) { ) : StyledPlayerView(context, attributeSet) {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@ -18,7 +17,7 @@ internal class CustomExoPlayerView(
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
if (isControllerFullyVisible) { if (isControllerFullyVisible) {
hideController() hideController()
}else { } else {
showController() showController()
} }
} }

View File

@ -12,7 +12,6 @@ import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
class DownloadDialog : DialogFragment() { class DownloadDialog : DialogFragment() {
private val TAG = "DownloadDialog" private val TAG = "DownloadDialog"
var vidName = arrayListOf<String>() var vidName = arrayListOf<String>()
@ -37,9 +36,9 @@ class DownloadDialog : DialogFragment() {
val inflater = requireActivity().layoutInflater val inflater = requireActivity().layoutInflater
var view: View = inflater.inflate(R.layout.dialog_download, null) var view: View = inflater.inflate(R.layout.dialog_download, null)
val videoSpinner = view.findViewById<Spinner>(R.id.video_spinner) val videoSpinner = view.findViewById<Spinner>(R.id.video_spinner)
val videoArrayAdapter =ArrayAdapter<String>(requireContext(),android.R.layout.simple_spinner_item,vidName) val videoArrayAdapter = ArrayAdapter<String>(requireContext(), android.R.layout.simple_spinner_item, vidName)
videoArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) videoArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
videoSpinner.adapter=videoArrayAdapter videoSpinner.adapter = videoArrayAdapter
videoSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { videoSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected( override fun onItemSelected(
parent: AdapterView<*>, parent: AdapterView<*>,
@ -48,12 +47,12 @@ class DownloadDialog : DialogFragment() {
id: Long id: Long
) { ) {
selectedVideo = position selectedVideo = position
Log.d(TAG,selectedVideo.toString()) Log.d(TAG, selectedVideo.toString())
} }
override fun onNothingSelected(parent: AdapterView<*>?) {} override fun onNothingSelected(parent: AdapterView<*>?) {}
} }
val audioSpinner = view.findViewById<Spinner>(R.id.audio_spinner) val audioSpinner = view.findViewById<Spinner>(R.id.audio_spinner)
val audioArrayAdapter = ArrayAdapter<String>(requireContext(),android.R.layout.simple_spinner_item,audioName) val audioArrayAdapter = ArrayAdapter<String>(requireContext(), android.R.layout.simple_spinner_item, audioName)
audioArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) audioArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
audioSpinner.adapter = audioArrayAdapter audioSpinner.adapter = audioArrayAdapter
audioSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { audioSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
@ -64,7 +63,7 @@ class DownloadDialog : DialogFragment() {
id: Long id: Long
) { ) {
selectedAudio = position selectedAudio = position
Log.d(TAG,selectedAudio.toString()) Log.d(TAG, selectedAudio.toString())
} }
override fun onNothingSelected(parent: AdapterView<*>?) {} override fun onNothingSelected(parent: AdapterView<*>?) {}
} }
@ -72,16 +71,16 @@ class DownloadDialog : DialogFragment() {
radioGroup.setOnCheckedChangeListener { group, checkedId -> radioGroup.setOnCheckedChangeListener { group, checkedId ->
val radio: RadioButton = view.findViewById(checkedId) val radio: RadioButton = view.findViewById(checkedId)
extension = radio.text.toString() extension = radio.text.toString()
Log.d(TAG,extension) Log.d(TAG, extension)
} }
view.findViewById<Button>(R.id.download).setOnClickListener { view.findViewById<Button>(R.id.download).setOnClickListener {
val intent = Intent(context,DownloadService::class.java) val intent = Intent(context, DownloadService::class.java)
intent.putExtra("videoId",videoId) intent.putExtra("videoId", videoId)
intent.putExtra("videoUrl",vidUrl[selectedVideo]) intent.putExtra("videoUrl", vidUrl[selectedVideo])
intent.putExtra("audioUrl",audioUrl[selectedAudio]) intent.putExtra("audioUrl", audioUrl[selectedAudio])
intent.putExtra("duration",duration) intent.putExtra("duration", duration)
intent.putExtra("extension",extension) intent.putExtra("extension", extension)
//intent.putExtra("command","-y -i ${response.videoStreams[which].url} -i ${response.audioStreams!![0].url} -c copy ${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/${videoId}.mkv") // intent.putExtra("command","-y -i ${response.videoStreams[which].url} -i ${response.audioStreams!![0].url} -c copy ${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/${videoId}.mkv")
context?.startService(intent) context?.startService(intent)
dismiss() dismiss()
} }

View File

@ -16,17 +16,16 @@ import androidx.core.app.NotificationManagerCompat
import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.FFmpegKit
import java.io.File import java.io.File
var IS_DOWNLOAD_RUNNING = false var IS_DOWNLOAD_RUNNING = false
class DownloadService : Service(){ class DownloadService : Service() {
val TAG = "DownloadService" val TAG = "DownloadService"
private var downloadId: Long =-1 private var downloadId: Long = -1
private lateinit var videoId: String private lateinit var videoId: String
private lateinit var videoUrl: String private lateinit var videoUrl: String
private lateinit var audioUrl: String private lateinit var audioUrl: String
private lateinit var extension: String private lateinit var extension: String
private var duration: Int = 0 private var duration: Int = 0
//private lateinit var command: String // private lateinit var command: String
private lateinit var audioDir: File private lateinit var audioDir: File
private lateinit var videoDir: File private lateinit var videoDir: File
lateinit var service: NotificationManager lateinit var service: NotificationManager
@ -41,29 +40,31 @@ class DownloadService : Service(){
videoUrl = intent.getStringExtra("videoUrl")!! videoUrl = intent.getStringExtra("videoUrl")!!
audioUrl = intent.getStringExtra("audioUrl")!! audioUrl = intent.getStringExtra("audioUrl")!!
extension = intent.getStringExtra("extension")!! extension = intent.getStringExtra("extension")!!
//command = intent.getStringExtra("command")!! // command = intent.getStringExtra("command")!!
duration = intent.getIntExtra("duration",1) duration = intent.getIntExtra("duration", 1)
service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channelId = val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val chan = NotificationChannel("service", val chan = NotificationChannel(
"DownloadService", NotificationManager.IMPORTANCE_NONE) "service",
chan.lightColor = Color.BLUE "DownloadService", NotificationManager.IMPORTANCE_NONE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE )
service.createNotificationChannel(chan) chan.lightColor = Color.BLUE
"service" chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
} else { service.createNotificationChannel(chan)
// If earlier version channel ID is not used "service"
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) } else {
"" // If earlier version channel ID is not used
} // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
var pendingIntent: PendingIntent? = null var pendingIntent: PendingIntent? = null
pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE) PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE)
} else { } else {
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT) PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT)
} }
//Creating a notification and setting its various attributes // Creating a notification and setting its various attributes
notification = notification =
NotificationCompat.Builder(this@DownloadService, channelId) NotificationCompat.Builder(this@DownloadService, channelId)
.setSmallIcon(R.drawable.ic_download) .setSmallIcon(R.drawable.ic_download)
@ -75,7 +76,7 @@ class DownloadService : Service(){
.setProgress(100, 0, true) .setProgress(100, 0, true)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setAutoCancel(true) .setAutoCancel(true)
startForeground(1,notification.build()) startForeground(1, notification.build())
downloadManager() downloadManager()
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
@ -112,10 +113,10 @@ class DownloadService : Service(){
val downloadManager: DownloadManager = val downloadManager: DownloadManager =
applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadId = downloadManager.enqueue(request) downloadId = downloadManager.enqueue(request)
if(audioUrl==""){downloadId = 0L} if (audioUrl == "") { downloadId = 0L }
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
Log.e(TAG, "download error $e") Log.e(TAG, "download error $e")
try{ try {
downloadId = 0L downloadId = 0L
val request: DownloadManager.Request = val request: DownloadManager.Request =
DownloadManager.Request(Uri.parse(audioUrl)) DownloadManager.Request(Uri.parse(audioUrl))
@ -128,36 +129,35 @@ class DownloadService : Service(){
val downloadManager: DownloadManager = val downloadManager: DownloadManager =
applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadManager.enqueue(request) downloadManager.enqueue(request)
} catch (e: Exception) {
}catch (e: Exception){
Log.e(TAG, "audio download error $e") Log.e(TAG, "audio download error $e")
stopService(Intent(this,DownloadService::class.java))} stopService(Intent(this, DownloadService::class.java))
}
} }
} }
private val onDownloadComplete: BroadcastReceiver = object : BroadcastReceiver() { private val onDownloadComplete: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
//Fetching the download id received with the broadcast // Fetching the download id received with the broadcast
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
//Checking if the received broadcast is for our enqueued download by matching download id // Checking if the received broadcast is for our enqueued download by matching download id
if (downloadId == id) { if (downloadId == id) {
downloadId=0L downloadId = 0L
try{ try {
val request: DownloadManager.Request = val request: DownloadManager.Request =
DownloadManager.Request(Uri.parse(audioUrl)) DownloadManager.Request(Uri.parse(audioUrl))
.setTitle("Audio") // Title of the Download Notification .setTitle("Audio") // Title of the Download Notification
.setDescription("Downloading") // Description of the Download Notification .setDescription("Downloading") // Description of the Download Notification
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) // Visibility of the download Notification .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) // Visibility of the download Notification
.setDestinationUri(Uri.fromFile(audioDir)) .setDestinationUri(Uri.fromFile(audioDir))
.setAllowedOverMetered(true) // Set if download is allowed on Mobile network .setAllowedOverMetered(true) // Set if download is allowed on Mobile network
.setAllowedOverRoaming(true) // .setAllowedOverRoaming(true) //
val downloadManager: DownloadManager = val downloadManager: DownloadManager =
applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager applicationContext.getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadManager.enqueue(request) downloadManager.enqueue(request)
}catch (e: Exception){} } catch (e: Exception) {}
}else if (downloadId == 0L){ } else if (downloadId == 0L) {
val libreTube = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),"LibreTube") val libreTube = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LibreTube")
if (!libreTube.exists()) { if (!libreTube.exists()) {
libreTube.mkdirs() libreTube.mkdirs()
Log.e(TAG, "libreTube Directory make") Log.e(TAG, "libreTube Directory make")
@ -165,18 +165,19 @@ class DownloadService : Service(){
Log.e(TAG, "libreTube Directory already have") Log.e(TAG, "libreTube Directory already have")
} }
var command: String = when { var command: String = when {
videoUrl=="" -> { videoUrl == "" -> {
"-y -i $audioDir -c copy ${libreTube}/${videoId}-audio$extension" "-y -i $audioDir -c copy $libreTube/$videoId-audio$extension"
} }
audioUrl=="" -> { audioUrl == "" -> {
"-y -i $videoDir -c copy ${libreTube}/${videoId}-video$extension" "-y -i $videoDir -c copy $libreTube/$videoId-video$extension"
} }
else -> { else -> {
"-y -i $videoDir -i $audioDir -c copy ${libreTube}/${videoId}$extension" "-y -i $videoDir -i $audioDir -c copy $libreTube/${videoId}$extension"
} }
} }
notification.setContentTitle("Muxing") notification.setContentTitle("Muxing")
FFmpegKit.executeAsync(command, FFmpegKit.executeAsync(
command,
{ session -> { session ->
val state = session.state val state = session.state
val returnCode = session.returnCode val returnCode = session.returnCode
@ -194,7 +195,7 @@ class DownloadService : Service(){
val folder_main = ".tmp" val folder_main = ".tmp"
val f = File(path, folder_main) val f = File(path, folder_main)
f.deleteRecursively() f.deleteRecursively()
if (returnCode.toString()!="0"){ if (returnCode.toString() != "0") {
var builder = NotificationCompat.Builder(this@DownloadService, "failed") var builder = NotificationCompat.Builder(this@DownloadService, "failed")
.setSmallIcon(R.drawable.ic_download) .setSmallIcon(R.drawable.ic_download)
.setContentTitle(resources.getString(R.string.downloadfailed)) .setContentTitle(resources.getString(R.string.downloadfailed))
@ -207,13 +208,14 @@ class DownloadService : Service(){
} }
} }
stopForeground(true) stopForeground(true)
stopService(Intent(this@DownloadService,DownloadService::class.java)) stopService(Intent(this@DownloadService, DownloadService::class.java))
}, { }, {
// CALLED WHEN SESSION PRINTS LOGS // CALLED WHEN SESSION PRINTS LOGS
Log.e(TAG,it.message.toString()) Log.e(TAG, it.message.toString())
}) { }
) {
// CALLED WHEN SESSION GENERATES STATISTICS // CALLED WHEN SESSION GENERATES STATISTICS
Log.e(TAG+"stat",it.time.toString()) Log.e(TAG + "stat", it.time.toString())
/*val progress = it.time/(10*duration!!) /*val progress = it.time/(10*duration!!)
if (progress<1){ if (progress<1){
notification notification
@ -221,7 +223,6 @@ class DownloadService : Service(){
service.notify(1,notification.build()) service.notify(1,notification.build())
}*/ }*/
} }
} }
} }
} }
@ -245,10 +246,9 @@ class DownloadService : Service(){
override fun onDestroy() { override fun onDestroy() {
try { try {
unregisterReceiver(onDownloadComplete) unregisterReceiver(onDownloadComplete)
}catch (e: Exception){} } catch (e: Exception) {}
IS_DOWNLOAD_RUNNING = false IS_DOWNLOAD_RUNNING = false
Log.d(TAG,"dl finished!") Log.d(TAG, "dl finished!")
super.onDestroy() super.onDestroy()
} }
} }

View File

@ -2,23 +2,21 @@ package com.github.libretube
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.fragment.app.Fragment
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.ProgressBar
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import okhttp3.*
import retrofit2.HttpException
import com.github.libretube.adapters.TrendingAdapter import com.github.libretube.adapters.TrendingAdapter
import java.io.IOException import java.io.IOException
import okhttp3.*
import retrofit2.HttpException
class Home : Fragment() { class Home : Fragment() {
@ -31,51 +29,47 @@ class Home : Fragment() {
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
// Inflate the layout for this fragment // Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false) return inflater.inflate(R.layout.fragment_home, container, false)
} }
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 recyclerView = view.findViewById<RecyclerView>(R.id.recview)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val grid = sharedPreferences.getString("grid", resources.getInteger(R.integer.grid_items).toString())!! val grid = sharedPreferences.getString("grid", resources.getInteger(R.integer.grid_items).toString())!!
recyclerView.layoutManager = GridLayoutManager(view.context, grid.toInt()) recyclerView.layoutManager = GridLayoutManager(view.context, grid.toInt())
val progressbar = view.findViewById<ProgressBar>(R.id.progressBar) val progressbar = view.findViewById<ProgressBar>(R.id.progressBar)
fetchJson(progressbar,recyclerView) fetchJson(progressbar, recyclerView)
refreshLayout = view.findViewById(R.id.home_refresh) refreshLayout = view.findViewById(R.id.home_refresh)
refreshLayout?.isEnabled = true refreshLayout?.isEnabled = true
refreshLayout?.setOnRefreshListener { refreshLayout?.setOnRefreshListener {
Log.d(TAG,"hmm") Log.d(TAG, "hmm")
fetchJson(progressbar,recyclerView) fetchJson(progressbar, recyclerView)
} }
} }
private fun fetchJson(progressBar: ProgressBar, recyclerView: RecyclerView) {
private fun fetchJson(progressBar: ProgressBar, recyclerView: RecyclerView) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
RetrofitInstance.api.getTrending(sharedPreferences.getString("region", "US")!!) RetrofitInstance.api.getTrending(sharedPreferences.getString("region", "US")!!)
}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")
Toast.makeText(context,R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
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 refreshLayout?.isRefreshing = false
} }
runOnUiThread { runOnUiThread {
@ -84,8 +78,7 @@ class Home : Fragment() {
} }
} }
} }
run() run()
} }
private fun Fragment?.runOnUiThread(action: () -> Unit) { private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return this ?: return
@ -94,9 +87,9 @@ class Home : Fragment() {
} }
override fun onDestroyView() { override fun onDestroyView() {
view?.findViewById<RecyclerView>(R.id.recview)?.adapter=null view?.findViewById<RecyclerView>(R.id.recview)?.adapter = null
refreshLayout = null refreshLayout = null
Log.e(TAG,"destroyview") Log.e(TAG, "destroyview")
super.onDestroyView() super.onDestroyView()
} }
} }

View File

@ -17,9 +17,8 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.libretube.adapters.PlaylistsAdapter import com.github.libretube.adapters.PlaylistsAdapter
import com.github.libretube.obj.Playlists import com.github.libretube.obj.Playlists
import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import retrofit2.HttpException
class Library : Fragment() { class Library : Fragment() {
@ -30,12 +29,12 @@ class Library : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
} }
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
// Inflate the layout for this fragment // Inflate the layout for this fragment
@ -47,107 +46,104 @@ class Library : Fragment() {
playlistRecyclerView = view.findViewById(R.id.playlist_recView) playlistRecyclerView = view.findViewById(R.id.playlist_recView)
playlistRecyclerView.layoutManager = LinearLayoutManager(view.context) playlistRecyclerView.layoutManager = LinearLayoutManager(view.context)
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
token = sharedPref?.getString("token","")!! token = sharedPref?.getString("token", "")!!
refreshLayout = view.findViewById(R.id.playlist_refresh) refreshLayout = view.findViewById(R.id.playlist_refresh)
if(token!="") { if (token != "") {
view.findViewById<ImageView>(R.id.boogh2).visibility=View.GONE view.findViewById<ImageView>(R.id.boogh2).visibility = View.GONE
view.findViewById<TextView>(R.id.textLike2).visibility=View.GONE view.findViewById<TextView>(R.id.textLike2).visibility = View.GONE
fetchPlaylists(view) fetchPlaylists(view)
refreshLayout?.isEnabled = true refreshLayout?.isEnabled = true
refreshLayout?.setOnRefreshListener { refreshLayout?.setOnRefreshListener {
Log.d(TAG,"hmm") Log.d(TAG, "hmm")
fetchPlaylists(view) fetchPlaylists(view)
} }
view.findViewById<Button>(R.id.create_playlist).setOnClickListener { view.findViewById<Button>(R.id.create_playlist).setOnClickListener {
val newFragment = CreatePlaylistDialog() val newFragment = CreatePlaylistDialog()
newFragment.show(childFragmentManager, "Create Playlist") newFragment.show(childFragmentManager, "Create Playlist")
} }
childFragmentManager.setFragmentResultListener("key_parent", this) { _, result-> childFragmentManager.setFragmentResultListener("key_parent", this) { _, result ->
val playlistName = result.getString("playlistName") val playlistName = result.getString("playlistName")
createPlaylist("$playlistName", view); createPlaylist("$playlistName", view)
} }
} else{ } else {
refreshLayout?.isEnabled = false refreshLayout?.isEnabled = false
view.findViewById<Button>(R.id.create_playlist).visibility = View.GONE view.findViewById<Button>(R.id.create_playlist).visibility = View.GONE
with(view.findViewById<ImageView>(R.id.boogh2)){ with(view.findViewById<ImageView>(R.id.boogh2)) {
visibility=View.VISIBLE visibility = View.VISIBLE
setImageResource(R.drawable.ic_login) setImageResource(R.drawable.ic_login)
} }
with(view.findViewById<TextView>(R.id.textLike2)){ with(view.findViewById<TextView>(R.id.textLike2)) {
visibility=View.VISIBLE visibility = View.VISIBLE
text = getString(R.string.please_login) text = getString(R.string.please_login)
} }
} }
} }
private fun fetchPlaylists(view: View){ private fun fetchPlaylists(view: View) {
fun run() { fun run() {
refreshLayout?.isRefreshing = true refreshLayout?.isRefreshing = true
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.playlists(token) RetrofitInstance.api.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")
Toast.makeText(context,R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
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 refreshLayout?.isRefreshing = false
} }
if (response.isNotEmpty()){ if (response.isNotEmpty()) {
runOnUiThread { runOnUiThread {
with(view.findViewById<ImageView>(R.id.boogh2)){ with(view.findViewById<ImageView>(R.id.boogh2)) {
visibility=View.GONE visibility = View.GONE
} }
with(view.findViewById<TextView>(R.id.textLike2)){ with(view.findViewById<TextView>(R.id.textLike2)) {
visibility=View.GONE visibility = View.GONE
} }
} }
val playlistsAdapter = PlaylistsAdapter(response.toMutableList(),requireActivity()) val playlistsAdapter = PlaylistsAdapter(response.toMutableList(), requireActivity())
playlistRecyclerView.adapter= playlistsAdapter playlistRecyclerView.adapter = playlistsAdapter
}else{ } else {
runOnUiThread { runOnUiThread {
with(view.findViewById<ImageView>(R.id.boogh2)){ with(view.findViewById<ImageView>(R.id.boogh2)) {
visibility=View.VISIBLE visibility = View.VISIBLE
setImageResource(R.drawable.ic_list) setImageResource(R.drawable.ic_list)
} }
with(view.findViewById<TextView>(R.id.textLike2)){ with(view.findViewById<TextView>(R.id.textLike2)) {
visibility=View.VISIBLE visibility = View.VISIBLE
text = getString(R.string.emptyList) text = getString(R.string.emptyList)
} }
} }
} }
} }
} }
run() run()
} }
private fun createPlaylist(name: String, view: View){ private fun createPlaylist(name: String, view: View) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.createPlaylist(token, Playlists(name = name)) RetrofitInstance.api.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")
Toast.makeText(context,R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response $e") Log.e(TAG, "HttpException, unexpected response $e")
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
} }
if (response != null){ if (response != null) {
Toast.makeText(context,R.string.playlistCreated, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.playlistCreated, Toast.LENGTH_SHORT).show()
fetchPlaylists(view) fetchPlaylists(view)
}else{ } else {
} }
} }
} }
run() run()

View File

@ -2,7 +2,6 @@ package com.github.libretube
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.util.TypedValue import android.util.TypedValue
@ -15,15 +14,12 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.github.libretube.adapters.TrendingAdapter
import com.github.libretube.obj.Login import com.github.libretube.obj.Login
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.w3c.dom.Text import org.w3c.dom.Text
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import java.lang.Exception import java.lang.Exception
import kotlin.math.log
class LoginDialog : DialogFragment() { class LoginDialog : DialogFragment() {
private val TAG = "LoginDialog" private val TAG = "LoginDialog"
@ -33,43 +29,43 @@ class LoginDialog : DialogFragment() {
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; val inflater = requireActivity().layoutInflater
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token","") val token = sharedPref?.getString("token", "")
var view: View var view: View
Log.e("dafaq",token!!) Log.e("dafaq", token!!)
if(token!=""){ if (token != "") {
val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE) val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE)
val user = sharedPref2?.getString("username","") val user = sharedPref2?.getString("username", "")
view = inflater.inflate(R.layout.dialog_logout, null) view = inflater.inflate(R.layout.dialog_logout, null)
view.findViewById<TextView>(R.id.user).text = view.findViewById<TextView>(R.id.user).text.toString()+" ("+user+")" view.findViewById<TextView>(R.id.user).text = view.findViewById<TextView>(R.id.user).text.toString() + " (" + user + ")"
view.findViewById<Button>(R.id.logout).setOnClickListener { view.findViewById<Button>(R.id.logout).setOnClickListener {
Toast.makeText(context,R.string.loggedout, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.loggedout, Toast.LENGTH_SHORT).show()
val sharedPref = context?.getSharedPreferences("token",Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
with (sharedPref!!.edit()) { with(sharedPref!!.edit()) {
putString("token","") putString("token", "")
apply() apply()
} }
dialog?.dismiss() dialog?.dismiss()
} }
}else{ } else {
view = inflater.inflate(R.layout.dialog_login, null) view = inflater.inflate(R.layout.dialog_login, null)
username=view.findViewById(R.id.username) username = view.findViewById(R.id.username)
password=view.findViewById(R.id.password) password = view.findViewById(R.id.password)
view.findViewById<Button>(R.id.login).setOnClickListener { view.findViewById<Button>(R.id.login).setOnClickListener {
if(username.text.toString()!="" && password.text.toString()!=""){ if (username.text.toString() != "" && password.text.toString() != "") {
val login = Login(username.text.toString(),password.text.toString()) val login = Login(username.text.toString(), password.text.toString())
login(login) login(login)
}else{ } else {
Toast.makeText(context,R.string.empty, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
} }
} }
view.findViewById<Button>(R.id.register).setOnClickListener { view.findViewById<Button>(R.id.register).setOnClickListener {
if(username.text.toString()!="" && password.text.toString()!=""){ if (username.text.toString() != "" && password.text.toString() != "") {
val login = Login(username.text.toString(),password.text.toString()) val login = Login(username.text.toString(), password.text.toString())
register(login) register(login)
}else{ } else {
Toast.makeText(context,R.string.empty, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.empty, Toast.LENGTH_SHORT).show()
} }
} }
} }
@ -88,83 +84,80 @@ class LoginDialog : DialogFragment() {
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
private fun login(login: Login){ private fun login(login: Login) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.login(login) RetrofitInstance.api.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")
Toast.makeText(context,R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
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
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG,"dafaq?"+e.toString()) Log.e(TAG, "dafaq?" + e.toString())
return@launchWhenCreated return@launchWhenCreated
} }
if (response.error!= null){ if (response.error != null) {
Toast.makeText(context, response.error, Toast.LENGTH_SHORT).show() Toast.makeText(context, response.error, Toast.LENGTH_SHORT).show()
}else if(response.token!=null){ } else if (response.token != null) {
Toast.makeText(context,R.string.loggedIn, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.loggedIn, Toast.LENGTH_SHORT).show()
val sharedPref = context?.getSharedPreferences("token",Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
with (sharedPref!!.edit()) { with(sharedPref!!.edit()) {
putString("token",response.token) putString("token", response.token)
apply() apply()
} }
val sharedPref2 = context?.getSharedPreferences("username",Context.MODE_PRIVATE) val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE)
with (sharedPref2!!.edit()) { with(sharedPref2!!.edit()) {
putString("username",login.username) putString("username", login.username)
apply() apply()
} }
dialog?.dismiss() dialog?.dismiss()
} }
} }
} }
run() run()
} }
private fun register(login: Login){ private fun register(login: Login) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.register(login) RetrofitInstance.api.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")
Toast.makeText(context,R.string.unknown_error, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
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
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG,"dafaq?"+e.toString()) Log.e(TAG, "dafaq?" + e.toString())
return@launchWhenCreated return@launchWhenCreated
} }
if (response.error!= null){ if (response.error != null) {
Toast.makeText(context, response.error, Toast.LENGTH_SHORT).show() Toast.makeText(context, response.error, Toast.LENGTH_SHORT).show()
}else if(response.token!=null){ } else if (response.token != null) {
Toast.makeText(context,R.string.registered, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.registered, Toast.LENGTH_SHORT).show()
val sharedPref = context?.getSharedPreferences("token",Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
with (sharedPref!!.edit()) { with(sharedPref!!.edit()) {
putString("token",response.token) putString("token", response.token)
apply() apply()
} }
val sharedPref2 = context?.getSharedPreferences("username",Context.MODE_PRIVATE) val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE)
with (sharedPref2!!.edit()) { with(sharedPref2!!.edit()) {
putString("username",login.username) putString("username", login.username)
apply() apply()
} }
dialog?.dismiss() dialog?.dismiss()
} }
} }
} }
run() run()
} }
} }

View File

@ -17,7 +17,6 @@ import android.view.inputmethod.InputMethodManager
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar 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
@ -30,13 +29,12 @@ import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import java.util.*
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
val TAG = "MainActivity" val TAG = "MainActivity"
lateinit var bottomNavigationView: BottomNavigationView lateinit var bottomNavigationView: BottomNavigationView
lateinit var toolbar: Toolbar lateinit var toolbar: Toolbar
lateinit var navController : NavController lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
DynamicColors.applyToActivityIfAvailable(this) DynamicColors.applyToActivityIfAvailable(this)
@ -50,42 +48,21 @@ class MainActivity : AppCompatActivity() {
SponsorBlockSettings.sponsorsEnabled = sharedPreferences.getBoolean("sponsors_category_key", false) SponsorBlockSettings.sponsorsEnabled = sharedPreferences.getBoolean("sponsors_category_key", false)
SponsorBlockSettings.outroEnabled = sharedPreferences.getBoolean("outro_category_key", false) SponsorBlockSettings.outroEnabled = sharedPreferences.getBoolean("outro_category_key", false)
updateAccentColor(this)
val languageName = sharedPreferences.getString("language", "sys") updateThemeMode(this)
if (languageName != "") { updateLanguage(this)
var 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()
}
val res = resources
val dm = res.displayMetrics
val conf = res.configuration
conf.setLocale(locale)
Locale.setDefault(locale)
res.updateConfiguration(conf, dm)
}
when (sharedPreferences.getString("theme_togglee", "A")!!) {
"A" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"L" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
"D" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo=connectivityManager.activeNetworkInfo val networkInfo = connectivityManager.activeNetworkInfo
val isConnected = networkInfo != null && networkInfo.isConnected val isConnected = networkInfo != null && networkInfo.isConnected
if (isConnected == false) { if (!isConnected) {
setContentView(R.layout.activity_nointernet) setContentView(R.layout.activity_nointernet)
findViewById<Button>(R.id.retry_button).setOnClickListener() { findViewById<Button>(R.id.retry_button).setOnClickListener() {
recreate() recreate()
} }
} else { } else {
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
bottomNavigationView = findViewById(R.id.bottomNav) bottomNavigationView = findViewById(R.id.bottomNav)
@ -98,6 +75,7 @@ class MainActivity : AppCompatActivity() {
"library" -> navController.navigate(R.id.library) "library" -> navController.navigate(R.id.library)
} }
bottomNavigationView.setBackgroundColor(0) // otherwise Navbar Theme doesn't change
bottomNavigationView.setOnItemSelectedListener { bottomNavigationView.setOnItemSelectedListener {
when (it.itemId) { when (it.itemId) {
R.id.home2 -> { R.id.home2 -> {
@ -106,12 +84,12 @@ class MainActivity : AppCompatActivity() {
true true
} }
R.id.subscriptions -> { R.id.subscriptions -> {
//navController.backQueue.clear() // navController.backQueue.clear()
navController.navigate(R.id.subscriptions) navController.navigate(R.id.subscriptions)
true true
} }
R.id.library -> { R.id.library -> {
//navController.backQueue.clear() // navController.backQueue.clear()
navController.navigate(R.id.library) navController.navigate(R.id.library)
true true
} }
@ -121,7 +99,7 @@ class MainActivity : AppCompatActivity() {
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
val typedValue = TypedValue() val typedValue = TypedValue()
this.theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true) this.theme.resolveAttribute(R.attr.colorPrimary, typedValue, true)
val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data)) val hexColor = String.format("#%06X", (0xFFFFFF and typedValue.data))
val appName = HtmlCompat.fromHtml( val appName = HtmlCompat.fromHtml(
"Libre<span style='color:$hexColor';>Tube</span>", "Libre<span style='color:$hexColor';>Tube</span>",
@ -129,12 +107,12 @@ class MainActivity : AppCompatActivity() {
) )
toolbar.title = appName toolbar.title = appName
toolbar.setNavigationOnClickListener{ 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)
true true
} }
toolbar.setOnMenuItemClickListener { toolbar.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
@ -146,134 +124,128 @@ class MainActivity : AppCompatActivity() {
false false
} }
} }
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val action: String? = intent?.action val action: String? = intent?.action
val data: Uri? = intent?.data val data: Uri? = intent?.data
Log.d(TAG, "dafaq"+data.toString()) Log.d(TAG, "dafaq" + data.toString())
if (data != null) { if (data != null) {
Log.d("dafaq",data.host+" ${data.path} ") Log.d("dafaq", data.host + " ${data.path} ")
if(data.host != null){ if (data.host != null) {
if(data.path != null){ if (data.path != null) {
//channel // channel
if(data.path!!.contains("/channel/") || data.path!!.contains("/c/") || data.path!!.contains("/user/")){ if (data.path!!.contains("/channel/") || data.path!!.contains("/c/") || data.path!!.contains("/user/")) {
var channel = data.path var channel = data.path
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.channel, bundle)
}else if(data.path!!.contains("/playlist")){ } else if (data.path!!.contains("/playlist")) {
var playlist = data.query!! var playlist = data.query!!
if (playlist.contains("&")) if (playlist.contains("&")) {
{ var playlists = playlist.split("&")
var playlists = playlist.split("&") for (v in playlists) {
for (v in playlists){ if (v.contains("list=")) {
if (v.contains("list=")){ playlist = v
playlist = v break
break
}
} }
} }
playlist = playlist.replace("list=","")
val bundle = bundleOf("playlist_id" to playlist)
navController.navigate(R.id.playlistFragment,bundle)
}else if(data.path!!.contains("/shorts/") || data.path!!.contains("/embed/") || data.path!!.contains("/v/")){
var watch = data.path!!.replace("/shorts/","").replace("/v/","").replace("/embed/","")
var bundle = Bundle()
bundle.putString("videoId",watch)
var frag = PlayerFragment()
frag.arguments = bundle
supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
}else if(data.path!!.contains("/watch") && data.query != null){
Log.d("dafaq",data.query!!)
var watch = data.query!!
if (watch.contains("&"))
{
var watches = watch.split("&")
for (v in watches){
if (v.contains("v=")){
watch = v
break
}
}
}
var bundle = Bundle()
bundle.putString("videoId",watch.replace("v=",""))
var frag = PlayerFragment()
frag.arguments = bundle
supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
}else{
var watch = data.path!!.replace("/","")
var bundle = Bundle()
bundle.putString("videoId",watch)
var frag = PlayerFragment()
frag.arguments = bundle
supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
} }
playlist = playlist.replace("list=", "")
val bundle = bundleOf("playlist_id" to playlist)
navController.navigate(R.id.playlistFragment, bundle)
} else if (data.path!!.contains("/shorts/") || data.path!!.contains("/embed/") || data.path!!.contains("/v/")) {
var watch = data.path!!.replace("/shorts/", "").replace("/v/", "").replace("/embed/", "")
var bundle = Bundle()
bundle.putString("videoId", watch)
var frag = PlayerFragment()
frag.arguments = bundle
supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
} else if (data.path!!.contains("/watch") && data.query != null) {
Log.d("dafaq", data.query!!)
var watch = data.query!!
if (watch.contains("&")) {
var watches = watch.split("&")
for (v in watches) {
if (v.contains("v=")) {
watch = v
break
}
}
}
var bundle = Bundle()
bundle.putString("videoId", watch.replace("v=", ""))
var frag = PlayerFragment()
frag.arguments = bundle
supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
} else {
var watch = data.path!!.replace("/", "")
var bundle = Bundle()
bundle.putString("videoId", watch)
var frag = PlayerFragment()
frag.arguments = bundle
supportFragmentManager.beginTransaction()
.remove(PlayerFragment())
.commit()
supportFragmentManager.beginTransaction()
.replace(R.id.container, frag)
.commitNow()
Handler().postDelayed({
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd()
motionLayout.transitionToStart()
}, 100)
} }
}
} }
} }
} }
override fun onBackPressed() { override fun onBackPressed() {
try{ try {
val mainMotionLayout = findViewById<MotionLayout>(R.id.mainMotionLayout) val mainMotionLayout = findViewById<MotionLayout>(R.id.mainMotionLayout)
if (mainMotionLayout.progress == 0.toFloat()){ if (mainMotionLayout.progress == 0.toFloat()) {
mainMotionLayout.transitionToEnd() mainMotionLayout.transitionToEnd()
findViewById<ConstraintLayout>(R.id.main_container).isClickable=false findViewById<ConstraintLayout>(R.id.main_container).isClickable = false
val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout) val motionLayout = findViewById<MotionLayout>(R.id.playerMotionLayout)
motionLayout.transitionToEnd() motionLayout.transitionToEnd()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
with(motionLayout) { with(motionLayout) {
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)
} }
findViewById<LinearLayout>(R.id.linLayout).visibility=View.VISIBLE findViewById<LinearLayout>(R.id.linLayout).visibility = View.VISIBLE
isFullScreen=false isFullScreen = false
}else{ } else {
navController.popBackStack() navController.popBackStack()
if (navController.currentBackStackEntry == null && (parent as View).id != R.id.settings){ if (navController.currentBackStackEntry == null && (parent as View).id != R.id.settings) {
super.onBackPressed() super.onBackPressed()
} }
} }
}catch (e: Exception){ } catch (e: Exception) {
navController.popBackStack() navController.popBackStack()
moveTaskToBack(true) moveTaskToBack(true)
} }
@ -303,15 +275,17 @@ class MainActivity : AppCompatActivity() {
} }
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_FULLSCREEN window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_IMMERSIVE or View.SYSTEM_UI_FLAG_IMMERSIVE
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
)
} }
} }
private fun unsetFullscreen(){ private fun unsetFullscreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.attributes.layoutInDisplayCutoutMode = window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
@ -335,7 +309,6 @@ class MainActivity : AppCompatActivity() {
(fragment as? PlayerFragment)?.onUserLeaveHint() (fragment as? PlayerFragment)?.onUserLeaveHint()
} }
} }
} }
fun Fragment.hideKeyboard() { fun Fragment.hideKeyboard() {
view?.let { activity?.hideKeyboard(it) } view?.let { activity?.hideKeyboard(it) }

View File

@ -17,7 +17,10 @@ interface PipedApi {
suspend fun getSegments(@Path("videoId") videoId: String, @Query("category") category: String): Segments suspend fun getSegments(@Path("videoId") videoId: String, @Query("category") category: String): Segments
@GET("nextpage/comments/{videoId}") @GET("nextpage/comments/{videoId}")
suspend fun getCommentsNextPage(@Path("videoId") videoId: String, @Query("nextpage") nextPage: String): CommentsPage suspend fun getCommentsNextPage(
@Path("videoId") videoId: String,
@Query("nextpage") nextPage: String
): CommentsPage
@GET("search") @GET("search")
suspend fun getSearchResults( suspend fun getSearchResults(
@ -39,13 +42,19 @@ interface PipedApi {
suspend fun getChannel(@Path("channelId") channelId: String): Channel suspend fun getChannel(@Path("channelId") channelId: String): Channel
@GET("nextpage/channel/{channelId}") @GET("nextpage/channel/{channelId}")
suspend fun getChannelNextPage(@Path("channelId") channelId: String, @Query("nextpage") nextPage: String): Channel suspend fun getChannelNextPage(
@Path("channelId") channelId: String,
@Query("nextpage") nextPage: String
): Channel
@GET("playlists/{playlistId}") @GET("playlists/{playlistId}")
suspend fun getPlaylist(@Path("playlistId") playlistId: String): Playlist suspend fun getPlaylist(@Path("playlistId") playlistId: String): Playlist
@GET("nextpage/playlists/{playlistId}") @GET("nextpage/playlists/{playlistId}")
suspend fun getPlaylistNextPage(@Path("playlistId") playlistId: String, @Query("nextpage") nextPage: String): Playlist suspend fun getPlaylistNextPage(
@Path("playlistId") playlistId: String,
@Query("nextpage") nextPage: String
): Playlist
@POST("login") @POST("login")
suspend fun login(@Body login: Login): Token suspend fun login(@Body login: Login): Token
@ -57,7 +66,10 @@ interface PipedApi {
suspend fun getFeed(@Query("authToken") token: String?): List<StreamItem> suspend fun getFeed(@Query("authToken") token: String?): List<StreamItem>
@GET("subscribed") @GET("subscribed")
suspend fun isSubscribed(@Query("channelId") channelId: String, @Header("Authorization") token: String): Subscribed suspend fun isSubscribed(
@Query("channelId") channelId: String,
@Header("Authorization") token: String
): Subscribed
@GET("subscriptions") @GET("subscriptions")
suspend fun subscriptions(@Header("Authorization") token: String): List<Subscription> suspend fun subscriptions(@Header("Authorization") token: String): List<Subscription>
@ -69,7 +81,11 @@ interface PipedApi {
suspend fun unsubscribe(@Header("Authorization") token: String, @Body subscribe: Subscribe): Message suspend fun unsubscribe(@Header("Authorization") token: String, @Body subscribe: Subscribe): Message
@POST("import") @POST("import")
suspend fun importSubscriptions(@Query("override") override: Boolean, @Header("Authorization") token: String, @Body channels: List<String>): Message suspend fun importSubscriptions(
@Query("override") override: Boolean,
@Header("Authorization") token: String,
@Body channels: List<String>
): Message
@GET("user/playlists") @GET("user/playlists")
suspend fun playlists(@Header("Authorization") token: String): List<Playlists> suspend fun playlists(@Header("Authorization") token: String): List<Playlists>
@ -84,12 +100,12 @@ interface PipedApi {
suspend fun addToPlaylist(@Header("Authorization") token: String, @Body playlistId: PlaylistId): Message suspend fun addToPlaylist(@Header("Authorization") token: String, @Body playlistId: PlaylistId): Message
@POST("user/playlists/remove") @POST("user/playlists/remove")
suspend fun removeFromPlaylist(@Header("Authorization") token: String, @Body playlistId: PlaylistId): Message suspend fun removeFromPlaylist(
@Header("Authorization") token: String,
@Body playlistId: PlaylistId
): Message
//only for fetching servers list // only for fetching servers list
@GET @GET
suspend fun getInstances(@Url url: String): List<Instances> suspend fun getInstances(@Url url: String): List<Instances>
} }

View File

@ -8,20 +8,11 @@ import com.google.android.material.color.DynamicColors
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
class Player : Activity() { class Player : Activity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
DynamicColors.applyToActivityIfAvailable(this) DynamicColors.applyToActivityIfAvailable(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player) setContentView(R.layout.activity_player)
} }
} }

View File

@ -58,13 +58,12 @@ 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.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import org.chromium.net.CronetEngine
import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import java.net.URLEncoder import java.net.URLEncoder
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.abs import kotlin.math.abs
import org.chromium.net.CronetEngine
import retrofit2.HttpException
var isFullScreen = false var isFullScreen = false
@ -247,7 +246,6 @@ class PlayerFragment : Fragment() {
) { ) {
fetchNextComments() fetchNextComments()
} }
} }
commentsRecView = view.findViewById(R.id.comments_recView) commentsRecView = view.findViewById(R.id.comments_recView)
@ -274,22 +272,21 @@ class PlayerFragment : Fragment() {
} }
} }
private fun checkForSegments() private fun checkForSegments() {
{
if (!exoPlayer.isPlaying || !SponsorBlockSettings.sponsorBlockEnabled) return if (!exoPlayer.isPlaying || !SponsorBlockSettings.sponsorBlockEnabled) return
exoPlayerView.postDelayed(this::checkForSegments, 100) exoPlayerView.postDelayed(this::checkForSegments, 100)
if(segmentData.segments.isEmpty() ) if (segmentData.segments.isEmpty())
return return
segmentData.segments.forEach { segment: Segment -> segmentData.segments.forEach { segment: Segment ->
val segmentStart = (segment.segment!![0] * 1000.0f).toLong() val segmentStart = (segment.segment!![0] * 1000.0f).toLong()
val segmentEnd = (segment.segment!![1] * 1000.0f).toLong() val segmentEnd = (segment.segment!![1] * 1000.0f).toLong()
val currentPosition = exoPlayer.currentPosition val currentPosition = exoPlayer.currentPosition
if(currentPosition in segmentStart until segmentEnd) { if (currentPosition in segmentStart until segmentEnd) {
Toast.makeText(context,R.string.segment_skipped, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.segment_skipped, Toast.LENGTH_SHORT).show()
exoPlayer.seekTo(segmentEnd); exoPlayer.seekTo(segmentEnd)
} }
} }
} }
@ -321,7 +318,7 @@ class PlayerFragment : 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
} }
if(SponsorBlockSettings.sponsorBlockEnabled) { if (SponsorBlockSettings.sponsorBlockEnabled) {
val categories: ArrayList<String> = arrayListOf() val categories: ArrayList<String> = arrayListOf()
if (SponsorBlockSettings.introEnabled) { if (SponsorBlockSettings.introEnabled) {
categories.add("intro") categories.add("intro")
@ -338,7 +335,7 @@ class PlayerFragment : Fragment() {
if (SponsorBlockSettings.outroEnabled) { if (SponsorBlockSettings.outroEnabled) {
categories.add("outro") categories.add("outro")
} }
if(categories.size > 0) { if (categories.size > 0) {
segmentData = try { segmentData = try {
RetrofitInstance.api.getSegments( RetrofitInstance.api.getSegments(
@ -401,10 +398,10 @@ class PlayerFragment : Fragment() {
exoPlayerView.setShowSubtitleButton(true) exoPlayerView.setShowSubtitleButton(true)
exoPlayerView.setShowNextButton(false) exoPlayerView.setShowNextButton(false)
exoPlayerView.setShowPreviousButton(false) exoPlayerView.setShowPreviousButton(false)
exoPlayerView.setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL); exoPlayerView.setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL)
// exoPlayerView.controllerShowTimeoutMs = 1500 // exoPlayerView.controllerShowTimeoutMs = 1500
exoPlayerView.controllerHideOnTouch = true exoPlayerView.controllerHideOnTouch = true
exoPlayer.setAudioAttributes(audioAttributes,true); exoPlayer.setAudioAttributes(audioAttributes, true)
exoPlayerView.player = exoPlayer exoPlayerView.player = exoPlayer
val sharedPreferences = val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireContext()) PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -433,9 +430,11 @@ class PlayerFragment : Fragment() {
ProgressiveMediaSource.Factory(dataSourceFactory) ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource( .createMediaSource(
fromUri( fromUri(
response.audioStreams!![getMostBitRate( response.audioStreams!![
response.audioStreams getMostBitRate(
)].url!! response.audioStreams
)
].url!!
) )
) )
} }
@ -479,9 +478,11 @@ class PlayerFragment : Fragment() {
audioSource = ProgressiveMediaSource.Factory(dataSourceFactory) audioSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource( .createMediaSource(
fromUri( fromUri(
response.audioStreams!![getMostBitRate( response.audioStreams!![
response.audioStreams getMostBitRate(
)].url!! response.audioStreams
)
].url!!
) )
) )
} }
@ -545,9 +546,11 @@ class PlayerFragment : Fragment() {
ProgressiveMediaSource.Factory(dataSourceFactory) ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource( .createMediaSource(
fromUri( fromUri(
response.audioStreams!![getMostBitRate( response.audioStreams!![
response.audioStreams getMostBitRate(
)].url!! response.audioStreams
)
].url!!
) )
) )
} }
@ -555,7 +558,7 @@ class PlayerFragment : Fragment() {
MergingMediaSource(videoSource, audioSource) MergingMediaSource(videoSource, audioSource)
exoPlayer.setMediaSource(mergeSource) exoPlayer.setMediaSource(mergeSource)
} }
exoPlayer.seekTo(lastPosition); exoPlayer.seekTo(lastPosition)
view.findViewById<TextView>(R.id.quality_text).text = view.findViewById<TextView>(R.id.quality_text).text =
videosNameArray[which] videosNameArray[which]
} }
@ -566,8 +569,7 @@ class PlayerFragment : Fragment() {
// Listener for play and pause icon change // Listener for play and pause icon change
exoPlayer!!.addListener(object : com.google.android.exoplayer2.Player.Listener { exoPlayer!!.addListener(object : com.google.android.exoplayer2.Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
if(isPlaying && SponsorBlockSettings.sponsorBlockEnabled) if (isPlaying && SponsorBlockSettings.sponsorBlockEnabled) {
{
exoPlayerView.postDelayed(this@PlayerFragment::checkForSegments, 100) exoPlayerView.postDelayed(this@PlayerFragment::checkForSegments, 100)
} }
} }
@ -578,9 +580,9 @@ class PlayerFragment : Fragment() {
) { ) {
exoPlayerView.keepScreenOn = !( exoPlayerView.keepScreenOn = !(
playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED || playbackState == Player.STATE_IDLE || playbackState == Player.STATE_ENDED ||
!playWhenReady !playWhenReady
) )
if (playWhenReady && playbackState == Player.STATE_READY) { if (playWhenReady && playbackState == Player.STATE_READY) {
// media actually playing // media actually playing
@ -678,9 +680,9 @@ class PlayerFragment : Fragment() {
requireContext(), requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission( ) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(
requireContext(), requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ) != PackageManager.PERMISSION_GRANTED
) { ) {
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(
mainActivity, mainActivity,
@ -772,7 +774,6 @@ class PlayerFragment : Fragment() {
return@launchWhenCreated return@launchWhenCreated
} }
runOnUiThread { runOnUiThread {
if (response.subscribed == true) { if (response.subscribed == true) {
isSubscribed = true isSubscribed = true
@ -864,25 +865,25 @@ class PlayerFragment : Fragment() {
super.onResume() super.onResume()
} }
private fun fetchNextComments(){ private fun fetchNextComments() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
if (!isLoading) { if (!isLoading) {
isLoading = true isLoading = true
val response = try { val response = try {
RetrofitInstance.api.getCommentsNextPage(videoId!!, nextPage!!) 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")
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response," + e.response()) Log.e(TAG, "HttpException, unexpected response," + e.response())
return@launchWhenCreated return@launchWhenCreated
}
nextPage = response.nextpage
commentsAdapter?.updateItems(response.comments!!)
isLoading = false
} }
nextPage = response.nextpage
commentsAdapter?.updateItems(response.comments!!)
isLoading = false
} }
}
} }
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
@ -898,7 +899,7 @@ class PlayerFragment : Fragment() {
view?.findViewById<FrameLayout>(R.id.top_bar)?.visibility = View.GONE view?.findViewById<FrameLayout>(R.id.top_bar)?.visibility = View.GONE
val mainActivity = activity as MainActivity val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
isFullScreen = false; isFullScreen = false
} else { } else {
with(motionLayout) { with(motionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0) getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
@ -917,6 +918,6 @@ class PlayerFragment : Fragment() {
if (SDK_INT >= Build.VERSION_CODES.N && exoPlayer.isPlaying && (scrollView?.getLocalVisibleRect(bounds) == true || isFullScreen)) { if (SDK_INT >= Build.VERSION_CODES.N && exoPlayer.isPlaying && (scrollView?.getLocalVisibleRect(bounds) == true || isFullScreen)) {
requireActivity().enterPictureInPictureMode() requireActivity().enterPictureInPictureMode()
}; }
} }
} }

View File

@ -3,27 +3,23 @@ package com.github.libretube
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.fragment.app.Fragment
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.ScrollView
import android.widget.TextView import android.widget.TextView
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.recyclerview.widget.RecyclerView
import com.github.libretube.adapters.ChannelAdapter
import com.github.libretube.adapters.PlaylistAdapter import com.github.libretube.adapters.PlaylistAdapter
import com.squareup.picasso.Picasso
import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import retrofit2.HttpException
class PlaylistFragment : Fragment() { class PlaylistFragment : Fragment() {
private var playlist_id: String? = null private var playlist_id: String? = null
private val TAG = "PlaylistFragment" private val TAG = "PlaylistFragment"
var nextPage: String? =null var nextPage: String? = null
var playlistAdapter: PlaylistAdapter? = null var playlistAdapter: PlaylistAdapter? = null
var isLoading = true var isLoading = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -34,7 +30,8 @@ class PlaylistFragment : Fragment() {
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
// Inflate the layout for this fragment // Inflate the layout for this fragment
@ -44,19 +41,19 @@ class PlaylistFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
playlist_id = playlist_id!!.replace("/playlist?list=","") playlist_id = playlist_id!!.replace("/playlist?list=", "")
view.findViewById<TextView>(R.id.playlist_name).text=playlist_id view.findViewById<TextView>(R.id.playlist_name).text = playlist_id
val recyclerView = view.findViewById<RecyclerView>(R.id.playlist_recView) val recyclerView = view.findViewById<RecyclerView>(R.id.playlist_recView)
recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.layoutManager = LinearLayoutManager(context)
fetchPlaylist(view) fetchPlaylist(view)
} }
private fun fetchPlaylist(view: View){ private fun fetchPlaylist(view: View) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getPlaylist(playlist_id!!) RetrofitInstance.api.getPlaylist(playlist_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")
return@launchWhenCreated return@launchWhenCreated
@ -65,15 +62,15 @@ class PlaylistFragment : Fragment() {
return@launchWhenCreated return@launchWhenCreated
} }
nextPage = response.nextpage nextPage = response.nextpage
isLoading=false isLoading = false
runOnUiThread { runOnUiThread {
view.findViewById<TextView>(R.id.playlist_name).text=response.name view.findViewById<TextView>(R.id.playlist_name).text = response.name
view.findViewById<TextView>(R.id.playlist_uploader).text=response.uploader view.findViewById<TextView>(R.id.playlist_uploader).text = response.uploader
view.findViewById<TextView>(R.id.playlist_totVideos).text=response.videos.toString()+" Videos" view.findViewById<TextView>(R.id.playlist_totVideos).text = response.videos.toString() + " Videos"
val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE) val sharedPref2 = context?.getSharedPreferences("username", Context.MODE_PRIVATE)
val user = sharedPref2?.getString("username","") val user = sharedPref2?.getString("username", "")
var isOwner = false var isOwner = false
if(response.uploaderUrl == null && response.uploader.equals(user, true)){ if (response.uploaderUrl == null && response.uploader.equals(user, true)) {
isOwner = true isOwner = true
} }
playlistAdapter = PlaylistAdapter(response.relatedStreams!!.toMutableList(), playlist_id!!, isOwner, requireActivity()) playlistAdapter = PlaylistAdapter(response.relatedStreams!!.toMutableList(), playlist_id!!, isOwner, requireActivity())
@ -82,15 +79,15 @@ class PlaylistFragment : Fragment() {
scrollView.viewTreeObserver scrollView.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom if (scrollView.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY)) { == (scrollView.height + scrollView.scrollY)
//scroll view is at bottom ) {
if(nextPage!=null && !isLoading){ // scroll view is at bottom
isLoading=true if (nextPage != null && !isLoading) {
isLoading = true
fetchNextPage() fetchNextPage()
} }
} else { } else {
//scroll view is not at bottom // scroll view is not at bottom
} }
} }
} }
@ -99,24 +96,23 @@ class PlaylistFragment : Fragment() {
run() run()
} }
private fun fetchNextPage(){ private fun fetchNextPage() {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getPlaylistNextPage(playlist_id!!,nextPage!!) RetrofitInstance.api.getPlaylistNextPage(playlist_id!!, 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")
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
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
playlistAdapter?.updateItems(response.relatedStreams!!) playlistAdapter?.updateItems(response.relatedStreams!!)
isLoading=false isLoading = false
} }
} }
run() run()

View File

@ -8,13 +8,13 @@ class ResettableLazyManager {
val managedDelegates = LinkedList<Resettable>() val managedDelegates = LinkedList<Resettable>()
fun register(managed: Resettable) { fun register(managed: Resettable) {
synchronized (managedDelegates) { synchronized(managedDelegates) {
managedDelegates.add(managed) managedDelegates.add(managed)
} }
} }
fun reset() { fun reset() {
synchronized (managedDelegates) { synchronized(managedDelegates) {
managedDelegates.forEach { it.reset() } managedDelegates.forEach { it.reset() }
managedDelegates.clear() managedDelegates.clear()
} }
@ -25,7 +25,7 @@ interface Resettable {
fun reset() fun reset()
} }
class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()->PROPTYPE): Resettable { class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: () -> PROPTYPE) : Resettable {
@Volatile var lazyHolder = makeInitBlock() @Volatile var lazyHolder = makeInitBlock()
operator fun getValue(thisRef: Any?, property: KProperty<*>): PROPTYPE { operator fun getValue(thisRef: Any?, property: KProperty<*>): PROPTYPE {
@ -44,7 +44,7 @@ class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()-
} }
} }
fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: ()->PROPTYPE): ResettableLazy<PROPTYPE> { fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: () -> PROPTYPE): ResettableLazy<PROPTYPE> {
return ResettableLazy(manager, init) return ResettableLazy(manager, init)
} }

View File

@ -13,5 +13,4 @@ object RetrofitInstance {
.build() .build()
.create(PipedApi::class.java) .create(PipedApi::class.java)
} }
} }

View File

@ -23,38 +23,37 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
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 java.io.IOException
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
private val TAG = "SearchFragment" private val TAG = "SearchFragment"
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 searchRecView: RecyclerView
private var searchAdapter : SearchAdapter? = null private var searchAdapter: SearchAdapter? = null
private var isLoading : Boolean = true private var isLoading: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments?.let {
} }
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
// Inflate the layout for this fragment // Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_search, container, false) return inflater.inflate(R.layout.fragment_search, container, false)
} }
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<RecyclerView>(R.id.search_recycler) searchRecView = view.findViewById<RecyclerView>(R.id.search_recycler)
@ -81,30 +80,37 @@ class SearchFragment : Fragment() {
MaterialAlertDialogBuilder(view.context) MaterialAlertDialogBuilder(view.context)
.setTitle(getString(R.string.choose_filter)) .setTitle(getString(R.string.choose_filter))
.setSingleChoiceItems(filterOptions, selectedFilter, DialogInterface.OnClickListener { .setSingleChoiceItems(
_, id -> tempSelectedItem = id filterOptions, selectedFilter,
}) DialogInterface.OnClickListener {
.setPositiveButton(getString(R.string.okay), DialogInterface.OnClickListener { _, _ -> _, id ->
selectedFilter = tempSelectedItem tempSelectedItem = id
apiSearchFilter = when (selectedFilter) {
0 -> "all"
1 -> "videos"
2 -> "channels"
3 -> "playlists"
4 -> "music_songs"
5 -> "music_videos"
6 -> "music_albums"
7 -> "music_playlists"
else -> "all"
} }
)
.setPositiveButton(
getString(R.string.okay),
DialogInterface.OnClickListener { _, _ ->
selectedFilter = tempSelectedItem
apiSearchFilter = when (selectedFilter) {
0 -> "all"
1 -> "videos"
2 -> "channels"
3 -> "playlists"
4 -> "music_songs"
5 -> "music_videos"
6 -> "music_albums"
7 -> "music_playlists"
else -> "all"
}
fetchSearch(autoTextView.text.toString()) fetchSearch(autoTextView.text.toString())
}) }
)
.setNegativeButton(getString(R.string.cancel), null) .setNegativeButton(getString(R.string.cancel), null)
.create() .create()
.show() .show()
} }
//show search history // show search history
searchRecView.visibility = GONE searchRecView.visibility = GONE
historyRecycler.visibility = VISIBLE historyRecycler.visibility = VISIBLE
@ -129,7 +135,6 @@ class SearchFragment : Fragment() {
count: Int, count: Int,
after: Int after: Int
) { ) {
} }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
@ -143,7 +148,6 @@ class SearchFragment : Fragment() {
if (!searchRecView.canScrollVertically(1)) { if (!searchRecView.canScrollVertically(1)) {
fetchNextSearchItems(autoTextView.text.toString()) fetchNextSearchItems(autoTextView.text.toString())
} }
} }
GlobalScope.launch { GlobalScope.launch {
@ -167,22 +171,23 @@ class SearchFragment : Fragment() {
} }
} }
} }
}) })
autoTextView.setOnEditorActionListener(OnEditorActionListener { _, actionId, _ -> autoTextView.setOnEditorActionListener(
if (actionId == EditorInfo.IME_ACTION_SEARCH) { OnEditorActionListener { _, actionId, _ ->
hideKeyboard() if (actionId == EditorInfo.IME_ACTION_SEARCH) {
autoTextView.dismissDropDown() hideKeyboard()
return@OnEditorActionListener true autoTextView.dismissDropDown()
return@OnEditorActionListener true
}
false
} }
false )
})
autoTextView.setOnItemClickListener { _, _, _, _ -> autoTextView.setOnItemClickListener { _, _, _, _ ->
hideKeyboard() hideKeyboard()
} }
} }
private fun fetchSuggestions(query: String, autoTextView: AutoCompleteTextView){ private fun fetchSuggestions(query: String, autoTextView: AutoCompleteTextView) {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getSuggestions(query) RetrofitInstance.api.getSuggestions(query)
@ -198,7 +203,7 @@ class SearchFragment : Fragment() {
autoTextView.setAdapter(adapter) autoTextView.setAdapter(adapter)
} }
} }
private fun fetchSearch(query: String){ private fun fetchSearch(query: String) {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getSearchResults(query, apiSearchFilter) RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
@ -211,22 +216,22 @@ class SearchFragment : Fragment() {
return@launchWhenCreated return@launchWhenCreated
} }
nextPage = response.nextpage nextPage = response.nextpage
if(response.items!!.isNotEmpty()){ if (response.items!!.isNotEmpty()) {
runOnUiThread { runOnUiThread {
searchAdapter = SearchAdapter(response.items) searchAdapter = SearchAdapter(response.items)
searchRecView.adapter = searchAdapter searchRecView.adapter = searchAdapter
} }
} }
isLoading = false isLoading = false
} }
} }
private fun fetchNextSearchItems(query: String){ private fun fetchNextSearchItems(query: String) {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
if (!isLoading) { if (!isLoading) {
isLoading = true isLoading = true
val response = try { val response = try {
RetrofitInstance.api.getSearchResultsNextPage(query,apiSearchFilter,nextPage!!) RetrofitInstance.api.getSearchResultsNextPage(query, apiSearchFilter, 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")
@ -240,7 +245,7 @@ class SearchFragment : Fragment() {
isLoading = false isLoading = false
} }
} }
} }
private fun Fragment?.runOnUiThread(action: () -> Unit) { private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return this ?: return
@ -263,18 +268,14 @@ class SearchFragment : Fragment() {
var historyList = getHistory() var historyList = getHistory()
if (historyList.size != 0 && query == historyList.get(historyList.size - 1)) { if (historyList.size != 0 && query == historyList.get(historyList.size - 1)) {
return return
} else if (query == "") { } else if (query == "") {
return return
} else { } else {
historyList = historyList + query historyList = historyList + query
} }
if (historyList.size > 10) { if (historyList.size > 10) {
historyList = historyList.takeLast(10) historyList = historyList.takeLast(10)
} }
@ -293,7 +294,5 @@ class SearchFragment : Fragment() {
} catch (e: Exception) { } catch (e: Exception) {
return emptyList() return emptyList()
} }
} }
} }

View File

@ -15,7 +15,6 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -25,26 +24,31 @@ import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.json.JSONObject
import org.json.JSONTokener
import retrofit2.HttpException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import org.json.JSONObject
import org.json.JSONTokener
import retrofit2.HttpException
class SettingsActivity : AppCompatActivity(), class SettingsActivity :
SharedPreferences.OnSharedPreferenceChangeListener{ AppCompatActivity(),
SharedPreferences.OnSharedPreferenceChangeListener {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
DynamicColors.applyToActivityIfAvailable(this) DynamicColors.applyToActivityIfAvailable(this)
updateAccentColor(this)
updateThemeMode(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
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) val view = this.findViewById<View>(android.R.id.content)
view.setAlpha(0F); view.alpha = 0F
view.animate().alpha(1F).setDuration(300); view.animate().alpha(1F).duration = 300
setContentView(R.layout.activity_settings) setContentView(R.layout.activity_settings)
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager supportFragmentManager
@ -55,12 +59,10 @@ class SettingsActivity : AppCompatActivity(),
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this) .registerOnSharedPreferenceChangeListener(this)
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, rootKey: String?) {} override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, rootKey: String?) {}
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
val TAG = "Settings" val TAG = "Settings"
@ -70,7 +72,6 @@ class SettingsActivity : AppCompatActivity(),
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
if (uri != null) { if (uri != null) {
try { try {
// Open a specific media item using ParcelFileDescriptor. // Open a specific media item using ParcelFileDescriptor.
@ -80,23 +81,23 @@ class SettingsActivity : AppCompatActivity(),
// "rw" for read-and-write; // "rw" for read-and-write;
// "rwt" for truncating or overwriting existing file contents. // "rwt" for truncating or overwriting existing file contents.
//val readOnlyMode = "r" // val readOnlyMode = "r"
// uri - I have got from onActivityResult // uri - I have got from onActivityResult
val type = resolver.getType(uri) val type = resolver.getType(uri)
var inputStream: InputStream? = resolver.openInputStream(uri) var inputStream: InputStream? = resolver.openInputStream(uri)
val channels = ArrayList<String>() val channels = ArrayList<String>()
if(type == "application/json"){ if (type == "application/json") {
val json = inputStream?.bufferedReader()?.readLines()?.get(0) val json = inputStream?.bufferedReader()?.readLines()?.get(0)
val jsonObject = JSONTokener(json).nextValue() as JSONObject val jsonObject = JSONTokener(json).nextValue() as JSONObject
Log.e(TAG,jsonObject.getJSONArray("subscriptions").toString()) Log.e(TAG, jsonObject.getJSONArray("subscriptions").toString())
for (i in 0 until jsonObject.getJSONArray("subscriptions").length()) { for (i in 0 until jsonObject.getJSONArray("subscriptions").length()) {
var url = jsonObject.getJSONArray("subscriptions").getJSONObject(i).getString("url") var url = jsonObject.getJSONArray("subscriptions").getJSONObject(i).getString("url")
url = url.replace("https://www.youtube.com/channel/","") url = url.replace("https://www.youtube.com/channel/", "")
Log.e(TAG,url) Log.e(TAG, url)
channels.add(url) channels.add(url)
} }
}else { } else {
if (type == "application/zip") { if (type == "application/zip") {
val zis = ZipInputStream(inputStream) val zis = ZipInputStream(inputStream)
var entry: ZipEntry? = zis.nextEntry var entry: ZipEntry? = zis.nextEntry
@ -129,8 +130,6 @@ class SettingsActivity : AppCompatActivity(),
).show() ).show()
} }
} }
} }
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
} }
@ -165,7 +164,7 @@ class SettingsActivity : AppCompatActivity(),
val sponsorblock = findPreference<Preference>("sponsorblock") val sponsorblock = findPreference<Preference>("sponsorblock")
sponsorblock?.setOnPreferenceClickListener { sponsorblock?.setOnPreferenceClickListener {
val newFragment = SponsorBlockSettings() val newFragment = SponsorBlockSettings()
parentFragmentManager.beginTransaction() parentFragmentManager.beginTransaction()
.replace(R.id.settings, newFragment) .replace(R.id.settings, newFragment)
.commitNow() .commitNow()
true true
@ -175,7 +174,7 @@ class SettingsActivity : AppCompatActivity(),
importFromYt?.setOnPreferenceClickListener { importFromYt?.setOnPreferenceClickListener {
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token", "")!! val token = sharedPref?.getString("token", "")!!
//check StorageAccess // check StorageAccess
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.d("myz", "" + Build.VERSION.SDK_INT) Log.d("myz", "" + Build.VERSION.SDK_INT)
if (ContextCompat.checkSelfPermission( if (ContextCompat.checkSelfPermission(
@ -185,11 +184,13 @@ class SettingsActivity : AppCompatActivity(),
!= PackageManager.PERMISSION_GRANTED != PackageManager.PERMISSION_GRANTED
) { ) {
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(
this.requireActivity(), arrayOf( this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE Manifest.permission.MANAGE_EXTERNAL_STORAGE
), 1 ),
) //permission request code is just an int 1
) // permission request code is just an int
} else if (token != "") { } else if (token != "") {
getContent.launch("*/*") getContent.launch("*/*")
} else { } else {
@ -200,9 +201,9 @@ class SettingsActivity : AppCompatActivity(),
requireContext(), requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission( ) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(
requireContext(), requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ) != PackageManager.PERMISSION_GRANTED
) { ) {
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(
this.requireActivity(), this.requireActivity(),
@ -223,11 +224,15 @@ class SettingsActivity : AppCompatActivity(),
val themeToggle = findPreference<ListPreference>("theme_togglee") val themeToggle = findPreference<ListPreference>("theme_togglee")
themeToggle?.setOnPreferenceChangeListener { _, newValue -> themeToggle?.setOnPreferenceChangeListener { _, newValue ->
when (newValue.toString()) { val refresh = Intent(context, SettingsActivity::class.java)
"A" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) startActivity(refresh)
"L" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) true
"D" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) }
}
val accentColor = findPreference<Preference>("accent_color")
accentColor?.setOnPreferenceChangeListener { _, _ ->
val refresh = Intent(context, SettingsActivity::class.java)
startActivity(refresh)
true true
} }
@ -255,16 +260,16 @@ class SettingsActivity : AppCompatActivity(),
val license = findPreference<Preference>("license") val license = findPreference<Preference>("license")
license?.setOnPreferenceClickListener { license?.setOnPreferenceClickListener {
val licenseString = view?.context?.assets!!.open("gpl3.html").bufferedReader().use{ val licenseString = view?.context?.assets!!.open("gpl3.html").bufferedReader().use {
it.readText() it.readText()
} }
val licenseHtml = if (Build.VERSION.SDK_INT >= 24) { val licenseHtml = if (Build.VERSION.SDK_INT >= 24) {
Html.fromHtml(licenseString,1) Html.fromHtml(licenseString, 1)
} else { } else {
Html.fromHtml(licenseString) Html.fromHtml(licenseString)
} }
AlertDialog.Builder(view?.context!!) AlertDialog.Builder(view?.context!!)
.setPositiveButton(getString(R.string.okay), DialogInterface.OnClickListener{ _,_ -> }) .setPositiveButton(getString(R.string.okay), DialogInterface.OnClickListener { _, _ -> })
.setMessage(licenseHtml) .setMessage(licenseHtml)
.create() .create()
.show() .show()
@ -318,7 +323,6 @@ class SettingsActivity : AppCompatActivity(),
activity?.runOnUiThread(action) activity?.runOnUiThread(action)
} }
private fun subscribe(channels: List<String>) { private fun subscribe(channels: List<String>) {
fun run() { fun run() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {

View File

@ -3,13 +3,10 @@ package com.github.libretube
import android.content.Context import android.content.Context
import android.graphics.Rect import android.graphics.Rect
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.recyclerview.widget.RecyclerView
/** /**
* *
@ -20,7 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeSet? = null) : MotionLayout(context, attributeSet) { class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeSet? = null) : MotionLayout(context, attributeSet) {
private val viewToDetectTouch by lazy { private val viewToDetectTouch by lazy {
findViewById<View>(R.id.main_container) //TODO move to Attributes findViewById<View>(R.id.main_container) // TODO move to Attributes
} }
private val viewRect = Rect() private val viewRect = Rect()
private var touchStarted = false private var touchStarted = false
@ -33,7 +30,6 @@ class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeS
startId: Int, startId: Int,
endId: Int endId: Int
) { ) {
} }
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) { override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
@ -49,7 +45,6 @@ class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeS
positive: Boolean, positive: Boolean,
progress: Float progress: Float
) { ) {
} }
}) })
@ -59,7 +54,6 @@ class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeS
startId: Int, startId: Int,
endId: Int endId: Int
) { ) {
} }
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) { override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
@ -78,7 +72,6 @@ class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeS
positive: Boolean, positive: Boolean,
progress: Float progress: Float
) { ) {
} }
}) })
} }
@ -91,15 +84,18 @@ class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeS
transitionListenerList += listener transitionListenerList += listener
} }
private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { private val gestureDetector = GestureDetector(
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { context,
transitionToEnd() object : GestureDetector.SimpleOnGestureListener() {
return false override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
transitionToEnd()
return false
}
} }
}) )
override fun onTouchEvent(event: MotionEvent): Boolean { override fun onTouchEvent(event: MotionEvent): Boolean {
//gestureDetector.onTouchEvent(event) // gestureDetector.onTouchEvent(event)
when (event.actionMasked) { when (event.actionMasked) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
touchStarted = false touchStarted = false

View File

@ -3,29 +3,28 @@ package com.github.libretube
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.fragment.app.Fragment
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.* import android.widget.*
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
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 java.io.IOException
import org.chromium.base.ThreadUtils.runOnUiThread import org.chromium.base.ThreadUtils.runOnUiThread
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException
class Subscriptions : Fragment() { class Subscriptions : Fragment() {
val TAG = "SubFragment" val TAG = "SubFragment"
lateinit var token: String lateinit var token: String
var isLoaded = false var isLoaded = false
private var subscriptionAdapter: SubscriptionAdapter? =null private var subscriptionAdapter: SubscriptionAdapter? = null
private var refreshLayout: SwipeRefreshLayout? = null private var refreshLayout: SwipeRefreshLayout? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -34,7 +33,8 @@ class Subscriptions : Fragment() {
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
// Inflate the layout for this fragment // Inflate the layout for this fragment
@ -44,14 +44,14 @@ class Subscriptions : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
token = sharedPref?.getString("token","")!! token = sharedPref?.getString("token", "")!!
refreshLayout = view.findViewById(R.id.sub_refresh) refreshLayout = view.findViewById(R.id.sub_refresh)
if(token!=""){ if (token != "") {
view.findViewById<RelativeLayout>(R.id.loginOrRegister).visibility=View.GONE view.findViewById<RelativeLayout>(R.id.loginOrRegister).visibility = View.GONE
refreshLayout?.isEnabled = true refreshLayout?.isEnabled = true
var progressBar = view.findViewById<ProgressBar>(R.id.sub_progress) var progressBar = view.findViewById<ProgressBar>(R.id.sub_progress)
progressBar.visibility=View.VISIBLE progressBar.visibility = View.VISIBLE
var channelRecView = view.findViewById<RecyclerView>(R.id.sub_channels) var channelRecView = view.findViewById<RecyclerView>(R.id.sub_channels)
@ -78,8 +78,7 @@ class Subscriptions : Fragment() {
} }
channelRecView.visibility = View.VISIBLE channelRecView.visibility = View.VISIBLE
feedRecView.visibility = View.GONE feedRecView.visibility = View.GONE
} } else {
else {
channelRecView.visibility = View.GONE channelRecView.visibility = View.GONE
feedRecView.visibility = View.VISIBLE feedRecView.visibility = View.VISIBLE
} }
@ -89,14 +88,14 @@ class Subscriptions : Fragment() {
scrollView.viewTreeObserver scrollView.viewTreeObserver
.addOnScrollChangedListener { .addOnScrollChangedListener {
if (scrollView.getChildAt(0).bottom if (scrollView.getChildAt(0).bottom
== (scrollView.height + scrollView.scrollY)) { == (scrollView.height + scrollView.scrollY)
//scroll view is at bottom ) {
if(isLoaded){ // scroll view is at bottom
if (isLoaded) {
refreshLayout?.isRefreshing = true refreshLayout?.isRefreshing = true
subscriptionAdapter?.updateItems() subscriptionAdapter?.updateItems()
refreshLayout?.isRefreshing = false refreshLayout?.isRefreshing = false
} }
} }
} }
} else { } else {
@ -109,8 +108,8 @@ class Subscriptions : Fragment() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getFeed(token) RetrofitInstance.api.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")
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
@ -119,25 +118,25 @@ class Subscriptions : Fragment() {
} finally { } finally {
refreshLayout?.isRefreshing = false refreshLayout?.isRefreshing = false
} }
if (response.isNotEmpty()){ if (response.isNotEmpty()) {
subscriptionAdapter = SubscriptionAdapter(response) subscriptionAdapter = SubscriptionAdapter(response)
feedRecView?.adapter= subscriptionAdapter feedRecView?.adapter = subscriptionAdapter
subscriptionAdapter?.updateItems() subscriptionAdapter?.updateItems()
}else{ } else {
runOnUiThread { runOnUiThread {
with(view.findViewById<ImageView>(R.id.boogh)){ with(view.findViewById<ImageView>(R.id.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(view.findViewById<TextView>(R.id.textLike)) {
visibility=View.VISIBLE visibility = View.VISIBLE
text = getString(R.string.emptyList) text = getString(R.string.emptyList)
} }
view.findViewById<RelativeLayout>(R.id.loginOrRegister).visibility=View.VISIBLE view.findViewById<RelativeLayout>(R.id.loginOrRegister).visibility = View.VISIBLE
} }
} }
progressBar.visibility=View.GONE progressBar.visibility = View.GONE
isLoaded=true isLoaded = true
} }
} }
run() run()
@ -148,8 +147,8 @@ class Subscriptions : Fragment() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.subscriptions(token) RetrofitInstance.api.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")
return@launchWhenCreated return@launchWhenCreated
} catch (e: HttpException) { } catch (e: HttpException) {
@ -158,25 +157,24 @@ class Subscriptions : Fragment() {
} finally { } finally {
refreshLayout?.isRefreshing = false refreshLayout?.isRefreshing = false
} }
if (response.isNotEmpty()){ if (response.isNotEmpty()) {
channelRecView?.adapter=SubscriptionChannelAdapter(response.toMutableList()) channelRecView?.adapter = SubscriptionChannelAdapter(response.toMutableList())
}else{ } else {
Toast.makeText(context,R.string.subscribeIsEmpty, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.subscribeIsEmpty, Toast.LENGTH_SHORT).show()
} }
} }
} }
run() run()
} }
override fun onDestroy() { override fun onDestroy() {
Log.e(TAG,"Destroyed") Log.e(TAG, "Destroyed")
super.onDestroy() super.onDestroy()
subscriptionAdapter = null subscriptionAdapter = null
view?.findViewById<RecyclerView>(R.id.sub_feed)?.adapter=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
activity?.runOnUiThread(action) activity?.runOnUiThread(action)
} }
} }

View File

@ -0,0 +1,53 @@
package com.github.libretube
import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import java.util.*
fun updateAccentColor(context: Context) {
val colorAccent = PreferenceManager.getDefaultSharedPreferences(context).getString("accent_color", "red")
when (colorAccent) {
"my" -> context.setTheme(R.style.Theme_MY)
"red" -> context.setTheme(R.style.Theme_Red)
"blue" -> context.setTheme(R.style.Theme_Blue)
"yellow" -> context.setTheme(R.style.Theme_Yellow)
"green" -> context.setTheme(R.style.Theme_Green)
"purple" -> context.setTheme(R.style.Theme_Purple)
}
}
fun updateThemeMode(context: Context) {
val themeMode = PreferenceManager.getDefaultSharedPreferences(context).getString("theme_togglee", "A")
when (themeMode) {
"A" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"L" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
"D" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
"O" -> oledMode(context)
}
}
fun oledMode(context: Context) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
context.setTheme(R.style.Theme_OLED)
}
fun updateLanguage(context: Context) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val languageName = sharedPreferences.getString("language", "sys")
if (languageName != "") {
var 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()
}
val res = context.resources
val dm = res.displayMetrics
val conf = res.configuration
conf.setLocale(locale)
Locale.setDefault(locale)
res.updateConfiguration(conf, dm)
}
}

View File

@ -15,31 +15,31 @@ import com.github.libretube.formatShort
import com.github.libretube.obj.StreamItem import com.github.libretube.obj.StreamItem
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
class ChannelAdapter(private val videoFeed: MutableList<StreamItem>): RecyclerView.Adapter<ChannelViewHolder>() { class ChannelAdapter(private val videoFeed: MutableList<StreamItem>) : RecyclerView.Adapter<ChannelViewHolder>() {
override fun getItemCount(): Int { override fun getItemCount(): Int {
return videoFeed.size return videoFeed.size
} }
fun updateItems(newItems: List<StreamItem>){ fun updateItems(newItems: List<StreamItem>) {
videoFeed.addAll(newItems) videoFeed.addAll(newItems)
notifyDataSetChanged() notifyDataSetChanged()
} }
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 cell = layoutInflater.inflate(R.layout.video_channel_row, parent, false)
return ChannelViewHolder(cell) return ChannelViewHolder(cell)
} }
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.v.findViewById<TextView>(R.id.channel_description).text = trending.title
holder.v.findViewById<TextView>(R.id.channel_views).text = trending.views.formatShort()+""+ DateUtils.getRelativeTimeSpanString(trending.uploaded!!) holder.v.findViewById<TextView>(R.id.channel_views).text = trending.views.formatShort() + "" + DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
holder.v.findViewById<TextView>(R.id.channel_duration).text = DateUtils.formatElapsedTime(trending.duration!!) holder.v.findViewById<TextView>(R.id.channel_duration).text = DateUtils.formatElapsedTime(trending.duration!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.channel_thumbnail) val thumbnailImage = holder.v.findViewById<ImageView>(R.id.channel_thumbnail)
Picasso.get().load(trending.thumbnail).into(thumbnailImage) Picasso.get().load(trending.thumbnail).into(thumbnailImage)
holder.v.setOnClickListener{ holder.v.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 = holder.v.context as AppCompatActivity
@ -52,7 +52,7 @@ class ChannelAdapter(private val videoFeed: MutableList<StreamItem>): RecyclerVi
} }
} }
} }
class ChannelViewHolder(val v: View): RecyclerView.ViewHolder(v){ class ChannelViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init { init {
} }
} }

View File

@ -1,6 +1,5 @@
package com.github.libretube.adapters package com.github.libretube.adapters
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
@ -15,9 +14,9 @@ import com.github.libretube.formatShort
import com.github.libretube.obj.Comment import com.github.libretube.obj.Comment
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
class CommentsAdapter(private val comments: MutableList<Comment>): RecyclerView.Adapter<ViewHolder>(){ class CommentsAdapter(private val comments: MutableList<Comment>) : RecyclerView.Adapter<ViewHolder>() {
fun updateItems(newItems: List<Comment>){ fun updateItems(newItems: List<Comment>) {
var commentsSize = comments.size var commentsSize = comments.size
comments.addAll(newItems) comments.addAll(newItems)
notifyItemRangeInserted(commentsSize, newItems.size) notifyItemRangeInserted(commentsSize, newItems.size)
@ -43,7 +42,7 @@ class CommentsAdapter(private val comments: MutableList<Comment>): RecyclerView
if (comments[position].hearted == true) { if (comments[position].hearted == true) {
holder.v.findViewById<ImageView>(R.id.hearted_imageView).visibility = View.VISIBLE holder.v.findViewById<ImageView>(R.id.hearted_imageView).visibility = View.VISIBLE
} }
channelImage.setOnClickListener{ channelImage.setOnClickListener {
val activity = holder.v.context as MainActivity val activity = holder.v.context as MainActivity
val bundle = bundleOf("channel_id" to comments[position].commentorUrl) val bundle = bundleOf("channel_id" to comments[position].commentorUrl)
activity.navController.navigate(R.id.channel, bundle) activity.navController.navigate(R.id.channel, bundle)
@ -53,8 +52,7 @@ class CommentsAdapter(private val comments: MutableList<Comment>): RecyclerView
mainMotionLayout.transitionToEnd() mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd() activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
} }
}catch (e: Exception){ } catch (e: Exception) {
} }
} }
} }
@ -62,10 +60,9 @@ class CommentsAdapter(private val comments: MutableList<Comment>): RecyclerView
override fun getItemCount(): Int { override fun getItemCount(): Int {
return comments.size return comments.size
} }
} }
class ViewHolder(val v: View): RecyclerView.ViewHolder(v){ class ViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init { init {
} }
} }

View File

@ -18,24 +18,29 @@ import com.github.libretube.RetrofitInstance
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.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import java.io.IOException
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException
class PlaylistAdapter(private val videoFeed: MutableList<StreamItem>, private val playlistId: String, private val isOwner: Boolean, private val activity: Activity): RecyclerView.Adapter<PlaylistViewHolder>() { class PlaylistAdapter(
private val videoFeed: MutableList<StreamItem>,
private val playlistId: String,
private val isOwner: Boolean,
private val activity: Activity
) : 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
} }
fun updateItems(newItems: List<StreamItem>){ fun updateItems(newItems: List<StreamItem>) {
videoFeed.addAll(newItems) videoFeed.addAll(newItems)
notifyDataSetChanged() notifyDataSetChanged()
} }
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 cell = layoutInflater.inflate(R.layout.playlist_row, parent, false)
return PlaylistViewHolder(cell) return PlaylistViewHolder(cell)
} }
@ -46,9 +51,9 @@ class PlaylistAdapter(private val videoFeed: MutableList<StreamItem>, private va
holder.v.findViewById<TextView>(R.id.playlist_duration).text = DateUtils.formatElapsedTime(streamItem.duration!!) holder.v.findViewById<TextView>(R.id.playlist_duration).text = DateUtils.formatElapsedTime(streamItem.duration!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.playlist_thumbnail) val thumbnailImage = holder.v.findViewById<ImageView>(R.id.playlist_thumbnail)
Picasso.get().load(streamItem.thumbnail).into(thumbnailImage) Picasso.get().load(streamItem.thumbnail).into(thumbnailImage)
holder.v.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=", ""))
var frag = PlayerFragment() var frag = PlayerFragment()
frag.arguments = bundle frag.arguments = bundle
val activity = holder.v.context as AppCompatActivity val activity = holder.v.context as AppCompatActivity
@ -59,34 +64,33 @@ class PlaylistAdapter(private val videoFeed: MutableList<StreamItem>, private va
.replace(R.id.container, frag) .replace(R.id.container, frag)
.commitNow() .commitNow()
} }
if(isOwner){ if (isOwner) {
val delete = holder.v.findViewById<ImageView>(R.id.delete_playlist) val delete = holder.v.findViewById<ImageView>(R.id.delete_playlist)
delete.visibility = View.VISIBLE delete.visibility = View.VISIBLE
delete.setOnClickListener { delete.setOnClickListener {
val sharedPref = holder.v.context.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = holder.v.context.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token","")!! val token = sharedPref?.getString("token", "")!!
removeFromPlaylist(token, position) removeFromPlaylist(token, position)
} }
} }
} }
private fun removeFromPlaylist(token: String, position: Int) { private fun removeFromPlaylist(token: String, position: Int) {
fun run() { fun run() {
GlobalScope.launch{ GlobalScope.launch {
val response = try { val response = try {
RetrofitInstance.api.removeFromPlaylist(token, PlaylistId(playlistId = playlistId, index = position)) RetrofitInstance.api.removeFromPlaylist(token, PlaylistId(playlistId = playlistId, index = position))
}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")
return@launch return@launch
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
return@launch return@launch
}finally { } finally {
} }
try{ try {
if(response.message == "ok"){ if (response.message == "ok") {
Log.d(TAG,"deleted!") Log.d(TAG, "deleted!")
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() }
@ -95,17 +99,15 @@ class PlaylistAdapter(private val videoFeed: MutableList<StreamItem>, private va
view.findViewById<ImageView>(R.id.boogh2).visibility=View.VISIBLE 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())
} }
} }
} }
run() run()
} }
} }
class PlaylistViewHolder(val v: View): RecyclerView.ViewHolder(v){ class PlaylistViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init { init {
} }
} }

View File

@ -17,25 +17,27 @@ import com.github.libretube.RetrofitInstance
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.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import java.io.IOException
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException
class PlaylistsAdapter(
class PlaylistsAdapter(private val playlists: MutableList<Playlists>, private val activity: Activity): RecyclerView.Adapter<PlaylistsViewHolder>() { private val playlists: MutableList<Playlists>,
private val activity: Activity
) : RecyclerView.Adapter<PlaylistsViewHolder>() {
val TAG = "PlaylistsAdapter" val TAG = "PlaylistsAdapter"
override fun getItemCount(): Int { override fun getItemCount(): Int {
return playlists.size return playlists.size
} }
fun updateItems(newItems: List<Playlists>){ fun updateItems(newItems: List<Playlists>) {
playlists.addAll(newItems) playlists.addAll(newItems)
notifyDataSetChanged() notifyDataSetChanged()
} }
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 cell = layoutInflater.inflate(R.layout.playlists_row, parent, false)
return PlaylistsViewHolder(cell) return PlaylistsViewHolder(cell)
} }
@ -50,7 +52,7 @@ class PlaylistsAdapter(private val playlists: MutableList<Playlists>, private va
builder.setMessage(R.string.areYouSure) builder.setMessage(R.string.areYouSure)
builder.setPositiveButton(R.string.yes) { dialog, which -> builder.setPositiveButton(R.string.yes) { dialog, which ->
val sharedPref = holder.v.context.getSharedPreferences("token", Context.MODE_PRIVATE) val sharedPref = holder.v.context.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token","")!! val token = sharedPref?.getString("token", "")!!
deletePlaylist(playlist.id!!, token, position) deletePlaylist(playlist.id!!, token, position)
} }
builder.setNegativeButton(R.string.cancel) { dialog, which -> builder.setNegativeButton(R.string.cancel) { dialog, which ->
@ -58,31 +60,29 @@ class PlaylistsAdapter(private val playlists: MutableList<Playlists>, private va
builder.show() builder.show()
} }
holder.v.setOnClickListener { holder.v.setOnClickListener {
//playlists clicked // playlists clicked
val activity = holder.v.context as MainActivity val activity = holder.v.context as MainActivity
val bundle = bundleOf("playlist_id" to playlist.id) val bundle = bundleOf("playlist_id" to playlist.id)
activity.navController.navigate(R.id.playlistFragment,bundle) 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{ GlobalScope.launch {
val response = try { val response = try {
RetrofitInstance.api.deletePlaylist(token, PlaylistId(id)) RetrofitInstance.api.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")
return@launch return@launch
} catch (e: HttpException) { } catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response") Log.e(TAG, "HttpException, unexpected response")
return@launch return@launch
}finally { } finally {
} }
try{ try {
if(response.message == "ok"){ if (response.message == "ok") {
Log.d(TAG,"deleted!") Log.d(TAG, "deleted!")
playlists.removeAt(position) playlists.removeAt(position)
// FIXME: This needs to run on UI thread? // FIXME: This needs to run on UI thread?
activity.runOnUiThread { notifyDataSetChanged() } activity.runOnUiThread { notifyDataSetChanged() }
@ -91,18 +91,15 @@ class PlaylistsAdapter(private val playlists: MutableList<Playlists>, private va
view.findViewById<ImageView>(R.id.boogh2).visibility=View.VISIBLE 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())
} }
} }
} }
run() run()
} }
} }
class PlaylistsViewHolder(val v: View): RecyclerView.ViewHolder(v){ class PlaylistsViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init { init {
} }
} }

View File

@ -17,10 +17,9 @@ import com.github.libretube.formatShort
import com.github.libretube.obj.SearchItem import com.github.libretube.obj.SearchItem
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
class SearchAdapter(private val searchItems: MutableList<SearchItem>) : RecyclerView.Adapter<CustomViewHolder1>() {
class SearchAdapter(private val searchItems: MutableList<SearchItem>): RecyclerView.Adapter<CustomViewHolder1>() { fun updateItems(newItems: List<SearchItem>) {
fun updateItems(newItems: List<SearchItem>){
var searchItemsSize = searchItems.size var searchItemsSize = searchItems.size
searchItems.addAll(newItems) searchItems.addAll(newItems)
notifyItemRangeInserted(searchItemsSize, newItems.size) notifyItemRangeInserted(searchItemsSize, newItems.size)
@ -38,7 +37,7 @@ class SearchAdapter(private val searchItems: MutableList<SearchItem>): RecyclerV
else -> throw IllegalArgumentException("Invalid type") else -> throw IllegalArgumentException("Invalid type")
} }
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(layout,parent,false) val cell = layoutInflater.inflate(layout, parent, false)
return CustomViewHolder1(cell) return CustomViewHolder1(cell)
} }
@ -48,14 +47,14 @@ class SearchAdapter(private val searchItems: MutableList<SearchItem>): RecyclerV
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when { return when {
searchItems[position].url!!.startsWith("/watch",false) -> 0 searchItems[position].url!!.startsWith("/watch", false) -> 0
searchItems[position].url!!.startsWith("/channel",false) -> 1 searchItems[position].url!!.startsWith("/channel", false) -> 1
searchItems[position].url!!.startsWith("/playlist",false) -> 2 searchItems[position].url!!.startsWith("/playlist", false) -> 2
else -> 3 else -> 3
} }
} }
} }
class CustomViewHolder1(private val v: View): RecyclerView.ViewHolder(v){ class CustomViewHolder1(private val v: View) : RecyclerView.ViewHolder(v) {
private fun bindWatch(item: SearchItem) { private fun bindWatch(item: SearchItem) {
val thumbnailImage = v.findViewById<ImageView>(R.id.search_thumbnail) val thumbnailImage = v.findViewById<ImageView>(R.id.search_thumbnail)
@ -72,9 +71,9 @@ class CustomViewHolder1(private val v: View): RecyclerView.ViewHolder(v){
views.text = if (viewsString != "" && uploadDate != "") viewsString + "" + uploadDate else viewsString + uploadDate views.text = if (viewsString != "" && uploadDate != "") viewsString + "" + uploadDate else viewsString + uploadDate
val channelName = v.findViewById<TextView>(R.id.search_channel_name) val channelName = v.findViewById<TextView>(R.id.search_channel_name)
channelName.text = item.uploaderName channelName.text = item.uploaderName
v.setOnClickListener{ v.setOnClickListener {
var bundle = Bundle() var bundle = Bundle()
bundle.putString("videoId",item.url!!.replace("/watch?v=","")) bundle.putString("videoId", item.url!!.replace("/watch?v=", ""))
var frag = PlayerFragment() var frag = PlayerFragment()
frag.arguments = bundle frag.arguments = bundle
val activity = v.context as AppCompatActivity val activity = v.context as AppCompatActivity
@ -101,9 +100,9 @@ class CustomViewHolder1(private val v: View): RecyclerView.ViewHolder(v){
v.setOnClickListener { v.setOnClickListener {
val activity = v.context as MainActivity val activity = v.context as MainActivity
val bundle = bundleOf("channel_id" to item.url) val bundle = bundleOf("channel_id" to item.url)
activity.navController.navigate(R.id.channel,bundle) activity.navController.navigate(R.id.channel, bundle)
} }
//todo sub button // todo sub button
} }
private fun bindPlaylist(item: SearchItem) { private fun bindPlaylist(item: SearchItem) {
val playlistImage = v.findViewById<ImageView>(R.id.search_thumbnail) val playlistImage = v.findViewById<ImageView>(R.id.search_thumbnail)
@ -117,18 +116,18 @@ class CustomViewHolder1(private val v: View): RecyclerView.ViewHolder(v){
val playlistVideosNumber = v.findViewById<TextView>(R.id.search_playlist_videos) 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()) if (item.videos?.toInt() != -1) playlistVideosNumber.text = v.context.getString(R.string.videoCount, item.videos.toString())
v.setOnClickListener { v.setOnClickListener {
//playlist clicked // playlist clicked
val activity = v.context as MainActivity val activity = v.context as MainActivity
val bundle = bundleOf("playlist_id" to item.url) val bundle = bundleOf("playlist_id" to item.url)
activity.navController.navigate(R.id.playlistFragment,bundle) activity.navController.navigate(R.id.playlistFragment, bundle)
} }
} }
fun bind(searchItem: SearchItem) { fun bind(searchItem: SearchItem) {
when { when {
searchItem.url!!.startsWith("/watch",false) -> bindWatch(searchItem) searchItem.url!!.startsWith("/watch", false) -> bindWatch(searchItem)
searchItem.url!!.startsWith("/channel",false) -> bindChannel(searchItem) searchItem.url!!.startsWith("/channel", false) -> bindChannel(searchItem)
searchItem.url!!.startsWith("/playlist",false) -> bindPlaylist(searchItem) searchItem.url!!.startsWith("/playlist", false) -> bindPlaylist(searchItem)
else -> { else -> {
} }
} }

View File

@ -11,16 +11,17 @@ import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.imageview.ShapeableImageView
class SearchHistoryAdapter(
class SearchHistoryAdapter(private val context: Context, private var historyList: List<String> , private val editText : AutoCompleteTextView) : private val context: Context,
private var historyList: List<String>,
private val editText: AutoCompleteTextView
) :
RecyclerView.Adapter<SearchHistoryViewHolder>() { RecyclerView.Adapter<SearchHistoryViewHolder>() {
override fun getItemCount(): Int { override fun getItemCount(): Int {
return historyList.size return historyList.size
} }
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 cell = layoutInflater.inflate(R.layout.searchhistory_row, parent, false)
@ -31,7 +32,6 @@ class SearchHistoryAdapter(private val context: Context, private var historyList
val history = historyList[position] val history = historyList[position]
holder.v.findViewById<TextView>(R.id.history_text).text = history holder.v.findViewById<TextView>(R.id.history_text).text = history
holder.v.findViewById<ShapeableImageView>(R.id.delete_history).setOnClickListener { holder.v.findViewById<ShapeableImageView>(R.id.delete_history).setOnClickListener {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)

View File

@ -18,35 +18,35 @@ import com.github.libretube.formatShort
import com.github.libretube.obj.StreamItem import com.github.libretube.obj.StreamItem
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
class SubscriptionAdapter(private val videoFeed: List<StreamItem>): RecyclerView.Adapter<SubscriptionViewHolder>() { class SubscriptionAdapter(private val videoFeed: List<StreamItem>) : RecyclerView.Adapter<SubscriptionViewHolder>() {
//private var limitedVideoFeed: MutableList<String> = [""].toMutableList() // 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("") // limitedVideoFeed.add("")
i += 10 i += 10
if(i>videoFeed.size) if (i > videoFeed.size)
i=videoFeed.size i = videoFeed.size
notifyDataSetChanged() notifyDataSetChanged()
} }
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 cell = layoutInflater.inflate(R.layout.trending_row, parent, false)
return SubscriptionViewHolder(cell) return SubscriptionViewHolder(cell)
} }
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.v.findViewById<TextView>(R.id.textView_title).text = trending.title
holder.v.findViewById<TextView>(R.id.textView_channel).text = trending.uploaderName +""+ trending.views.formatShort()+""+ DateUtils.getRelativeTimeSpanString(trending.uploaded!!) holder.v.findViewById<TextView>(R.id.textView_channel).text = trending.uploaderName + "" + trending.views.formatShort() + "" + DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.thumbnail) val thumbnailImage = holder.v.findViewById<ImageView>(R.id.thumbnail)
holder.v.findViewById<TextView>(R.id.thumbnail_duration).text = DateUtils.formatElapsedTime(trending.duration!!) holder.v.findViewById<TextView>(R.id.thumbnail_duration).text = DateUtils.formatElapsedTime(trending.duration!!)
val channelImage = holder.v.findViewById<ImageView>(R.id.channel_image) val channelImage = holder.v.findViewById<ImageView>(R.id.channel_image)
channelImage.setOnClickListener{ channelImage.setOnClickListener {
val activity = holder.v.context as MainActivity val activity = holder.v.context as MainActivity
val bundle = bundleOf("channel_id" to trending.uploaderUrl) val bundle = bundleOf("channel_id" to trending.uploaderUrl)
activity.navController.navigate(R.id.channel, bundle) activity.navController.navigate(R.id.channel, bundle)
@ -56,15 +56,14 @@ class SubscriptionAdapter(private val videoFeed: List<StreamItem>): RecyclerView
mainMotionLayout.transitionToEnd() mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd() activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
} }
}catch (e: Exception){ } catch (e: Exception) {
} }
} }
Picasso.get().load(trending.thumbnail).into(thumbnailImage) Picasso.get().load(trending.thumbnail).into(thumbnailImage)
Picasso.get().load(trending.uploaderAvatar).into(channelImage) Picasso.get().load(trending.uploaderAvatar).into(channelImage)
holder.v.setOnClickListener{ holder.v.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 = holder.v.context as AppCompatActivity
@ -77,7 +76,7 @@ class SubscriptionAdapter(private val videoFeed: List<StreamItem>): RecyclerView
} }
} }
} }
class SubscriptionViewHolder(val v: View): RecyclerView.ViewHolder(v){ class SubscriptionViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init { init {
} }
} }

View File

@ -12,30 +12,30 @@ import com.github.libretube.R
import com.github.libretube.obj.Subscription import com.github.libretube.obj.Subscription
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscription>): RecyclerView.Adapter<SubscriptionChannelViewHolder>() { class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscription>) : RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
override fun getItemCount(): Int { override fun getItemCount(): Int {
return subscriptions.size return subscriptions.size
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionChannelViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 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 cell = layoutInflater.inflate(R.layout.channel_subscription_row, parent, false)
return SubscriptionChannelViewHolder(cell) return SubscriptionChannelViewHolder(cell)
} }
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.v.findViewById<TextView>(R.id.subscription_channel_name).text = subscription.name
val avatar = holder.v.findViewById<ImageView>(R.id.subscription_channel_image) val avatar = holder.v.findViewById<ImageView>(R.id.subscription_channel_image)
Picasso.get().load(subscription.avatar).into(avatar) Picasso.get().load(subscription.avatar).into(avatar)
holder.v.setOnClickListener{ holder.v.setOnClickListener {
val activity = holder.v.context as MainActivity val activity = holder.v.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.channel, bundle)
} }
} }
} }
class SubscriptionChannelViewHolder(val v: View): RecyclerView.ViewHolder(v){ class SubscriptionChannelViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init { init {
} }
} }

View File

@ -18,25 +18,25 @@ import com.github.libretube.formatShort
import com.github.libretube.obj.StreamItem import com.github.libretube.obj.StreamItem
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
class TrendingAdapter(private val videoFeed: List<StreamItem>): RecyclerView.Adapter<CustomViewHolder>() { class TrendingAdapter(private val videoFeed: List<StreamItem>) : RecyclerView.Adapter<CustomViewHolder>() {
override fun getItemCount(): Int { override fun getItemCount(): Int {
return videoFeed.size return videoFeed.size
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
val cell = layoutInflater.inflate(R.layout.trending_row,parent,false) val cell = layoutInflater.inflate(R.layout.trending_row, parent, false)
return CustomViewHolder(cell) return CustomViewHolder(cell)
} }
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val trending = videoFeed[position] val trending = videoFeed[position]
holder.v.findViewById<TextView>(R.id.textView_title).text = trending.title holder.v.findViewById<TextView>(R.id.textView_title).text = trending.title
holder.v.findViewById<TextView>(R.id.textView_channel).text = trending.uploaderName +""+ trending.views.formatShort()+""+DateUtils.getRelativeTimeSpanString(trending.uploaded!!) holder.v.findViewById<TextView>(R.id.textView_channel).text = trending.uploaderName + "" + trending.views.formatShort() + "" + DateUtils.getRelativeTimeSpanString(trending.uploaded!!)
val thumbnailImage = holder.v.findViewById<ImageView>(R.id.thumbnail) val thumbnailImage = holder.v.findViewById<ImageView>(R.id.thumbnail)
holder.v.findViewById<TextView>(R.id.thumbnail_duration).text = DateUtils.formatElapsedTime(trending.duration!!) holder.v.findViewById<TextView>(R.id.thumbnail_duration).text = DateUtils.formatElapsedTime(trending.duration!!)
val channelImage = holder.v.findViewById<ImageView>(R.id.channel_image) val channelImage = holder.v.findViewById<ImageView>(R.id.channel_image)
channelImage.setOnClickListener{ channelImage.setOnClickListener {
val activity = holder.v.context as MainActivity val activity = holder.v.context as MainActivity
val bundle = bundleOf("channel_id" to trending.uploaderUrl) val bundle = bundleOf("channel_id" to trending.uploaderUrl)
activity.navController.navigate(R.id.channel, bundle) activity.navController.navigate(R.id.channel, bundle)
@ -46,23 +46,21 @@ class TrendingAdapter(private val videoFeed: List<StreamItem>): RecyclerView.Ada
mainMotionLayout.transitionToEnd() mainMotionLayout.transitionToEnd()
activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd() activity.findViewById<MotionLayout>(R.id.playerMotionLayout).transitionToEnd()
} }
}catch (e: Exception){ } catch (e: Exception) {
} }
} }
if (trending.thumbnail!!.isEmpty()) { if (trending.thumbnail!!.isEmpty()) {
} else{ } else {
Picasso.get().load(trending.thumbnail).into(thumbnailImage) Picasso.get().load(trending.thumbnail).into(thumbnailImage)
} }
if (trending.uploaderAvatar!!.isEmpty()) { if (trending.uploaderAvatar!!.isEmpty()) {
} else{ } else {
Picasso.get().load(trending.uploaderAvatar).into(channelImage) Picasso.get().load(trending.uploaderAvatar).into(channelImage)
} }
holder.v.setOnClickListener {
holder.v.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 = holder.v.context as AppCompatActivity
@ -75,7 +73,7 @@ class TrendingAdapter(private val videoFeed: List<StreamItem>): RecyclerView.Ada
} }
} }
} }
class CustomViewHolder(val v: View): RecyclerView.ViewHolder(v){ class CustomViewHolder(val v: View) : RecyclerView.ViewHolder(v) {
init { init {
} }
} }

View File

@ -2,6 +2,4 @@ package com.github.libretube
import android.app.Application import android.app.Application
class myApp : Application() { class myApp : Application()
}

View File

@ -7,6 +7,6 @@ data class ChapterSegment(
var title: String?, var title: String?,
var image: String?, var image: String?,
var start: Int? var start: Int?
){ ) {
constructor(): this("","",-1) constructor() : this("", "", -1)
} }

View File

@ -14,7 +14,6 @@ data class Comment(
val pinned: Boolean?, val pinned: Boolean?,
val thumbnail: String?, val thumbnail: String?,
val verified: Boolean? val verified: Boolean?
){ ) {
constructor(): this("", "","","","",null,0,null,"",null) constructor() : this("", "", "", "", "", null, 0, null, "", null)
} }

View File

@ -4,9 +4,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class CommentsPage( data class CommentsPage(
val comments: MutableList<Comment> = arrayListOf(), val comments: MutableList<Comment> = arrayListOf(),
val disabled: Boolean? = null, val disabled: Boolean? = null,
val nextpage: String? = "", val nextpage: String? = "",
){ ) {
constructor(): this(arrayListOf(),null,"") constructor() : this(arrayListOf(), null, "")
} }

View File

@ -18,6 +18,6 @@ data class PipedStream(
var width: Int?, var width: Int?,
var height: Int?, var height: Int?,
var fps: Int? var fps: Int?
){ ) {
constructor(): this("","","","","",null,-1,-1,-1,-1,-1,-1,-1,-1) constructor() : this("", "", "", "", "", null, -1, -1, -1, -1, -1, -1, -1, -1)
} }

View File

@ -9,7 +9,7 @@ data class SearchItem(
var uploaderName: String?, var uploaderName: String?,
var uploaded: Long?, var uploaded: Long?,
var shortDescription: String?, var shortDescription: String?,
//Video only attributes // Video only attributes
var title: String?, var title: String?,
var uploaderUrl: String?, var uploaderUrl: String?,
var uploaderAvatar: String?, var uploaderAvatar: String?,
@ -17,12 +17,12 @@ data class SearchItem(
var duration: Long?, var duration: Long?,
var views: Long?, var views: Long?,
var uploaderVerified: Boolean?, var uploaderVerified: Boolean?,
//Channel and Playlist attributes // Channel and Playlist attributes
var name: String? = null, var name: String? = null,
var description: String? = null, var description: String? = null,
var subscribers: Long? = -1, var subscribers: Long? = -1,
var videos: Long? = -1, var videos: Long? = -1,
var verified: Boolean? = null var verified: Boolean? = null
){ ) {
constructor() : this("","","",0,"","","","","",0,0,null) constructor() : this("", "", "", 0, "", "", "", "", "", 0, 0, null)
} }

View File

@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class SearchResult( data class SearchResult(
val items: MutableList<SearchItem>? = arrayListOf(), val items: MutableList<SearchItem>? = arrayListOf(),
val nextpage: String? ="", val nextpage: String? = "",
val suggestion: String?="", val suggestion: String? = "",
val corrected: Boolean? = null val corrected: Boolean? = null
) )

View File

@ -7,6 +7,6 @@ data class Segment(
val actionType: String?, val actionType: String?,
val category: String?, val category: String?,
val segment: List<Float>? val segment: List<Float>?
){ ) {
constructor(): this("", "", arrayListOf()) constructor() : this("", "", arrayListOf())
} }

View File

@ -5,6 +5,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class Segments( data class Segments(
val segments: MutableList<Segment> = arrayListOf() val segments: MutableList<Segment> = arrayListOf()
){ ) {
constructor(): this(arrayListOf()) constructor() : this(arrayListOf())
} }

View File

@ -16,6 +16,6 @@ data class StreamItem(
var uploaderVerified: Boolean?, var uploaderVerified: Boolean?,
var uploaded: Long?, var uploaded: Long?,
var shortDescription: String? var shortDescription: String?
){ ) {
constructor() : this("","","","","","","",0,0,null,0,"") constructor() : this("", "", "", "", "", "", "", 0, 0, null, 0, "")
} }

View File

@ -26,7 +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>?
){ ) {
constructor(): this("","","","","","","","","","",null,-1,-1,-1,-1, emptyList(), emptyList(), constructor() : this(
emptyList(), emptyList(), null,"", emptyList()) "", "", "", "", "", "", "", "", "", "", null, -1, -1, -1, -1, emptyList(), emptyList(),
emptyList(), emptyList(), null, "", emptyList()
)
} }

View File

@ -9,6 +9,6 @@ data class Subtitle(
val name: String?, val name: String?,
val code: String?, val code: String?,
val autoGenerated: Boolean? val autoGenerated: Boolean?
){ ) {
constructor(): this("","","","",null) constructor() : this("", "", "", "", null)
} }

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>

View File

@ -50,7 +50,7 @@
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:text="@string/subscribe" android:text="@string/subscribe"
android:textColor="@color/colorPrimary" android:textColor="?attr/colorPrimary"
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toEndOf="@+id/search_channel_image" /> app:layout_constraintStart_toEndOf="@+id/search_channel_image" />

View File

@ -97,6 +97,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="15dp" android:layout_marginBottom="15dp"
android:text="" /> android:text="" />

View File

@ -236,7 +236,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:backgroundTint="?android:attr/colorBackground" android:backgroundTint="?attr/colorOnPrimary"
android:drawableLeft="@drawable/ic_bell" android:drawableLeft="@drawable/ic_bell"
android:drawableTint="?android:attr/textColorPrimary" android:drawableTint="?android:attr/textColorPrimary"
android:text="@string/subscribe" android:text="@string/subscribe"

View File

@ -1,36 +1,104 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.LibreTube" parent="Theme.Material3.Dark.NoActionBar"> <style name="Theme.MY" parent="Theme.Material3.Dark.NoActionBar"></style>
<item name="colorPrimary">@color/md_theme_dark_primary</item>
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item> <style name="Theme.Red" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimaryContainer">@color/md_theme_dark_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/md_theme_dark_onPrimaryContainer</item> <item name="colorPrimary">@color/red_dark_accentLight</item> // container
<item name="colorSecondary">@color/md_theme_dark_secondary</item> <item name="colorOnPrimary">@color/red_dark_accentDark</item> // title
<item name="colorOnSecondary">@color/md_theme_dark_onSecondary</item> <item name="colorPrimaryContainer">@color/red_dark_accentLight</item> //
<item name="colorSecondaryContainer">@color/md_theme_dark_secondaryContainer</item> <item name="colorOnPrimaryContainer">@color/red_dark_accentLight</item> //
<item name="colorOnSecondaryContainer">@color/md_theme_dark_onSecondaryContainer</item> <item name="colorSecondary">@color/red_dark_accentLight</item> // Settings Categories
<item name="colorTertiary">@color/md_theme_dark_tertiary</item> <item name="colorOnSecondary">@color/red_dark_accentDark</item> //
<item name="colorOnTertiary">@color/md_theme_dark_onTertiary</item> <item name="colorSecondaryContainer">@color/red_dark_accentDark</item> // navbar surround
<item name="colorTertiaryContainer">@color/md_theme_dark_tertiaryContainer</item> <!-- <item name="colorOnSecondaryContainer">@color/red_dark_background</item> --> // navbar icon fill
<item name="colorOnTertiaryContainer">@color/md_theme_dark_onTertiaryContainer</item> <item name="android:colorBackground">@color/red_dark_background</item> // background
<item name="colorError">@color/md_theme_dark_error</item> <item name="colorOnBackground">@color/red_dark_background</item>
<item name="colorErrorContainer">@color/md_theme_dark_errorContainer</item>
<item name="colorOnError">@color/md_theme_dark_onError</item>
<item name="colorOnErrorContainer">@color/md_theme_dark_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_dark_background</item>
<item name="colorOnBackground">@color/md_theme_dark_onBackground</item>
<item name="colorSurface">@color/md_theme_dark_surface</item>
<item name="colorOnSurface">@color/md_theme_dark_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_dark_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_dark_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_dark_outline</item>
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
<item name="android:statusBarColor" tools:targetApi="m">@android:color/transparent</item> <item name="android:statusBarColor" tools:targetApi="m">@android:color/transparent</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item> <item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">@android:color/transparent</item>
</style> </style>
<style name="Theme.Blue" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/blue_dark_accentLight</item> // container
<item name="colorOnPrimary">@color/blue_dark_accentDark</item> // title
<item name="colorPrimaryContainer">@color/blue_dark_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/blue_dark_accentLight</item> //
<item name="colorSecondary">@color/blue_dark_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/blue_dark_accentDark</item> //
<item name="colorSecondaryContainer">@color/blue_dark_accentDark</item> // navbar surround
<!-- <item name="colorOnSecondaryContainer">@color/blue_dark_background</item> --> // navbar icon fill
<item name="android:colorBackground">@color/blue_dark_background</item> // background
<item name="colorOnBackground">@color/blue_dark_background</item>
<item name="android:statusBarColor" tools:targetApi="m">@android:color/transparent</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Yellow" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/yellow_dark_accentLight</item> // container
<item name="colorOnPrimary">@color/yellow_dark_accentDark</item> // title
<item name="colorPrimaryContainer">@color/yellow_dark_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/yellow_dark_accentLight</item> //
<item name="colorSecondary">@color/yellow_dark_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/yellow_dark_accentDark</item> //
<item name="colorSecondaryContainer">@color/yellow_dark_accentDark</item> // navbar surround
<!-- <item name="colorOnSecondaryContainer">@color/yellow_dark_background</item> --> // navbar icon fill
<item name="android:colorBackground">@color/yellow_dark_background</item> // background
<item name="colorOnBackground">@color/yellow_dark_background</item>
<item name="android:statusBarColor" tools:targetApi="m">@android:color/transparent</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Green" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/green_dark_accentLight</item> // container
<item name="colorOnPrimary">@color/green_dark_accentDark</item> // title
<item name="colorPrimaryContainer">@color/green_dark_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/green_dark_accentLight</item> //
<item name="colorSecondary">@color/green_dark_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/green_dark_accentDark</item> //
<item name="colorSecondaryContainer">@color/green_dark_accentDark</item> // navbar surround
<!-- <item name="colorOnSecondaryContainer">@color/green_dark_background</item> --> // navbar icon fill
<item name="android:colorBackground">@color/green_dark_background</item> // background
<item name="colorOnBackground">@color/green_dark_background</item>
<item name="android:statusBarColor" tools:targetApi="m">@android:color/transparent</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Purple" parent="Theme.Material3.Dark.NoActionBar">
<item name="colorPrimary">@color/purple_dark_accentLight</item> // container
<item name="colorOnPrimary">@color/purple_dark_accentDark</item> // title
<item name="colorPrimaryContainer">@color/purple_dark_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/purple_dark_accentLight</item> //
<item name="colorSecondary">@color/purple_dark_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/purple_dark_accentDark</item> //
<item name="colorSecondaryContainer">@color/purple_dark_accentDark</item> // navbar surround
<!-- <item name="colorOnSecondaryContainer">@color/purple_dark_background</item> --> // navbar icon fill
<item name="android:colorBackground">@color/purple_dark_background</item> // background
<item name="colorOnBackground">@color/purple_dark_background</item>
<item name="android:statusBarColor" tools:targetApi="m">@android:color/transparent</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.OLED" parent="Theme.Material3.Dark.NoActionBar">
<item name="android:colorBackground">@color/black</item>
</style>
</resources> </resources>

View File

@ -1,37 +1,100 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.LibreTube" parent="Theme.Material3.Light.NoActionBar"> <style name="Theme.MY" parent="Theme.Material3.Light.NoActionBar"></style>
<item name="colorPrimary">@color/md_theme_light_primary</item>
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item> <style name="Theme.Red" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimaryContainer">@color/md_theme_light_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/md_theme_light_onPrimaryContainer</item> <item name="colorPrimary">@color/red_light_accentLight</item> // container
<item name="colorSecondary">@color/md_theme_light_secondary</item> <item name="colorOnPrimary">@color/red_light_accentDark</item> // title
<item name="colorOnSecondary">@color/md_theme_light_onSecondary</item> <item name="colorPrimaryContainer">@color/red_light_accentLight</item> //
<item name="colorSecondaryContainer">@color/md_theme_light_secondaryContainer</item> <item name="colorOnPrimaryContainer">@color/red_light_accentLight</item> //
<item name="colorOnSecondaryContainer">@color/md_theme_light_onSecondaryContainer</item> <item name="colorSecondary">@color/red_light_accentLight</item> // Settings Categories
<item name="colorTertiary">@color/md_theme_light_tertiary</item> <item name="colorOnSecondary">@color/red_light_accentDark</item> //
<item name="colorOnTertiary">@color/md_theme_light_onTertiary</item> <item name="colorSecondaryContainer">@color/red_light_accentDark</item> // navbar surround
<item name="colorTertiaryContainer">@color/md_theme_light_tertiaryContainer</item> <item name="colorOnSecondaryContainer">@color/red_light_background</item> // navbar icon fill
<item name="colorOnTertiaryContainer">@color/md_theme_light_onTertiaryContainer</item> <item name="android:colorBackground">@color/red_light_background</item> // background
<item name="colorError">@color/md_theme_light_error</item> <item name="colorOnBackground">@color/red_light_background</item>
<item name="colorErrorContainer">@color/md_theme_light_errorContainer</item>
<item name="colorOnError">@color/md_theme_light_onError</item>
<item name="colorOnErrorContainer">@color/md_theme_light_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_light_background</item>
<item name="colorOnBackground">@color/md_theme_light_onBackground</item>
<item name="colorSurface">@color/md_theme_light_surface</item>
<item name="colorOnSurface">@color/md_theme_light_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_light_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_light_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_light_outline</item>
<item name="colorOnSurfaceInverse">@color/md_theme_light_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
<item name="android:statusBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item> <item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">@android:color/transparent</item>
</style> </style>
<style name="Theme.Blue" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/blue_light_accentLight</item> // container
<item name="colorOnPrimary">@color/blue_light_accentDark</item> // title
<item name="colorPrimaryContainer">@color/blue_light_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/blue_light_accentLight</item> //
<item name="colorSecondary">@color/blue_light_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/blue_light_accentDark</item> //
<item name="colorSecondaryContainer">@color/blue_light_accentDark</item> // navbar surround
<item name="colorOnSecondaryContainer">@color/blue_light_background</item> // navbar icon fill
<item name="android:colorBackground">@color/blue_light_background</item> // background
<item name="colorOnBackground">@color/blue_light_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Yellow" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/yellow_light_accentLight</item> // container
<item name="colorOnPrimary">@color/yellow_light_accentDark</item> // title
<item name="colorPrimaryContainer">@color/yellow_light_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/yellow_light_accentLight</item> //
<item name="colorSecondary">@color/yellow_light_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/yellow_light_accentDark</item> //
<item name="colorSecondaryContainer">@color/yellow_light_accentDark</item> // navbar surround
<item name="colorOnSecondaryContainer">@color/yellow_light_background</item> // navbar icon fill
<item name="android:colorBackground">@color/yellow_light_background</item> // background
<item name="colorOnBackground">@color/yellow_light_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Green" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/green_light_accentLight</item> // container
<item name="colorOnPrimary">@color/green_light_accentDark</item> // title
<item name="colorPrimaryContainer">@color/green_light_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/green_light_accentLight</item> //
<item name="colorSecondary">@color/green_light_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/green_light_accentDark</item> //
<item name="colorSecondaryContainer">@color/green_light_accentDark</item> // navbar surround
<item name="colorOnSecondaryContainer">@color/green_light_background</item> // navbar icon fill
<item name="android:colorBackground">@color/green_light_background</item> // background
<item name="colorOnBackground">@color/green_light_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Purple" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/purple_light_accentLight</item> // container
<item name="colorOnPrimary">@color/purple_light_accentDark</item> // title
<item name="colorPrimaryContainer">@color/purple_light_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/purple_light_accentLight</item> //
<item name="colorSecondary">@color/purple_light_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/purple_light_accentDark</item> //
<item name="colorSecondaryContainer">@color/purple_light_accentDark</item> // navbar surround
<item name="colorOnSecondaryContainer">@color/purple_light_background</item> // navbar icon fill
<item name="android:colorBackground">@color/purple_light_background</item> // background
<item name="colorOnBackground">@color/purple_light_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
</resources> </resources>

View File

@ -463,12 +463,32 @@
<item>@string/systemDefault</item> <item>@string/systemDefault</item>
<item>@string/lightTheme</item> <item>@string/lightTheme</item>
<item>@string/darkTheme</item> <item>@string/darkTheme</item>
<item>@string/oledTheme</item>
</string-array> </string-array>
<string-array name="themesValue"> <string-array name="themesValue">
<item>A</item> <item>A</item>
<item>L</item> <item>L</item>
<item>D</item> <item>D</item>
<item>O</item>
</string-array>
<string-array name="accents">
<item>@string/material_you</item>
<item>@string/color_red</item>
<item>@string/color_blue</item>
<item>@string/color_yellow</item>
<item>@string/color_green</item>
<item>@string/color_purple</item>
</string-array>
<string-array name="accentsValue">
<item>my</item>
<item>red</item>
<item>blue</item>
<item>yellow</item>
<item>green</item>
<item>purple</item>
</string-array> </string-array>
<string-array name="tabs"> <string-array name="tabs">

View File

@ -6,59 +6,44 @@
<color name="duration_background_color">#AA000000</color> <color name="duration_background_color">#AA000000</color>
<color name="duration_text_color">#EEFFFFFF</color> <color name="duration_text_color">#EEFFFFFF</color>
<color name="colorPrimary">#B81B3B</color> <color name="red_light_accentLight">#F1395E</color>
<color name="md_theme_light_primary">#B81B3B</color> <color name="red_light_accentDark">#B81B3B</color>
<color name="md_theme_light_onPrimary">#FFFFFF</color> <color name="red_light_background">#FFC3C3</color>
<color name="md_theme_light_primaryContainer">#FFDADC</color>
<color name="md_theme_light_onPrimaryContainer">#40000B</color> <color name="red_dark_accentLight">#F1395E</color>
<color name="md_theme_light_secondary">#9C4145</color> <color name="red_dark_accentDark">#8F001D</color>
<color name="md_theme_light_onSecondary">#FFFFFF</color> <color name="red_dark_background">#1E0D0D</color>
<color name="md_theme_light_secondaryContainer">#FFD9DA</color>
<color name="md_theme_light_onSecondaryContainer">#400008</color> <color name="blue_light_accentLight">#2196F3</color>
<color name="md_theme_light_tertiary">#98470F</color> <color name="blue_light_accentDark">#0E4B67</color>
<color name="md_theme_light_onTertiary">#FFFFFF</color> <color name="blue_light_background">#DDF0FF</color>
<color name="md_theme_light_tertiaryContainer">#FFDBC8</color>
<color name="md_theme_light_onTertiaryContainer">#341100</color> <color name="blue_dark_accentLight">#2196F3</color>
<color name="md_theme_light_error">#BA1B1B</color> <color name="blue_dark_accentDark">#0E4B67</color>
<color name="md_theme_light_errorContainer">#FFDAD4</color> <color name="blue_dark_background">#080C20</color>
<color name="md_theme_light_onError">#FFFFFF</color>
<color name="md_theme_light_onErrorContainer">#410001</color> <color name="yellow_light_accentLight">#F3E570</color>
<color name="md_theme_light_background">#FCFCFC</color> <color name="yellow_light_accentDark">#EDE06A</color>
<color name="md_theme_light_onBackground">#201A1A</color> <color name="yellow_light_background">#FFF9C8</color>
<color name="md_theme_light_surface">#FCFCFC</color>
<color name="md_theme_light_onSurface">#201A1A</color> <color name="yellow_dark_accentLight">#E2EF55</color>
<color name="md_theme_light_surfaceVariant">#F4DDDD</color> <color name="yellow_dark_accentDark">#999520</color>
<color name="md_theme_light_onSurfaceVariant">#524343</color> <color name="yellow_dark_background">#1C1A05</color>
<color name="md_theme_light_outline">#847373</color>
<color name="md_theme_light_inverseOnSurface">#FBEDED</color> <color name="green_light_accentLight">#8BC34A</color>
<color name="md_theme_light_inverseSurface">#362F2F</color> <color name="green_light_accentDark">#5BD861</color>
<color name="md_theme_light_primaryInverse">#FFB3B8</color> <color name="green_light_background">#E8FFCE</color>
<color name="md_theme_dark_primary">#FFB3B8</color>
<color name="md_theme_dark_onPrimary">#680018</color> <color name="green_dark_accentLight">#8BC34A</color>
<color name="md_theme_dark_primaryContainer">#920026</color> <color name="green_dark_accentDark">#155C1B</color>
<color name="md_theme_dark_onPrimaryContainer">#FFDADC</color> <color name="green_dark_background">#131C09</color>
<color name="md_theme_dark_secondary">#FFB2B3</color>
<color name="md_theme_dark_onSecondary">#60131B</color> <color name="purple_light_accentLight">#db1fb6</color>
<color name="md_theme_dark_secondaryContainer">#7D2A2F</color> <color name="purple_light_accentDark">#d42cb2</color>
<color name="md_theme_dark_onSecondaryContainer">#FFD9DA</color> <color name="purple_light_background">#FFDAE6</color>
<color name="md_theme_dark_tertiary">#FFB68C</color>
<color name="md_theme_dark_onTertiary">#552100</color> <color name="purple_dark_accentLight">#9621AA</color>
<color name="md_theme_dark_tertiaryContainer">#783200</color> <color name="purple_dark_accentDark">#371377</color>
<color name="md_theme_dark_onTertiaryContainer">#FFDBC8</color> <color name="purple_dark_background">#120B20</color>
<color name="md_theme_dark_error">#FFB4A9</color>
<color name="md_theme_dark_errorContainer">#930006</color>
<color name="md_theme_dark_onError">#680003</color>
<color name="md_theme_dark_onErrorContainer">#FFDAD4</color>
<color name="md_theme_dark_background">#201A1A</color>
<color name="md_theme_dark_onBackground">#ECDFDF</color>
<color name="md_theme_dark_surface">#201A1A</color>
<color name="md_theme_dark_onSurface">#ECDFDF</color>
<color name="md_theme_dark_surfaceVariant">#524343</color>
<color name="md_theme_dark_onSurfaceVariant">#D7C2C2</color>
<color name="md_theme_dark_outline">#9F8C8C</color>
<color name="md_theme_dark_inverseOnSurface">#201A1A</color>
<color name="md_theme_dark_inverseSurface">#ECDFDF</color>
<color name="md_theme_dark_primaryInverse">#B81B3B</color>
<color name="seed">#BD1F3E</color>
<color name="error">#BA1B1B</color>
</resources> </resources>

View File

@ -101,4 +101,12 @@
<string name="category_outro">Endcards/Credits</string> <string name="category_outro">Endcards/Credits</string>
<string name="category_outro_description">Credits or when the YouTube endcards appear. Not for conclusions with information.</string> <string name="category_outro_description">Credits or when the YouTube endcards appear. Not for conclusions with information.</string>
<string name="license">License</string> <string name="license">License</string>
<string name="color_accent">Color Accent</string>
<string name="color_red">Red</string>
<string name="color_blue">Blue</string>
<string name="color_yellow">Yellow</string>
<string name="color_green">Green</string>
<string name="color_purple">Purple</string>
<string name="oledTheme">OLED Theme</string>
<string name="material_you">Material You</string>
</resources> </resources>

View File

@ -1,33 +1,89 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.LibreTube" parent="Theme.Material3.Light.NoActionBar"> <style name="Theme.MY" parent="Theme.Material3.Light.NoActionBar"></style>
<item name="colorPrimary">@color/md_theme_light_primary</item>
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item> <style name="Theme.Red" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimaryContainer">@color/md_theme_light_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/md_theme_light_onPrimaryContainer</item> <item name="colorPrimary">@color/red_light_accentLight</item> // container
<item name="colorSecondary">@color/md_theme_light_secondary</item> <item name="colorOnPrimary">@color/red_light_accentDark</item> // title
<item name="colorOnSecondary">@color/md_theme_light_onSecondary</item> <item name="colorPrimaryContainer">@color/red_light_accentLight</item> //
<item name="colorSecondaryContainer">@color/md_theme_light_secondaryContainer</item> <item name="colorOnPrimaryContainer">@color/red_light_accentLight</item> //
<item name="colorOnSecondaryContainer">@color/md_theme_light_onSecondaryContainer</item> <item name="colorSecondary">@color/red_light_accentLight</item> // Settings Categories
<item name="colorTertiary">@color/md_theme_light_tertiary</item> <item name="colorOnSecondary">@color/red_light_accentDark</item> //
<item name="colorOnTertiary">@color/md_theme_light_onTertiary</item> <item name="colorSecondaryContainer">@color/red_light_accentDark</item> // navbar surround
<item name="colorTertiaryContainer">@color/md_theme_light_tertiaryContainer</item> <item name="colorOnSecondaryContainer">@color/red_light_background</item> // navbar icon fill
<item name="colorOnTertiaryContainer">@color/md_theme_light_onTertiaryContainer</item> <item name="android:colorBackground">@color/red_light_background</item> // background
<item name="colorError">@color/md_theme_light_error</item> <item name="colorOnBackground">@color/red_light_background</item>
<item name="colorErrorContainer">@color/md_theme_light_errorContainer</item>
<item name="colorOnError">@color/md_theme_light_onError</item>
<item name="colorOnErrorContainer">@color/md_theme_light_onErrorContainer</item>
<item name="android:colorBackground">@color/md_theme_light_background</item>
<item name="colorOnBackground">@color/md_theme_light_onBackground</item>
<item name="colorSurface">@color/md_theme_light_surface</item>
<item name="colorOnSurface">@color/md_theme_light_onSurface</item>
<item name="colorSurfaceVariant">@color/md_theme_light_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/md_theme_light_onSurfaceVariant</item>
<item name="colorOutline">@color/md_theme_light_outline</item>
<item name="colorOnSurfaceInverse">@color/md_theme_light_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">@android:color/transparent</item>
</style> </style>
<style name="Theme.Blue" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/blue_light_accentLight</item> // container
<item name="colorOnPrimary">@color/blue_light_accentDark</item> // title
<item name="colorPrimaryContainer">@color/blue_light_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/blue_light_accentLight</item> //
<item name="colorSecondary">@color/blue_light_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/blue_light_accentDark</item> //
<item name="colorSecondaryContainer">@color/blue_light_accentDark</item> // navbar surround
<item name="colorOnSecondaryContainer">@color/blue_light_background</item> // navbar icon fill
<item name="android:colorBackground">@color/blue_light_background</item> // background
<item name="colorOnBackground">@color/blue_light_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Yellow" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/yellow_light_accentLight</item> // container
<item name="colorOnPrimary">@color/yellow_light_accentDark</item> // title
<item name="colorPrimaryContainer">@color/yellow_light_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/yellow_light_accentLight</item> //
<item name="colorSecondary">@color/yellow_light_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/yellow_light_accentDark</item> //
<item name="colorSecondaryContainer">@color/yellow_light_accentDark</item> // navbar surround
<item name="colorOnSecondaryContainer">@color/yellow_light_background</item> // navbar icon fill
<item name="android:colorBackground">@color/yellow_light_background</item> // background
<item name="colorOnBackground">@color/yellow_light_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Green" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/green_light_accentLight</item> // container
<item name="colorOnPrimary">@color/green_light_accentDark</item> // title
<item name="colorPrimaryContainer">@color/green_light_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/green_light_accentLight</item> //
<item name="colorSecondary">@color/green_light_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/green_light_accentDark</item> //
<item name="colorSecondaryContainer">@color/green_light_accentDark</item> // navbar surround
<item name="colorOnSecondaryContainer">@color/green_light_background</item> // navbar icon fill
<item name="android:colorBackground">@color/green_light_background</item> // background
<item name="colorOnBackground">@color/green_light_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Purple" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/purple_light_accentLight</item> // container
<item name="colorOnPrimary">@color/purple_light_accentDark</item> // title
<item name="colorPrimaryContainer">@color/purple_light_accentLight</item> //
<item name="colorOnPrimaryContainer">@color/purple_light_accentLight</item> //
<item name="colorSecondary">@color/purple_light_accentLight</item> // Settings Categories
<item name="colorOnSecondary">@color/purple_light_accentDark</item> //
<item name="colorSecondaryContainer">@color/purple_light_accentDark</item> // navbar surround
<item name="colorOnSecondaryContainer">@color/purple_light_background</item> // navbar icon fill
<item name="android:colorBackground">@color/purple_light_background</item> // background
<item name="colorOnBackground">@color/purple_light_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources> </resources>

View File

@ -71,6 +71,15 @@
android:icon="@drawable/ic_theme" android:icon="@drawable/ic_theme"
/> />
<ListPreference
app:title="@string/color_accent"
app:key="accent_color"
app:entries="@array/accents"
app:entryValues="@array/accentsValue"
app:defaultValue="red"
android:icon="@drawable/ic_color"
/>
<androidx.preference.Preference <androidx.preference.Preference
app:title="@string/sponsorblock" app:title="@string/sponsorblock"
app:key="sponsorblock" app:key="sponsorblock"

View File

@ -1,8 +1,7 @@
package com.github.libretube package com.github.libretube
import org.junit.Test
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Test
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).