14 Commits

Author SHA1 Message Date
0f8c3a7126 Release v0.7.0 2024-04-15 22:18:02 -06:00
d6aeadc489 Fix lint errors 2024-04-15 22:07:14 -06:00
826bb79431 Merge branch 'feature/threads' into 'master'
Feature/threads

See merge request agosto182/p2play!16
2024-04-16 03:55:53 +00:00
2484406cc7 Feature/threads 2024-04-16 03:55:53 +00:00
0705d6dd80 Merge branch 'feature/playbackService' into 'master'
Feature/playback service

See merge request agosto182/p2play!15
2024-04-11 03:15:06 +00:00
dd54d214ff Feature/playback service 2024-04-11 03:15:06 +00:00
48738f100a fix all lint errors 2024-04-06 18:03:23 -06:00
b6e1a979ca Fix lint errors automated 2024-04-06 14:38:04 -06:00
96d8ae19c6 Fix buffer, forward and rewind video 2024-04-05 21:44:56 -06:00
8856984dc3 Fix share and report icons color 2024-04-05 21:30:37 -06:00
2ffb7daede Fix dialogs design 2024-04-05 21:30:22 -06:00
f0b5ba10e8 Fix for wrong pagination 2024-04-05 18:47:08 -06:00
4d2644674e Add 2fa login support 2024-04-05 18:46:57 -06:00
6a8d3baccb Update setting view 2024-03-31 16:59:57 -06:00
58 changed files with 1440 additions and 708 deletions

2
.editorconfig Normal file
View File

@ -0,0 +1,2 @@
[*.{kt,kts}]
ktlint_code_style = intellij_idea

View File

@ -11,8 +11,8 @@ android {
applicationId "org.libre.agosto.p2play"
minSdkVersion 26
targetSdkVersion 32
versionCode 9
versionName "0.6.0"
versionCode 10
versionName "0.7.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
@ -26,27 +26,29 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
namespace 'org.libre.agosto.p2play'
lint {
abortOnError false
checkReleaseBuilds false
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.android.support:appcompat-v7:23.2.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.google.android.material:material:1.6.0'
testImplementation 'junit:junit:4.12'
implementation 'androidx.preference:preference:1.2.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-exoplayer-dash:1.1.1'
implementation 'androidx.media3:media3-ui:1.1.1'
implementation 'androidx.media3:media3-exoplayer-hls:1.1.1'
implementation "androidx.media3:media3-session:1.1.1"
}

View File

@ -3,6 +3,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application
android:allowBackup="true"
@ -11,13 +13,23 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.P2play">
<activity android:name=".ChannelActivity"
android:theme="@style/Theme.P2play.NoActionBar"
android:exported="false" />
<activity
android:name=".SettingsActivity2"
android:exported="false"
android:label="@string/title_activity_settings"
android:theme="@style/Theme.P2play">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
<activity
android:name=".ChannelActivity"
android:exported="false"
android:theme="@style/Theme.P2play.NoActionBar" />
<activity
android:name=".SplashActivity"
android:theme="@style/Theme.P2play.NoActionBar"
android:exported="true">
android:exported="true"
android:theme="@style/Theme.P2play.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -26,26 +38,41 @@
</activity>
<activity
android:name=".HostActivity"
android:theme="@style/Theme.P2play.NoActionBar"
android:exported="false" />
android:exported="false"
android:theme="@style/Theme.P2play.NoActionBar" />
<activity
android:name=".MainActivity"
android:theme="@style/Theme.P2play.NoActionBar"
android:exported="false" />
android:exported="false"
android:theme="@style/Theme.P2play.NoActionBar" />
<activity
android:name=".ReproductorActivity"
android:configChanges="orientation|screenSize"
android:exported="false"
android:hardwareAccelerated="true"
android:theme="@style/Theme.P2play.NoActionBar"
android:theme="@style/Theme.P2play.NoActionBar" />
<activity
android:name=".LoginActivity"
android:exported="false" />
<activity
android:name=".RegisterActivity"
android:exported="false" />
<activity
android:name=".AboutActivity"
android:exported="false" />
<activity android:name=".LoginActivity" android:exported="false" />
<activity android:name=".RegisterActivity" android:exported="false" />
<activity android:name=".AboutActivity" android:exported="false" />
<activity
android:name=".SettingsActivity"
android:exported="false"
android:label="@string/title_activity_settings"
android:theme="@style/Theme.P2play"
android:exported="false"/>
android:theme="@style/Theme.P2play" />
<service
android:name=".services.PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
</application>
</manifest>

View File

@ -1,6 +1,5 @@
package org.libre.agosto.p2play
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_about.*

View File

@ -1,6 +1,5 @@
package org.libre.agosto.p2play
import androidx.appcompat.app.ActionBar
import android.content.res.Configuration
import android.os.Bundle
import android.preference.PreferenceActivity
@ -8,6 +7,7 @@ import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar

View File

@ -1,11 +1,11 @@
package org.libre.agosto.p2play
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_channel.*
import org.libre.agosto.p2play.adapters.VideosAdapter
@ -14,14 +14,13 @@ import org.libre.agosto.p2play.ajax.Channels
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.models.ChannelModel
import org.libre.agosto.p2play.models.VideoModel
class ChannelActivity : AppCompatActivity() {
private lateinit var channelId: String
private lateinit var channel: ChannelModel
private var isSubcribed: Boolean = false
private val _channel = Channels()
private val _videos = Videos()
private val _actions = Actions()
private val channelService = Channels()
private val videosService = Videos()
private val actionsService = Actions()
private lateinit var recyclerView: RecyclerView
private lateinit var viewAdapter: RecyclerView.Adapter<VideosAdapter.ViewHolder>
@ -55,28 +54,28 @@ class ChannelActivity : AppCompatActivity() {
private fun getChannel() {
AsyncTask.execute {
channel = _channel.getChannelInfo(channelId)
channel = channelService.getChannelInfo(channelId)
runOnUiThread {
usernameProfile.text = channel.name
hostTxt.text = channel.host
subcriptionsTxt.text = channel.followers.toString()
if(channel.channelImg != "")
if (channel.channelImg != "") {
Picasso.get().load("https://${ManagerSingleton.url}${channel.channelImg}").into(channelImg)
}
}
}
}
private fun subscribe() {
AsyncTask.execute {
val res = _actions.subscribe(ManagerSingleton.token.token, channel.getAccount())
val res = actionsService.subscribe(ManagerSingleton.token.token, channel.getAccount())
runOnUiThread {
if (res == 1) {
subcriptionBtn.text = getString(R.string.unSubscribeBtn)
ManagerSingleton.Toast(getString(R.string.subscribeMsg), this)
ManagerSingleton.toast(getString(R.string.subscribeMsg), this)
getSubscription()
}
else {
ManagerSingleton.Toast(getString(R.string.errorMsg), this)
} else {
ManagerSingleton.toast(getString(R.string.errorMsg), this)
}
}
}
@ -84,35 +83,34 @@ class ChannelActivity : AppCompatActivity() {
private fun unSubscribe() {
AsyncTask.execute {
val res = _actions.unSubscribe(ManagerSingleton.token.token, channel.getAccount())
val res = actionsService.unSubscribe(ManagerSingleton.token.token, channel.getAccount())
runOnUiThread {
if (res == 1) {
subcriptionBtn.text = getString(R.string.subscribeBtn)
ManagerSingleton.Toast(getString(R.string.unSubscribeMsg), this)
ManagerSingleton.toast(getString(R.string.unSubscribeMsg), this)
getSubscription()
}
else {
ManagerSingleton.Toast(getString(R.string.errorMsg), this)
} else {
ManagerSingleton.toast(getString(R.string.errorMsg), this)
}
}
}
}
private fun subscribeAction() {
if(isSubcribed)
if (isSubcribed) {
unSubscribe()
else
} else {
subscribe()
}
}
private fun getSubscription() {
AsyncTask.execute {
isSubcribed = _actions.getSubscription(ManagerSingleton.token.token, channel.getAccount())
isSubcribed = actionsService.getSubscription(ManagerSingleton.token.token, channel.getAccount())
runOnUiThread {
if (isSubcribed) {
subcriptionBtn.text = getText(R.string.unSubscribeBtn)
}
else {
} else {
subcriptionBtn.text = getText(R.string.subscribeBtn)
}
}
@ -121,7 +119,7 @@ class ChannelActivity : AppCompatActivity() {
private fun getVideos() {
AsyncTask.execute {
val videos = _videos.channelVideos(channel.getAccount(), 0)
val videos = videosService.channelVideos(channel.getAccount(), 0)
runOnUiThread {
initRecycler(videos)
}

View File

@ -72,7 +72,6 @@ class Database(context:Context): SQLiteOpenHelper(context,"p2play",null,1) {
cursor.close()
return token
} catch (e: SQLiteException) {
db?.execSQL(dbTokens)
} catch (e: Exception) {
@ -103,7 +102,6 @@ class Database(context:Context): SQLiteOpenHelper(context,"p2play",null,1) {
cursor.close()
return user
} catch (e: SQLiteException) {
db?.execSQL(dbTokens)
} catch (e: Exception) {
@ -126,5 +124,4 @@ class Database(context:Context): SQLiteOpenHelper(context,"p2play",null,1) {
closeUsers()
closeTokens()
}
}

View File

@ -3,10 +3,10 @@ package org.libre.agosto.p2play
import android.content.Intent
import android.content.SharedPreferences
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.preference.PreferenceManager
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.activity_host.*
import org.libre.agosto.p2play.ajax.Auth
@ -14,7 +14,7 @@ class HostActivity : AppCompatActivity() {
lateinit var settings: SharedPreferences
lateinit var editor: SharedPreferences.Editor
val client: Auth = Auth()
val _db = Database(this)
private val db = Database(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -31,7 +31,7 @@ class HostActivity : AppCompatActivity() {
val lastHost = settings.getString("last_host", "")
if (host != "") {
if (lastHost != host) {
_db.logout()
db.logout()
ManagerSingleton.logout()
getKeys(host!!)
} else {
@ -41,13 +41,11 @@ class HostActivity : AppCompatActivity() {
}
}
fun saveHost(host: String) {
editor.putString("last_host", host)
editor.putString("hostP2play", host)
editor.apply()
ManagerSingleton.Toast(getString(R.string.finallyMsg), this)
ManagerSingleton.toast(getString(R.string.finallyMsg), this)
ManagerSingleton.url = host
startApp()
}
@ -60,8 +58,9 @@ class HostActivity : AppCompatActivity() {
host = host.replace("/", "")
ManagerSingleton.url = host
AsyncTask.execute {
if (Looper.myLooper()==null)
if (Looper.myLooper() == null) {
Looper.prepare()
}
val keys = client.getKeys()
if (keys.client_id != "") {
@ -69,10 +68,9 @@ class HostActivity : AppCompatActivity() {
editor.putString("client_secret", keys.client_secret)
editor.apply()
saveHost(host)
}
else{
} else {
runOnUiThread {
ManagerSingleton.Toast(getString(R.string.errorMsg), this)
ManagerSingleton.toast(getString(R.string.errorMsg), this)
button.isEnabled = true
}
}

View File

@ -3,84 +3,109 @@ package org.libre.agosto.p2play
import android.content.Intent
import android.content.SharedPreferences
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.preference.PreferenceManager
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.activity_login.*
import org.libre.agosto.p2play.ajax.Auth
class LoginActivity : AppCompatActivity() {
private val _auth = Auth()
private val auth = Auth()
lateinit var settings: SharedPreferences
lateinit var client_id: String
lateinit var client_secret: String
private lateinit var _db: Database
lateinit var clientId: String
lateinit var clientSecret: String
private lateinit var db: Database
private var optCode: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
setTitle(R.string.action_login)
_db = Database(this)
db = Database(this)
settings = PreferenceManager.getDefaultSharedPreferences(this)
client_id = settings.getString("client_id", "")!!
client_secret = settings.getString("client_secret", "")!!
clientId = settings.getString("client_id", "")!!
clientSecret = settings.getString("client_secret", "")!!
registerActionBtn.setOnClickListener { startActivity(Intent(this, RegisterActivity::class.java)) }
loginBtn.setOnClickListener { tryLogin() }
}
fun tryLogin() {
loginBtn.isEnabled = false;
loginBtn.isEnabled = false
val username = userText.text.toString()
val password = passwordText.text.toString()
AsyncTask.execute {
if (Looper.myLooper()==null)
if (Looper.myLooper() == null) {
Looper.prepare()
}
val token = _auth.login(username, password, client_id, client_secret)
val token = auth.login(username, password, clientId, clientSecret, optCode)
// Log.d("token", token.token )
// Log.d("status", token.status.toString() )
when (token.status.toString()) {
"1" -> {
_db.newToken(token)
db.newToken(token)
ManagerSingleton.token = token
getUser()
}
"0" -> {
runOnUiThread {
ManagerSingleton.Toast(getString(R.string.loginError_msg), this)
ManagerSingleton.toast(getString(R.string.loginError_msg), this)
}
}
"-1" -> {
runOnUiThread {
loginBtn.isEnabled = true
ManagerSingleton.Toast(getString(R.string.loginFailed_msg), this)
}
ManagerSingleton.toast(getString(R.string.loginFailed_msg), this)
}
}
"-2" -> {
// TODO: Start 2FA modal
runOnUiThread {
val builder = AlertDialog.Builder(this, R.style.Widget_Material3_MaterialCalendar_Fullscreen)
val dialog = layoutInflater.inflate(R.layout.two_factor_dialog, null)
val inputTwoFactor = dialog.findViewById<EditText>(R.id.twoFactorText)
builder.setView(dialog)
.setTitle(R.string.twoFactorLabel)
// Add action buttons
.setPositiveButton(R.string.loginBtn) { d, _ ->
this.optCode = inputTwoFactor.text.toString()
this.tryLogin()
d.dismiss()
}
.setNegativeButton("Cancel") { d, _ ->
dialog.run { d.cancel() }
loginBtn.isEnabled = true
}
val alertDialog = builder.create()
alertDialog.show()
}
}
}
}
}
fun getUser() {
val user = _auth.me(ManagerSingleton.token.token)
val user = auth.me(ManagerSingleton.token.token)
if (user.status == 1) {
_db.newUser(user)
db.newUser(user)
ManagerSingleton.user = user
runOnUiThread {
ManagerSingleton.Toast(getString(R.string.loginSuccess_msg), this)
ManagerSingleton.toast(getString(R.string.loginSuccess_msg), this)
finish()
}
}
else{
} else {
runOnUiThread {
ManagerSingleton.Toast(getString(R.string.loginError_msg), this)
ManagerSingleton.toast(getString(R.string.loginError_msg), this)
}
}
}

View File

@ -2,13 +2,11 @@ package org.libre.agosto.p2play
import android.content.Intent
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import android.view.View
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
@ -20,11 +18,17 @@ import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.drawer_layout
import kotlinx.android.synthetic.main.activity_main.nav_view
import kotlinx.android.synthetic.main.app_bar_main.toolbar
import kotlinx.android.synthetic.main.content_main.mini
import kotlinx.android.synthetic.main.content_main.swipeContainer
import kotlinx.android.synthetic.main.mini_player.mini_play_pause
import kotlinx.android.synthetic.main.mini_player.mini_player_author
import kotlinx.android.synthetic.main.mini_player.mini_player_image
import kotlinx.android.synthetic.main.mini_player.mini_player_title
import kotlinx.android.synthetic.main.nav_header_main.*
import org.libre.agosto.p2play.adapters.VideosAdapter
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.models.VideoModel
import org.libre.agosto.p2play.singletons.PlaybackSingleton
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var recyclerView: RecyclerView
@ -32,14 +36,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
private lateinit var viewManager: RecyclerView.LayoutManager
private val client: Videos = Videos()
private lateinit var lastItem: MenuItem
private lateinit var subItem: MenuItem
lateinit var myMenu: Menu
private val _db = Database(this)
private val db = Database(this)
var section: String = ""
var searchVal: String = ""
var pagination = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@ -67,6 +68,12 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
this.refresh()
}
mini_player_image.setOnClickListener { this.resumeVideo() }
mini_player_title.setOnClickListener { this.resumeVideo() }
mini_player_author.setOnClickListener { this.resumeVideo() }
mini.setOnClickListener { this.resumeVideo() }
mini_play_pause.setOnClickListener { this.playPausePlayer() }
Handler().postDelayed({
// Title for nav_bar
side_emailTxt?.text = getString(R.string.nav_header_subtitle) + " " + this.packageManager.getPackageInfo(this.packageName, 0).versionName
@ -117,7 +124,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
(viewAdapter as VideosAdapter).addData(videos)
} catch (err: Exception) {
err.printStackTrace()
ManagerSingleton.Toast(getString(R.string.errorMsg), this)
ManagerSingleton.toast(getString(R.string.errorMsg), this)
}
this.swipeContainer.isRefreshing = false
@ -134,17 +141,18 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
"sub" -> this.getSubscriptionVideos()
"search" -> this.searchVideos()
"my_videos" -> {
if(ManagerSingleton.token.token != "")
if (ManagerSingleton.token.token != "") {
this.getMyVideos()
else
} else {
this.getLastVideos()
}
}
}
}
private fun getSubscriptionVideos() {
if (ManagerSingleton.user.status != 1) {
ManagerSingleton.Toast("Inicia session primero", this)
ManagerSingleton.toast("Inicia session primero", this)
startActivity(Intent(this, LoginActivity::class.java))
return
}
@ -253,11 +261,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
}
else if(!section.equals("trending")) {
} else if (!section.equals("trending")) {
// Hot fix
pagination = 0
this.getTrengindVideos()
}
else {
} else {
super.onBackPressed()
}
}
@ -283,10 +291,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
return true
}
})
myMenu = menu
setSideData()
return true
@ -304,7 +310,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// as you specify a parent activity in AndroidManifest.xml.
when (item.itemId) {
R.id.action_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
val intent = Intent(this, SettingsActivity2::class.java)
startActivity(intent)
return true
}
@ -353,6 +359,24 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
override fun onResume() {
super.onResume()
setSideData()
if (PlaybackSingleton.player != null && PlaybackSingleton.player!!.isPlaying) {
PlaybackSingleton.runMediaSession(this)
mini_player_title.text = PlaybackSingleton.video!!.name
mini_player_author.text = PlaybackSingleton.video!!.username
Picasso.get().load("https://${ManagerSingleton.url}${PlaybackSingleton.video!!.thumbUrl}").into(mini_player_image)
mini_play_pause.setImageResource(R.drawable.ic_pause_24)
mini.visibility = View.VISIBLE
} else {
mini.visibility = View.GONE
}
}
override fun onDestroy() {
if (PlaybackSingleton.player != null) {
PlaybackSingleton.release()
}
super.onDestroy()
}
private fun setSideData() {
@ -372,7 +396,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
if (::myMenu.isInitialized) {
myMenu.findItem(R.id.action_login).isVisible = false
myMenu.findItem(R.id.action_logout).isVisible = true
}
} else {
nav_view.menu.findItem(R.id.ml).isVisible = false
@ -389,17 +412,17 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
side_emailTxt?.text = getString(R.string.nav_header_subtitle) + " " + this.packageManager.getPackageInfo(this.packageName, 0).versionName
side_imageView?.setImageResource(R.drawable.default_avatar)
side_imageView?.setOnClickListener { }
_db.logout()
db.logout()
ManagerSingleton.logout()
this.refresh()
ManagerSingleton.Toast(getString(R.string.logout_msg), this)
ManagerSingleton.toast(getString(R.string.logout_msg), this)
setSideData()
}
private fun loadMore() {
swipeContainer.isRefreshing = true
this.pagination += ManagerSingleton.videos_count
this.pagination += ManagerSingleton.videosCount
when (section) {
"local" -> this.getLocalVideos()
@ -409,11 +432,12 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
"sub" -> this.getSubscriptionVideos()
"search" -> this.searchVideos()
"my_videos" -> {
if(ManagerSingleton.token.token != "")
if (ManagerSingleton.token.token != "") {
this.getMyVideos()
else
} else {
this.getLastVideos()
}
}
"liked" -> this.getMostLiked()
}
}
@ -430,4 +454,21 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
}
private fun resumeVideo() {
val intent = Intent(this, ReproductorActivity::class.java)
intent.putExtra("resume", true)
startActivity(intent)
}
private fun playPausePlayer() {
PlaybackSingleton.player?.let {
if (it.isPlaying) {
it.pause()
mini_play_pause.setImageResource(R.drawable.ic_play_arrow_24)
} else {
it.play()
mini_play_pause.setImageResource(R.drawable.ic_pause_24)
}
}
}
}

View File

@ -1,6 +1,7 @@
package org.libre.agosto.p2play
import android.content.Context
import android.content.SharedPreferences
import org.libre.agosto.p2play.models.TokenModel
import org.libre.agosto.p2play.models.UserModel
@ -9,14 +10,30 @@ object ManagerSingleton {
var user: UserModel = UserModel()
var token: TokenModel = TokenModel()
var nfsw: Boolean = false
var videos_count: Int = 0
fun Toast(text: String?, context: Context) {
var videosCount: Int = 0
lateinit var settings: SharedPreferences
lateinit var db: Database
fun toast(text: String?, context: Context) {
android.widget.Toast.makeText(context, text, android.widget.Toast.LENGTH_SHORT).show()
}
fun logout() {
db.logout()
user = UserModel()
token = TokenModel()
}
fun reloadSettings() {
val host = settings.getString("hostP2play", "")
val lastHost = settings.getString("last_host", "")
if (host != "") {
if (lastHost != host) {
logout()
}
url = host
}
nfsw = settings.getBoolean("show_nsfw", false)
videosCount = settings.getString("videos_count", "15")!!.toInt()
}
}

View File

@ -2,19 +2,19 @@ package org.libre.agosto.p2play
import android.content.SharedPreferences
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.preference.PreferenceManager
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.activity_register.*
import org.libre.agosto.p2play.ajax.Auth
class RegisterActivity : AppCompatActivity() {
private val _auth = Auth()
private val auth = Auth()
lateinit var settings: SharedPreferences
lateinit var client_id: String
lateinit var client_secret: String
lateinit var clientId: String
lateinit var clientSecret: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -22,31 +22,32 @@ class RegisterActivity : AppCompatActivity() {
setTitle(R.string.registerActionBtn)
settings = PreferenceManager.getDefaultSharedPreferences(this)
client_id = settings.getString("client_id", "")!!
client_secret = settings.getString("client_secret", "")!!
clientId = settings.getString("client_id", "")!!
clientSecret = settings.getString("client_secret", "")!!
registerBtn.setOnClickListener { registerUser() }
}
fun registerUser(){
private fun registerUser() {
registerBtn.isEnabled = false
val username = userText2.text.toString()
val password = passwordText2.text.toString()
val email = emailText.text.toString()
AsyncTask.execute {
if (Looper.myLooper()==null)
if (Looper.myLooper() == null) {
Looper.prepare()
}
val res = _auth.register(username, password, email)
val res = auth.register(username, password, email)
Log.d("Res register", res.toString())
runOnUiThread {
when (res) {
1 -> {
ManagerSingleton.Toast(getString(R.string.registerSuccess_msg), this)
ManagerSingleton.toast(getString(R.string.registerSuccess_msg), this)
finish()
}
0 -> ManagerSingleton.Toast(getString(R.string.registerFailed_msg), this)
-1 -> ManagerSingleton.Toast(getString(R.string.registerError_msg), this)
0 -> ManagerSingleton.toast(getString(R.string.registerFailed_msg), this)
-1 -> ManagerSingleton.toast(getString(R.string.registerError_msg), this)
}
registerBtn.isEnabled = true
}

View File

@ -5,25 +5,33 @@ import android.content.Intent
import android.content.pm.ActivityInfo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.Image
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.Looper
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.view.WindowManager
import android.webkit.WebChromeClient
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.upstream.DefaultAllocator
import androidx.media3.session.MediaController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_reproductor.*
import kotlinx.android.synthetic.main.comment_component.commentaryBtn
import kotlinx.android.synthetic.main.comment_component.commentaryLayout
import kotlinx.android.synthetic.main.comment_component.commentaryText
import kotlinx.android.synthetic.main.comment_component.userImgCom
import org.libre.agosto.p2play.adapters.CommentariesAdapter
import org.libre.agosto.p2play.ajax.Actions
import org.libre.agosto.p2play.ajax.Comments
@ -31,12 +39,14 @@ import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.helpers.setFullscreen
import org.libre.agosto.p2play.models.CommentaryModel
import org.libre.agosto.p2play.models.VideoModel
import org.libre.agosto.p2play.singletons.PlaybackSingleton
@Suppress("NAME_SHADOWING")
class ReproductorActivity : AppCompatActivity() {
private val clientVideo: Videos = Videos()
lateinit var video: VideoModel
private val _actions: Actions = Actions()
lateinit var videoPlayback: VideoModel
private val actions: Actions = Actions()
private val client: Comments = Comments()
private val videos: Videos = Videos()
@ -48,9 +58,14 @@ class ReproductorActivity : AppCompatActivity() {
// Exoplayer
private lateinit var player: ExoPlayer
private lateinit var mediaControl: MediaController
// Fullscreen info
private var isFullscreen = false
// Resume info
private var isResume = false
@SuppressLint("SetJavaScriptEnabled", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -69,7 +84,15 @@ class ReproductorActivity : AppCompatActivity() {
videoView.settings.domStorageEnabled = true
try {
val resume = this.intent.extras?.getSerializable("resume")
if (resume == null) {
video = this.intent.extras?.getSerializable("video") as VideoModel
isResume = false
} else {
video = PlaybackSingleton.video!!
isResume = true
}
tittleVideoTxt.text = this.video.name
viewsTxt.text = "${this.video.views} ${getString(R.string.view_text)}"
userTxt.text = this.video.username
@ -81,11 +104,11 @@ class ReproductorActivity : AppCompatActivity() {
hostTxt.text = this.video.userHost
// Check if user had profile image
if (this.video.userImageUrl != "")
if (this.video.userImageUrl != "") {
Picasso.get().load("https://" + ManagerSingleton.url + this.video.userImageUrl).into(userImg)
}
// Load the video
videoView.loadUrl("https://" + ManagerSingleton.url + this.video.embedUrl)
} catch (err: Exception) {
err.printStackTrace()
}
@ -111,21 +134,41 @@ class ReproductorActivity : AppCompatActivity() {
startActivity(intent)
}
AsyncTask.execute {
val video = this.clientVideo.getVideo(this.video.uuid)
videoPlayback = this.clientVideo.getVideo(this.video.uuid)
// TODO: Make this configurable
val bufferSize = 1024 * 1024 // 1mb
val allocator = DefaultAllocator(true, bufferSize)
val loadControl = DefaultLoadControl.Builder()
.setAllocator(allocator)
.build()
runOnUiThread {
try {
player = ExoPlayer.Builder(this.baseContext).build()
if (PlaybackSingleton.player == null || !PlaybackSingleton.player!!.playWhenReady) {
PlaybackSingleton.player = ExoPlayer.Builder(this.baseContext)
.setSeekBackIncrementMs(10000)
.setSeekForwardIncrementMs(10000)
.setLoadControl(loadControl).build()
}
player = PlaybackSingleton.player!!
exoPlayer.player = player
println("----- video --------")
println(video.streamingData?.playlistUrl)
val mediaItem = MediaItem.fromUri(video.streamingData?.playlistUrl!!)
// Set the media item to be played.
player.setMediaItem(mediaItem)
// Prepare the player.
player.prepare()
println(videoPlayback.streamingData?.playlistUrl)
if (!isResume) {
val mediaItem = MediaItem.Builder()
.setUri(videoPlayback.streamingData?.playlistUrl!!)
.setMediaMetadata(
MediaMetadata.Builder()
.setArtist(videoPlayback.username)
.setTitle(videoPlayback.name)
.setArtworkUri(Uri.parse("https://${ManagerSingleton.url}${videoPlayback.thumbUrl}"))
.build(),
).build()
PlaybackSingleton.setData(mediaItem, video)
}
// Start the playback.
// player.play()
} catch (err: Exception) {
@ -137,12 +180,13 @@ class ReproductorActivity : AppCompatActivity() {
private fun subscribe() {
AsyncTask.execute {
if (Looper.myLooper() == null)
if (Looper.myLooper() == null) {
Looper.prepare()
val res = this._actions.subscribe(ManagerSingleton.token.token, video.getChannel())
}
val res = this.actions.subscribe(ManagerSingleton.token.token, video.getChannel())
if (res == 1) {
runOnUiThread {
ManagerSingleton.Toast(getString(R.string.subscribeMsg), this)
ManagerSingleton.toast(getString(R.string.subscribeMsg), this)
this.changeSubscribeBtn(true)
}
}
@ -151,12 +195,13 @@ class ReproductorActivity : AppCompatActivity() {
private fun unSubscribe() {
AsyncTask.execute {
if (Looper.myLooper() == null)
if (Looper.myLooper() == null) {
Looper.prepare()
val res = this._actions.unSubscribe(ManagerSingleton.token.token, video.getChannel())
}
val res = this.actions.unSubscribe(ManagerSingleton.token.token, video.getChannel())
if (res == 1) {
runOnUiThread {
ManagerSingleton.Toast(getString(R.string.unSubscribeMsg), this)
ManagerSingleton.toast(getString(R.string.unSubscribeMsg), this)
this.changeSubscribeBtn(false)
}
}
@ -165,12 +210,13 @@ class ReproductorActivity : AppCompatActivity() {
private fun rate(rate: String) {
AsyncTask.execute {
if (Looper.myLooper() == null)
if (Looper.myLooper() == null) {
Looper.prepare()
val res = this._actions.rate(ManagerSingleton.token.token, this.video.id, rate)
}
val res = this.actions.rate(ManagerSingleton.token.token, this.video.id, rate)
if (res == 1) {
runOnUiThread {
ManagerSingleton.Toast(getString(R.string.rateMsg), this)
ManagerSingleton.toast(getString(R.string.rateMsg), this)
if (rate == "like") {
textViewLike.setTextColor(ContextCompat.getColor(this, R.color.colorLike))
textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
@ -185,9 +231,10 @@ class ReproductorActivity : AppCompatActivity() {
private fun getRate() {
AsyncTask.execute {
if (Looper.myLooper() == null)
if (Looper.myLooper() == null) {
Looper.prepare()
val rate = this._actions.getRate(ManagerSingleton.token.token, this.video.id)
}
val rate = this.actions.getRate(ManagerSingleton.token.token, this.video.id)
runOnUiThread {
when (rate) {
"like" -> {
@ -210,9 +257,10 @@ class ReproductorActivity : AppCompatActivity() {
private fun getSubscription() {
val account = this.video.nameChannel + "@" + this.video.userHost
AsyncTask.execute {
if (Looper.myLooper() == null)
if (Looper.myLooper() == null) {
Looper.prepare()
val isSubscribed = this._actions.getSubscription(ManagerSingleton.token.token, account)
}
val isSubscribed = this.actions.getSubscription(ManagerSingleton.token.token, account)
runOnUiThread {
this.changeSubscribeBtn(isSubscribed)
}
@ -231,7 +279,7 @@ class ReproductorActivity : AppCompatActivity() {
private fun setDataComments(data: ArrayList<CommentaryModel>) {
// Set data for RecyclerView
viewAdapter = CommentariesAdapter(data)
viewAdapter = CommentariesAdapter(data).setFragmentManager(supportFragmentManager)
recyclerView = findViewById<RecyclerView>(R.id.listCommentaries).apply {
// use this setting to improve performance if you know that changes
@ -257,7 +305,7 @@ class ReproductorActivity : AppCompatActivity() {
private fun makeComment() {
if (commentaryText.text.toString() == "") {
ManagerSingleton.Toast(getString(R.string.emptyCommentaryMsg), this)
ManagerSingleton.toast(getString(R.string.emptyCommentaryMsg), this)
return
}
val text = commentaryText.text.toString()
@ -265,11 +313,11 @@ class ReproductorActivity : AppCompatActivity() {
val res = this.client.makeCommentary(ManagerSingleton.token.token, this.video.id, text)
runOnUiThread {
if (res) {
ManagerSingleton.Toast(getString(R.string.makedCommentaryMsg), this)
ManagerSingleton.toast(getString(R.string.makedCommentaryMsg), this)
commentaryText.text?.clear()
this.getComments()
} else {
ManagerSingleton.Toast(getString(R.string.errorCommentaryMsg), this)
ManagerSingleton.toast(getString(R.string.errorCommentaryMsg), this)
}
}
}
@ -286,10 +334,12 @@ class ReproductorActivity : AppCompatActivity() {
if (ManagerSingleton.user.avatar != "") {
Picasso.get().load("https://" + ManagerSingleton.url + ManagerSingleton.user.avatar).into(userImgCom)
}
} else {
commentaryLayout.visibility = View.GONE
}
}
fun getDescription() {
private fun getDescription() {
AsyncTask.execute {
val fullDescription = this.videos.fullDescription(this.video.id)
runOnUiThread {
@ -318,6 +368,7 @@ class ReproductorActivity : AppCompatActivity() {
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(dialog)
.setTitle(R.string.reportDialog)
// Add action buttons
.setPositiveButton(R.string.reportBtn) { _, _ ->
val reason = inputReason.text.toString()
@ -332,14 +383,13 @@ class ReproductorActivity : AppCompatActivity() {
private fun reportVideo(reason: String) {
AsyncTask.execute {
val res = _actions.reportVideo(video.id, reason, ManagerSingleton.token.token)
val res = actions.reportVideo(video.id, reason, ManagerSingleton.token.token)
runOnUiThread {
if (res) {
ManagerSingleton.Toast(getText(R.string.reportDialogMsg).toString(), this)
}
else {
ManagerSingleton.Toast(getText(R.string.errorMsg).toString(), this)
ManagerSingleton.toast(getText(R.string.reportDialogMsg).toString(), this)
} else {
ManagerSingleton.toast(getText(R.string.errorMsg).toString(), this)
}
}
}
@ -360,8 +410,7 @@ class ReproductorActivity : AppCompatActivity() {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
isFullscreen = false
}
else {
} else {
val matchParent = WindowManager.LayoutParams.MATCH_PARENT
nonFullScreen.visibility = View.GONE
@ -378,7 +427,9 @@ class ReproductorActivity : AppCompatActivity() {
}
override fun onDestroy() {
player.release()
if (!player.isPlaying) {
PlaybackSingleton.release()
}
super.onDestroy()
}
@ -390,12 +441,14 @@ class ReproductorActivity : AppCompatActivity() {
override fun getDefaultVideoPoster(): Bitmap? {
AsyncTask.execute {
this@ReproductorActivity._actions.watchVideo(this@ReproductorActivity.video.id, ManagerSingleton.token.token)
this@ReproductorActivity.actions.watchVideo(this@ReproductorActivity.video.id, ManagerSingleton.token.token)
}
return if (mCustomView == null) {
null
} else BitmapFactory.decodeResource(this@ReproductorActivity.resources, 2130837573)
} else {
BitmapFactory.decodeResource(this@ReproductorActivity.resources, 2130837573)
}
}
override fun onHideCustomView() {
@ -416,8 +469,7 @@ class ReproductorActivity : AppCompatActivity() {
attrs.flags = attrs.flags and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON.inv()
window.attributes = attrs
this@ReproductorActivity.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
}
}
@ -432,18 +484,17 @@ class ReproductorActivity : AppCompatActivity() {
this.mOriginalSystemUiVisibility = this@ReproductorActivity.window.decorView.systemUiVisibility
this.mOriginalOrientation = this@ReproductorActivity.requestedOrientation
this.mCustomViewCallback = paramCustomViewCallback
val match_parent = WindowManager.LayoutParams.MATCH_PARENT
val matchParent = WindowManager.LayoutParams.MATCH_PARENT
this@ReproductorActivity.nonFullScreen.visibility = View.GONE
this@ReproductorActivity.fullScreen.visibility = View.VISIBLE
this@ReproductorActivity.fullScreen.addView(paramView, FrameLayout.LayoutParams(match_parent, match_parent))
this@ReproductorActivity.fullScreen.addView(paramView, FrameLayout.LayoutParams(matchParent, matchParent))
setFullscreen(this@ReproductorActivity.window)
this@ReproductorActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
}
}

View File

@ -4,8 +4,6 @@ import android.annotation.TargetApi
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.preference.ListPreference
@ -13,9 +11,6 @@ import android.preference.Preference
import android.preference.PreferenceActivity
import android.preference.PreferenceFragment
import android.preference.PreferenceManager
import android.preference.RingtonePreference
import android.text.TextUtils
import android.util.Log
import android.view.MenuItem
/**
@ -37,7 +32,7 @@ class SettingsActivity : AppCompatPreferenceActivity() {
override fun onBackPressed() {
super.onBackPressed()
ManagerSingleton.Toast(getString(R.string.pref_message_exit), this)
ManagerSingleton.toast(getString(R.string.pref_message_exit), this)
}
/**
@ -67,8 +62,8 @@ class SettingsActivity : AppCompatPreferenceActivity() {
* Make sure to deny any unknown fragments here.
*/
override fun isValidFragment(fragmentName: String): Boolean {
return PreferenceFragment::class.java.name == fragmentName
|| GeneralPreferenceFragment::class.java.name == fragmentName
return PreferenceFragment::class.java.name == fragmentName ||
GeneralPreferenceFragment::class.java.name == fragmentName
}
/**
@ -116,11 +111,12 @@ class SettingsActivity : AppCompatPreferenceActivity() {
// Set the summary to reflect the new value.
preference.setSummary(
if (index >= 0)
if (index >= 0) {
listPreference.entries[index]
else
null)
} else {
null
},
)
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
@ -152,10 +148,12 @@ class SettingsActivity : AppCompatPreferenceActivity() {
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
sBindPreferenceSummaryToValueListener.onPreferenceChange(
preference,
PreferenceManager
.getDefaultSharedPreferences(preference.context)
.getString(preference.key, ""))
.getString(preference.key, ""),
)
}
}
}

View File

@ -0,0 +1,31 @@
package org.libre.agosto.p2play
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
class SettingsActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onDestroy() {
super.onDestroy()
ManagerSingleton.reloadSettings()
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
}

View File

@ -3,36 +3,34 @@ package org.libre.agosto.p2play
import android.content.Intent
import android.content.SharedPreferences
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.preference.PreferenceManager
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import org.libre.agosto.p2play.ajax.Auth
import java.lang.Exception
class SplashActivity : AppCompatActivity() {
lateinit var settings: SharedPreferences
lateinit var editor: SharedPreferences.Editor
val client: Auth = Auth()
val _db = Database(this)
val db = Database(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
settings = PreferenceManager.getDefaultSharedPreferences(this)
ManagerSingleton.settings = settings
ManagerSingleton.db = db
ManagerSingleton.nfsw = settings.getBoolean("show_nfsw", false)
ManagerSingleton.videos_count = settings.getString("videos_count", "15")!!.toInt()
ManagerSingleton.reloadSettings()
val host = settings.getString("hostP2play", "")
val lastHost = settings.getString("last_host", "")
if (host != "") {
if (lastHost != host) {
_db.logout()
Handler().postDelayed({
startHostActivity()
}, 2000)
@ -40,8 +38,7 @@ class SplashActivity : AppCompatActivity() {
ManagerSingleton.url = host
checkUser()
}
}
else{
} else {
Handler().postDelayed({
startHostActivity()
}, 2000)
@ -49,42 +46,37 @@ class SplashActivity : AppCompatActivity() {
}
private fun checkUser() {
Log.d("was", "Chequed")
Log.d("was", "Checked")
try {
val token = _db.getToken()
val user = _db.getUser()
val token = db.getToken()
val user = db.getUser()
AsyncTask.execute {
if (Looper.myLooper() == null)
if (Looper.myLooper() == null) {
Looper.prepare()
}
if (token.status == 1 && user.status == 1) {
val client_id = settings.getString("client_id", "")!!
val client_secret = settings.getString("client_secret", "")!!
val clientId = settings.getString("client_id", "")!!
val clientSecret = settings.getString("client_secret", "")!!
val newToken = client.refreshToken(token, client_id, client_secret)
val newToken = client.refreshToken(token, clientId, clientSecret)
when (token.status.toString()) {
"1" -> {
_db.newToken(newToken)
db.newToken(newToken)
ManagerSingleton.token = newToken
ManagerSingleton.user = user
}
else -> _db.logout()
else -> ManagerSingleton.logout()
}
} else {
_db.logout()
ManagerSingleton.logout()
}
startApp()
Log.d("Aqui", "81")
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
Log.d("Aqui", "89")
Handler().postDelayed({
startApp()
}, 2000)

View File

@ -1,23 +1,33 @@
package org.libre.agosto.p2play.adapters
import android.content.Context
import android.content.Intent
import androidx.recyclerview.widget.RecyclerView
import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.view_video.view.userImg
import org.libre.agosto.p2play.*
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.dialogs.ThreadDialog
import org.libre.agosto.p2play.models.CommentaryModel
import java.io.Serializable
@Suppress("DEPRECATION")
class CommentariesAdapter(private val myDataset: ArrayList<CommentaryModel>) :
RecyclerView.Adapter<CommentariesAdapter.ViewHolder>() {
private lateinit var fragmentManager: FragmentManager
fun setFragmentManager(manager: FragmentManager): CommentariesAdapter {
this.fragmentManager = manager
return this
}
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
@ -26,6 +36,7 @@ class CommentariesAdapter(private val myDataset: ArrayList<CommentaryModel>) :
val userImg: ImageView
val username: TextView
val commentary: TextView
val replyBtn: TextView
val context: Context
init {
@ -33,14 +44,16 @@ class CommentariesAdapter(private val myDataset: ArrayList<CommentaryModel>) :
username = view.findViewById(R.id.userTxt)
commentary = view.findViewById(R.id.userCommentary)
userImg = view.findViewById(R.id.userCommentImg)
replyBtn = view.findViewById(R.id.replyBtn)
context = view.context
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): CommentariesAdapter.ViewHolder {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): ViewHolder {
// create a new view
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.view_commentary, parent, false) as View
@ -61,10 +74,16 @@ class CommentariesAdapter(private val myDataset: ArrayList<CommentaryModel>) :
// holder.context.startActivity(intent)
// }
if(myDataset[position].userImageUrl!="")
Picasso.get().load("https://"+ManagerSingleton.url+myDataset[position].userImageUrl).into(holder.userImg);
if (myDataset[position].userImageUrl != "") {
Picasso.get().load("https://" + ManagerSingleton.url + myDataset[position].userImageUrl).into(holder.userImg)
}
holder.commentary.text = Html.fromHtml(myDataset[position].commentary)
holder.replyBtn.setOnClickListener { this.initRepliesDialog(myDataset[position]) }
if (myDataset[position].replies > 0) {
holder.replyBtn.text = holder.itemView.context.getString(R.string.see_replies, myDataset[position].replies)
}
// TODO: Support for view and account (is different than a video channel)
// holder.userImg.setOnClickListener {
@ -76,4 +95,22 @@ class CommentariesAdapter(private val myDataset: ArrayList<CommentaryModel>) :
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = myDataset.size
private fun initRepliesDialog(commentData: CommentaryModel) {
val dialog = ThreadDialog()
val bundle = Bundle()
bundle.putSerializable("comment", commentData as Serializable)
dialog.arguments = bundle
dialog.fragmentManager2 = this.fragmentManager
val transaction = fragmentManager.beginTransaction()
// For a polished look, specify a transition animation.
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
// To make it fullscreen, use the 'content' root view as the container
// for the fragment, which is always the root view for the activity.
transaction
.add(android.R.id.content, dialog)
.addToBackStack("comments")
.commit()
}
}

View File

@ -2,14 +2,17 @@ package org.libre.agosto.p2play.adapters
import android.content.Context
import android.content.Intent
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import org.libre.agosto.p2play.*
import org.libre.agosto.p2play.ChannelActivity
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ReproductorActivity
import org.libre.agosto.p2play.helpers.mapSeconds
import org.libre.agosto.p2play.models.VideoModel
import java.io.Serializable
@ -17,7 +20,6 @@ import java.io.Serializable
class VideosAdapter(private val myDataset: ArrayList<VideoModel>) :
RecyclerView.Adapter<VideosAdapter.ViewHolder>() {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
@ -60,13 +62,14 @@ class VideosAdapter(private val myDataset: ArrayList<VideoModel>) :
holder.context.startActivity(intent)
}
if(myDataset[position].userImageUrl!="")
if (myDataset[position].userImageUrl != "") {
Picasso.get().load("https://" + ManagerSingleton.url + myDataset[position].userImageUrl).into(holder.userImg)
else
} else {
Picasso.get().load(R.drawable.default_avatar).into(holder.userImg)
}
val viewsText = holder.context.getString(R.string.view_text)
val seconds = myDataset[position].duration.toInt();
val seconds = myDataset[position].duration.toInt()
val timeString = mapSeconds(seconds)
holder.duration.text = timeString

View File

@ -6,7 +6,7 @@ import java.io.InputStreamReader
class Actions : Client() {
fun subscribe(token: String, account: String): Int {
val con = this._newCon("users/me/subscriptions","POST", token)
val con = this.newCon("users/me/subscriptions", "POST", token)
val params: String = "uri=$account"
con.outputStream.write(params.toByteArray())
var response = 0
@ -15,8 +15,7 @@ class Actions: Client() {
if (con.responseCode == 204) {
response = 1
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
response = -1
}
@ -26,15 +25,14 @@ class Actions: Client() {
}
fun unSubscribe(token: String, account: String): Int {
val con = this._newCon("users/me/subscriptions/$account","DELETE", token)
val con = this.newCon("users/me/subscriptions/$account", "DELETE", token)
var response = 0
try {
if (con.responseCode == 204) {
response = 1
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
response = -1
}
@ -44,7 +42,7 @@ class Actions: Client() {
}
fun getSubscription(token: String, account: String): Boolean {
val con = this._newCon("users/me/subscriptions/exist?uris=$account","GET", token)
val con = this.newCon("users/me/subscriptions/exist?uris=$account", "GET", token)
var isSubscribed = false
try {
@ -65,8 +63,7 @@ class Actions: Client() {
}
data.close()
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
isSubscribed = false
}
@ -76,7 +73,7 @@ class Actions: Client() {
}
fun rate(token: String, id_video: Int, rate: String): Int {
val con = this._newCon("videos/$id_video/rate","PUT", token)
val con = this.newCon("videos/$id_video/rate", "PUT", token)
val params = "rating=$rate"
con.outputStream.write(params.toByteArray())
var response = 0
@ -85,8 +82,7 @@ class Actions: Client() {
if (con.responseCode == 204) {
response = 1
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
response = -1
}
@ -96,7 +92,7 @@ class Actions: Client() {
}
fun getRate(token: String, id_video: Int): String {
val con = this._newCon("users/me/videos/$id_video/rating","GET", token)
val con = this.newCon("users/me/videos/$id_video/rating", "GET", token)
var rating = "none"
try {
@ -117,8 +113,7 @@ class Actions: Client() {
}
con.disconnect()
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
rating = "none"
}
@ -128,7 +123,7 @@ class Actions: Client() {
}
fun reportVideo(videoId: Int, reason: String, token: String): Boolean {
val con = this._newCon("videos/$videoId/abuse", "POST", token)
val con = this.newCon("videos/$videoId/abuse", "POST", token)
val params = "reason=$reason"
con.outputStream.write(params.toByteArray())
@ -146,7 +141,7 @@ class Actions: Client() {
}
fun watchVideo(videoId: Int, token: String): Boolean {
val con = this._newCon("videos/$videoId/watching", "PUT", token)
val con = this.newCon("videos/$videoId/watching", "PUT", token)
val params = "currentTime=1"
con.outputStream.write(params.toByteArray())

View File

@ -4,7 +4,6 @@ package org.libre.agosto.p2play.ajax
import android.util.JsonReader
import android.util.JsonToken
import android.util.Log
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.models.TokenModel
import org.libre.agosto.p2play.models.UserModel
import java.io.InputStreamReader
@ -12,15 +11,20 @@ import java.io.InputStreamReader
class Auth : Client() {
private val stockParams = "grant_type=password"
fun login(username: String, password: String, client_id: String, client_secret: String): TokenModel{
val con = this._newCon("users/token","POST")
fun login(username: String, password: String, client_id: String, client_secret: String, twoFactorCode: String? = null): TokenModel {
val con = this.newCon("users/token", "POST")
val params = "$stockParams&username=$username&password=$password&client_id=$client_id&client_secret=$client_secret"
if (twoFactorCode !== null) {
con.setRequestProperty("x-peertube-otp", twoFactorCode)
}
con.outputStream.write(params.toByteArray())
val token = TokenModel()
try {
if(con.responseCode==200){
when (con.responseCode) {
200 -> {
val response = InputStreamReader(con.inputStream)
val data = JsonReader(response)
data.beginObject()
@ -37,13 +41,16 @@ class Auth: Client() {
data.endObject()
data.close()
token.status = 1
}
else{
401 -> {
// User require 2FA code
token.status = -2
}
else -> {
Log.d("Status", con.responseMessage)
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
token.status = 0
}
@ -53,7 +60,7 @@ class Auth: Client() {
}
fun register(username: String, password: String, email: String): Int {
val con = this._newCon("users/register","POST")
val con = this.newCon("users/register", "POST")
val params = "username=$username&password=$password&email=$email"
con.outputStream.write(params.toByteArray())
@ -63,19 +70,17 @@ class Auth: Client() {
if (con.responseCode == 204) {
response = 1
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
response = -1
}
con.disconnect()
return response
}
fun refreshToken(token: TokenModel, client_id: String, client_secret: String): TokenModel {
val con = this._newCon("users/token", "POST", token.token)
val con = this.newCon("users/token", "POST", token.token)
val params = "refresh_token=${token.refresh_token}&response_type=code&grant_type=refresh_token&client_id=$client_id&client_secret=$client_secret"
con.outputStream.write(params.toByteArray())
// val token = TokenModel()
@ -98,13 +103,10 @@ class Auth: Client() {
data.endObject()
data.close()
token.status = 1
}
else{
} else {
Log.d("Status", con.responseMessage)
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
token.status = 0
}
@ -114,11 +116,10 @@ class Auth: Client() {
}
fun me(token: String): UserModel {
val con = this._newCon("users/me","GET", token)
val con = this.newCon("users/me", "GET", token)
val user = UserModel()
try {
if (con.responseCode == 200) {
val response = InputStreamReader(con.inputStream)
val data = JsonReader(response)
@ -148,8 +149,7 @@ class Auth: Client() {
}
}
data.endObject()
}
else{
} else {
data.skipValue()
}
}
@ -165,13 +165,10 @@ class Auth: Client() {
data.endObject()
data.close()
user.status = 1
}
else{
} else {
Log.d("Status", con.responseMessage)
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
user.status = 0
}
@ -179,5 +176,4 @@ class Auth: Client() {
con.disconnect()
return user
}
}

View File

@ -1,9 +1,7 @@
package org.libre.agosto.p2play.ajax
import android.util.JsonReader
import android.util.JsonToken
import org.libre.agosto.p2play.models.ChannelModel
import org.libre.agosto.p2play.models.CommentaryModel
import java.io.InputStreamReader
class Channels : Client() {
@ -17,7 +15,7 @@ class Channels: Client() {
}
fun getChannelInfo(account: String): ChannelModel {
val con = this._newCon("video-channels/$account", "GET")
val con = this.newCon("video-channels/$account", "GET")
var channel = ChannelModel()
try {
if (con.responseCode == 200) {

View File

@ -5,12 +5,11 @@ import android.util.Log
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.models.HostModel
import java.io.InputStreamReader
import java.io.Reader
import java.net.HttpURLConnection
import java.net.URL
open class Client {
protected fun _newCon(uri: String, method: String, token: String = ""): HttpURLConnection {
protected fun newCon(uri: String, method: String, token: String = ""): HttpURLConnection {
val url = URL("https://${ManagerSingleton.url}/api/v1/$uri")
val con = url.openConnection() as HttpURLConnection
@ -26,15 +25,16 @@ open class Client {
con.connectTimeout = 60000
con.readTimeout = 60000
if(method == "POST")
if (method == "POST") {
con.doOutput = true
}
Log.d("Petition", url.toString())
return con
}
fun getKeys(): HostModel {
val con = this._newCon("oauth-clients/local","GET")
val con = this.newCon("oauth-clients/local", "GET")
val keys = HostModel("", "")
try {
if (con.responseCode == 200) {
@ -64,5 +64,4 @@ open class Client {
con.disconnect()
return keys
}
}

View File

@ -1,13 +1,11 @@
package org.libre.agosto.p2play.ajax
import android.util.JsonReader
import android.util.JsonToken
import android.util.Log
import org.libre.agosto.p2play.models.CommentaryModel
import java.io.InputStreamReader
class Comments : Client() {
private fun parseCommentaries(data: JsonReader): ArrayList<CommentaryModel> {
val commentaries = arrayListOf<CommentaryModel>()
data.beginObject()
@ -32,7 +30,7 @@ class Comments: Client() {
fun getCommentaries(videoId: Int): ArrayList<CommentaryModel> {
var commentaries = arrayListOf<CommentaryModel>()
val con = this._newCon("videos/$videoId/comment-threads", "GET")
val con = this.newCon("videos/$videoId/comment-threads", "GET")
try {
if (con.responseCode == 200) {
@ -49,7 +47,7 @@ class Comments: Client() {
}
fun makeCommentary(token: String, videoId: Int, text: String): Boolean {
val con = this._newCon("videos/$videoId/comment-threads", "POST", token)
val con = this.newCon("videos/$videoId/comment-threads", "POST", token)
val params = "text=$text"
con.outputStream.write(params.toByteArray())
@ -59,13 +57,11 @@ class Comments: Client() {
if (con.responseCode == 200) {
con.disconnect()
response = true
}
else{
} else {
Log.d("Status", con.responseMessage)
response = false
}
}
catch (err: Exception){
} catch (err: Exception) {
err.printStackTrace()
response = false
}
@ -74,4 +70,67 @@ class Comments: Client() {
return response
}
fun getCommentariesThread(videoId: Int, threadId: Int): ArrayList<CommentaryModel> {
var commentaries = arrayListOf<CommentaryModel>()
val con = this.newCon("videos/$videoId/comment-threads/$threadId", "GET")
try {
if (con.responseCode == 200) {
val response = InputStreamReader(con.inputStream)
val data = JsonReader(response)
data.beginObject()
while (data.hasNext()) {
when (data.nextName()) {
"children" -> {
data.beginArray()
while (data.hasNext()) {
data.beginObject()
while (data.hasNext()) {
when (data.nextName()) {
"comment" -> {
val comment = CommentaryModel()
comment.parseCommentary(data)
commentaries.add(comment)
}
else -> data.skipValue()
}
}
data.endObject()
}
data.endArray()
}
else -> data.skipValue()
}
}
data.endObject()
data.close()
}
} catch (err: Exception) {
err.printStackTrace()
}
con.disconnect()
return commentaries
}
fun replyThread(token: String, videoId: Int, threadId: Int, text: String): Boolean {
val con = this.newCon("videos/$videoId/comments/$threadId", "POST", token)
val params = "text=$text"
con.outputStream.write(params.toByteArray())
val response: Boolean = try {
if (con.responseCode == 200) {
con.disconnect()
true
} else {
Log.d("Status", con.responseMessage)
false
}
} catch (err: Exception) {
err.printStackTrace()
false
}
con.disconnect()
return response
}
}

View File

@ -31,10 +31,10 @@ class Videos: Client() {
private fun getVideos(start: Int, sort: String = "-publishedAt", isLocal: Boolean = false): ArrayList<VideoModel> {
val nsfw = ManagerSingleton.nfsw
val count = ManagerSingleton.videos_count
val count = ManagerSingleton.videosCount
var params = "start=$start&count=$count&sort=$sort&nsfw=$nsfw&isLocal=$isLocal"
val con = this._newCon("videos?$params","GET")
val con = this.newCon("videos?$params", "GET")
var videos = arrayListOf<VideoModel>()
try {
if (con.responseCode == 200) {
@ -67,9 +67,9 @@ class Videos: Client() {
}
fun myVideos(token: String, start: Int = 0): ArrayList<VideoModel> {
val count = ManagerSingleton.videos_count
val count = ManagerSingleton.videosCount
val params = "start=$start&count=$count"
val con = this._newCon("users/me/videos?$params","GET", token)
val con = this.newCon("users/me/videos?$params", "GET", token)
var videos = arrayListOf<VideoModel>()
try {
if (con.responseCode == 200) {
@ -87,9 +87,9 @@ class Videos: Client() {
}
fun videoSubscriptions(token: String, start: Int = 0): ArrayList<VideoModel> {
val count = ManagerSingleton.videos_count
val count = ManagerSingleton.videosCount
val params = "start=$start&count=$count"
val con = this._newCon("users/me/subscriptions/videos?$params","GET", token)
val con = this.newCon("users/me/subscriptions/videos?$params", "GET", token)
var videos = arrayListOf<VideoModel>()
try {
if (con.responseCode == 200) {
@ -111,9 +111,9 @@ class Videos: Client() {
}
fun videoHistory(token: String, start: Int = 0): ArrayList<VideoModel> {
val count = ManagerSingleton.videos_count
val count = ManagerSingleton.videosCount
val params = "start=$start&count=$count"
val con = this._newCon("users/me/history/videos?$params","GET", token)
val con = this.newCon("users/me/history/videos?$params", "GET", token)
var videos = arrayListOf<VideoModel>()
try {
if (con.responseCode == 200) {
@ -131,10 +131,10 @@ class Videos: Client() {
}
fun search(text: String, start: Int = 0): ArrayList<VideoModel> {
val count = ManagerSingleton.videos_count
val count = ManagerSingleton.videosCount
val nsfw = ManagerSingleton.nfsw
val params = "search=$text&start=$start&count=$count&nsfw=$nsfw"
val con = this._newCon("search/videos?$params","GET")
val con = this.newCon("search/videos?$params", "GET")
var videos = arrayListOf<VideoModel>()
try {
if (con.responseCode == 200) {
@ -151,7 +151,7 @@ class Videos: Client() {
}
fun fullDescription(videoId: Int): String {
val con = this._newCon("videos/$videoId/description","GET")
val con = this.newCon("videos/$videoId/description", "GET")
var description = ""
try {
if (con.responseCode == 200) {
@ -178,9 +178,9 @@ class Videos: Client() {
}
fun channelVideos(account: String, start: Int): ArrayList<VideoModel> {
val count = ManagerSingleton.videos_count
val count = ManagerSingleton.videosCount
val params = "start=$start&count=$count"
val con = this._newCon("video-channels/$account/videos?$params","GET")
val con = this.newCon("video-channels/$account/videos?$params", "GET")
var videos = arrayListOf<VideoModel>()
try {
if (con.responseCode == 200) {
@ -202,7 +202,7 @@ class Videos: Client() {
}
fun getVideo(uuid: String): VideoModel {
val con = this._newCon("videos/$uuid","GET")
val con = this.newCon("videos/$uuid", "GET")
val video = VideoModel()
try {
if (con.responseCode == 200) {

View File

@ -0,0 +1,132 @@
package org.libre.agosto.p2play.dialogs
import android.app.Dialog
import android.os.AsyncTask
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.comment_component.commentaryLayout
import kotlinx.android.synthetic.main.comment_component.commentaryText
import kotlinx.android.synthetic.main.comment_component.userImgCom
import kotlinx.android.synthetic.main.comment_component.view.commentaryBtn
import kotlinx.android.synthetic.main.comment_component.view.commentaryLayout
import kotlinx.android.synthetic.main.comment_component.view.commentaryText
import kotlinx.android.synthetic.main.comment_component.view.userImgCom
import kotlinx.android.synthetic.main.dialog_thread.view.materialToolbar
import kotlinx.android.synthetic.main.view_commentary.view.replyBtn
import kotlinx.android.synthetic.main.view_commentary.view.userCommentImg
import kotlinx.android.synthetic.main.view_commentary.view.userCommentary
import kotlinx.android.synthetic.main.view_commentary.view.userTxt
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.adapters.CommentariesAdapter
import org.libre.agosto.p2play.ajax.Comments
import org.libre.agosto.p2play.models.CommentaryModel
class ThreadDialog : DialogFragment() {
private lateinit var comment: CommentaryModel
lateinit var fragmentManager2: FragmentManager
private val client: Comments = Comments()
// The system calls this to get the DialogFragment's layout, regardless of
// whether it's being displayed as a dialog or an embedded fragment.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
// Inflate the layout to use as a dialog or embedded fragment.
val view = inflater.inflate(R.layout.dialog_thread, container, false)
comment = arguments?.getSerializable("comment") as CommentaryModel
view.userTxt.text = comment.username
view.userCommentary.text = comment.commentary
Picasso.get().load("https://${ManagerSingleton.url}${comment.userImageUrl}").into(view.userCommentImg)
view.replyBtn.visibility = View.GONE
if (ManagerSingleton.user.status == 1) {
view.commentaryText.setText("${comment.username}@${comment.userHost} ")
if (ManagerSingleton.user.avatar != "") {
Picasso.get().load("https://${ManagerSingleton.url}${ManagerSingleton.user.avatar}").into(view.userImgCom)
}
view.commentaryText.requestFocus()
view.commentaryBtn.setOnClickListener { this.replyThread() }
} else {
view.commentaryLayout.visibility = View.GONE
}
view.materialToolbar.setTitle("Thread")
view.materialToolbar.setNavigationIcon(R.drawable.baseline_arrow_back_24)
view.materialToolbar.setNavigationOnClickListener {
dismiss()
this.fragmentManager2.popBackStack()
}
this.getComments()
return view
}
// The system calls this only when creating the layout in a dialog.
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
return dialog
}
private fun getComments() {
AsyncTask.execute {
val data =
this.client.getCommentariesThread(this.comment.videoId, this.comment.id)
activity?.runOnUiThread {
this.setDataComments(data)
}
}
}
private fun setDataComments(data: ArrayList<CommentaryModel>) {
// Set data for RecyclerView
val viewAdapter = CommentariesAdapter(data).setFragmentManager(this.fragmentManager2)
view?.findViewById<RecyclerView>(R.id.listCommentaries)?.let {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
it.setHasFixedSize(true)
// use a linear layout manager
it.layoutManager = LinearLayoutManager(activity)
// specify an viewAdapter (see also next example)
it.adapter = viewAdapter
}
}
private fun replyThread() {
val commentary = view?.commentaryText?.text.toString()
if (commentary == "") {
ManagerSingleton.toast(getString(R.string.emptyCommentaryMsg), requireActivity())
return
}
AsyncTask.execute {
val res = this.client.replyThread(ManagerSingleton.token.token, this.comment.videoId, this.comment.id, commentary)
activity?.runOnUiThread {
if (res) {
ManagerSingleton.toast(getString(R.string.makedCommentaryMsg), requireActivity())
commentaryText.text?.clear()
this.getComments()
} else {
ManagerSingleton.toast(getString(R.string.errorCommentaryMsg), requireActivity())
}
}
}
}
}

View File

@ -12,7 +12,7 @@ class ChannelModel (
var name: String = "",
var description: String = "",
var support: String = "",
var channelImg: String = ""
var channelImg: String = "",
) {
fun getAccount(): String {
return "$nameChannel@$host"
@ -22,7 +22,6 @@ class ChannelModel (
data.beginObject()
while (data.hasNext()) {
when (data.nextName()) {
"id" -> this.id = data.nextInt()
"url" -> this.url = data.nextString()
@ -31,17 +30,19 @@ class ChannelModel (
"followersCount" -> this.followers = data.nextInt()
"displayName" -> this.name = data.nextString()
"description" -> {
if(data.peek() == JsonToken.STRING)
if (data.peek() == JsonToken.STRING) {
this.description = data.nextString()
else
} else {
data.skipValue()
}
}
"support" -> {
if(data.peek() == JsonToken.STRING)
if (data.peek() == JsonToken.STRING) {
this.support = data.nextString()
else
} else {
data.skipValue()
}
}
"avatar" -> {
if (data.peek() == JsonToken.BEGIN_OBJECT) {
data.beginObject()
@ -52,10 +53,10 @@ class ChannelModel (
}
}
data.endObject()
}
else
} else {
data.skipValue()
}
}
else -> data.skipValue()
}
}

View File

@ -2,6 +2,7 @@ package org.libre.agosto.p2play.models
import android.util.JsonReader
import android.util.JsonToken
import java.io.Serializable
class CommentaryModel(
var id: Int = 0,
@ -12,8 +13,9 @@ class CommentaryModel (
var commentary: String = "",
var userHost: String = "",
var replies: Int = 0,
var nameChannel: String = ""
) {
var nameChannel: String = "",
var videoId: Int = 0,
) : Serializable {
fun parseCommentary(data: JsonReader) {
data.beginObject()
while (data.hasNext()) {
@ -23,6 +25,7 @@ class CommentaryModel (
"threadId" -> this.threadId = data.nextInt()
"text" -> this.commentary = data.nextString()
"totalReplies" -> this.replies = data.nextInt()
"videoId" -> this.videoId = data.nextInt()
"account" -> {
data.beginObject()
while (data.hasNext()) {
@ -40,10 +43,9 @@ class CommentaryModel (
}
}
data.endObject()
}
else
} else {
data.skipValue()
}
}
"uuid" -> this.userUuid = data.nextString()
"host" -> this.userHost = data.nextString()
@ -62,5 +64,4 @@ class CommentaryModel (
fun getAccount(): String {
return "$nameChannel@$userHost"
}
}

View File

@ -3,5 +3,5 @@ package org.libre.agosto.p2play.models
class TokenModel(
var token: String = "",
var refresh_token: String = "",
var status: Int = -1
var status: Int = -1,
)

View File

@ -8,5 +8,5 @@ class UserModel (
var nsfw: Boolean = true,
var followers: Int = 0,
var avatar: String = "",
var status: Int = -1
var status: Int = -1,
)

View File

@ -19,7 +19,7 @@ class VideoModel(
var userHost: String = "",
var nameChannel: String = "",
var isLive: Boolean = false,
var streamingData: StreamingModel? = null
var streamingData: StreamingModel? = null,
) : Serializable {
fun getChannel(): String {
return "$nameChannel@$userHost"
@ -40,11 +40,12 @@ class VideoModel(
this.name = data.nextString()
}
"description" -> {
if(data.peek() == JsonToken.STRING)
if (data.peek() == JsonToken.STRING) {
this.description = data.nextString()
else
} else {
data.skipValue()
}
}
"duration" -> {
this.duration = data.nextInt()
}
@ -70,14 +71,14 @@ class VideoModel(
data.endArray()
}
"files" -> {
if (streamingData === null) {
data.beginArray()
if (streamingData === null) {
if (data.hasNext()) {
data.beginObject()
while (data.hasNext()) {
val key2 = data.nextName()
when (key2.toString()) {
"fileDownloadUrl"->{
"fileUrl" -> {
streamingData = StreamingModel()
streamingData!!.playlistUrl = data.nextString()
}
@ -86,9 +87,12 @@ class VideoModel(
}
data.endObject()
}
data.endArray()
while (data.hasNext()) {
data.skipValue()
}
}
data.endArray()
}
"channel" -> {
data.beginObject()
while (data.hasNext()) {
@ -106,10 +110,9 @@ class VideoModel(
}
}
data.endObject()
}
else
} else {
data.skipValue()
}
}
"uuid" -> this.userUuid = data.nextString()
"host" -> this.userHost = data.nextString()

View File

@ -0,0 +1,47 @@
package org.libre.agosto.p2play.services
import android.app.PendingIntent
import android.content.Intent
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import org.libre.agosto.p2play.ReproductorActivity
import org.libre.agosto.p2play.singletons.PlaybackSingleton
class PlaybackService : MediaSessionService() {
private var mediaSession: MediaSession? = null
// Create your Player and MediaSession in the onCreate lifecycle event
override fun onCreate() {
super.onCreate()
val player = PlaybackSingleton.player!!
mediaSession = MediaSession.Builder(this, player)
.build()
val contentIntent = Intent(this, ReproductorActivity::class.java)
contentIntent.putExtra("resume", true)
val pendingIntent = PendingIntent.getActivity(
this,
0,
contentIntent,
PendingIntent.FLAG_MUTABLE,
)
mediaSession!!.setSessionActivity(pendingIntent)
}
// Remember to release the player and media session in onDestroy
override fun onDestroy() {
mediaSession?.run {
release()
mediaSession = null
}
super.onDestroy()
}
override fun onTaskRemoved(rootIntent: Intent?) {
this.mediaSession!!.player.stop()
super.onTaskRemoved(rootIntent)
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
return mediaSession
}
}

View File

@ -0,0 +1,51 @@
package org.libre.agosto.p2play.singletons
import android.content.ComponentName
import android.content.Context
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import com.google.common.util.concurrent.MoreExecutors
import org.libre.agosto.p2play.models.VideoModel
import org.libre.agosto.p2play.services.PlaybackService
object PlaybackSingleton {
var player: ExoPlayer? = null
var video: VideoModel? = null
private var withMediaSession = false
fun setData(mediaItem: MediaItem, video: VideoModel): ExoPlayer? {
player?.let {
if (it.isPlaying) {
it.pause()
}
it.setMediaItem(mediaItem)
it.prepare()
this.video = video
return it
}
return null
}
fun release() {
player?.release()
}
fun runMediaSession(context: Context) {
if (!this.withMediaSession) {
val sessionToken = SessionToken(context, ComponentName(context, PlaybackService::class.java))
val controllerFuture = MediaController.Builder(context, sessionToken).buildAsync()
controllerFuture.addListener(
{
val med = controllerFuture.get()
},
MoreExecutors.directExecutor(),
)
this.withMediaSession = true
}
}
}

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@ -8,18 +8,18 @@
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeColor="#585858"
android:fillType="evenOdd"/>
<path
android:pathData="M12,17.75C12.6904,17.75 13.25,17.1904 13.25,16.5C13.25,15.8096 12.6904,15.25 12,15.25C11.3096,15.25 10.75,15.8096 10.75,16.5C10.75,17.1904 11.3096,17.75 12,17.75Z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
android:fillColor="#585858"
android:strokeColor="#585858"
android:fillType="evenOdd" />
<path
android:pathData="M12,9L12,9A1,1 0,0 1,13 10L13,13A1,1 0,0 1,12 14L12,14A1,1 0,0 1,11 13L11,10A1,1 0,0 1,12 9z"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
android:fillColor="#585858"
android:strokeColor="#585858"
android:fillType="evenOdd" />
</vector>

View File

@ -7,7 +7,7 @@
android:pathData="M20,15L20,18.0026C20,19.1057 19.1074,20 18.0049,20L5.9951,20C4.8932,20 4,19.1074 4,18.0049L4,5.9951C4,4.8932 4.8959,4 5.9974,4L9,4"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeColor="#585858"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
@ -15,7 +15,7 @@
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeColor="#585858"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
<path
@ -23,7 +23,7 @@
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeColor="#585858"
android:fillType="evenOdd"
android:strokeLineCap="round"/>
</vector>

View File

@ -65,14 +65,14 @@
<WebView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="222dp"
android:layout_height="205dp"
android:layout_weight="1"
android:visibility="gone" />
<androidx.media3.ui.PlayerView
android:id="@+id/exoPlayer"
android:layout_width="match_parent"
android:layout_height="222dp"
android:layout_height="205dp"
app:use_controller="true"
app:controller_layout_id="@layout/custom_player_controls"
app:player_layout_id="@layout/exo_player_view"/>
@ -220,15 +220,16 @@
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_weight="1"
android:gravity="center|center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/userImg"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_width="35dp"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:layout_weight="1"
android:adjustViewBounds="false"
android:adjustViewBounds="true"
android:cropToPadding="false"
android:padding="5dp"
android:scaleType="fitCenter"
@ -236,7 +237,7 @@
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:orientation="vertical">
@ -244,6 +245,7 @@
android:id="@+id/userTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxWidth="300dp"
android:textAppearance="@android:style/TextAppearance.Material.Large"
android:textSize="18sp"
android:textStyle="bold" />
@ -323,50 +325,7 @@
android:layout_width="match_parent"
android:layout_height="20dp" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/commentaryLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/userImgCom"
android:layout_width="10dp"
android:layout_height="60dp"
android:layout_weight="1"
app:srcCompat="@drawable/default_avatar" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_weight="3"
android:hint="@string/commentHolder">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/commentaryText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ems="10"
android:inputType="textMultiLine" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/commentaryBtn"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/commentaryText" />
</androidx.appcompat.widget.LinearLayoutCompat>
<include layout="@layout/comment_component" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listCommentaries"
@ -379,4 +338,5 @@
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/commentaryLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/userImgCom"
android:layout_width="5dp"
android:layout_height="57dp"
android:layout_weight="1"
app:srcCompat="@drawable/default_avatar" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_weight="3"
android:hint="@string/commentHolder">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/commentaryText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ems="10"
android:inputType="textMultiLine" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/commentaryBtn"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/commentaryText" />
</androidx.appcompat.widget.LinearLayoutCompat>

View File

@ -25,4 +25,16 @@
app:layout_constraintTop_toTopOf="parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include
android:id="@+id/mini"
layout="@layout/mini_player"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="?attr/colorSurface"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="130dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/materialToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize" />
<include
layout="@layout/view_commentary"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listCommentaries"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="10dp"/>
</LinearLayout>
<include
layout="@layout/comment_component"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/androidBackgroundSecondary"
android:elevation="5dp"
android:clickable="true"
android:id="@+id/mini_player">
<ImageView
android:id="@+id/mini_player_image"
android:layout_width="wrap_content"
android:layout_height="60dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/default_avatar"
tools:srcCompat="@drawable/default_avatar" />
<TextView
android:id="@+id/mini_player_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:maxWidth="180dp"
android:text="Video"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
app:layout_constraintStart_toEndOf="@+id/mini_player_image"
app:layout_constraintTop_toTopOf="parent"
android:maxLines="1"
android:ellipsize="end"
android:textColor="?attr/androidOnBackgroundSecondary"/>
<TextView
android:id="@+id/mini_player_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginBottom="8dp"
android:maxWidth="180dp"
android:text="Author"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/mini_player_image"
android:maxLines="1"
android:ellipsize="end" />
<ImageView
android:id="@+id/mini_play_pause"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:adjustViewBounds="false"
android:contentDescription="@string/likeBtn"
android:cropToPadding="false"
android:scaleType="center"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_pause_24"
app:tint="@color/colorAccent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_root"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp" >
@ -10,16 +10,13 @@
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_weight="3"
android:hint="@string/reportDialog">
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/reportText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine" >
<requestFocus />
</com.google.android.material.textfield.TextInputEditText>

View File

@ -0,0 +1,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp" >
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/twoFactorText"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<requestFocus />
</com.google.android.material.textfield.TextInputEditText>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@ -7,6 +7,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp"
@ -46,6 +47,14 @@
android:linksClickable="true"
android:maxLength="1000"
android:maxLines="10" />
<TextView
android:id="@+id/replyBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/reply"
android:textColor="?attr/colorSecondary"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -73,7 +73,7 @@
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="false"
android:adjustViewBounds="true"
android:clickable="false"
android:cropToPadding="false"
android:padding="5dp"

View File

@ -14,6 +14,8 @@
<item name="colorPrimaryDark">@color/colorAccent</item>
<item name="colorAccent">@color/md_theme_dark_onBackground</item>
<item name="android:textColorLink">@color/md_theme_dark_secondary</item>
<item name="androidBackgroundSecondary">@color/md_theme_dark_tertiaryContainer</item>
<item name="androidOnBackgroundSecondary">@color/md_theme_dark_onTertiaryContainer</item>
</style>
<style name="Theme.P2play.NoActionBar" parent="Theme.P2play">

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="androidBackgroundSecondary" format="reference" />
<attr name="androidOnBackgroundSecondary" format="reference" />
</resources>

View File

@ -34,6 +34,7 @@
<string name="passwordText" translatable="false"> </string>
<string name="loginBtn">Login now</string>
<string name="registerActionBtn">Create new account</string>
<string name="twoFactorLabel">Two factor code</string>
<!-- Toast msg -->
<string name="loginSuccess_msg">You are now logged in</string>
<string name="loginError_msg">An error has occurred</string>
@ -94,6 +95,9 @@
<string name="unSubscribeBtn">Unsubscribe</string>
<string name="commentaryText">Comment</string>
<string name="showMore">Show more</string>
<!-- Comments -->
<string name="reply">Reply</string>
<string name="see_replies">See replies (%1$d)</string>
<!-- Messages -->
<string name="subscribeMsg">You have subscribed to this channel</string>
<string name="rateMsg">You have rated the video</string>

View File

@ -12,8 +12,10 @@
<item name="colorSurface">@color/md_theme_light_surface</item>
<item name="colorOnSurface">@color/md_theme_light_onSurface</item>
<item name="colorPrimaryDark">@color/md_theme_light_primary</item>
<item name="colorAccent">@color/md_theme_light_onPrimary</item>
<item name="colorAccent">@color/md_theme_light_secondary</item>
<item name="android:textColorLink">@color/md_theme_light_secondary</item>
<item name="androidBackgroundSecondary">@color/md_theme_light_tertiaryContainer</item>
<item name="androidOnBackgroundSecondary">@color/md_theme_light_onTertiaryContainer</item>
</style>
<style name="Theme.P2play.NoActionBar" parent="Theme.P2play">

View File

@ -0,0 +1,31 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory app:title="@string/pref_header_general">
<EditTextPreference
app:defaultValue="@string/pref_hostname_error"
android:inputType="text"
app:key="hostP2play"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true"
app:useSimpleSummaryProvider="true"
app:title="@string/pref_hostname_title" />
<SwitchPreference
app:defaultValue="false"
app:key="show_nsfw"
app:summary="@string/pref_nfsw_description"
app:title="@string/pref_nfsw_title" />
<EditTextPreference
app:defaultValue="15"
android:inputType="number"
app:key="videos_count"
android:selectAllOnFocus="true"
app:singleLine="true"
app:useSimpleSummaryProvider="true"
app:title="@string/pref_videos_count_title" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -1,9 +1,8 @@
package org.libre.agosto.p2play
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*

View File

@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.0'
classpath 'com.android.tools.build:gradle:8.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -12,6 +12,7 @@ android.useAndroidX=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
org.gradle.configuration-cache=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit

View File

@ -0,0 +1,6 @@
- 2FA login support!
- Background player!
- See and reply video threads!
- New Setting menu added
- Code improved
- Bugs fixed