Merge branch 'master' into title_description_seperation

This commit is contained in:
Farbod 2022-04-04 00:22:22 -07:00 committed by GitHub
commit 33d634b713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 217 additions and 93 deletions

View File

@ -10,8 +10,8 @@ android {
applicationId 'com.github.libretube'
minSdk 21
targetSdk 31
versionCode 6
versionName '0.2.4'
versionCode 7
versionName '0.2.5'
multiDexEnabled true
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
resValue "string", "app_name", "LibreTube"
@ -71,7 +71,8 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-jackson:2.9.0'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.2'
//do not update jackson annotations! it does not supports <api26
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.1'
implementation 'com.arthenica:ffmpeg-kit-min:4.5.1.LTS'

View File

@ -16,10 +16,23 @@
}
],
"attributes": [],
"versionCode": 5,
"versionName": "0.2.3",
"versionCode": 7,
"versionName": "0.2.5",
"outputFile": "app-x86_64-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 7,
"versionName": "0.2.5",
"outputFile": "app-armeabi-v7a-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
@ -29,8 +42,8 @@
}
],
"attributes": [],
"versionCode": 5,
"versionName": "0.2.3",
"versionCode": 7,
"versionName": "0.2.5",
"outputFile": "app-x86-release.apk"
},
{
@ -42,22 +55,9 @@
}
],
"attributes": [],
"versionCode": 5,
"versionName": "0.2.3",
"versionCode": 7,
"versionName": "0.2.5",
"outputFile": "app-arm64-v8a-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 5,
"versionName": "0.2.3",
"outputFile": "app-armeabi-v7a-release.apk"
}
],
"elementType": "File"

View File

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:allowBackup="true"

View File

@ -43,7 +43,9 @@ class Home : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<RecyclerView>(R.id.recview)
recyclerView.layoutManager = GridLayoutManager(view.context, resources.getInteger(R.integer.grid_items))
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val grid = sharedPreferences.getString("grid", resources.getInteger(R.integer.grid_items).toString())!!
recyclerView.layoutManager = GridLayoutManager(view.context, grid.toInt())
val progressbar = view.findViewById<ProgressBar>(R.id.progressBar)
fetchJson(progressbar,recyclerView)
refreshLayout = view.findViewById(R.id.home_refresh)

View File

@ -13,6 +13,7 @@ import android.util.Log
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.widget.ConstraintLayout
@ -39,6 +40,11 @@ class MainActivity : AppCompatActivity() {
RetrofitInstance.url = sharedPreferences.getString("instance", "https://pipedapi.kavin.rocks/")!!
DynamicColors.applyToActivitiesIfAvailable(application)
setContentView(R.layout.activity_main)
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)
}
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
bottomNavigationView = findViewById(R.id.bottomNav)

View File

@ -52,8 +52,13 @@ interface PipedApi {
@POST("unsubscribe")
suspend fun unsubscribe(@Header("Authorization") token: String, @Body subscribe: Subscribe): Message
@POST("import")
suspend fun importSubscriptions(@Header("Authorization") token: String, @Body channels: List<String>): Message
//only for fetching servers list
@GET
suspend fun getInstances(@Url url: String): List<Instances>
}

View File

@ -194,9 +194,9 @@ class PlayerFragment : Fragment() {
view.findViewById<ConstraintLayout>(R.id.main_container).isClickable = true
view.findViewById<LinearLayout>(R.id.linLayout).visibility = View.GONE
val mainActivity = activity as MainActivity
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
isFullScreen = true
} else {
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
isFullScreen=true
}else{
with(motionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
enableTransition(R.id.yt_transition, true)
@ -231,9 +231,11 @@ class PlayerFragment : Fragment() {
} catch (e: IOException) {
println(e)
Log.e(TAG, "IOException, you might not have internet connection")
Toast.makeText(context,R.string.unknown_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response")
Toast.makeText(context,R.string.server_error, Toast.LENGTH_SHORT).show()
return@launchWhenCreated
}
var videosNameArray: Array<CharSequence> = arrayOf()
@ -266,10 +268,12 @@ class PlayerFragment : Fragment() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val defres = sharedPreferences.getString("default_res", "")!!
when {
defres != "" -> {
run lit@{
defres!="" -> {
var foundRes = false
run lit@ {
response.videoStreams!!.forEachIndexed { index, pipedStream ->
if (pipedStream.quality!!.contains(defres)) {
if (pipedStream.quality!!.contains(defres)){
foundRes = true
val dataSourceFactory: DataSource.Factory =
DefaultHttpDataSource.Factory()
val videoItem: MediaItem = MediaItem.Builder()
@ -288,6 +292,12 @@ class PlayerFragment : Fragment() {
exoPlayer.setMediaSource(mergeSource)
view.findViewById<TextView>(R.id.quality_text).text = videosNameArray[index + 1]
return@lit
}else if (index+1 == response.videoStreams.size){
val mediaItem: MediaItem = MediaItem.Builder()
.setUri(response.hls)
.setSubtitleConfigurations(subtitle)
.build()
exoPlayer.setMediaItem(mediaItem)
}
}
}

View File

@ -9,9 +9,11 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.TextView.OnEditorActionListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
@ -75,6 +77,17 @@ class SearchFragment : Fragment() {
}
})
autoTextView.setOnEditorActionListener(OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
hideKeyboard();
autoTextView.dismissDropDown();
return@OnEditorActionListener true
}
false
})
autoTextView.setOnDismissListener {
hideKeyboard();
}
}
private fun fetchSuggestions(query: String, autoTextView: AutoCompleteTextView){
@ -125,4 +138,8 @@ class SearchFragment : Fragment() {
requireActivity().window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
}
override fun onStop() {
super.onStop()
hideKeyboard()
}
}

View File

@ -1,13 +1,12 @@
package com.github.libretube
import android.Manifest
import android.content.ContentValues.TAG
import android.content.ContentResolver
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
@ -15,59 +14,100 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.blankj.utilcode.util.UriUtils
import com.github.libretube.obj.Subscribe
import retrofit2.HttpException
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.*
import java.util.zip.ZipFile
class Settings : PreferenceFragmentCompat() {
class Settings : PreferenceFragmentCompat() {
val TAG = "Settings"
companion object {
lateinit var getContent: ActivityResultLauncher<String>
}
override fun onCreate(savedInstanceState: Bundle?) {
getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
if (uri != null) {
var zipfile = ZipFile(UriUtils.uri2File(uri))
try{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
var zipentry =
zipfile.getEntry("Takeout/YouTube and YouTube Music/subscriptions/subscriptions.csv")
// Open a specific media item using ParcelFileDescriptor.
val resolver: ContentResolver =
requireActivity()
.contentResolver
var inputStream = zipfile.getInputStream(zipentry)
// "rw" for read-and-write;
// "rwt" for truncating or overwriting existing file contents.
val readOnlyMode = "r"
// uri - I have got from onActivityResult
//uri = data.getData();
val parcelFile = resolver.openFileDescriptor(uri, readOnlyMode)
val fileReader = FileReader(parcelFile!!.fileDescriptor)
val reader = BufferedReader(fileReader)
var line: String?
var channels: MutableList<String> = emptyList<String>().toMutableList()
var subscribedCount = 0
while (reader.readLine().also { line = it } != null) {
if (line!!.replace(" ","") != "" && subscribedCount >0) {
val channel = line!!.split(",")[0]
channels.add(channel)
val baos = ByteArrayOutputStream()
Log.d(TAG, "subscribed: " + line + " total: " + subscribedCount)
}
subscribedCount++
}
subscribe(channels)
reader.close()
fileReader.close()
}else{
Log.d(TAG,UriUtils.uri2File(uri).toString())
val file = UriUtils.uri2File(uri)
var inputStream: InputStream? = null
if (file.extension == "zip") {
var zipfile = ZipFile(file)
inputStream.use { it.copyTo(baos) }
var zipentry =
zipfile.getEntry("Takeout/YouTube and YouTube Music/subscriptions/subscriptions.csv")
var subscriptions = baos.toByteArray().decodeToString()
inputStream = zipfile.getInputStream(zipentry)
}else if(file.extension == "csv"){
inputStream = file.inputStream()
}
val baos = ByteArrayOutputStream()
var subscribedCount = 0
inputStream?.use { it.copyTo(baos) }
for (text in subscriptions.lines()) {
if (text.take(24) != "Channel Id,Channel Url,C" && !text.take(24).isEmpty()) {
subscribe(text.take(24))
subscribedCount++
Log.d(TAG, "subscribed: " + text + " total: " + subscribedCount)
var subscriptions = baos.toByteArray().decodeToString()
var channels: MutableList<String> = emptyList<String>().toMutableList()
var subscribedCount = 0
for (text in subscriptions.lines().subList(1,subscriptions.lines().size)) {
if (text.replace(" ","") != "") {
val channel = text.split(",")[0]
channels.add(channel)
subscribedCount++
Log.d(TAG, "subscribed: " + text + " total: " + subscribedCount)
}
}
subscribe(channels)
}
}
Toast.makeText(
context,
"Subscribed to " + subscribedCount + " channels.",
Toast.LENGTH_SHORT
).show()
}catch (e: Exception){
Log.e(TAG,e.toString())
Toast.makeText(
context,
R.string.error,
Toast.LENGTH_SHORT
).show()
}
}
}
super.onCreate(savedInstanceState)
@ -100,17 +140,24 @@ class Settings : PreferenceFragmentCompat() {
val importFromYt = findPreference<Preference>("import_from_yt")
importFromYt?.setOnPreferenceClickListener {
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token","")!!
//check StorageAccess
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.d("myz", "" + Build.VERSION.SDK_INT)
if (!Environment.isExternalStorageManager()) {
if (ContextCompat.checkSelfPermission(this.requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(), arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
), 1
) //permission request code is just an int
}else if (token != ""){
getContent.launch("*/*")
}else{
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
} else {
if (ActivityCompat.checkSelfPermission(
@ -129,12 +176,12 @@ class Settings : PreferenceFragmentCompat() {
),
1
)
}else if (token != ""){
getContent.launch("*/*")
}else{
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
getContent.launch("application/zip")
true
}
@ -197,15 +244,12 @@ class Settings : PreferenceFragmentCompat() {
}
private fun subscribe(channel_id: String) {
private fun subscribe(channels: List<String>) {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
RetrofitInstance.api.subscribe(
sharedPref?.getString("token", "")!!,
Subscribe(channel_id)
)
RetrofitInstance.api.importSubscriptions(sharedPref?.getString("token", "")!!,channels)
} catch (e: IOException) {
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
@ -213,9 +257,17 @@ class Settings : PreferenceFragmentCompat() {
Log.e(TAG, "HttpException, unexpected response$e")
return@launchWhenCreated
}
if(response.message == "ok"){
Toast.makeText(
context,
R.string.importsuccess,
Toast.LENGTH_SHORT
).show()
}
}
}
run()
}
}

View File

@ -45,7 +45,6 @@ class Subscriptions : Fragment() {
super.onViewCreated(view, savedInstanceState)
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
token = sharedPref?.getString("token","")!!
Log.e(TAG,token)
refreshLayout = view.findViewById(R.id.sub_refresh)
if(token!=""){
view.findViewById<RelativeLayout>(R.id.loginOrRegister).visibility=View.GONE
@ -59,7 +58,9 @@ class Subscriptions : Fragment() {
fetchChannels(channelRecView)
var feedRecView = view.findViewById<RecyclerView>(R.id.sub_feed)
feedRecView.layoutManager = GridLayoutManager(view.context, resources.getInteger(R.integer.grid_items))
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val grid = sharedPreferences.getString("grid", resources.getInteger(R.integer.grid_items).toString())!!
feedRecView.layoutManager = GridLayoutManager(view.context, grid.toInt())
fetchFeed(feedRecView, progressBar)
refreshLayout?.setOnRefreshListener {
@ -92,7 +93,7 @@ class Subscriptions : Fragment() {
val response = try {
RetrofitInstance.api.getFeed(token)
}catch(e: IOException) {
println(e)
Log.e(TAG,e.toString())
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
@ -119,7 +120,7 @@ class Subscriptions : Fragment() {
val response = try {
RetrofitInstance.api.subscriptions(token)
}catch(e: IOException) {
println(e)
Log.e(TAG,e.toString())
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
@ -137,16 +138,11 @@ class Subscriptions : Fragment() {
}
run()
}
override fun onStop() {
Log.e(TAG,"Stopped")
subscriptionAdapter = null
view?.findViewById<RecyclerView>(R.id.sub_feed)?.adapter=null
super.onStop()
}
override fun onDestroy() {
Log.e(TAG,"Destroyed")
super.onDestroy()
subscriptionAdapter = null
view?.findViewById<RecyclerView>(R.id.sub_feed)?.adapter=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="M10,18h5L15,5h-5v13zM4,18h5L9,5L4,5v13zM16,5v13h5L21,5h-5z"/>
</vector>

View File

@ -236,12 +236,12 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/player_channelImage"
android:layout_toStartOf="@+id/player_subscribe"
android:text=""
android:textStyle="bold"
android:layout_toEndOf="@+id/player_channelImage"
android:ellipsize="end"
android:maxLines="1"/>
android:maxLines="1"
android:text=""
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton

View File

@ -51,7 +51,9 @@
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/thumbnailcard"
app:layout_constraintStart_toEndOf="@+id/channel_image"
app:layout_constraintTop_toBottomOf="@+id/thumbnailcard" />
app:layout_constraintTop_toBottomOf="@+id/thumbnailcard"
android:ellipsize="end"
android:maxLines="2"/>
<TextView
android:id="@+id/textView_channel"

View File

@ -31,7 +31,6 @@
<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>
<item name="fontFamily">@font/roboto</item>
</style>
</resources>

View File

@ -32,7 +32,6 @@
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="fontFamily">@font/roboto</item>
</style>
</resources>

View File

@ -417,6 +417,7 @@
<item>D</item>
</string-array>
<string-array name="defres">
<item>HLS</item>
<item>1080p</item>
<item>720p</item>
<item>480p</item>
@ -424,4 +425,20 @@
<item>240p</item>
<item>144p</item>
</string-array>
<string-array name="defresValue">
<item></item>
<item>1080p</item>
<item>720p</item>
<item>480p</item>
<item>360p</item>
<item>240p</item>
<item>144p</item>
</string-array>
<string-array name="grid">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
</resources>

View File

@ -17,11 +17,13 @@
<string name="loggedout">Logged out successfully!</string>
<string name="registered">Registered successfully! You may now subscribe to channels you want.</string>
<string name="already_logged_in">You are already logged in, you may logout of your account.</string>
<string name="login_first">Please login and try again!</string>
<string name="instances">Choose an instance</string>
<string name="customInstance">Add a custom instance</string>
<string name="region">Choose a region</string>
<string name="login_register">Login/Register</string>
<string name="please_login">Please Login or Register from the settings to show your Subscriptions!</string>
<string name="importsuccess">Subscribed successfully!</string>
<string name="subscribeIsEmpty">Subscribe to some channels first!</string>
<string name="cannotDownload">Can\'t Download this stream!</string>
<string name="dlcomplete">Download is completed!</string>
@ -33,7 +35,9 @@
<string name="app_theme">App theme</string>
<string name="server_error">Server countered a problem. Maybe try another instance?</string>
<string name="unknown_error">Network error!</string>
<string name="error">Something went wrong!</string>
<string name="empty">Username and Password can\'t be empty!</string>
<string name="notgmail">This is not your gmail account!</string>
<string name="defres">Default Video Resolution</string>
<string name="grid">Choose the grid columns</string>
</resources>

View File

@ -29,6 +29,5 @@
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="fontFamily">@font/roboto</item>
</style>
</resources>

View File

@ -1,11 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.preference.SwitchPreferenceCompat
app:key="darkMode"
app:title="Toggle dark mode"
app:isPreferenceVisible="false"/>
<ListPreference
app:key="region"
app:title="@string/region"
@ -54,10 +49,19 @@
app:title="@string/defres"
app:key="default_res"
app:entries="@array/defres"
app:entryValues="@array/defres"
app:entryValues="@array/defresValue"
app:defaultValue=""
android:icon="@drawable/ic_hd"
app:useSimpleSummaryProvider="true"
/>
<ListPreference
app:title="@string/grid"
app:key="grid"
app:entries="@array/grid"
app:entryValues="@array/grid"
app:defaultValue="@integer/grid_items"
android:icon="@drawable/ic_column"
app:useSimpleSummaryProvider="true"
/>
</androidx.preference.PreferenceScreen>