23 Commits

Author SHA1 Message Date
cbbc15d9fb Save progress 2025-02-07 18:25:23 -06:00
10836f538d Finished videos view 2025-02-06 22:53:02 -06:00
012fdd5b8e release v0.8.4 2025-02-06 22:32:49 -06:00
71a6b9a31f Add menu on topbar 2025-02-04 21:49:21 -06:00
ac9004fe36 Add implement for infinite scroll 2025-02-04 21:48:36 -06:00
b3d8347200 Dependencies upgraded 2025-02-04 21:47:42 -06:00
b27bfbd01d release version v0.8.3 2025-02-01 19:03:17 -06:00
8004d14e64 Fix play private videos 2025-02-01 18:52:47 -06:00
a530f4c059 Fix session fetch 2025-02-01 17:40:54 -06:00
e5cbedb4ef Fist commit including jetpack compose and new Main view 2025-01-26 21:38:22 -06:00
a1deaa844c Prepare release: fix media player and splashscreen bug 2025-01-24 17:41:32 -06:00
4de1408ba6 Fix player stucks on resume player 2024-08-09 15:13:09 -06:00
48eec0dc88 Fix to logout when token expires 2024-08-09 15:12:42 -06:00
4bbeb284da Fix release 2024-06-17 11:47:13 -06:00
3141500c53 Add download video option 2024-06-05 12:28:06 -06:00
f86d66fdab Merge branch 'account-activity' into 'master'
Account activity

See merge request agosto182/p2play!18
2024-06-05 18:27:38 +00:00
de2f130826 Account activity 2024-06-05 18:27:38 +00:00
7d865cd181 Merge branch 'feature/removve-kotlin-extensions' into 'master'
Feature/removve kotlin extensions

See merge request agosto182/p2play!17
2024-05-26 21:47:25 +00:00
c4c5737344 Feature/removve kotlin extensions 2024-05-26 21:47:25 +00:00
ff995e383d Add view manager helper 2024-05-14 18:39:45 -06:00
d3466f9cce Add reponsive views 2024-05-14 18:20:46 -06:00
f0fbd02cf4 Fix scroll on player activity 2024-05-11 14:26:13 -06:00
3364f60d32 Update exo player with better UX 2024-05-11 13:51:16 -06:00
81 changed files with 3622 additions and 797 deletions

View File

@ -1,18 +1,16 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "kotlin-android-extensions"
}
android {
compileSdkVersion 34
defaultConfig {
applicationId "org.libre.agosto.p2play"
compileSdk 35
minSdkVersion 26
targetSdkVersion 32
versionCode 10
versionName "0.7.0"
targetSdkVersion 33
versionCode 15
versionName "0.8.4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
@ -22,33 +20,67 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
viewBinding = true
compose true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_18
targetCompatibility JavaVersion.VERSION_18
}
namespace 'org.libre.agosto.p2play'
lint {
abortOnError false
checkReleaseBuilds false
}
kotlinOptions {
jvmTarget = '18'
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.4'
}
}
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.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.activity:activity-ktx:1.10.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
implementation 'androidx.activity:activity-compose:1.10.0'
implementation platform('androidx.compose:compose-bom:2025.01.01')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.navigation:navigation-compose:2.8.6'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.5.2'
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"
implementation 'androidx.media3:media3-exoplayer:1.3.1'
implementation 'androidx.media3:media3-exoplayer-dash:1.3.1'
implementation 'androidx.media3:media3-ui:1.3.1'
implementation 'androidx.media3:media3-exoplayer-hls:1.3.1'
implementation "androidx.media3:media3-session:1.3.1"
implementation 'com.google.code.gson:gson:2.10.1'
implementation "androidx.compose.material3:material3:1.3.1"
androidTestImplementation platform('androidx.compose:compose-bom:2025.01.01')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
implementation "androidx.compose.runtime:runtime"
implementation "androidx.compose.ui:ui"
implementation "androidx.compose.foundation:foundation"
implementation "androidx.compose.foundation:foundation-layout"
implementation "androidx.compose.material3:material3"
implementation "androidx.compose.runtime:runtime-livedata"
implementation "androidx.compose.ui:ui-tooling"
implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0"
implementation "io.coil-kt.coil3:coil-compose:3.0.4"
implementation "io.coil-kt.coil3:coil-network-okhttp:3.0.4"
}

View File

@ -13,6 +13,14 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.P2play">
<activity
android:name=".activities.MainActivity"
android:exported="false"
android:theme="@style/Theme.P2play" />
<activity
android:name=".AccountActivity"
android:exported="false"
android:theme="@style/Theme.P2play.NoActionBar" />
<activity
android:name=".SettingsActivity2"
android:exported="false"
@ -59,18 +67,18 @@
<activity
android:name=".AboutActivity"
android:exported="false" />
<activity
android:name=".SettingsActivity"
android:exported="false"
android:label="@string/title_activity_settings"
android:name=".activities.HostActivityV2"
android:exported="true"
android:theme="@style/Theme.P2play" />
<service
android:name=".services.PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
android:exported="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
</application>

View File

@ -2,16 +2,18 @@ package org.libre.agosto.p2play
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_about.*
import org.libre.agosto.p2play.databinding.ActivityAboutBinding
class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
val binding = ActivityAboutBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
aboutUrl.text = "https://" + ManagerSingleton.url + "/about/instance"
binding.aboutUrl.text = "https://" + ManagerSingleton.url + "/about/instance"
aboutLabel.text = aboutLabel.text.toString() + " " + this.packageManager.getPackageInfo(this.packageName, 0).versionName
binding.aboutLabel.text = binding.aboutLabel.text.toString() + " " + this.packageManager.getPackageInfo(this.packageName, 0).versionName
}
}

View File

@ -0,0 +1,68 @@
package org.libre.agosto.p2play
import android.os.AsyncTask
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.google.android.material.tabs.TabLayoutMediator
import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.libre.agosto.p2play.ajax.Accounts
import org.libre.agosto.p2play.databinding.ActivityAccountBinding
import org.libre.agosto.p2play.databinding.ActivityMainBinding
import org.libre.agosto.p2play.fragmentAdapters.AccountAdapter
import org.libre.agosto.p2play.models.AccountModel
class AccountActivity : AppCompatActivity() {
private lateinit var binding: ActivityAccountBinding
private val client = Accounts()
private lateinit var accountId: String
private lateinit var adapter: AccountAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAccountBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
accountId = this.intent.extras?.getString("accountId")!!
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
adapter = AccountAdapter(supportFragmentManager, lifecycle)
binding.viewpager.adapter = adapter
adapter.accountId = accountId
TabLayoutMediator(binding.tabs, binding.viewpager, false, false) { tab, position ->
tab.text = adapter.get(position)
}.attach()
}
override fun onResume() {
super.onResume()
getChannelInfo()
}
private fun getChannelInfo() {
CoroutineScope(Dispatchers.IO).launch {
val account = client.get(accountId)
withContext(Dispatchers.Main) {
binding.collapsingToolbar.title = account.displayName
adapter.account = account
if (account.avatars.size > 0) {
Picasso.get().load("https://${ManagerSingleton.url}${account.avatars.last().path}").into(binding.profileImage)
}
}
}
}
}

View File

@ -1,90 +0,0 @@
package org.libre.agosto.p2play
import android.content.res.Configuration
import android.os.Bundle
import android.preference.PreferenceActivity
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
/**
* A [android.preference.PreferenceActivity] which implements and proxies the necessary calls
* to be used with AppCompat.
*/
abstract class AppCompatPreferenceActivity : PreferenceActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
delegate.installViewFactory()
delegate.onCreate(savedInstanceState)
super.onCreate(savedInstanceState)
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
delegate.onPostCreate(savedInstanceState)
}
val supportActionBar: ActionBar?
get() = delegate.supportActionBar
fun setSupportActionBar(toolbar: Toolbar?) {
delegate.setSupportActionBar(toolbar)
}
override fun getMenuInflater(): MenuInflater {
return delegate.menuInflater
}
override fun setContentView(@LayoutRes layoutResID: Int) {
delegate.setContentView(layoutResID)
}
override fun setContentView(view: View) {
delegate.setContentView(view)
}
override fun setContentView(view: View, params: ViewGroup.LayoutParams) {
delegate.setContentView(view, params)
}
override fun addContentView(view: View, params: ViewGroup.LayoutParams) {
delegate.addContentView(view, params)
}
override fun onPostResume() {
super.onPostResume()
delegate.onPostResume()
}
override fun onTitleChanged(title: CharSequence, color: Int) {
super.onTitleChanged(title, color)
delegate.setTitle(title)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
delegate.onConfigurationChanged(newConfig)
}
override fun onStop() {
super.onStop()
delegate.onStop()
}
override fun onDestroy() {
super.onDestroy()
delegate.onDestroy()
}
override fun invalidateOptionsMenu() {
delegate.invalidateOptionsMenu()
}
private val delegate: AppCompatDelegate by lazy {
AppCompatDelegate.create(this, null)
}
}

View File

@ -4,14 +4,14 @@ import android.os.AsyncTask
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_channel.*
import org.libre.agosto.p2play.adapters.VideosAdapter
import org.libre.agosto.p2play.ajax.Actions
import org.libre.agosto.p2play.ajax.Channels
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.databinding.ActivityChannelBinding
import org.libre.agosto.p2play.helpers.getViewManager
import org.libre.agosto.p2play.models.ChannelModel
import org.libre.agosto.p2play.models.VideoModel
class ChannelActivity : AppCompatActivity() {
@ -26,15 +26,19 @@ class ChannelActivity : AppCompatActivity() {
private lateinit var viewAdapter: RecyclerView.Adapter<VideosAdapter.ViewHolder>
private lateinit var viewManager: RecyclerView.LayoutManager
private lateinit var binding: ActivityChannelBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_channel)
binding = ActivityChannelBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
channelId = this.intent.extras?.getString("channel")!!
viewManager = LinearLayoutManager(this)
viewManager = getViewManager(this, resources)
subcriptionBtn.setOnClickListener {
binding.subcriptionBtn.setOnClickListener {
subscribeAction()
}
}
@ -47,7 +51,7 @@ class ChannelActivity : AppCompatActivity() {
getVideos()
if (ManagerSingleton.user.status == 1) {
subcriptionBtn.visibility = View.VISIBLE
binding.subcriptionBtn.visibility = View.VISIBLE
getSubscription()
}
}
@ -56,11 +60,11 @@ class ChannelActivity : AppCompatActivity() {
AsyncTask.execute {
channel = channelService.getChannelInfo(channelId)
runOnUiThread {
usernameProfile.text = channel.name
hostTxt.text = channel.host
subcriptionsTxt.text = channel.followers.toString()
binding.usernameProfile.text = channel.name
binding.hostTxt.text = channel.host
binding.subcriptionsTxt.text = channel.followers.toString()
if (channel.channelImg != "") {
Picasso.get().load("https://${ManagerSingleton.url}${channel.channelImg}").into(channelImg)
Picasso.get().load("https://${ManagerSingleton.url}${channel.channelImg}").into(binding.channelImg)
}
}
}
@ -71,7 +75,7 @@ class ChannelActivity : AppCompatActivity() {
val res = actionsService.subscribe(ManagerSingleton.token.token, channel.getAccount())
runOnUiThread {
if (res == 1) {
subcriptionBtn.text = getString(R.string.unSubscribeBtn)
binding.subcriptionBtn.text = getString(R.string.unSubscribeBtn)
ManagerSingleton.toast(getString(R.string.subscribeMsg), this)
getSubscription()
} else {
@ -86,7 +90,7 @@ class ChannelActivity : AppCompatActivity() {
val res = actionsService.unSubscribe(ManagerSingleton.token.token, channel.getAccount())
runOnUiThread {
if (res == 1) {
subcriptionBtn.text = getString(R.string.subscribeBtn)
binding.subcriptionBtn.text = getString(R.string.subscribeBtn)
ManagerSingleton.toast(getString(R.string.unSubscribeMsg), this)
getSubscription()
} else {
@ -109,9 +113,9 @@ class ChannelActivity : AppCompatActivity() {
isSubcribed = actionsService.getSubscription(ManagerSingleton.token.token, channel.getAccount())
runOnUiThread {
if (isSubcribed) {
subcriptionBtn.text = getText(R.string.unSubscribeBtn)
binding.subcriptionBtn.text = getText(R.string.unSubscribeBtn)
} else {
subcriptionBtn.text = getText(R.string.subscribeBtn)
binding.subcriptionBtn.text = getText(R.string.subscribeBtn)
}
}
}

View File

@ -7,8 +7,8 @@ import android.os.Bundle
import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.activity_host.*
import org.libre.agosto.p2play.ajax.Auth
import org.libre.agosto.p2play.databinding.ActivityHostBinding
class HostActivity : AppCompatActivity() {
lateinit var settings: SharedPreferences
@ -16,15 +16,19 @@ class HostActivity : AppCompatActivity() {
val client: Auth = Auth()
private val db = Database(this)
private lateinit var binding: ActivityHostBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_host)
binding = ActivityHostBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
settings = PreferenceManager.getDefaultSharedPreferences(this)
editor = settings.edit()
button.setOnClickListener {
getKeys(hostText.text.toString())
binding.button.setOnClickListener {
getKeys(binding.hostText.text.toString())
}
val host = settings.getString("hostP2play", "")
@ -51,7 +55,7 @@ class HostActivity : AppCompatActivity() {
}
private fun getKeys(hostText: String) {
button.isEnabled = false
binding.button.isEnabled = false
var host = hostText.toString()
host = host.replace("http://", "")
host = host.replace("https://", "")
@ -71,7 +75,7 @@ class HostActivity : AppCompatActivity() {
} else {
runOnUiThread {
ManagerSingleton.toast(getString(R.string.errorMsg), this)
button.isEnabled = true
binding.button.isEnabled = true
}
}
}

View File

@ -9,8 +9,8 @@ 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
import org.libre.agosto.p2play.databinding.ActivityLoginBinding
class LoginActivity : AppCompatActivity() {
private val auth = Auth()
@ -20,9 +20,13 @@ class LoginActivity : AppCompatActivity() {
private lateinit var db: Database
private var optCode: String? = null
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
binding = ActivityLoginBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setTitle(R.string.action_login)
db = Database(this)
@ -30,14 +34,14 @@ class LoginActivity : AppCompatActivity() {
clientId = settings.getString("client_id", "")!!
clientSecret = settings.getString("client_secret", "")!!
registerActionBtn.setOnClickListener { startActivity(Intent(this, RegisterActivity::class.java)) }
loginBtn.setOnClickListener { tryLogin() }
binding.registerActionBtn.setOnClickListener { startActivity(Intent(this, RegisterActivity::class.java)) }
binding.loginBtn.setOnClickListener { tryLogin() }
}
fun tryLogin() {
loginBtn.isEnabled = false
val username = userText.text.toString()
val password = passwordText.text.toString()
binding.loginBtn.isEnabled = false
val username = binding.userText.text.toString()
val password = binding.passwordText.text.toString()
AsyncTask.execute {
if (Looper.myLooper() == null) {
@ -62,7 +66,7 @@ class LoginActivity : AppCompatActivity() {
}
"-1" -> {
runOnUiThread {
loginBtn.isEnabled = true
binding.loginBtn.isEnabled = true
ManagerSingleton.toast(getString(R.string.loginFailed_msg), this)
}
}
@ -84,7 +88,7 @@ class LoginActivity : AppCompatActivity() {
}
.setNegativeButton("Cancel") { d, _ ->
dialog.run { d.cancel() }
loginBtn.isEnabled = true
binding.loginBtn.isEnabled = true
}
val alertDialog = builder.create()
alertDialog.show()

View File

@ -7,26 +7,19 @@ import android.os.Handler
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.view.GravityCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.navigation.NavigationView
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.databinding.ActivityMainBinding
import org.libre.agosto.p2play.helpers.getViewManager
import org.libre.agosto.p2play.models.VideoModel
import org.libre.agosto.p2play.singletons.PlaybackSingleton
@ -41,43 +34,38 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
var section: String = ""
var searchVal: String = ""
var pagination = 0
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setSupportActionBar(binding.toolbar)
/* fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
} */
val toggle = ActionBarDrawerToggle(this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
val toggle = ActionBarDrawerToggle(this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
binding.drawerLayout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
binding.navView.setNavigationItemSelectedListener(this)
viewManager = LinearLayoutManager(this)
viewManager = getViewManager(this, resources)
// Init RecyclerView
this.initRecycler()
this.getTrengindVideos()
swipeContainer.setOnRefreshListener {
binding.content.swipeContainer.setOnRefreshListener {
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
}, 2000)
binding.content.mini.miniPlayerImage.setOnClickListener { this.resumeVideo() }
binding.content.mini.miniPlayerTitle.setOnClickListener { this.resumeVideo() }
binding.content.mini.miniPlayerAuthor.setOnClickListener { this.resumeVideo() }
// binding.content.mini.setOnClickListener { this.resumeVideo() }
binding.content.mini.miniPlayPause.setOnClickListener { this.playPausePlayer() }
}
// Generic function for set data to RecyclerView
@ -101,7 +89,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
super.onScrolled(recyclerView, dx, dy)
// super.onScrolled(recyclerView!!, dx, dy)
if (!swipeContainer.isRefreshing) {
if (!binding.content.swipeContainer.isRefreshing) {
if (!canScrollVertically(1)) {
loadMore()
}
@ -109,11 +97,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
})
}
swipeContainer.isRefreshing = false
binding.content.swipeContainer.isRefreshing = false
}
private fun addVideos(videos: ArrayList<VideoModel>) {
this.swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
try {
if (this.pagination == 0) {
@ -127,11 +115,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
ManagerSingleton.toast(getString(R.string.errorMsg), this)
}
this.swipeContainer.isRefreshing = false
binding.content.swipeContainer.isRefreshing = false
}
private fun refresh() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
this.pagination = 0
when (section) {
"local" -> this.getLocalVideos()
@ -156,7 +144,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
startActivity(Intent(this, LoginActivity::class.java))
return
}
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "sub"
setTitle(R.string.title_subscriptions)
AsyncTask.execute {
@ -169,7 +157,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// Last videos
private fun getLastVideos() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "last"
setTitle(R.string.title_recent)
AsyncTask.execute {
@ -182,7 +170,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// Popular videos
private fun getPopularVideos() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "popular"
setTitle(R.string.title_popular)
AsyncTask.execute {
@ -195,7 +183,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// Trending videos
private fun getTrengindVideos() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "trending"
setTitle(R.string.title_trending)
AsyncTask.execute {
@ -208,7 +196,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// Local videos
private fun getLocalVideos() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "local"
setTitle(R.string.title_local)
AsyncTask.execute {
@ -221,7 +209,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// Videos of user
private fun getMyVideos() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "my_videos"
setTitle(R.string.title_myVideos)
AsyncTask.execute {
@ -234,7 +222,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// Videos history of user
private fun getHistory() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "my_videos"
setTitle(R.string.nav_history)
AsyncTask.execute {
@ -247,7 +235,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
// Most liked
private fun getMostLiked() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "liked"
setTitle(R.string.nav_likes)
AsyncTask.execute {
@ -259,8 +247,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else if (!section.equals("trending")) {
// Hot fix
pagination = 0
@ -352,7 +340,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
R.id.nav_likes -> getMostLiked()
}
drawer_layout.closeDrawer(GravityCompat.START)
binding.drawerLayout.closeDrawer(GravityCompat.START)
return true
}
@ -362,13 +350,14 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
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
binding.content.mini.miniPlayerTitle.text = PlaybackSingleton.video!!.name
binding.content.mini.miniPlayerAuthor.text = PlaybackSingleton.video!!.username
Picasso.get().load("https://${ManagerSingleton.url}${PlaybackSingleton.video!!.thumbUrl}").into(binding.content.mini.miniPlayerImage)
binding.content.mini.miniPlayPause.setImageResource(R.drawable.ic_pause_24)
binding.content.mini.miniPlayer.visibility = View.VISIBLE
} else {
mini.visibility = View.GONE
binding.content.mini.miniPlayer.visibility = View.GONE
}
}
@ -380,25 +369,27 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
private fun setSideData() {
val headerView = binding.navView.getHeaderView(0)
if (ManagerSingleton.user.status == 1) {
nav_view.menu.findItem(R.id.ml).isVisible = true
binding.navView.menu.findItem(R.id.ml).isVisible = true
side_usernameTxt?.text = ManagerSingleton.user.username
side_emailTxt?.text = ManagerSingleton.user.email
if (ManagerSingleton.user.avatar != "" && side_imageView != null) {
Picasso.get().load("https://" + ManagerSingleton.url + ManagerSingleton.user.avatar).into(side_imageView)
headerView.findViewById<TextView>(R.id.side_usernameTxt).text = ManagerSingleton.user.username
headerView.findViewById<TextView>(R.id.side_emailTxt).text = ManagerSingleton.user.email
if (ManagerSingleton.user.avatar != "" && headerView.findViewById<ImageView>(R.id.side_imageView) != null) {
Picasso.get().load("https://" + ManagerSingleton.url + ManagerSingleton.user.avatar).into(headerView.findViewById<ImageView>(R.id.side_imageView))
}
side_imageView?.setOnClickListener {
headerView.findViewById<ImageView>(R.id.side_imageView).setOnClickListener {
pagination = 0
getMyVideos()
drawer_layout.closeDrawer(GravityCompat.START)
binding.drawerLayout.closeDrawer(GravityCompat.START)
}
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
binding.navView.menu.findItem(R.id.ml).isVisible = false
headerView.findViewById<TextView>(R.id.side_emailTxt).text = getString(R.string.nav_header_subtitle) + " " + this.packageManager.getPackageInfo(this.packageName, 0).versionName
}
}
@ -407,11 +398,12 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
myMenu.findItem(R.id.action_login).isVisible = true
myMenu.findItem(R.id.action_logout).isVisible = false
}
// nav_view.menu.findItem(R.id.ml).isVisible = false
side_usernameTxt?.text = getString(R.string.nav_header_title)
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 { }
val headerView = binding.navView.getHeaderView(0)
binding.navView.menu.findItem(R.id.ml).isVisible = false
headerView.findViewById<TextView>(R.id.side_usernameTxt).text = getString(R.string.nav_header_title)
headerView.findViewById<TextView>(R.id.side_emailTxt).text = getString(R.string.nav_header_subtitle) + " " + this.packageManager.getPackageInfo(this.packageName, 0).versionName
headerView.findViewById<ImageView>(R.id.side_imageView).setImageResource(R.drawable.default_avatar)
headerView.findViewById<ImageView>(R.id.side_imageView).setOnClickListener { }
db.logout()
ManagerSingleton.logout()
@ -421,7 +413,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
private fun loadMore() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
this.pagination += ManagerSingleton.videosCount
when (section) {
@ -443,7 +435,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}
private fun searchVideos() {
swipeContainer.isRefreshing = true
binding.content.swipeContainer.isRefreshing = true
section = "search"
this.title = this.searchVal
AsyncTask.execute {
@ -464,10 +456,10 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
PlaybackSingleton.player?.let {
if (it.isPlaying) {
it.pause()
mini_play_pause.setImageResource(R.drawable.ic_play_arrow_24)
binding.content.mini.miniPlayPause.setImageResource(R.drawable.ic_play_arrow_24)
} else {
it.play()
mini_play_pause.setImageResource(R.drawable.ic_pause_24)
binding.content.mini.miniPlayPause.setImageResource(R.drawable.ic_pause_24)
}
}
}

View File

@ -13,6 +13,7 @@ object ManagerSingleton {
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()
}
@ -21,6 +22,8 @@ object ManagerSingleton {
db.logout()
user = UserModel()
token = TokenModel()
// TODO: Close the session in the user instance
}
fun reloadSettings() {
@ -36,4 +39,8 @@ object ManagerSingleton {
nfsw = settings.getBoolean("show_nsfw", false)
videosCount = settings.getString("videos_count", "15")!!.toInt()
}
fun isLogged (): Boolean {
return token.token !== ""
}
}

View File

@ -7,8 +7,8 @@ import android.os.Looper
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
import org.libre.agosto.p2play.databinding.ActivityRegisterBinding
class RegisterActivity : AppCompatActivity() {
private val auth = Auth()
@ -16,23 +16,27 @@ class RegisterActivity : AppCompatActivity() {
lateinit var clientId: String
lateinit var clientSecret: String
private lateinit var binding: ActivityRegisterBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register)
binding = ActivityRegisterBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setTitle(R.string.registerActionBtn)
settings = PreferenceManager.getDefaultSharedPreferences(this)
clientId = settings.getString("client_id", "")!!
clientSecret = settings.getString("client_secret", "")!!
registerBtn.setOnClickListener { registerUser() }
binding.registerBtn.setOnClickListener { registerUser() }
}
private fun registerUser() {
registerBtn.isEnabled = false
val username = userText2.text.toString()
val password = passwordText2.text.toString()
val email = emailText.text.toString()
binding.registerBtn.isEnabled = false
val username = binding.userText2.text.toString()
val password = binding.passwordText2.text.toString()
val email = binding.emailText.text.toString()
AsyncTask.execute {
if (Looper.myLooper() == null) {
Looper.prepare()
@ -49,7 +53,7 @@ class RegisterActivity : AppCompatActivity() {
0 -> ManagerSingleton.toast(getString(R.string.registerFailed_msg), this)
-1 -> ManagerSingleton.toast(getString(R.string.registerError_msg), this)
}
registerBtn.isEnabled = true
binding.registerBtn.isEnabled = true
}
}
}

View File

@ -1,6 +1,7 @@
package org.libre.agosto.p2play
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.Intent
import android.content.pm.ActivityInfo
import android.graphics.Bitmap
@ -9,39 +10,36 @@ import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.Looper
import android.util.Log
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 android.widget.LinearLayout
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.common.Player
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
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.databinding.ActivityReproductorBinding
import org.libre.agosto.p2play.helpers.setFullscreen
import org.libre.agosto.p2play.models.CommentaryModel
import org.libre.agosto.p2play.models.DownloadFiles
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
@ -66,14 +64,19 @@ class ReproductorActivity : AppCompatActivity() {
// Resume info
private var isResume = false
private lateinit var binding: ActivityReproductorBinding
@SuppressLint("SetJavaScriptEnabled", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_reproductor)
binding = ActivityReproductorBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val fullscreenButton: ImageView = exoPlayer.findViewById(R.id.exo_fullscreen_custom)
val fullscreenButton2 = fullscreenPlayer.findViewById<ImageView>(R.id.exo_fullscreen_custom)
val fullscreenButton = binding.exoPlayer.findViewById<ImageView>(R.id.exo_fullscreen_custom)
val fullscreenButton2 = binding.fullscreenPlayer.findViewById<ImageView>(R.id.exo_fullscreen_custom)
val videoView = binding.videoView
videoView.webChromeClient = WebClient()
videoView.settings.javaScriptEnabled = true
videoView.settings.allowContentAccess = true
@ -93,19 +96,19 @@ class ReproductorActivity : AppCompatActivity() {
isResume = true
}
tittleVideoTxt.text = this.video.name
viewsTxt.text = "${this.video.views} ${getString(R.string.view_text)}"
userTxt.text = this.video.username
descriptionVideoTxt.text = this.video.description
binding.tittleVideoTxt.text = this.video.name
binding.viewsTxt.text = "${this.video.views} ${getString(R.string.view_text)}"
binding.userTxt.text = this.video.username
binding.descriptionVideoTxt.text = this.video.description
val haveDescription = this.video.description.endsWith("...")
if (haveDescription) {
showMoreBtn.visibility = View.VISIBLE
binding.showMoreBtn.visibility = View.VISIBLE
}
hostTxt.text = this.video.userHost
binding.hostTxt.text = this.video.userHost
// Check if user had profile image
if (this.video.userImageUrl != "") {
Picasso.get().load("https://" + ManagerSingleton.url + this.video.userImageUrl).into(userImg)
Picasso.get().load("https://" + ManagerSingleton.url + this.video.userImageUrl).into(binding.userImg)
}
// Load the video
videoView.loadUrl("https://" + ManagerSingleton.url + this.video.embedUrl)
@ -118,44 +121,62 @@ class ReproductorActivity : AppCompatActivity() {
this.getComments()
subscribeBtn.setOnClickListener { subscribe() }
likeLayout.setOnClickListener { rate("like") }
dislikeLayout.setOnClickListener { rate("dislike") }
commentaryBtn.setOnClickListener { makeComment() }
showMoreBtn.setOnClickListener { getDescription() }
shareLayout.setOnClickListener { shareIntent() }
reportLayout.setOnClickListener { reportIntent() }
binding.subscribeBtn.setOnClickListener { subscribe() }
binding.likeLayout.setOnClickListener { rate("like") }
binding.dislikeLayout.setOnClickListener { rate("dislike") }
binding.commentBox.commentaryBtn.setOnClickListener { makeComment() }
binding.showMoreBtn.setOnClickListener { getDescription() }
binding.shareLayout.setOnClickListener { shareIntent() }
binding.reportLayout.setOnClickListener { reportIntent() }
fullscreenButton.setOnClickListener { toggleFullscreen() }
fullscreenButton2.setOnClickListener { toggleFullscreen() }
binding.downloadLayout.setOnClickListener { downloadVideo() }
userImg.setOnClickListener {
binding.userImg.setOnClickListener {
val intent = Intent(this, ChannelActivity::class.java)
intent.putExtra("channel", video.getChannel())
startActivity(intent)
}
AsyncTask.execute {
videoPlayback = this.clientVideo.getVideo(this.video.uuid)
videoPlayback = if (ManagerSingleton.token.status == 1)
this.clientVideo.getVideo(this.video.uuid, ManagerSingleton.token.token)
else
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()
// val bufferSize = 1024 * 1024 // 1mb
// val allocator = DefaultAllocator(true, bufferSize)
// val loadControl = DefaultLoadControl.Builder()
// .setAllocator(allocator)
// .build()
runOnUiThread {
try {
if (PlaybackSingleton.player == null || !PlaybackSingleton.player!!.playWhenReady) {
PlaybackSingleton.player = ExoPlayer.Builder(this.baseContext)
.setSeekBackIncrementMs(10000)
.setSeekForwardIncrementMs(10000)
.setLoadControl(loadControl).build()
if (PlaybackSingleton.player == null || !PlaybackSingleton.player!!.isPlaying) {
PlaybackSingleton.player = ExoPlayer.Builder(this)
//.setSeekBackIncrementMs(10000)
//.setSeekForwardIncrementMs(10000)
//.setLoadControl(loadControl)
.build()
}
player = PlaybackSingleton.player!!
exoPlayer.player = player
println("----- video --------")
println(videoPlayback.streamingData?.playlistUrl)
player = PlaybackSingleton.player!!
binding.exoPlayer.player = player
player.addListener(
object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
val controls = binding.exoPlayer.findViewById<LinearLayout>(R.id.exo_center_controls)
if (playbackState == Player.STATE_BUFFERING || playbackState == Player.STATE_IDLE) {
controls.visibility = View.INVISIBLE
} else if (playbackState == Player.STATE_READY) {
controls.visibility = View.VISIBLE
}
}
}
)
if (!isResume) {
val mediaItem = MediaItem.Builder()
@ -170,7 +191,8 @@ class ReproductorActivity : AppCompatActivity() {
PlaybackSingleton.setData(mediaItem, video)
}
// Start the playback.
// player.play()
// TODO: Setting for autoplay
player.playWhenReady = true
} catch (err: Exception) {
err.printStackTrace()
}
@ -218,11 +240,11 @@ class ReproductorActivity : AppCompatActivity() {
runOnUiThread {
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))
binding.textViewLike.setTextColor(ContextCompat.getColor(this, R.color.colorLike))
binding.textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
} else if (rate == "dislike") {
textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.colorDislike))
textViewLike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
binding.textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.colorDislike))
binding.textViewLike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
}
}
}
@ -238,16 +260,16 @@ class ReproductorActivity : AppCompatActivity() {
runOnUiThread {
when (rate) {
"like" -> {
textViewLike.setTextColor(ContextCompat.getColor(this, R.color.colorLike))
textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
binding.textViewLike.setTextColor(ContextCompat.getColor(this, R.color.colorLike))
binding.textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
}
"dislike" -> {
textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.colorDislike))
textViewLike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
binding.textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.colorDislike))
binding.textViewLike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
}
else -> {
textViewLike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
binding.textViewLike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
binding.textViewDislike.setTextColor(ContextCompat.getColor(this, R.color.primary_dark_material_light))
}
}
}
@ -269,11 +291,11 @@ class ReproductorActivity : AppCompatActivity() {
private fun changeSubscribeBtn(subscribed: Boolean) {
if (subscribed) {
subscribeBtn.text = getText(R.string.unSubscribeBtn)
subscribeBtn.setOnClickListener { this.unSubscribe() }
binding.subscribeBtn.text = getText(R.string.unSubscribeBtn)
binding.subscribeBtn.setOnClickListener { this.unSubscribe() }
} else {
subscribeBtn.text = getText(R.string.subscribeBtn)
subscribeBtn.setOnClickListener { this.subscribe() }
binding.subscribeBtn.text = getText(R.string.subscribeBtn)
binding.subscribeBtn.setOnClickListener { this.subscribe() }
}
}
@ -304,17 +326,17 @@ class ReproductorActivity : AppCompatActivity() {
}
private fun makeComment() {
if (commentaryText.text.toString() == "") {
if (binding.commentBox.commentaryText.text.toString() == "") {
ManagerSingleton.toast(getString(R.string.emptyCommentaryMsg), this)
return
}
val text = commentaryText.text.toString()
val text = binding.commentBox.commentaryText.text.toString()
AsyncTask.execute {
val res = this.client.makeCommentary(ManagerSingleton.token.token, this.video.id, text)
runOnUiThread {
if (res) {
ManagerSingleton.toast(getString(R.string.makedCommentaryMsg), this)
commentaryText.text?.clear()
binding.commentBox.commentaryText.text?.clear()
this.getComments()
} else {
ManagerSingleton.toast(getString(R.string.errorCommentaryMsg), this)
@ -328,14 +350,18 @@ class ReproductorActivity : AppCompatActivity() {
if (ManagerSingleton.user.status == 1) {
this.getRate()
this.getSubscription()
actionsLayout.visibility = View.VISIBLE
subscribeBtn.visibility = View.VISIBLE
commentaryLayout.visibility = View.VISIBLE
binding.actionsLayout.visibility = View.VISIBLE
binding.subscribeBtn.visibility = View.VISIBLE
binding.commentBox.commentaryLayout.visibility = View.VISIBLE
if (ManagerSingleton.user.avatar != "") {
Picasso.get().load("https://" + ManagerSingleton.url + ManagerSingleton.user.avatar).into(userImgCom)
Picasso.get().load("https://" + ManagerSingleton.url + ManagerSingleton.user.avatar).into(binding.commentBox.userImgCom)
}
} else {
commentaryLayout.visibility = View.GONE
binding.commentBox.commentaryLayout.visibility = View.GONE
}
if (::player.isInitialized && !player.isPlaying) {
binding.exoPlayer.onResume()
}
}
@ -343,8 +369,8 @@ class ReproductorActivity : AppCompatActivity() {
AsyncTask.execute {
val fullDescription = this.videos.fullDescription(this.video.id)
runOnUiThread {
descriptionVideoTxt.text = fullDescription
showMoreBtn.visibility = View.GONE
binding.descriptionVideoTxt.text = fullDescription
binding.showMoreBtn.visibility = View.GONE
}
}
}
@ -397,10 +423,10 @@ class ReproductorActivity : AppCompatActivity() {
private fun toggleFullscreen() {
if (isFullscreen) {
nonFullScreen.visibility = View.VISIBLE
fullScreenExo.visibility = View.GONE
exoPlayer.player = player
fullscreenPlayer.player = null
binding.nonFullScreen.visibility = View.VISIBLE
binding.fullScreenExo.visibility = View.GONE
binding.exoPlayer.player = player
binding.fullscreenPlayer.player = null
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
val attrs = window.attributes
@ -413,11 +439,11 @@ class ReproductorActivity : AppCompatActivity() {
} else {
val matchParent = WindowManager.LayoutParams.MATCH_PARENT
nonFullScreen.visibility = View.GONE
fullScreenExo.visibility = View.VISIBLE
binding.nonFullScreen.visibility = View.GONE
binding.fullScreenExo.visibility = View.VISIBLE
exoPlayer.player = null
fullscreenPlayer.player = player
binding.exoPlayer.player = null
binding.fullscreenPlayer.player = player
setFullscreen(window)
@ -426,6 +452,31 @@ class ReproductorActivity : AppCompatActivity() {
}
}
private fun downloadVideo() {
val downloadFile = videoPlayback.streamingData?.downloadFiles?.first()
if (downloadFile != null) {
val manager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
val request = DownloadManager.Request(Uri.parse(downloadFile.url))
val fileName = "${videoPlayback.name}-${downloadFile.resolution}.${downloadFile.url.split('.').last()}"
try {
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(fileName)
.setDescription(getString(R.string.downloadText))
.setDestinationInExternalPublicDir(
android.os.Environment.DIRECTORY_DOWNLOADS,
fileName
)
manager.enqueue(request)
ManagerSingleton.toast(getString(R.string.downloadStarted), this)
} catch (_: Exception) {
ManagerSingleton.toast(getString(R.string.downloadFailed), this)
}
} else {
ManagerSingleton.toast(getString(R.string.downloadFailed), this)
}
}
override fun onDestroy() {
if (!player.isPlaying) {
PlaybackSingleton.release()
@ -453,9 +504,9 @@ class ReproductorActivity : AppCompatActivity() {
override fun onHideCustomView() {
try {
this@ReproductorActivity.nonFullScreen.visibility = View.VISIBLE
this@ReproductorActivity.fullScreen.visibility = View.GONE
this@ReproductorActivity.fullScreen.removeView(this.mCustomView)
this@ReproductorActivity.binding.nonFullScreen.visibility = View.VISIBLE
this@ReproductorActivity.binding.fullScreen.visibility = View.GONE
this@ReproductorActivity.binding.fullScreen.removeView(this.mCustomView)
this.mCustomView = null
this.mCustomViewCallback!!.onCustomViewHidden()
@ -486,10 +537,10 @@ class ReproductorActivity : AppCompatActivity() {
this.mCustomViewCallback = paramCustomViewCallback
val matchParent = WindowManager.LayoutParams.MATCH_PARENT
this@ReproductorActivity.nonFullScreen.visibility = View.GONE
this@ReproductorActivity.fullScreen.visibility = View.VISIBLE
this@ReproductorActivity.binding.nonFullScreen.visibility = View.GONE
this@ReproductorActivity.binding.fullScreen.visibility = View.VISIBLE
this@ReproductorActivity.fullScreen.addView(paramView, FrameLayout.LayoutParams(matchParent, matchParent))
this@ReproductorActivity.binding.fullScreen.addView(paramView, FrameLayout.LayoutParams(matchParent, matchParent))
setFullscreen(this@ReproductorActivity.window)

View File

@ -1,159 +0,0 @@
package org.libre.agosto.p2play
import android.annotation.TargetApi
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.preference.ListPreference
import android.preference.Preference
import android.preference.PreferenceActivity
import android.preference.PreferenceFragment
import android.preference.PreferenceManager
import android.view.MenuItem
/**
* A [PreferenceActivity] that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
*
* See [Android Design: Settings](http://developer.android.com/design/patterns/settings.html)
* for design guidelines and the [Settings API Guide](http://developer.android.com/guide/topics/ui/settings.html)
* for more information on developing a Settings UI.
*/
class SettingsActivity : AppCompatPreferenceActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupActionBar()
}
override fun onBackPressed() {
super.onBackPressed()
ManagerSingleton.toast(getString(R.string.pref_message_exit), this)
}
/**
* Set up the [android.app.ActionBar], if the API is available.
*/
private fun setupActionBar() {
supportActionBar?.setDisplayHomeAsUpEnabled(false)
}
/**
* {@inheritDoc}
*/
override fun onIsMultiPane(): Boolean {
return isXLargeTablet(this)
}
/**
* {@inheritDoc}
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
override fun onBuildHeaders(target: List<PreferenceActivity.Header>) {
loadHeadersFromResource(R.xml.pref_headers, target)
}
/**
* This method stops fragment injection in malicious applications.
* 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
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
class GeneralPreferenceFragment : PreferenceFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_general)
setHasOptionsMenu(true)
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
bindPreferenceSummaryToValue(findPreference("hostP2play"))
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == android.R.id.home) {
startActivity(Intent(activity, SettingsActivity::class.java))
return true
}
return super.onOptionsItemSelected(item)
}
}
companion object {
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private val sBindPreferenceSummaryToValueListener = Preference.OnPreferenceChangeListener { preference, value ->
val stringValue = value.toString()
if (preference is ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
val listPreference = preference
val index = listPreference.findIndexOfValue(stringValue)
// Set the summary to reflect the new value.
preference.setSummary(
if (index >= 0) {
listPreference.entries[index]
} else {
null
},
)
} else {
// For all other preferences, set the summary to the value's
// simple string representation.
preference.summary = stringValue
}
true
}
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private fun isXLargeTablet(context: Context): Boolean {
return context.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_XLARGE
}
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
* @see .sBindPreferenceSummaryToValueListener
*/
private fun bindPreferenceSummaryToValue(preference: Preference) {
// Set the listener to watch for value changes.
preference.onPreferenceChangeListener = sBindPreferenceSummaryToValueListener
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(
preference,
PreferenceManager
.getDefaultSharedPreferences(preference.context)
.getString(preference.key, ""),
)
}
}
}

View File

@ -2,14 +2,13 @@ package org.libre.agosto.p2play
import android.content.Intent
import android.content.SharedPreferences
import android.os.AsyncTask
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import org.libre.agosto.p2play.activities.MainActivity
import org.libre.agosto.p2play.ajax.Auth
import org.libre.agosto.p2play.helpers.TaskManager
import org.libre.agosto.p2play.models.TokenModel
import java.lang.Exception
class SplashActivity : AppCompatActivity() {
@ -31,55 +30,50 @@ class SplashActivity : AppCompatActivity() {
val lastHost = settings.getString("last_host", "")
if (host != "") {
if (lastHost != host) {
Handler().postDelayed({
startHostActivity()
}, 2000)
Thread.sleep(2000)
startHostActivity()
} else {
ManagerSingleton.url = host
checkUser()
}
} else {
Handler().postDelayed({
startHostActivity()
}, 2000)
Thread.sleep(2000)
startHostActivity()
}
}
private fun checkUser() {
Log.d("was", "Checked")
try {
val token = db.getToken()
val user = db.getUser()
AsyncTask.execute {
if (Looper.myLooper() == null) {
Looper.prepare()
}
if (token.status == 1 && user.status == 1) {
val clientId = settings.getString("client_id", "")!!
val clientSecret = settings.getString("client_secret", "")!!
if (token.status == 1 && user.status == 1) {
val clientId = settings.getString("client_id", "")!!
val clientSecret = settings.getString("client_secret", "")!!
val task = TaskManager<TokenModel>()
val newToken = client.refreshToken(token, clientId, clientSecret)
when (token.status.toString()) {
"1" -> {
db.newToken(newToken)
ManagerSingleton.token = newToken
ManagerSingleton.user = user
task.runTask(
{
client.refreshToken(token, clientId, clientSecret)
}, {
when (it.status.toString()) {
"1" -> {
db.newToken(it)
ManagerSingleton.token = it
ManagerSingleton.user = user
}
else -> ManagerSingleton.logout()
}
else -> ManagerSingleton.logout()
}
} else {
ManagerSingleton.logout()
}
startApp()
})
} else {
ManagerSingleton.logout()
startApp()
}
} catch (err: Exception) {
err.printStackTrace()
Handler().postDelayed({
startApp()
}, 2000)
Thread.sleep(2000)
startHostActivity()
}
}

View File

@ -0,0 +1,69 @@
package org.libre.agosto.p2play.activities
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ChainStyle
import androidx.constraintlayout.compose.ConstraintLayout
import org.libre.agosto.p2play.MainActivity
import org.libre.agosto.p2play.activities.ui.theme.P2playTheme
class HostActivityV2: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
P2playTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
ConstraintLayout(modifier = Modifier.padding(innerPadding).fillMaxSize()) {
val (textLabel, emailField, passwordField) = createRefs()
Text("Iniciar session", modifier = Modifier.constrainAs(textLabel) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(emailField.top)
})
TextField("", {}, label = { Text("Email") }, modifier = Modifier.padding(10.dp).constrainAs(emailField) {
top.linkTo(textLabel.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(passwordField.top)
})
TextField("", {}, label = { Text("Password") }, modifier = Modifier.constrainAs(passwordField) {
top.linkTo(emailField.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
})
createVerticalChain(textLabel, emailField, passwordField, chainStyle = ChainStyle.Packed)
}
}
}
}
}
private fun instanceSelected () {
}
private fun startApp() {
runOnUiThread {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
this.finish()
}
}
}

View File

@ -0,0 +1,58 @@
package org.libre.agosto.p2play.activities
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import org.libre.agosto.p2play.ui.Routes
import org.libre.agosto.p2play.viewModels.VideosViewModel
import org.libre.agosto.p2play.activities.ui.theme.P2playTheme
import org.libre.agosto.p2play.ui.bars.MainNavigationBar
import org.libre.agosto.p2play.ui.bars.MainTopAppBar
import org.libre.agosto.p2play.ui.bars.SearchTopBar
import org.libre.agosto.p2play.ui.views.SearchView
import org.libre.agosto.p2play.ui.views.SubscriptionsView
import org.libre.agosto.p2play.ui.views.VideosView
import org.libre.agosto.p2play.viewModels.SubscriptionsViewModel
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val videoViewModel: VideosViewModel = viewModel()
val subscriptionsViewModel: SubscriptionsViewModel = viewModel()
val navController = rememberNavController()
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
P2playTheme {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
when(currentRoute) {
Routes.Videos.route -> MainTopAppBar(navController)
Routes.Search.route -> SearchTopBar()
else -> MainTopAppBar(navController)
}
},
bottomBar = { MainNavigationBar(navController) }
) { innerPadding ->
NavHost(navController, startDestination = Routes.Videos.route, modifier = Modifier.padding(innerPadding)) {
composable(Routes.Videos.route) { VideosView(videoViewModel) }
composable(Routes.Subscriptions.route) { SubscriptionsView(subscriptionsViewModel) }
composable(Routes.Search.route) { SearchView() }
}
}
}
}
}
}

View File

@ -0,0 +1,226 @@
package org.libre.agosto.p2play.activities.ui.theme
import androidx.compose.ui.graphics.Color
val primaryLight = Color(0xFF8A5022)
val onPrimaryLight = Color(0xFFFFFFFF)
val primaryContainerLight = Color(0xFFFFDCC6)
val onPrimaryContainerLight = Color(0xFF6D390C)
val secondaryLight = Color(0xFF755845)
val onSecondaryLight = Color(0xFFFFFFFF)
val secondaryContainerLight = Color(0xFFFFDCC6)
val onSecondaryContainerLight = Color(0xFF5B412F)
val tertiaryLight = Color(0xFF5F6135)
val onTertiaryLight = Color(0xFFFFFFFF)
val tertiaryContainerLight = Color(0xFFE4E6AE)
val onTertiaryContainerLight = Color(0xFF47491F)
val errorLight = Color(0xFFBA1A1A)
val onErrorLight = Color(0xFFFFFFFF)
val errorContainerLight = Color(0xFFFFDAD6)
val onErrorContainerLight = Color(0xFF93000A)
val backgroundLight = Color(0xFFFFF8F5)
val onBackgroundLight = Color(0xFF221A15)
val surfaceLight = Color(0xFFFFF8F5)
val onSurfaceLight = Color(0xFF221A15)
val surfaceVariantLight = Color(0xFFF3DED2)
val onSurfaceVariantLight = Color(0xFF52443B)
val outlineLight = Color(0xFF84746A)
val outlineVariantLight = Color(0xFFD7C3B7)
val scrimLight = Color(0xFF000000)
val inverseSurfaceLight = Color(0xFF382F29)
val inverseOnSurfaceLight = Color(0xFFFEEDE4)
val inversePrimaryLight = Color(0xFFFFB784)
val surfaceDimLight = Color(0xFFE7D7CE)
val surfaceBrightLight = Color(0xFFFFF8F5)
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
val surfaceContainerLowLight = Color(0xFFFFF1EA)
val surfaceContainerLight = Color(0xFFFBEBE2)
val surfaceContainerHighLight = Color(0xFFF6E5DC)
val surfaceContainerHighestLight = Color(0xFFF0DFD6)
val primaryLightMediumContrast = Color(0xFF582900)
val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
val primaryContainerLightMediumContrast = Color(0xFF9C5E2F)
val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
val secondaryLightMediumContrast = Color(0xFF493120)
val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
val secondaryContainerLightMediumContrast = Color(0xFF856753)
val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
val tertiaryLightMediumContrast = Color(0xFF363810)
val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
val tertiaryContainerLightMediumContrast = Color(0xFF6E7042)
val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
val errorLightMediumContrast = Color(0xFF740006)
val onErrorLightMediumContrast = Color(0xFFFFFFFF)
val errorContainerLightMediumContrast = Color(0xFFCF2C27)
val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
val backgroundLightMediumContrast = Color(0xFFFFF8F5)
val onBackgroundLightMediumContrast = Color(0xFF221A15)
val surfaceLightMediumContrast = Color(0xFFFFF8F5)
val onSurfaceLightMediumContrast = Color(0xFF17100B)
val surfaceVariantLightMediumContrast = Color(0xFFF3DED2)
val onSurfaceVariantLightMediumContrast = Color(0xFF40342B)
val outlineLightMediumContrast = Color(0xFF5E5047)
val outlineVariantLightMediumContrast = Color(0xFF7A6A60)
val scrimLightMediumContrast = Color(0xFF000000)
val inverseSurfaceLightMediumContrast = Color(0xFF382F29)
val inverseOnSurfaceLightMediumContrast = Color(0xFFFEEDE4)
val inversePrimaryLightMediumContrast = Color(0xFFFFB784)
val surfaceDimLightMediumContrast = Color(0xFFD3C3BB)
val surfaceBrightLightMediumContrast = Color(0xFFFFF8F5)
val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
val surfaceContainerLowLightMediumContrast = Color(0xFFFFF1EA)
val surfaceContainerLightMediumContrast = Color(0xFFF6E5DC)
val surfaceContainerHighLightMediumContrast = Color(0xFFEADAD1)
val surfaceContainerHighestLightMediumContrast = Color(0xFFDECFC6)
val primaryLightHighContrast = Color(0xFF492100)
val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
val primaryContainerLightHighContrast = Color(0xFF703B0E)
val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
val secondaryLightHighContrast = Color(0xFF3D2717)
val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
val secondaryContainerLightHighContrast = Color(0xFF5E4432)
val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
val tertiaryLightHighContrast = Color(0xFF2C2E07)
val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
val tertiaryContainerLightHighContrast = Color(0xFF494C22)
val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
val errorLightHighContrast = Color(0xFF600004)
val onErrorLightHighContrast = Color(0xFFFFFFFF)
val errorContainerLightHighContrast = Color(0xFF98000A)
val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
val backgroundLightHighContrast = Color(0xFFFFF8F5)
val onBackgroundLightHighContrast = Color(0xFF221A15)
val surfaceLightHighContrast = Color(0xFFFFF8F5)
val onSurfaceLightHighContrast = Color(0xFF000000)
val surfaceVariantLightHighContrast = Color(0xFFF3DED2)
val onSurfaceVariantLightHighContrast = Color(0xFF000000)
val outlineLightHighContrast = Color(0xFF362A22)
val outlineVariantLightHighContrast = Color(0xFF54463E)
val scrimLightHighContrast = Color(0xFF000000)
val inverseSurfaceLightHighContrast = Color(0xFF382F29)
val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
val inversePrimaryLightHighContrast = Color(0xFFFFB784)
val surfaceDimLightHighContrast = Color(0xFFC5B6AD)
val surfaceBrightLightHighContrast = Color(0xFFFFF8F5)
val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
val surfaceContainerLowLightHighContrast = Color(0xFFFEEDE4)
val surfaceContainerLightHighContrast = Color(0xFFF0DFD6)
val surfaceContainerHighLightHighContrast = Color(0xFFE1D1C9)
val surfaceContainerHighestLightHighContrast = Color(0xFFD3C3BB)
val primaryDark = Color(0xFFFFB784)
val onPrimaryDark = Color(0xFF4F2500)
val primaryContainerDark = Color(0xFF6D390C)
val onPrimaryContainerDark = Color(0xFFFFDCC6)
val secondaryDark = Color(0xFFE4BFA8)
val onSecondaryDark = Color(0xFF422B1B)
val secondaryContainerDark = Color(0xFF5B412F)
val onSecondaryContainerDark = Color(0xFFFFDCC6)
val tertiaryDark = Color(0xFFC8CA94)
val onTertiaryDark = Color(0xFF30330B)
val tertiaryContainerDark = Color(0xFF47491F)
val onTertiaryContainerDark = Color(0xFFE4E6AE)
val errorDark = Color(0xFFFFB4AB)
val onErrorDark = Color(0xFF690005)
val errorContainerDark = Color(0xFF93000A)
val onErrorContainerDark = Color(0xFFFFDAD6)
val backgroundDark = Color(0xFF19120D)
val onBackgroundDark = Color(0xFFF0DFD6)
val surfaceDark = Color(0xFF19120D)
val onSurfaceDark = Color(0xFFF0DFD6)
val surfaceVariantDark = Color(0xFF52443B)
val onSurfaceVariantDark = Color(0xFFD7C3B7)
val outlineDark = Color(0xFF9F8D83)
val outlineVariantDark = Color(0xFF52443B)
val scrimDark = Color(0xFF000000)
val inverseSurfaceDark = Color(0xFFF0DFD6)
val inverseOnSurfaceDark = Color(0xFF382F29)
val inversePrimaryDark = Color(0xFF8A5022)
val surfaceDimDark = Color(0xFF19120D)
val surfaceBrightDark = Color(0xFF413731)
val surfaceContainerLowestDark = Color(0xFF140D08)
val surfaceContainerLowDark = Color(0xFF221A15)
val surfaceContainerDark = Color(0xFF261E18)
val surfaceContainerHighDark = Color(0xFF312822)
val surfaceContainerHighestDark = Color(0xFF3C332D)
val primaryDarkMediumContrast = Color(0xFFFFD4B8)
val onPrimaryDarkMediumContrast = Color(0xFF3F1C00)
val primaryContainerDarkMediumContrast = Color(0xFFC5814F)
val onPrimaryContainerDarkMediumContrast = Color(0xFF000000)
val secondaryDarkMediumContrast = Color(0xFFFBD5BC)
val onSecondaryDarkMediumContrast = Color(0xFF362111)
val secondaryContainerDarkMediumContrast = Color(0xFFAB8A75)
val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
val tertiaryDarkMediumContrast = Color(0xFFDEE0A8)
val onTertiaryDarkMediumContrast = Color(0xFF262802)
val tertiaryContainerDarkMediumContrast = Color(0xFF929462)
val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
val errorDarkMediumContrast = Color(0xFFFFD2CC)
val onErrorDarkMediumContrast = Color(0xFF540003)
val errorContainerDarkMediumContrast = Color(0xFFFF5449)
val onErrorContainerDarkMediumContrast = Color(0xFF000000)
val backgroundDarkMediumContrast = Color(0xFF19120D)
val onBackgroundDarkMediumContrast = Color(0xFFF0DFD6)
val surfaceDarkMediumContrast = Color(0xFF19120D)
val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF)
val surfaceVariantDarkMediumContrast = Color(0xFF52443B)
val onSurfaceVariantDarkMediumContrast = Color(0xFFEDD8CC)
val outlineDarkMediumContrast = Color(0xFFC1AEA3)
val outlineVariantDarkMediumContrast = Color(0xFF9E8D82)
val scrimDarkMediumContrast = Color(0xFF000000)
val inverseSurfaceDarkMediumContrast = Color(0xFFF0DFD6)
val inverseOnSurfaceDarkMediumContrast = Color(0xFF312822)
val inversePrimaryDarkMediumContrast = Color(0xFF6F3A0D)
val surfaceDimDarkMediumContrast = Color(0xFF19120D)
val surfaceBrightDarkMediumContrast = Color(0xFF4D423C)
val surfaceContainerLowestDarkMediumContrast = Color(0xFF0C0603)
val surfaceContainerLowDarkMediumContrast = Color(0xFF241C17)
val surfaceContainerDarkMediumContrast = Color(0xFF2F2620)
val surfaceContainerHighDarkMediumContrast = Color(0xFF3A312B)
val surfaceContainerHighestDarkMediumContrast = Color(0xFF463C35)
val primaryDarkHighContrast = Color(0xFFFFECE2)
val onPrimaryDarkHighContrast = Color(0xFF000000)
val primaryContainerDarkHighContrast = Color(0xFFFEB17B)
val onPrimaryContainerDarkHighContrast = Color(0xFF180700)
val secondaryDarkHighContrast = Color(0xFFFFECE2)
val onSecondaryDarkHighContrast = Color(0xFF000000)
val secondaryContainerDarkHighContrast = Color(0xFFE0BBA4)
val onSecondaryContainerDarkHighContrast = Color(0xFF170700)
val tertiaryDarkHighContrast = Color(0xFFF2F4BB)
val onTertiaryDarkHighContrast = Color(0xFF000000)
val tertiaryContainerDarkHighContrast = Color(0xFFC4C690)
val onTertiaryContainerDarkHighContrast = Color(0xFF0B0C00)
val errorDarkHighContrast = Color(0xFFFFECE9)
val onErrorDarkHighContrast = Color(0xFF000000)
val errorContainerDarkHighContrast = Color(0xFFFFAEA4)
val onErrorContainerDarkHighContrast = Color(0xFF220001)
val backgroundDarkHighContrast = Color(0xFF19120D)
val onBackgroundDarkHighContrast = Color(0xFFF0DFD6)
val surfaceDarkHighContrast = Color(0xFF19120D)
val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
val surfaceVariantDarkHighContrast = Color(0xFF52443B)
val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF)
val outlineDarkHighContrast = Color(0xFFFFECE2)
val outlineVariantDarkHighContrast = Color(0xFFD2BFB3)
val scrimDarkHighContrast = Color(0xFF000000)
val inverseSurfaceDarkHighContrast = Color(0xFFF0DFD6)
val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
val inversePrimaryDarkHighContrast = Color(0xFF6F3A0D)
val surfaceDimDarkHighContrast = Color(0xFF19120D)
val surfaceBrightDarkHighContrast = Color(0xFF594E47)
val surfaceContainerLowestDarkHighContrast = Color(0xFF000000)
val surfaceContainerLowDarkHighContrast = Color(0xFF261E18)
val surfaceContainerDarkHighContrast = Color(0xFF382F29)
val surfaceContainerHighDarkHighContrast = Color(0xFF433933)
val surfaceContainerHighestDarkHighContrast = Color(0xFF4F453E)

View File

@ -0,0 +1,278 @@
package org.libre.agosto.p2play.activities.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
private val lightScheme = lightColorScheme(
primary = primaryLight,
onPrimary = onPrimaryLight,
primaryContainer = primaryContainerLight,
onPrimaryContainer = onPrimaryContainerLight,
secondary = secondaryLight,
onSecondary = onSecondaryLight,
secondaryContainer = secondaryContainerLight,
onSecondaryContainer = onSecondaryContainerLight,
tertiary = tertiaryLight,
onTertiary = onTertiaryLight,
tertiaryContainer = tertiaryContainerLight,
onTertiaryContainer = onTertiaryContainerLight,
error = errorLight,
onError = onErrorLight,
errorContainer = errorContainerLight,
onErrorContainer = onErrorContainerLight,
background = backgroundLight,
onBackground = onBackgroundLight,
surface = surfaceLight,
onSurface = onSurfaceLight,
surfaceVariant = surfaceVariantLight,
onSurfaceVariant = onSurfaceVariantLight,
outline = outlineLight,
outlineVariant = outlineVariantLight,
scrim = scrimLight,
inverseSurface = inverseSurfaceLight,
inverseOnSurface = inverseOnSurfaceLight,
inversePrimary = inversePrimaryLight,
surfaceDim = surfaceDimLight,
surfaceBright = surfaceBrightLight,
surfaceContainerLowest = surfaceContainerLowestLight,
surfaceContainerLow = surfaceContainerLowLight,
surfaceContainer = surfaceContainerLight,
surfaceContainerHigh = surfaceContainerHighLight,
surfaceContainerHighest = surfaceContainerHighestLight,
)
private val darkScheme = darkColorScheme(
primary = primaryDark,
onPrimary = onPrimaryDark,
primaryContainer = primaryContainerDark,
onPrimaryContainer = onPrimaryContainerDark,
secondary = secondaryDark,
onSecondary = onSecondaryDark,
secondaryContainer = secondaryContainerDark,
onSecondaryContainer = onSecondaryContainerDark,
tertiary = tertiaryDark,
onTertiary = onTertiaryDark,
tertiaryContainer = tertiaryContainerDark,
onTertiaryContainer = onTertiaryContainerDark,
error = errorDark,
onError = onErrorDark,
errorContainer = errorContainerDark,
onErrorContainer = onErrorContainerDark,
background = backgroundDark,
onBackground = onBackgroundDark,
surface = surfaceDark,
onSurface = onSurfaceDark,
surfaceVariant = surfaceVariantDark,
onSurfaceVariant = onSurfaceVariantDark,
outline = outlineDark,
outlineVariant = outlineVariantDark,
scrim = scrimDark,
inverseSurface = inverseSurfaceDark,
inverseOnSurface = inverseOnSurfaceDark,
inversePrimary = inversePrimaryDark,
surfaceDim = surfaceDimDark,
surfaceBright = surfaceBrightDark,
surfaceContainerLowest = surfaceContainerLowestDark,
surfaceContainerLow = surfaceContainerLowDark,
surfaceContainer = surfaceContainerDark,
surfaceContainerHigh = surfaceContainerHighDark,
surfaceContainerHighest = surfaceContainerHighestDark,
)
private val mediumContrastLightColorScheme = lightColorScheme(
primary = primaryLightMediumContrast,
onPrimary = onPrimaryLightMediumContrast,
primaryContainer = primaryContainerLightMediumContrast,
onPrimaryContainer = onPrimaryContainerLightMediumContrast,
secondary = secondaryLightMediumContrast,
onSecondary = onSecondaryLightMediumContrast,
secondaryContainer = secondaryContainerLightMediumContrast,
onSecondaryContainer = onSecondaryContainerLightMediumContrast,
tertiary = tertiaryLightMediumContrast,
onTertiary = onTertiaryLightMediumContrast,
tertiaryContainer = tertiaryContainerLightMediumContrast,
onTertiaryContainer = onTertiaryContainerLightMediumContrast,
error = errorLightMediumContrast,
onError = onErrorLightMediumContrast,
errorContainer = errorContainerLightMediumContrast,
onErrorContainer = onErrorContainerLightMediumContrast,
background = backgroundLightMediumContrast,
onBackground = onBackgroundLightMediumContrast,
surface = surfaceLightMediumContrast,
onSurface = onSurfaceLightMediumContrast,
surfaceVariant = surfaceVariantLightMediumContrast,
onSurfaceVariant = onSurfaceVariantLightMediumContrast,
outline = outlineLightMediumContrast,
outlineVariant = outlineVariantLightMediumContrast,
scrim = scrimLightMediumContrast,
inverseSurface = inverseSurfaceLightMediumContrast,
inverseOnSurface = inverseOnSurfaceLightMediumContrast,
inversePrimary = inversePrimaryLightMediumContrast,
surfaceDim = surfaceDimLightMediumContrast,
surfaceBright = surfaceBrightLightMediumContrast,
surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
surfaceContainerLow = surfaceContainerLowLightMediumContrast,
surfaceContainer = surfaceContainerLightMediumContrast,
surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
)
private val highContrastLightColorScheme = lightColorScheme(
primary = primaryLightHighContrast,
onPrimary = onPrimaryLightHighContrast,
primaryContainer = primaryContainerLightHighContrast,
onPrimaryContainer = onPrimaryContainerLightHighContrast,
secondary = secondaryLightHighContrast,
onSecondary = onSecondaryLightHighContrast,
secondaryContainer = secondaryContainerLightHighContrast,
onSecondaryContainer = onSecondaryContainerLightHighContrast,
tertiary = tertiaryLightHighContrast,
onTertiary = onTertiaryLightHighContrast,
tertiaryContainer = tertiaryContainerLightHighContrast,
onTertiaryContainer = onTertiaryContainerLightHighContrast,
error = errorLightHighContrast,
onError = onErrorLightHighContrast,
errorContainer = errorContainerLightHighContrast,
onErrorContainer = onErrorContainerLightHighContrast,
background = backgroundLightHighContrast,
onBackground = onBackgroundLightHighContrast,
surface = surfaceLightHighContrast,
onSurface = onSurfaceLightHighContrast,
surfaceVariant = surfaceVariantLightHighContrast,
onSurfaceVariant = onSurfaceVariantLightHighContrast,
outline = outlineLightHighContrast,
outlineVariant = outlineVariantLightHighContrast,
scrim = scrimLightHighContrast,
inverseSurface = inverseSurfaceLightHighContrast,
inverseOnSurface = inverseOnSurfaceLightHighContrast,
inversePrimary = inversePrimaryLightHighContrast,
surfaceDim = surfaceDimLightHighContrast,
surfaceBright = surfaceBrightLightHighContrast,
surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
surfaceContainerLow = surfaceContainerLowLightHighContrast,
surfaceContainer = surfaceContainerLightHighContrast,
surfaceContainerHigh = surfaceContainerHighLightHighContrast,
surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
)
private val mediumContrastDarkColorScheme = darkColorScheme(
primary = primaryDarkMediumContrast,
onPrimary = onPrimaryDarkMediumContrast,
primaryContainer = primaryContainerDarkMediumContrast,
onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
secondary = secondaryDarkMediumContrast,
onSecondary = onSecondaryDarkMediumContrast,
secondaryContainer = secondaryContainerDarkMediumContrast,
onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
tertiary = tertiaryDarkMediumContrast,
onTertiary = onTertiaryDarkMediumContrast,
tertiaryContainer = tertiaryContainerDarkMediumContrast,
onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
error = errorDarkMediumContrast,
onError = onErrorDarkMediumContrast,
errorContainer = errorContainerDarkMediumContrast,
onErrorContainer = onErrorContainerDarkMediumContrast,
background = backgroundDarkMediumContrast,
onBackground = onBackgroundDarkMediumContrast,
surface = surfaceDarkMediumContrast,
onSurface = onSurfaceDarkMediumContrast,
surfaceVariant = surfaceVariantDarkMediumContrast,
onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
outline = outlineDarkMediumContrast,
outlineVariant = outlineVariantDarkMediumContrast,
scrim = scrimDarkMediumContrast,
inverseSurface = inverseSurfaceDarkMediumContrast,
inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
inversePrimary = inversePrimaryDarkMediumContrast,
surfaceDim = surfaceDimDarkMediumContrast,
surfaceBright = surfaceBrightDarkMediumContrast,
surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
surfaceContainer = surfaceContainerDarkMediumContrast,
surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
)
private val highContrastDarkColorScheme = darkColorScheme(
primary = primaryDarkHighContrast,
onPrimary = onPrimaryDarkHighContrast,
primaryContainer = primaryContainerDarkHighContrast,
onPrimaryContainer = onPrimaryContainerDarkHighContrast,
secondary = secondaryDarkHighContrast,
onSecondary = onSecondaryDarkHighContrast,
secondaryContainer = secondaryContainerDarkHighContrast,
onSecondaryContainer = onSecondaryContainerDarkHighContrast,
tertiary = tertiaryDarkHighContrast,
onTertiary = onTertiaryDarkHighContrast,
tertiaryContainer = tertiaryContainerDarkHighContrast,
onTertiaryContainer = onTertiaryContainerDarkHighContrast,
error = errorDarkHighContrast,
onError = onErrorDarkHighContrast,
errorContainer = errorContainerDarkHighContrast,
onErrorContainer = onErrorContainerDarkHighContrast,
background = backgroundDarkHighContrast,
onBackground = onBackgroundDarkHighContrast,
surface = surfaceDarkHighContrast,
onSurface = onSurfaceDarkHighContrast,
surfaceVariant = surfaceVariantDarkHighContrast,
onSurfaceVariant = onSurfaceVariantDarkHighContrast,
outline = outlineDarkHighContrast,
outlineVariant = outlineVariantDarkHighContrast,
scrim = scrimDarkHighContrast,
inverseSurface = inverseSurfaceDarkHighContrast,
inverseOnSurface = inverseOnSurfaceDarkHighContrast,
inversePrimary = inversePrimaryDarkHighContrast,
surfaceDim = surfaceDimDarkHighContrast,
surfaceBright = surfaceBrightDarkHighContrast,
surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
surfaceContainerLow = surfaceContainerLowDarkHighContrast,
surfaceContainer = surfaceContainerDarkHighContrast,
surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
)
@Immutable
data class ColorFamily(
val color: Color,
val onColor: Color,
val colorContainer: Color,
val onColorContainer: Color
)
val unspecified_scheme = ColorFamily(
Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified
)
@Composable
fun P2playTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable() () -> Unit
) {
val colorScheme = when {
// dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
// val context = LocalContext.current
// if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
//}
darkTheme -> darkScheme
else -> lightScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
content = content
)
}

View File

@ -0,0 +1,9 @@
package org.libre.agosto.p2play.activities.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
val AppTypography = Typography()

View File

@ -0,0 +1,130 @@
package org.libre.agosto.p2play.adapters
import android.content.Context
import android.content.Intent
import android.os.AsyncTask
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import org.libre.agosto.p2play.AccountActivity
import org.libre.agosto.p2play.ChannelActivity
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ajax.Actions
import org.libre.agosto.p2play.models.ChannelModel
class ChannelAdapter(private val myDataset: ArrayList<ChannelModel>): RecyclerView.Adapter<ChannelAdapter.ViewHolder>() {
private val actionsService = Actions()
lateinit var parent: FragmentActivity
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
// Define click listener for the ViewHolder's View
val channelImage: ImageView = view.findViewById(R.id.channelImage)
val channelName: TextView = view.findViewById(R.id.channelName)
val channelDescription: TextView = view.findViewById(R.id.channelDescription)
val subscribeButton: Button = view.findViewById(R.id.subscribeBtn)
val context: Context = view.context
var isSubscribed: Boolean = false
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.view_channel, parent, false) as View
return ViewHolder(view)
}
override fun getItemCount() = myDataset.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.channelName.text = myDataset[position].name
holder.channelDescription.text = myDataset[position].description
if (myDataset[position].channelImg != "") {
Picasso.get().load("https://${ManagerSingleton.url}${myDataset[position].channelImg}").into(holder.channelImage)
} else {
holder.channelImage.setImageResource(R.drawable.default_avatar)
}
if (ManagerSingleton.user.status == 1) {
getSubscription(myDataset[position], holder)
} else {
holder.subscribeButton.visibility = View.GONE
}
holder.subscribeButton.setOnClickListener {
this.subscribeAction(myDataset[position], holder)
}
holder.channelImage.setOnClickListener {
this.launchChannelActivity(myDataset[position].getAccount())
}
holder.channelName.setOnClickListener {
this.launchChannelActivity(myDataset[position].getAccount())
}
}
private fun getSubscription(channel: ChannelModel, holder: ViewHolder) {
AsyncTask.execute {
holder.isSubscribed = actionsService.getSubscription(ManagerSingleton.token.token, channel.getAccount())
parent.runOnUiThread {
if (holder.isSubscribed) {
holder.subscribeButton.text = parent.getText(R.string.unSubscribeBtn)
} else {
holder.subscribeButton.text = parent.getText(R.string.subscribeBtn)
}
}
}
}
private fun subscribe(channel: ChannelModel, holder: ViewHolder) {
AsyncTask.execute {
val res = actionsService.subscribe(ManagerSingleton.token.token, channel.getAccount())
parent.runOnUiThread {
if (res == 1) {
holder.subscribeButton.text = parent.getString(R.string.unSubscribeBtn)
ManagerSingleton.toast(parent.getString(R.string.subscribeMsg), parent)
getSubscription(channel, holder)
} else {
ManagerSingleton.toast(parent.getString(R.string.errorMsg), parent)
}
}
}
}
private fun unSubscribe(channel: ChannelModel, holder: ViewHolder) {
AsyncTask.execute {
val res = actionsService.unSubscribe(ManagerSingleton.token.token, channel.getAccount())
parent.runOnUiThread {
if (res == 1) {
holder.subscribeButton.text = parent.getString(R.string.subscribeBtn)
ManagerSingleton.toast(parent.getString(R.string.unSubscribeMsg), parent)
getSubscription(channel, holder)
} else {
ManagerSingleton.toast(parent.getString(R.string.errorMsg), parent)
}
}
}
}
private fun subscribeAction(channel: ChannelModel, holder: ViewHolder) {
if (holder.isSubscribed) {
unSubscribe(channel, holder)
} else {
subscribe(channel, holder)
}
}
private fun launchChannelActivity (channelId: String) {
val intent = Intent(parent, ChannelActivity::class.java)
intent.putExtra("channel", channelId)
parent.startActivity(intent)
}
}

View File

@ -1,6 +1,7 @@
package org.libre.agosto.p2play.adapters
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater
@ -12,6 +13,7 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import org.libre.agosto.p2play.AccountActivity
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.dialogs.ThreadDialog
@ -86,11 +88,11 @@ class CommentariesAdapter(private val myDataset: ArrayList<CommentaryModel>) :
}
// TODO: Support for view and account (is different than a video channel)
// holder.userImg.setOnClickListener {
// val intent = Intent(holder.context, ChannelActivity::class.java)
// intent.putExtra("channel", myDataset[position].getAccount())
// holder.context.startActivity(intent)
// }
holder.userImg.setOnClickListener {
val intent = Intent(holder.context, AccountActivity::class.java)
intent.putExtra("accountId", myDataset[position].getAccount())
holder.context.startActivity(intent)
}
}
// Return the size of your dataset (invoked by the layout manager)

View File

@ -9,6 +9,7 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import org.libre.agosto.p2play.AccountActivity
import org.libre.agosto.p2play.ChannelActivity
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R

View File

@ -0,0 +1,58 @@
package org.libre.agosto.p2play.ajax
import android.util.JsonReader
import com.google.gson.Gson
import org.libre.agosto.p2play.models.AccountModel
import org.libre.agosto.p2play.models.ChannelModel
import org.libre.agosto.p2play.models.VideoModel
import java.io.InputStreamReader
class Accounts : Client() {
fun get(accountId: String): AccountModel {
val con = this.newCon("accounts/$accountId", "GET")
lateinit var account: AccountModel
try {
if (con.responseCode == 200) {
val response = InputStreamReader(con.inputStream)
account = Gson().fromJson(response, AccountModel::class.java)
}
} catch (err: Exception) {
err.printStackTrace()
}
return account
}
fun getChannels(accountId: String): ArrayList<ChannelModel> {
val con = this.newCon("accounts/$accountId/video-channels", "GET")
val channels = arrayListOf<ChannelModel>()
try {
if (con.responseCode == 200) {
val response = InputStreamReader(con.inputStream)
val data = JsonReader(response)
data.beginObject()
while (data.hasNext()) {
when (data.nextName()) {
"data" -> {
data.beginArray()
while (data.hasNext()) {
val channel = ChannelModel()
channel.parseChannel(data)
channels.add(channel)
}
data.endArray()
}
else -> data.skipValue()
}
}
data.endObject()
data.close()
}
} catch (err: Exception) {
err.printStackTrace()
}
return channels
}
}

View File

@ -105,6 +105,7 @@ class Auth : Client() {
token.status = 1
} else {
Log.d("Status", con.responseMessage)
token.status = 0
}
} catch (err: Exception) {
err.printStackTrace()

View File

@ -6,14 +6,6 @@ import java.io.InputStreamReader
class Channels : Client() {
private fun parseChannel(data: JsonReader): ChannelModel {
val channel = ChannelModel()
data.close()
return channel
}
fun getChannelInfo(account: String): ChannelModel {
val con = this.newCon("video-channels/$account", "GET")
var channel = ChannelModel()

View File

@ -9,7 +9,7 @@ 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? = null): HttpURLConnection {
val url = URL("https://${ManagerSingleton.url}/api/v1/$uri")
val con = url.openConnection() as HttpURLConnection
@ -17,7 +17,7 @@ open class Client {
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
con.setRequestProperty("Accept", "*/*")
if (token != "") {
if (token !== null) {
con.setRequestProperty("Authorization", "Bearer $token")
}

View File

@ -201,8 +201,8 @@ class Videos : Client() {
return this.getVideos(start, "-likes")
}
fun getVideo(uuid: String): VideoModel {
val con = this.newCon("videos/$uuid", "GET")
fun getVideo(uuid: String, token: String? = null): VideoModel {
val con = this.newCon("videos/$uuid", "GET", token)
val video = VideoModel()
try {
if (con.responseCode == 200) {
@ -218,4 +218,24 @@ class Videos : Client() {
con.disconnect()
return video
}
fun accountVideos(account: String, start: Int): ArrayList<VideoModel> {
val count = ManagerSingleton.videosCount
val params = "start=$start&count=$count"
val con = this.newCon("accounts/$account/videos?$params", "GET")
var videos = arrayListOf<VideoModel>()
try {
if (con.responseCode == 200) {
val response = InputStreamReader(con.inputStream)
val data = JsonReader(response)
videos = parseVideos(data)
data.close()
}
} catch (err: Exception) {
err.printStackTrace()
}
con.disconnect()
return videos
}
}

View File

@ -12,22 +12,11 @@ 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.databinding.DialogThreadBinding
import org.libre.agosto.p2play.models.CommentaryModel
class ThreadDialog : DialogFragment() {
@ -35,6 +24,12 @@ class ThreadDialog : DialogFragment() {
lateinit var fragmentManager2: FragmentManager
private val client: Comments = Comments()
private var _binding: DialogThreadBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
// 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(
@ -43,29 +38,30 @@ class ThreadDialog : DialogFragment() {
savedInstanceState: Bundle?,
): View {
// Inflate the layout to use as a dialog or embedded fragment.
val view = inflater.inflate(R.layout.dialog_thread, container, false)
_binding = DialogThreadBinding.inflate(inflater, container, false)
val view = binding.root
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
binding.commentThread?.userTxt?.text = comment.username
binding.commentThread?.userCommentary?.text = comment.commentary
Picasso.get().load("https://${ManagerSingleton.url}${comment.userImageUrl}").into(binding.commentThread?.userCommentImg)
binding.commentThread?.replyBtn?.visibility = View.GONE
if (ManagerSingleton.user.status == 1) {
view.commentaryText.setText("${comment.username}@${comment.userHost} ")
binding.commentBox?.commentaryText?.setText("${comment.nameChannel}@${comment.userHost} ")
if (ManagerSingleton.user.avatar != "") {
Picasso.get().load("https://${ManagerSingleton.url}${ManagerSingleton.user.avatar}").into(view.userImgCom)
Picasso.get().load("https://${ManagerSingleton.url}${ManagerSingleton.user.avatar}").into(binding.commentBox?.userImgCom)
}
view.commentaryText.requestFocus()
view.commentaryBtn.setOnClickListener { this.replyThread() }
binding.commentBox?.commentaryText?.requestFocus()
binding.commentBox?.commentaryBtn?.setOnClickListener { this.replyThread() }
} else {
view.commentaryLayout.visibility = View.GONE
binding.commentBox?.commentaryLayout?.visibility = View.GONE
}
view.materialToolbar.setTitle("Thread")
view.materialToolbar.setNavigationIcon(R.drawable.baseline_arrow_back_24)
view.materialToolbar.setNavigationOnClickListener {
binding.materialToolbar.setTitle("Thread")
binding.materialToolbar.setNavigationIcon(R.drawable.baseline_arrow_back_24)
binding.materialToolbar.setNavigationOnClickListener {
dismiss()
this.fragmentManager2.popBackStack()
}
@ -109,7 +105,7 @@ class ThreadDialog : DialogFragment() {
}
private fun replyThread() {
val commentary = view?.commentaryText?.text.toString()
val commentary = binding.commentBox?.commentaryText?.text.toString()
if (commentary == "") {
ManagerSingleton.toast(getString(R.string.emptyCommentaryMsg), requireActivity())
@ -121,7 +117,7 @@ class ThreadDialog : DialogFragment() {
activity?.runOnUiThread {
if (res) {
ManagerSingleton.toast(getString(R.string.makedCommentaryMsg), requireActivity())
commentaryText.text?.clear()
binding.commentBox?.commentaryText?.text?.clear()
this.getComments()
} else {
ManagerSingleton.toast(getString(R.string.errorCommentaryMsg), requireActivity())

View File

@ -0,0 +1,10 @@
package org.libre.agosto.p2play.domain.data
enum class VideoFilterEnum(val filter: String) {
TRENDING("trending"),
HOT("hot"),
LOCAL("local"),
RECENT("recent"),
LIKES("likes"),
POPULAR("popular")
}

View File

@ -0,0 +1,38 @@
package org.libre.agosto.p2play.fragmentAdapters
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import org.libre.agosto.p2play.fragments.AccountVideosFragment
import org.libre.agosto.p2play.fragments.AccountChannelsFragment
import org.libre.agosto.p2play.fragments.AccountInfoFragment
import org.libre.agosto.p2play.models.AccountModel
val TAB_NAMES = arrayOf(
"Videos",
"Channels",
"Info"
)
class AccountAdapter(fm: FragmentManager, c: Lifecycle) : FragmentStateAdapter(fm, c) {
lateinit var accountId: String
lateinit var account: AccountModel
override fun getItemCount(): Int = 3
override fun createFragment(i: Int): Fragment {
val fragment = when (i) {
0 -> AccountVideosFragment.newInstance(accountId)
1 -> AccountChannelsFragment.newInstance(accountId)
2 -> AccountInfoFragment.newInstance(account)
else -> throw Error("Invalid tab")
}
return fragment
}
fun get(position: Int): CharSequence {
return TAB_NAMES[position]
}
}

View File

@ -0,0 +1,82 @@
package org.libre.agosto.p2play.fragments
import android.os.AsyncTask
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.libre.agosto.p2play.adapters.ChannelAdapter
import org.libre.agosto.p2play.ajax.Accounts
import org.libre.agosto.p2play.databinding.FragmentChannelsBinding
import org.libre.agosto.p2play.helpers.getViewManager
import org.libre.agosto.p2play.models.ChannelModel
private const val ARG_PARAM1 = "accountId"
class AccountChannelsFragment : Fragment() {
private var _binding: FragmentChannelsBinding? = null
private val binding get() = _binding!!
private var accountId: String? = null
private val client = Accounts()
private lateinit var viewManager: RecyclerView.LayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
accountId = it.getString(ARG_PARAM1)
}
viewManager = getViewManager(this.requireContext(), resources)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentChannelsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onResume() {
super.onResume()
getChannels()
}
private fun getChannels() {
AsyncTask.execute {
val channels = client.getChannels(this.accountId!!)
requireActivity().runOnUiThread {
initRecycler(channels)
}
}
}
private fun initRecycler(data: ArrayList<ChannelModel>) {
val viewAdapter = ChannelAdapter(data)
binding.channelList.apply {
viewAdapter.parent = requireActivity()
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
}
}
companion object {
@JvmStatic
fun newInstance(accountId: String) =
AccountChannelsFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, accountId)
}
}
}
}

View File

@ -0,0 +1,50 @@
package org.libre.agosto.p2play.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import org.libre.agosto.p2play.databinding.FragmentChannelInfoBinding
import org.libre.agosto.p2play.models.AccountModel
import java.io.Serializable
private const val ARG_PARAM1 = "account"
class AccountInfoFragment : Fragment() {
private var _binding: FragmentChannelInfoBinding? = null
private val binding get() = _binding!!
private var account: AccountModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
account = it.getSerializable(ARG_PARAM1) as AccountModel
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentChannelInfoBinding.inflate(inflater, container, false)
binding.account.text = account?.name
binding.host.text = account?.host
binding.description.text = account?.description
return binding.root
}
companion object {
@JvmStatic
fun newInstance(account: AccountModel) =
AccountInfoFragment().apply {
arguments = Bundle().apply {
val param = account as Serializable
putSerializable(ARG_PARAM1, param)
}
}
}
}

View File

@ -0,0 +1,86 @@
package org.libre.agosto.p2play.fragments
import android.os.AsyncTask
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.libre.agosto.p2play.adapters.VideosAdapter
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.databinding.FragmentChannelVideosBinding
import org.libre.agosto.p2play.helpers.getViewManager
import org.libre.agosto.p2play.models.VideoModel
private const val ARG_PARAM1 = "accountId"
class AccountVideosFragment : Fragment() {
private var _binding: FragmentChannelVideosBinding? = null
private val binding get() = _binding!!
private var accountId: String? = "agosto182"
private val videosService = Videos()
private lateinit var viewManager: RecyclerView.LayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
accountId = it.getString(ARG_PARAM1, accountId)
}
viewManager = getViewManager(this.requireContext(), resources)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentChannelVideosBinding.inflate(inflater, container, false)
return binding.root
}
override fun onResume() {
super.onResume()
getVideos()
}
private fun getVideos() {
AsyncTask.execute {
val videos = videosService.accountVideos(this.accountId!!, 0)
activity?.runOnUiThread {
initRecycler(videos)
}
}
}
private fun initRecycler(data: ArrayList<VideoModel>) {
// val data = arrayListOf<VideoModel>()
val viewAdapter = VideosAdapter(data)
binding.videosList.apply {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
}
}
companion object {
@JvmStatic
fun newInstance(param1: String) =
AccountVideosFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
}
}
}
}

View File

@ -0,0 +1,24 @@
package org.libre.agosto.p2play.helpers
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView.LayoutManager
fun getViewManager (context: Context, resources: Resources): LayoutManager {
val screenSize = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
val manager = if (screenSize > Configuration.SCREENLAYOUT_SIZE_LARGE) {
val orientation = resources.configuration.orientation
val gridItems = when (orientation) {
Configuration.ORIENTATION_LANDSCAPE -> 4
else -> 3
}
GridLayoutManager(context, gridItems)
} else {
LinearLayoutManager(context)
}
return manager
}

View File

@ -0,0 +1,23 @@
package org.libre.agosto.p2play.helpers
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
@Composable
fun InfiniteScrollHandler(
lazyState: LazyListState,
buffer: Int = 1,
onLoadMore: () -> Unit
) {
LaunchedEffect(lazyState) {
snapshotFlow { lazyState.layoutInfo.visibleItemsInfo }
.collect { visibleItems ->
val items = lazyState.layoutInfo.totalItemsCount
if (visibleItems.isNotEmpty() && items > buffer && visibleItems.last().index >= items - 1) {
onLoadMore()
}
}
}
}

View File

@ -0,0 +1,22 @@
package org.libre.agosto.p2play.helpers
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class TaskManager<T> : ViewModel() {
val result = MutableLiveData<T>()
fun runTask(task: () -> T, callback: (T) -> Unit) {
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
task()
}
result.postValue(data)
callback(data)
}
}
}

View File

@ -0,0 +1,16 @@
package org.libre.agosto.p2play.models
import java.io.Serializable
data class AccountAvatar (
val path: String
)
class AccountModel (
val name: String,
val url: String,
val host: String,
val avatars: ArrayList<AccountAvatar>,
val displayName: String,
val description: String
): Serializable

View File

@ -2,10 +2,15 @@ package org.libre.agosto.p2play.models
import android.util.JsonReader
class DownloadFiles(
var resolution: String = "",
var url: String = ""
)
class StreamingModel(
var playlistUrl: String = "",
var segmentsSha256Url: String = "",
// TODO: Download Files
var downloadFiles: ArrayList<DownloadFiles> = arrayListOf<DownloadFiles>()
) {
fun parse(data: JsonReader) {
data.beginObject()
@ -18,6 +23,37 @@ class StreamingModel(
"segmentsSha256Url" -> {
this.segmentsSha256Url = data.nextString()
}
"files" -> {
data.beginArray()
if (data.hasNext()) {
data.beginObject()
val downloadFile = DownloadFiles()
while (data.hasNext()) {
val key2 = data.nextName()
when (key2.toString()) {
"fileDownloadUrl" -> downloadFile.url = data.nextString()
"resolution" -> {
data.beginObject()
while(data.hasNext()) {
val keyRes = data.nextName()
when (keyRes!!) {
"label" -> downloadFile.resolution = data.nextString()
else -> data.skipValue()
}
}
data.endObject()
}
else -> data.skipValue()
}
}
this.downloadFiles.add(downloadFile)
data.endObject()
}
while (data.hasNext()) {
data.skipValue()
}
data.endArray()
}
else -> data.skipValue()
}
}

View File

@ -77,13 +77,27 @@ class VideoModel(
data.beginObject()
while (data.hasNext()) {
val key2 = data.nextName()
streamingData = StreamingModel()
val downloadFile = DownloadFiles()
when (key2.toString()) {
"fileUrl" -> {
streamingData = StreamingModel()
streamingData!!.playlistUrl = data.nextString()
}
"fileDownloadUrl" -> downloadFile.url = data.nextString()
"resolution" -> {
data.beginObject()
while(data.hasNext()) {
val keyRes = data.nextName()
when (keyRes!!) {
"label" -> downloadFile.resolution = data.nextString()
else -> data.skipValue()
}
}
data.endObject()
}
else -> data.skipValue()
}
streamingData!!.downloadFiles.add(downloadFile)
}
data.endObject()
}

View File

@ -2,6 +2,8 @@ package org.libre.agosto.p2play.services
import android.app.PendingIntent
import android.content.Intent
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import org.libre.agosto.p2play.ReproductorActivity
@ -11,6 +13,7 @@ class PlaybackService : MediaSessionService() {
private var mediaSession: MediaSession? = null
// Create your Player and MediaSession in the onCreate lifecycle event
@OptIn(UnstableApi::class)
override fun onCreate() {
super.onCreate()
val player = PlaybackSingleton.player!!
@ -24,7 +27,8 @@ class PlaybackService : MediaSessionService() {
contentIntent,
PendingIntent.FLAG_MUTABLE,
)
mediaSession!!.setSessionActivity(pendingIntent)
mediaSession?.setSessionActivity(pendingIntent)
}
// Remember to release the player and media session in onDestroy

View File

@ -0,0 +1,9 @@
package org.libre.agosto.p2play.ui
import org.libre.agosto.p2play.R
sealed class Routes (val route: String, val title: Int?) {
data object Videos: Routes("videos", R.string.app_name)
data object Subscriptions: Routes("subscriptions", R.string.title_subscriptions)
data object Search: Routes("search", null)
}

View File

@ -0,0 +1,80 @@
package org.libre.agosto.p2play.ui.bars
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.currentBackStackEntryAsState
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ui.Routes
@Composable
fun MainNavigationBar (navController: NavController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Filled.Home, "home") },
label = { Text(text = stringResource(R.string.nav_menu_videos), fontSize = 11.sp) },
selected = currentDestination?.route == Routes.Videos.route,
onClick = {
navController.navigate(Routes.Videos.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
NavigationBarItem(
icon = { Icon(painterResource(R.drawable.ic_live_tv_black_24dp), "home") },
label = { Text(stringResource(R.string.nav_subscriptions), fontSize = 11.sp) },
selected = currentDestination?.route == Routes.Subscriptions.route,
onClick = {
navController.navigate(Routes.Subscriptions.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
// NavigationBarItem(
// icon = { Icon(painterResource(R.drawable.ic_menu_slideshow), "home") },
// label = { Text(stringResource(R.string.playlists), fontSize = 11.sp) },
// selected = false,
// onClick = {}
// )
NavigationBarItem(
icon = { Icon(Icons.Filled.AccountCircle, "home") },
label = { Text(stringResource(R.string.you), fontSize = 11.sp) },
selected = false,
onClick = {}
)
}
}

View File

@ -0,0 +1,89 @@
package org.libre.agosto.p2play.ui.bars
import android.content.Intent
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.currentBackStackEntryAsState
import org.libre.agosto.p2play.AboutActivity
import org.libre.agosto.p2play.LoginActivity
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ui.Routes
import org.libre.agosto.p2play.SettingsActivity2
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainTopAppBar(navController: NavController, modifier: Modifier = Modifier) {
var isMenuOpen by remember { mutableStateOf(false) }
val context = LocalContext.current
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val title = when (currentDestination?.route) {
Routes.Subscriptions.route -> stringResource(Routes.Subscriptions.title!!)
else -> stringResource(R.string.app_name)
}
TopAppBar(
{ Text(title) },
modifier,
actions = {
IconButton({
navController.navigate(Routes.Search.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}) { Icon(Icons.Default.Search, "More") }
// IconButton({}) { Icon(Icons.Default.Notifications, "More") }
IconButton({ isMenuOpen = true }) {
Icon(Icons.Default.MoreVert, "More")
DropdownMenu(isMenuOpen, { isMenuOpen = false }) {
if (ManagerSingleton.isLogged()) {
DropdownMenuItem({ Text(stringResource(R.string.action_logout))}, {
isMenuOpen = false
ManagerSingleton.logout()
})
} else {
DropdownMenuItem({ Text(stringResource(R.string.action_login))}, {
isMenuOpen = false
val intent = Intent(context, LoginActivity::class.java)
context.startActivity(intent)
})
}
DropdownMenuItem({ Text(stringResource(R.string.action_settings))}, {
isMenuOpen = false
val intent = Intent(context, SettingsActivity2::class.java)
context.startActivity(intent)
})
DropdownMenuItem({ Text(stringResource(R.string.nav_about))}, {
isMenuOpen = false
val intent = Intent(context, AboutActivity::class.java)
context.startActivity(intent)
})
}
}
}
)
}

View File

@ -0,0 +1,52 @@
package org.libre.agosto.p2play.ui.bars
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchTopBar () {
var searchText by remember { mutableStateOf("") }
var active by remember { mutableStateOf(true) }
SearchBar(
query = searchText,
onQueryChange = { searchText = it },
onSearch = { active = false },
active = active,
onActiveChange = { active = it },
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("Buscar...") },
leadingIcon = {
IconButton (onClick = {}) {
Icon(Icons.Default.ArrowBack, contentDescription = "Volver")
}
},
trailingIcon = {
if (searchText.isNotEmpty()) {
IconButton(onClick = { searchText = "" }) {
Icon(Icons.Default.Close, contentDescription = "Limpiar")
}
}
}) {
if (searchText.isNotEmpty()) {
Text("Resultados para: $searchText")
} else {
Text("Ingresa un término de búsqueda")
}
}
}

View File

@ -0,0 +1,47 @@
package org.libre.agosto.p2play.ui.components.molecules
import android.graphics.drawable.Icon
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
interface ChipValues<T> {
val text: String
val value: T
val icon: ImageVector?
}
@Composable
fun <T> ChipSelector (values: ArrayList<ChipValues<T>>, value: T? = null, modifier: Modifier = Modifier, callback: (T) -> Unit) {
Row (modifier.horizontalScroll(rememberScrollState())) {
Spacer(modifier.width(10.dp))
for (v in values) {
val isSelected = value == v.value
FilterChip(
selected = isSelected,
{ callback(v.value) },
label = { Text(v.text) },
leadingIcon = {
if (isSelected) {
Icon(Icons.Default.Check, "", Modifier.height(20.dp))
} else if (v.icon !== null) {
Icon(v.icon!!, "", Modifier.height(20.dp))
}
}
)
Spacer(modifier.width(10.dp))
}
}
}

View File

@ -0,0 +1,113 @@
package org.libre.agosto.p2play.ui.components.organisms
import android.content.Intent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import coil3.compose.AsyncImage
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.activities.ui.theme.AppTypography
import org.libre.agosto.p2play.helpers.mapSeconds
import org.libre.agosto.p2play.models.VideoModel
@Composable
fun VideoItem (
videoData: VideoModel
) {
val context = LocalContext.current
val userImgSource = if (videoData.userImageUrl !== "") {
"https://${ManagerSingleton.url}${videoData.userImageUrl}"
} else {
R.drawable.default_avatar
}
Card({
val intent = Intent(context, ReproductorActivity::class.java)
intent.putExtra("video", videoData)
context.startActivity(intent)
}, modifier = Modifier.padding(vertical = 10.dp)) {
ConstraintLayout() {
val (thumbailImg, durationTxt, titleTxt, infoBox, userImg) = createRefs()
Box(modifier = Modifier.fillMaxWidth().aspectRatio(16f / 9f).constrainAs(thumbailImg) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
}) {
AsyncImage(
"https://${ManagerSingleton.url}${videoData.thumbUrl}",
videoData.name,
contentScale = ContentScale.Crop,
modifier = Modifier.matchParentSize()
)
}
Text(mapSeconds(videoData.duration.toInt()), color = Color.Black, modifier = Modifier.alpha(0.5F).padding(5.dp).background(Color.White).constrainAs(durationTxt) {
end.linkTo(thumbailImg.end)
bottom.linkTo(thumbailImg.bottom)
})
Box(modifier = Modifier.constrainAs(userImg) {
top.linkTo(thumbailImg.bottom)
start.linkTo(parent.start)
bottom.linkTo(parent.bottom)
}.clickable {
val intent = Intent(context, ChannelActivity::class.java)
intent.putExtra("channel", videoData.getChannel())
context.startActivity(intent)
}) {
AsyncImage(
userImgSource,
"Profile photo",
contentScale = ContentScale.Fit,
modifier = Modifier.padding(10.dp).size(40.dp).clip(CircleShape),
)
}
Text(
videoData.name,
style = AppTypography.titleMedium,
overflow = TextOverflow.Ellipsis,
lineHeight = 19.sp,
textAlign = TextAlign.Left,
modifier = Modifier.constrainAs(titleTxt) {
top.linkTo(thumbailImg.bottom)
start.linkTo(userImg.end)
}.padding(top = 12.dp, end = 70.dp)
)
Row (modifier = Modifier.constrainAs(infoBox) {
top.linkTo(titleTxt.bottom)
start.linkTo(userImg.end)
}.padding(bottom = 5.dp)) {
Text(videoData.nameChannel, style = AppTypography.labelSmall)
Text(" - ", style = AppTypography.labelSmall)
Text("${videoData.views.toString()} ${stringResource(R.string.view_text)}", style = AppTypography.labelSmall)
}
// createVerticalChain(thumbailImg, titleTxt, infoBox, chainStyle = ChainStyle.Packed)
}
}
}

View File

@ -0,0 +1,77 @@
package org.libre.agosto.p2play.ui.lists
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import org.libre.agosto.p2play.ui.components.organisms.VideoItem
import org.libre.agosto.p2play.models.VideoModel
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.libre.agosto.p2play.helpers.InfiniteScrollHandler
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun VideoList (videos: ArrayList<VideoModel>, header: @Composable (() -> Unit)? = null, isLoading: Boolean = false, onRefresh: (() -> Unit)? = null, onLoadMore: (() -> Unit)? = null) {
var isRefreshing by remember { mutableStateOf(false) }
val lazyState = rememberLazyListState()
PullToRefreshBox(
isRefreshing,
{
if (onRefresh !== null) {
onRefresh()
}
}
) {
LazyColumn(state = lazyState) {
if (header !== null) {
item(key = "header") { header() }
}
items(videos, key = { it.id }) {
VideoItem(it)
}
if (isLoading) {
item(key = "loading") {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 20.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
}
}
if (onLoadMore !== null) {
LaunchedEffect (isLoading) {
snapshotFlow { isLoading }
.collect {
if (!it) {
isRefreshing = false
}
}
}
InfiniteScrollHandler(lazyState, 2) {
onLoadMore()
}
}
}

View File

@ -0,0 +1,15 @@
package org.libre.agosto.p2play.ui.views
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun SearchView (modifier: Modifier = Modifier) {
Box(modifier = modifier.background(Color.Red).width(100.dp).height(100.dp))
}

View File

@ -0,0 +1,60 @@
package org.libre.agosto.p2play.ui.views
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import kotlinx.coroutines.flow.distinctUntilChanged
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.ui.lists.VideoList
import org.libre.agosto.p2play.ui.components.molecules.ChipSelector
import org.libre.agosto.p2play.ui.components.molecules.ChipValues
import org.libre.agosto.p2play.helpers.TaskManager
import org.libre.agosto.p2play.models.VideoModel
import org.libre.agosto.p2play.viewModels.SubscriptionsViewModel
import java.util.ArrayList
@SuppressLint("MutableCollectionMutableState")
@Composable
fun SubscriptionsView (subscriptionsViewModel: SubscriptionsViewModel, modifier: Modifier = Modifier) {
val videos by subscriptionsViewModel.videos.observeAsState(initial = listOf())
val isLoading: Boolean by subscriptionsViewModel.isLoading.observeAsState(initial = false)
LaunchedEffect(Unit) {
if (videos.isEmpty()) {
subscriptionsViewModel.loadVideos()
}
}
Column (modifier) {
VideoList(
ArrayList(videos),
isLoading = isLoading,
onRefresh = {
if (!isLoading) {
subscriptionsViewModel.refresh()
}
},
onLoadMore = {
if (!isLoading) {
subscriptionsViewModel.loadVideos()
}
}
)
}
}

View File

@ -0,0 +1,83 @@
package org.libre.agosto.p2play.ui.views
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import org.libre.agosto.p2play.R
import org.libre.agosto.p2play.viewModels.VideosViewModel
import org.libre.agosto.p2play.ui.lists.VideoList
import org.libre.agosto.p2play.ui.components.molecules.ChipSelector
import org.libre.agosto.p2play.ui.components.molecules.ChipValues
import org.libre.agosto.p2play.domain.data.VideoFilterEnum
import java.util.ArrayList
@SuppressLint("MutableCollectionMutableState")
@Composable
fun VideosView (videosViewModel: VideosViewModel,modifier: Modifier = Modifier) {
val chips = arrayListOf(
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_trending)
override val value = VideoFilterEnum.TRENDING
override val icon = ImageVector.vectorResource(R.drawable.ic_trending_up_black_24dp)
},
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_likes)
override val value = VideoFilterEnum.LIKES
override val icon = ImageVector.vectorResource(R.drawable.ic_thumb_up_black_24dp)
},
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_popular)
override val value = VideoFilterEnum.POPULAR
override val icon = Icons.Filled.Star
},
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_recent)
override val value = VideoFilterEnum.RECENT
override val icon = ImageVector.vectorResource(R.drawable.ic_add_circle_black_24dp)
},
object : ChipValues<VideoFilterEnum> {
override val text = stringResource(R.string.nav_local)
override val value = VideoFilterEnum.LOCAL
override val icon = ImageVector.vectorResource(R.drawable.ic_home_black_24dp)
}
)
val videoFilter by videosViewModel.videoFilter.observeAsState(initial = VideoFilterEnum.TRENDING)
val videos by videosViewModel.videos.observeAsState(initial = listOf())
val isLoading: Boolean by videosViewModel.isLoading.observeAsState(initial = false)
LaunchedEffect(Unit) {
if (videos.isEmpty()) {
videosViewModel.loadVideos()
}
}
Column (modifier) {
VideoList(
ArrayList(videos),
header = {
ChipSelector(chips, videoFilter) {
videosViewModel.changeCategory(it)
}
},
isLoading = isLoading,
onRefresh = {
videosViewModel.refresh()
},
onLoadMore = {
if (!isLoading) {
videosViewModel.loadVideos()
}
}
)
}
}

View File

@ -0,0 +1,44 @@
package org.libre.agosto.p2play.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.libre.agosto.p2play.ManagerSingleton
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.models.VideoModel
class SubscriptionsViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val client = Videos()
private val _videos = MutableLiveData<List<VideoModel>>()
val videos: LiveData<List<VideoModel>> = _videos
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
fun loadVideos() {
_isLoading.value = true
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
client.videoSubscriptions(ManagerSingleton.token.token, videos.value?.size ?: 0)
}
val data = if (videos.value !== null)
ArrayList(videos.value!!)
else
ArrayList()
data.addAll(result)
_videos.postValue(data)
_isLoading.value = false
}
}
fun refresh() {
_videos.value = arrayListOf()
loadVideos()
}
}

View File

@ -0,0 +1,76 @@
package org.libre.agosto.p2play.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.libre.agosto.p2play.ajax.Videos
import org.libre.agosto.p2play.domain.data.VideoFilterEnum
import org.libre.agosto.p2play.models.VideoModel
class VideosViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val client = Videos()
private val _videos = MutableLiveData<List<VideoModel>>()
val videos: LiveData<List<VideoModel>> = _videos
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
private val _videoFilter = MutableLiveData<VideoFilterEnum>()
val videoFilter: LiveData<VideoFilterEnum> = _videoFilter
fun loadVideos() {
_isLoading.value = true
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
getVideoResource()
}
val data = if (videos.value !== null)
ArrayList(videos.value!!)
else
ArrayList()
data.addAll(result)
_videos.postValue(data)
_isLoading.value = false
}
}
fun changeCategory(category: VideoFilterEnum) {
_videoFilter.value = category
refresh()
}
fun refresh() {
_videos.value = arrayListOf()
loadVideos()
}
private fun getVideoResource(): ArrayList<VideoModel> {
val skip = videos.value?.size ?: 0
return when (videoFilter.value) {
VideoFilterEnum.TRENDING -> {
return client.getTrendingVideos(skip)
}
VideoFilterEnum.POPULAR -> {
client.getPopularVideos(skip)
}
VideoFilterEnum.LIKES -> {
client.getMostLikedVideos(skip)
}
VideoFilterEnum.RECENT -> {
client.getLastVideos(skip)
}
VideoFilterEnum.LOCAL -> {
client.getLocalVideos(skip)
}
else -> {
client.getTrendingVideos(skip)
}
}
}
}

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#88ffffff" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View File

@ -1,4 +1,4 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#fff" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="48dp" android:tint="#fff" android:viewportHeight="24" android:viewportWidth="24" android:width="48dp">
<path android:fillColor="@android:color/white" android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M260,800Q169,800 104.5,737Q40,674 40,583Q40,505 87,444Q134,383 210,366Q227,294 295,229Q363,164 440,164Q473,164 496.5,187.5Q520,211 520,244L520,486L584,424L640,480L480,640L320,480L376,424L440,486L440,244Q364,258 322,317.5Q280,377 280,440L260,440Q202,440 161,481Q120,522 120,580Q120,638 161,679Q202,720 260,720L740,720Q782,720 811,691Q840,662 840,620Q840,578 811,549Q782,520 740,520L680,520L680,440Q680,392 658,350.5Q636,309 600,280L600,187Q674,222 717,290.5Q760,359 760,440L760,440L760,440Q829,448 874.5,499.5Q920,551 920,620Q920,695 867.5,747.5Q815,800 740,800L260,800ZM480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442L480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442Q480,442 480,442L480,442L480,442Q480,442 480,442Q480,442 480,442Z"/>
</vector>

View File

@ -0,0 +1,389 @@
<?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="match_parent"
tools:context=".ReproductorActivity">
<RelativeLayout
android:id="@+id/fullScreen"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout
android:id="@+id/fullScreenExo"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<androidx.media3.ui.PlayerView
android:id="@+id/fullscreenPlayer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
app:use_controller="true"
app:show_buffering="always"
app:controller_layout_id="@layout/custom_player_controls"
app:player_layout_id="@layout/exo_player_view"/>
</RelativeLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/nonFullScreen"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<WebView
android:id="@+id/videoView"
android:layout_width="match_parent"
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="500dp"
app:show_buffering="always"
app:use_controller="true"
app:controller_layout_id="@layout/custom_player_controls"/>
<TextView
android:id="@+id/tittleVideoTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="5dp"
android:textAppearance="@android:style/TextAppearance.Material.Display1"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/viewsTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textSize="12sp" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/actionsLayout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="top"
android:orientation="horizontal"
android:visibility="gone">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/likeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="false"
android:contentDescription="@string/likeBtn"
android:cropToPadding="false"
android:scaleType="center"
android:visibility="visible"
app:srcCompat="@drawable/ic_like" />
<TextView
android:id="@+id/textViewLike"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/likeBtn"
android:textAlignment="center" />
</androidx.appcompat.widget.LinearLayoutCompat>
<LinearLayout
android:id="@+id/dislikeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:id="@+id/imageView2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="false"
android:contentDescription="@string/dislikeBtn"
android:cropToPadding="false"
android:visibility="visible"
app:srcCompat="@drawable/ic_dislike" />
<TextView
android:id="@+id/textViewDislike"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/dislikeBtn"
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:id="@+id/downloadLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:id="@+id/downloadImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="false"
android:contentDescription="@string/reportBtn"
android:cropToPadding="false"
android:visibility="visible"
app:srcCompat="@drawable/ic_outline_cloud_download_24"
app:tint="#585858" />
<TextView
android:id="@+id/downloadText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/downloadText"
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:id="@+id/reportLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:id="@+id/imageViewAlert"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="false"
android:contentDescription="@string/dislikeBtn"
android:cropToPadding="false"
android:visibility="visible"
app:srcCompat="@drawable/ic_alert" />
<TextView
android:id="@+id/textViewAlert"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/reportBtn"
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:id="@+id/shareLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:id="@+id/imageViewShare"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="false"
android:contentDescription="@string/shareBtn"
android:cropToPadding="false"
android:visibility="visible"
app:srcCompat="@drawable/ic_share" />
<TextView
android:id="@+id/textViewShare"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/shareBtn"
android:textAlignment="center" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center|center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/userImg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:layout_weight="1"
android:adjustViewBounds="true"
android:cropToPadding="false"
android:padding="5dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/default_avatar" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:orientation="vertical">
<TextView
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" />
<TextView
android:id="@+id/hostTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="10sp"
android:textStyle="italic" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/subscribeBtn"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/subscribeBtn"
android:textSize="12sp"
android:visibility="invisible" />
</LinearLayout>
<TextView
android:id="@+id/descriptionTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLength="1000"
android:maxLines="100"
android:paddingLeft="5dp"
android:text="@string/descriptionTxt"
android:textStyle="bold" />
<TextView
android:id="@+id/descriptionVideoTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:autoLink="web"
android:linksClickable="true"
android:maxLength="10000"
android:maxLines="100"
android:paddingLeft="10dp"
android:paddingRight="10dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/showMoreBtn"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/showMore"
android:visibility="gone" />
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="20dp" />
</LinearLayout>
<androidx.constraintlayout.widget.Barrier
android:layout_width="match_parent"
android:layout_height="match_parent"
app:barrierDirection="left" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_weight="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
android:id="@+id/commentBox"
layout="@layout/comment_component" />
<TextView
android:id="@+id/commentariesTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:text="@string/commentariesTxt"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listCommentaries"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 0dp dimensions are used to prevent this view from influencing the size of
the parent view if it uses "wrap_content". It is expanded to occupy the
entirety of the parent in code, after the parent's size has been
determined. See: https://github.com/google/ExoPlayer/issues/8726.
-->
<View android:id="@id/exo_controls_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/exo_black_opacity_60"/>
<FrameLayout
android:id="@id/exo_bottom_bar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
android:background="@color/exo_bottom_bar_background"
android:layoutDirection="ltr">
<LinearLayout
android:id="@id/exo_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layoutDirection="ltr"
android:paddingStart="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingLeft="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingEnd="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingRight="@dimen/exo_styled_bottom_bar_time_padding">
<TextView
android:id="@id/exo_position"
style="@style/ExoStyledControls.TimeText.Position" />
<TextView
style="@style/ExoStyledControls.TimeText.Separator"
android:text="/" />
<TextView
android:id="@id/exo_duration"
style="@style/ExoStyledControls.TimeText.Duration" />
</LinearLayout>
<LinearLayout
android:id="@id/exo_basic_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layoutDirection="ltr">
<ImageButton
android:id="@id/exo_subtitle"
style="@style/ExoStyledControls.Button.Bottom.CC" />
<ImageButton
android:id="@id/exo_settings"
style="@style/ExoStyledControls.Button.Bottom.Settings" />
<ImageButton
android:id="@+id/exo_fullscreen_custom"
style="@style/ExoStyledControls.Button.Bottom.Settings"
android:src="@drawable/ic_fullscreen_24"
android:visibility="visible" />
</LinearLayout>
</FrameLayout>
<View android:id="@id/exo_progress_placeholder"
android:layout_width="match_parent"
android:layout_height="@dimen/exo_styled_progress_layout_height"
android:layout_gravity="bottom"
android:layout_marginBottom="@dimen/exo_styled_progress_margin_bottom"/>
<LinearLayout
android:id="@id/exo_center_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:gravity="center"
android:padding="@dimen/exo_styled_controls_padding"
android:clipToPadding="false"
android:layoutDirection="ltr">
<include layout="@layout/exo_player_control_rewind_button" />
<ImageButton android:id="@id/exo_play_pause"
style="@style/ExoStyledControls.Button.Center.PlayPause"/>
<include layout="@layout/exo_player_control_ffwd_button" />
</LinearLayout>
</merge>

View File

@ -0,0 +1,53 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#44000000">
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="800dp"
android:layout_height="0dp"
android:background="?attr/colorSurface"
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
android:id="@+id/commentThread"
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="wrap_content"
android:layout_marginStart="10dp" />
</LinearLayout>
<include
android:id="@+id/commentBox"
layout="@layout/comment_component"
android:layout_width="700dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
app:layout_collapseMode="pin" />
<ImageView
android:id="@+id/backgroundImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
app:layout_collapseMode="pin"
android:orientation="vertical">
<ImageView
android:id="@+id/profileImage"
android:layout_width="80dp"
android:layout_height="80dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/default_avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -6,9 +6,9 @@
android:layout_height="wrap_content"
tools:context=".ChannelActivity">
<ScrollView
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -56,8 +56,8 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false">
android:baselineAligned="false"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
@ -147,7 +147,7 @@
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,97 +6,92 @@
android:layout_height="match_parent"
tools:context=".LoginActivity">
<FrameLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="32dp"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
app:layout_constraintWidth_max="500dp">
<LinearLayout
<ImageView
android:id="@+id/imageView2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="wrap_content"
android:contentDescription="Logo"
app:srcCompat="@drawable/icon" />
<ImageView
android:id="@+id/imageView2"
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="30dp" />
<TextView
android:id="@+id/loginInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/loginInfo"
android:textAlignment="center"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium" />
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="30dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/userTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/userTxt"
android:maxWidth="600dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/userText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="Logo"
app:srcCompat="@drawable/icon" />
android:ems="10"
android:hint="@string/userText"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="30dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/passwordTxt"
android:maxWidth="600dp"
android:maxEms="10"
app:endIconMode="password_toggle">
<TextView
android:id="@+id/loginInfo"
<EditText
android:id="@+id/passwordText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/loginInfo"
android:textAlignment="center"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"/>
android:ems="10"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="30dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/loginBtn"
android:textAlignment="center" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/userTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:hint="@string/userTxt">
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="30dp" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/userText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/userText"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/registerActionBtn"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/registerActionBtn"
android:visibility="invisible" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
app:endIconMode="password_toggle"
android:hint="@string/passwordTxt" >
</LinearLayout>
<EditText
android:id="@+id/passwordText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/loginBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/loginBtn"
android:textAlignment="center" />
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="30dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/registerActionBtn"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/registerActionBtn"
android:visibility="invisible" />
</LinearLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,10 +8,27 @@
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="@layout/app_bar_main"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="@+id/content"
layout="@layout/content_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -29,7 +29,7 @@
android:contentDescription="Logo"
app:srcCompat="@drawable/icon" />
<Space
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="30dp" />
@ -42,7 +42,7 @@
android:textAppearance="@android:style/TextAppearance.Material.Medium.Inverse"
android:textColor="@android:color/black" />
<Space
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="30dp" />
@ -99,4 +99,4 @@
</LinearLayout>
</ScrollView>
</android.support.constraint.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -39,15 +39,16 @@
android:layout_height="match_parent"
android:background="#000"
app:use_controller="true"
app:show_buffering="always"
app:controller_layout_id="@layout/custom_player_controls"
app:player_layout_id="@layout/exo_player_view"/>
</RelativeLayout>
<ScrollView
<androidx.core.widget.NestedScrollView
android:id="@+id/nonFullScreen"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -73,9 +74,9 @@
android:id="@+id/exoPlayer"
android:layout_width="match_parent"
android:layout_height="205dp"
app:show_buffering="always"
app:use_controller="true"
app:controller_layout_id="@layout/custom_player_controls"
app:player_layout_id="@layout/exo_player_view"/>
app:controller_layout_id="@layout/custom_player_controls" />
<TextView
android:id="@+id/tittleVideoTxt"
@ -159,6 +160,35 @@
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:id="@+id/downloadLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="visible">
<ImageView
android:id="@+id/downloadImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="false"
android:contentDescription="@string/reportBtn"
android:cropToPadding="false"
android:visibility="visible"
app:srcCompat="@drawable/ic_outline_cloud_download_24"
app:tint="#585858" />
<TextView
android:id="@+id/downloadText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/downloadText"
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:id="@+id/reportLayout"
android:layout_width="match_parent"
@ -173,7 +203,7 @@
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="false"
android:contentDescription="@string/dislikeBtn"
android:contentDescription="@string/reportBtn"
android:cropToPadding="false"
android:visibility="visible"
app:srcCompat="@drawable/ic_alert" />
@ -187,6 +217,7 @@
android:textAlignment="center" />
</LinearLayout>
<LinearLayout
android:id="@+id/shareLayout"
android:layout_width="match_parent"
@ -325,7 +356,9 @@
android:layout_width="match_parent"
android:layout_height="20dp" />
<include layout="@layout/comment_component" />
<include
android:id="@+id/commentBox"
layout="@layout/comment_component" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listCommentaries"
@ -337,6 +370,6 @@
</LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,17 +6,6 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -16,7 +16,7 @@
android:visibility="visible"
app:layout_constraintTop_toTopOf="parent">
<view class="androidx.recyclerview.widget.RecyclerView"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -37,4 +37,4 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,99 +1,113 @@
<?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="#aa000000"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Copyright 2020 The Android Open Source Project
<LinearLayout
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 0dp dimensions are used to prevent this view from influencing the size of
the parent view if it uses "wrap_content". It is expanded to occupy the
entirety of the parent in code, after the parent's size has been
determined. See: https://github.com/google/ExoPlayer/issues/8726.
-->
<View android:id="@id/exo_controls_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/exo_black_opacity_60"/>
<FrameLayout
android:id="@id/exo_bottom_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:id="@+id/exo_rew"
android:src="@drawable/ic_replay_10_24"
/>
<ImageView
android:id="@+id/exo_play_pause"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginHorizontal="30dp"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:id="@+id/exo_ffwd"
android:src="@drawable/ic_forward_10_24"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
android:layout_height="50dp"
android:layout_gravity="bottom"
android:layout_marginTop="@dimen/exo_styled_bottom_bar_margin_top"
android:background="@color/exo_bottom_bar_background"
android:layoutDirection="ltr">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="20dp"
android:orientation="horizontal">
android:id="@id/exo_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layoutDirection="ltr"
android:paddingStart="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingLeft="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingEnd="@dimen/exo_styled_bottom_bar_time_padding"
android:paddingRight="@dimen/exo_styled_bottom_bar_time_padding">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/exo_position"
android:textColor="#fff"/>
android:id="@id/exo_position"
style="@style/ExoStyledControls.TimeText.Position" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/"
android:textColor="#CBCDC8"
android:layout_marginHorizontal="4dp"/>
style="@style/ExoStyledControls.TimeText.Separator"
android:text="/" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/exo_duration"
android:textColor="#CBCDC8"/>
android:id="@id/exo_duration"
style="@style/ExoStyledControls.TimeText.Duration" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
android:id="@id/exo_basic_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layoutDirection="ltr">
<androidx.media3.ui.DefaultTimeBar
android:id="@+id/exo_progress"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="2" />
<ImageButton
android:id="@id/exo_subtitle"
style="@style/ExoStyledControls.Button.Bottom.CC" />
<ImageView
<ImageButton
android:id="@id/exo_settings"
style="@style/ExoStyledControls.Button.Bottom.Settings" />
<ImageButton
android:id="@+id/exo_fullscreen_custom"
android:layout_width="42dp"
android:layout_height="42dp"
style="@style/ExoStyledControls.Button.Bottom.Settings"
android:src="@drawable/ic_fullscreen_24"
android:layout_weight="0"
android:layout_gravity="center"/>
android:visibility="visible" />
</LinearLayout>
</FrameLayout>
<View android:id="@id/exo_progress_placeholder"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom"
android:layout_marginBottom="45dp"/>
<LinearLayout
android:id="@id/exo_center_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:gravity="center"
android:padding="@dimen/exo_styled_controls_padding"
android:clipToPadding="false"
android:layoutDirection="ltr">
<include layout="@layout/exo_player_control_rewind_button" />
<ImageButton android:id="@id/exo_play_pause"
style="@style/ExoStyledControls.Button.Center.PlayPause"/>
<include layout="@layout/exo_player_control_ffwd_button" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>

View File

@ -1,7 +1,6 @@
<?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">
@ -24,6 +23,7 @@
android:minHeight="?attr/actionBarSize" />
<include
android:id="@+id/commentThread"
layout="@layout/view_commentary"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
@ -42,6 +42,7 @@
</LinearLayout>
<include
android:id="@+id/commentBox"
layout="@layout/comment_component"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.AccountInfoFragment">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/accountTitle"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/accountName" />
<TextView
android:id="@+id/account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="" />
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="8dp" />
<TextView
android:id="@+id/hostTitle"
style="@style/TextAppearance.MaterialComponents.Body2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hostIndicator" />
<TextView
android:id="@+id/host"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="" />
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="8dp" />
<TextView
android:id="@+id/descriptionTitle"
style="@style/TextAppearance.MaterialComponents.Body2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/descriptionTxt" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="" />
<androidx.legacy.widget.Space
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.AccountVideosFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/videosList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.AccountChannelsFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/channelList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@ -0,0 +1,53 @@
<?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">
<ImageView
android:id="@+id/channelImage"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:srcCompat="@drawable/default_avatar" />
<TextView
android:id="@+id/channelName"
android:layout_width="200dp"
android:layout_height="26dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="TextView"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
app:layout_constraintStart_toEndOf="@+id/channelImage"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/channelDescription"
android:layout_width="198dp"
android:layout_height="17dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="TextView"
app:layout_constraintStart_toEndOf="@+id/channelImage"
app:layout_constraintTop_toBottomOf="@+id/channelName" />
<Button
android:id="@+id/subscribeBtn"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/subscribeBtn"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,7 +2,7 @@
<resources>
<!-- Start Global strings -->
<string name="comming">Proximamente</string>
<string name="charging">Cargando...</string>
<string name="charging">Cargando</string>
<!-- End Global strings -->
<!-- Start About strings -->

View File

@ -0,0 +1,124 @@
<resources>
<!-- Start Global string -->
<string name="comming">Bientôt disponible !</string>
<string name="charging">Chargement…</string>
<!-- End Global string -->
<!-- Start About strings -->
<string name="aboutLabel">À propos de P2Play</string>
<string name="aboutText">P2Play est une application Android non officielle pour PeerTube. Vous pouvez regarder et contribuer avec le code sur GitLab :</string>
<string name="aboutStatus">Vous pouvez suivre notre blog :</string>
<string name="aboutLicense">Licence GNU GPLv3</string>
<string name="aboutInstance">À propos de l\'instance</string>
<string name="aboutInWeb">Vous pouvez voir les termes et plus sur le web :</string>
<!-- End About strings -->
<!-- Start Host strings -->
<string name="hostInfoText">Sélectionnez votre instance</string>
<string name="okButton">Accepter</string>
<string name="errorMsg">Erreur, réessayez</string>
<string name="finallyMsg">Hôte enregistré</string>
<string name="instance">Instance</string>
<!-- End Host strings -->
<!-- Start Login strings -->
<string name="loginInfo">Connectez-vous ou créez un nouveau compte</string>
<string name="userTxt">Nom d\'utilisateur :</string>
<string name="userText" translatable="false"> </string>
<string name="passwordTxt">Mot de passe :</string>
<string name="passwordText" translatable="false"> </string>
<string name="loginBtn">Connectez-vous maintenant</string>
<string name="registerActionBtn">Créer un nouveau compte</string>
<string name="twoFactorLabel">Code à deux facteurs</string>
<!-- Toast msg -->
<string name="loginSuccess_msg">Vous êtes maintenant connecté</string>
<string name="loginError_msg">Une erreur est survenue</string>
<string name="loginFailed_msg">Identifiants invalides</string>
<string name="registerSuccess_msg">Vous êtes maintenant enregistré</string>
<string name="registerError_msg">Une erreur est survenue</string>
<string name="registerFailed_msg">Données invalides</string>
<!-- Register msg -->
<string name="registerBtn">Inscrivez-vous maintenant</string>
<string name="emailTxt">E-mail :</string>
<string name="emailText" translatable="false">user@mail.com</string>
<!-- End Login strings -->
<!-- Start Main strings -->
<string name="title_subscriptions">Abonnements</string>
<string name="title_recent">Récent</string>
<string name="title_popular">Populaire</string>
<string name="title_trending">Tendance</string>
<string name="title_local">Vidéos locales</string>
<string name="title_myVideos">Mes vidéos</string>
<string name="view_text">vues</string>
<string name="timeSec_text">secondes</string>
<string name="timeMin_text">minutes</string>
<string name="timeHrs_text">heures</string>
<string name="nav_header_title">Se connecter</string>
<string name="is_live_video">En direct</string>
<!-- Toast msg -->
<string name="logout_msg">Vous êtes maintenant déconnecté</string>
<!-- End Main strings -->
<!-- Start Menu strings -->
<string name="nav_subscriptions">Abonnements</string>
<string name="nav_popular">Populaire</string>
<string name="nav_trending">Tendance</string>
<string name="nav_recent">Récent</string>
<string name="nav_local">Local</string>
<string name="nav_about">À propos</string>
<string name="nav_history">Historique</string>
<string name="nav_menu_myLibrary">Ma bibliothèque</string>
<string name="nav_menu_videos">Vidéos</string>
<string name="nav_menu_more">Plus</string>
<string name="nav_likes">Les plus aimés</string>
<!-- End Menu strings -->
<!-- Start MiniMenu strings -->
<string name="action_settings">Paramètres</string>
<string name="action_login">Se connecter</string>
<string name="action_logout">Déconnexion</string>
<!-- End MiniMenu strings -->
<!-- Start Reproductor strings -->
<string name="descriptionTxt">Description :</string>
<string name="commentariesTxt">Commentaires :</string>
<string name="commentHolder">Faire un commentaire</string>
<!-- Actions -->
<string name="subscribeBtn">S\'abonner</string>
<string name="likeBtn">J\'aime</string>
<string name="dislikeBtn">Je n\'aime pas</string>
<string name="shareBtn">Partager</string>
<string name="reportBtn">Signaler</string>
<string name="unSubscribeBtn">Se désabonner</string>
<string name="commentaryText">Commentaire</string>
<string name="showMore">Afficher plus</string>
<!-- Comments -->
<string name="reply">Répondre</string>
<string name="see_replies">Voir les réponses (%1$d)</string>
<!-- Messages -->
<string name="subscribeMsg">Vous êtes abonné à cette chaîne</string>
<string name="rateMsg">Vous avez évalué la vidéo</string>
<string name="unSubscribeMsg">Vous vous êtes désabonné de cette chaîne</string>
<string name="makedCommentaryMsg">Vous avez commenté cette vidéo</string>
<string name="errorCommentaryMsg">Une erreur est survenue, réessayez</string>
<string name="emptyCommentaryMsg">Veuillez d\'abord faire un commentaire</string>
<!-- End Reproductor strings -->
<!-- Start Settings strings -->
<string name="title_activity_settings">Paramètres</string>
<!-- Example General settings -->
<string name="pref_header_general">Général</string>
<string name="pref_nfsw_title">Contenu NFSW</string>
<string name="pref_nfsw_description">Quand activé, cela peut afficher du contenu adulte et sensible.</string>
<string name="pref_hostname_title">Instance Peertube</string>
<string name="pref_hostname_error" translatable="false">-</string>
<string name="pref_message_exit">Redémarrez l\'application pour appliquer les changements</string>
<string name="pref_videos_count_title">Vidéos par page</string>
<!-- End Settings strings -->
<!-- Start Channel/Account strings -->
<string name="followersIndicator">Abonnés :</string>
<string name="hostIndicator">Hôte :</string>
<string name="accountName">Nom du compte :</string>
<!-- End Channel strings -->
<!-- Start Prompt string -->
<string name="reportDialog">Raison pour signaler cette vidéo :</string>
<string name="reportDialogMsg">Vous avez signalé la vidéo</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Bonjour fragment vide</string>
<!-- End Prompt strings -->
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="comming">In arrivo!</string>
<string name="charging">Caricamento...</string>
<string name="charging">Caricamento</string>
<string name="aboutLabel">Informazioni su P2Play</string>
<string name="aboutText">P2Play è un\'applicazione Android non ufficiale per PeerTube. Puoi visualizzare e contribuire al codice su GitLab:</string>
<string name="aboutStatus">Puoi seguire il nostro blog:</string>
@ -79,5 +79,4 @@
<string name="hostIndicator">Host:</string>
<string name="reportDialog">Motivo per la segnalazione di questo video:</string>
<string name="reportDialogMsg">Hai segnalato il video</string>
</resources>

View File

@ -105,6 +105,9 @@
<string name="makedCommentaryMsg">You have commented on this video</string>
<string name="errorCommentaryMsg">An error has occurred, try again</string>
<string name="emptyCommentaryMsg">Please make a comment first</string>
<string name="downloadText">Download</string>
<string name="downloadStarted">Download as started</string>
<string name="downloadFailed">Unable to download this video</string>
<!-- End Reproductor strings -->
<!-- Start Settings strings -->
<string name="title_activity_settings">Settings</string>
@ -117,12 +120,15 @@
<string name="pref_message_exit">Restart the app to apply changes</string>
<string name="pref_videos_count_title">Videos per page</string>
<!-- End Settings strings -->
<!-- Start Channel strings -->
<!-- Start Channel/Account strings -->
<string name="followersIndicator">Followers:</string>
<string name="hostIndicator">Host:</string>
<string name="accountName">Account Name:</string>
<!-- End Channel strings -->
<!-- Start Prompt string -->
<string name="reportDialog">Reason to report this video:</string>
<string name="reportDialogMsg">You reported the video</string>
<!-- End Prompt strings -->
<string name="playlists">Playlists</string>
<string name="you">You</string>
</resources>

View File

@ -1,5 +1,5 @@
<resources>
<style name="Theme.P2play" parent="Theme.MaterialComponents.DayNight">
<item name="colorPrimary">@color/md_theme_light_primary</item>
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
@ -22,4 +22,8 @@
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.P2play.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.P2play.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

View File

@ -1,29 +0,0 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- NOTE: EditTextPreference accepts EditText attributes. -->
<!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
<EditTextPreference
android:capitalize="words"
android:defaultValue="@string/pref_hostname_error"
android:inputType="text"
android:key="hostP2play"
android:maxLines="1"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_hostname_title" />
<SwitchPreference
android:defaultValue="false"
android:key="show_nfsw"
android:summary="@string/pref_nfsw_description"
android:title="@string/pref_nfsw_title" />
<EditTextPreference
android:defaultValue="15"
android:inputType="number"
android:key="videos_count"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_videos_count_title" />
</PreferenceScreen>

View File

@ -1,23 +0,0 @@
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- These settings headers are only used on tablets. -->
<header
android:fragment="org.libre.agosto.p2play.SettingsActivity$GeneralPreferenceFragment"
android:icon="@drawable/ic_info_black_24dp"
android:title="@string/pref_header_general"
android:textColor="@color/md_theme_light_secondary"/>
<!-- <header
android:fragment="org.libre.agosto.p2play.SettingsActivity$NotificationPreferenceFragment"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/pref_header_notifications" />
<header
android:fragment="org.libre.agosto.p2play.SettingsActivity$DataSyncPreferenceFragment"
android:icon="@drawable/ic_sync_black_24dp"
android:title="@string/pref_header_data_sync" /> -->
</preference-headers>

View File

@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.6.21'
ext.kotlin_version = '1.9.20'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.2'
classpath 'com.android.tools.build:gradle:8.8.0'
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
@ -21,6 +21,6 @@ allprojects {
}
}
task clean(type: Delete) {
tasks.register('clean', Delete) {
delete rootProject.buildDir
}

View File

@ -1,6 +1,6 @@
#Mon Mar 18 13:17:32 CST 2024
#Fri Jan 24 15:00:50 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists