Compare commits
55 Commits
0.1-alpha3
...
0.2.1
Author | SHA1 | Date | |
---|---|---|---|
77e657d37c
|
|||
20407d9cac
|
|||
dbd4b26a65
|
|||
ac5aee20de
|
|||
32844223fc
|
|||
d01e87bf14
|
|||
bb8c8ca85a
|
|||
3ed55ca3c9
|
|||
dfaf359952
|
|||
78d9f3cfa5
|
|||
db5758edf9
|
|||
2de1419d36
|
|||
7df99ea0cc
|
|||
8d1c3d9a3f
|
|||
c0c5cb9110
|
|||
21b6e358e7
|
|||
0e5c697bce
|
|||
830f7e753b
|
|||
71079ddc92
|
|||
57897077ab
|
|||
dcd6ebccea
|
|||
91c9b6d716
|
|||
256c32aa3c
|
|||
3880b3ab75
|
|||
0f0573e5bd
|
|||
6ce263832b
|
|||
fd099e97e6
|
|||
d4fa726f9c
|
|||
c8d80ddc9f
|
|||
14377c3f18
|
|||
23713fc1e6
|
|||
353ae6937a
|
|||
2e0a114a80
|
|||
0e9500e39d
|
|||
27e8e1c3c2
|
|||
e51fb0b290
|
|||
d3f078c661
|
|||
6526b8868e | |||
1118c8339c
|
|||
1595ef52bc
|
|||
406434809f | |||
1523e0235a
|
|||
a51f4ca490 | |||
4ec5d0fdc4
|
|||
8a516c640d
|
|||
49430e10bf
|
|||
81b041ab61
|
|||
cf6a110455
|
|||
c138ab4587
|
|||
f0ed6aa379
|
|||
a5fffd5d02
|
|||
ff0727da22
|
|||
ce84cb57a8
|
|||
4c274eb062
|
|||
a25ec81f6b
|
15
README.md
@ -1,20 +1,19 @@
|
|||||||
# teapod
|
# teapod
|
||||||
|
|
||||||
A unoffical App for Anime-on-Demand.
|
A unofficial App for Anime-on-Demand.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* acces all media in the library
|
* acces all media in the library
|
||||||
* search the library
|
* search the library
|
||||||
* play movies/tv shows via integrated exoplayer
|
* play movies/tv shows via integrated exoplayer
|
||||||
* add movies/tv shows to you list, for easier access
|
* add movies/tv shows to "My List", for easier access
|
||||||
|
* prefer the OmU version via the app settings
|
||||||
### Missing Features
|
|
||||||
* a alternative/secondary stream is currently not supported (for dub titles the subtitle version is missing)
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
[<img src="https://www.mosad.xyz/images/Teapod/Teapod_Library.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Library.png)
|
[<img src="https://www.mosad.xyz/images/Teapod/Teapod_Home_200px.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Home.png)
|
||||||
[<img src="https://www.mosad.xyz/images/Teapod/Teapod_Media.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Media.png)
|
[<img src="https://www.mosad.xyz/images/Teapod/Teapod_Library_200px.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Library.png)
|
||||||
[<img src="https://www.mosad.xyz/images/Teapod/Teapod_Search.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Search.png)
|
[<img src="https://www.mosad.xyz/images/Teapod/Teapod_Media_200px.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Media.png)
|
||||||
|
[<img src="https://www.mosad.xyz/images/Teapod/Teapod_Search_200px.png" width=180>](https://www.mosad.xyz/images/Teapod/Teapod_Search.png)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
This App is licensed under the terms and conditions of GPL 3. This Project is not associated with Anime-on-Demand in any way.
|
This App is licensed under the terms and conditions of GPL 3. This Project is not associated with Anime-on-Demand in any way.
|
||||||
|
@ -10,17 +10,22 @@ android {
|
|||||||
applicationId "org.mosad.teapod"
|
applicationId "org.mosad.teapod"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 1
|
versionCode 2100 //00.02.100
|
||||||
versionName "0.1-alpha3"
|
versionName "0.2.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resValue "string", "build_time", buildTime()
|
resValue "string", "build_time", buildTime()
|
||||||
setProperty("archivesBaseName", "teapod-$versionName")
|
setProperty("archivesBaseName", "teapod-$versionName")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,18 +45,18 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.3.2'
|
implementation 'androidx.core:core-ktx:1.3.2'
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
|
||||||
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.3.0-alpha03'
|
implementation 'com.google.android.material:material:1.3.0-alpha04'
|
||||||
implementation 'com.google.code.gson:gson:2.8.6'
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.12.1'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-hls:2.12.0'
|
implementation 'com.google.android.exoplayer:exoplayer-hls:2.12.1'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0'
|
implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.1'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'
|
implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.1'
|
||||||
|
|
||||||
implementation 'org.jsoup:jsoup:1.13.1'
|
implementation 'org.jsoup:jsoup:1.13.1'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
|
10
app/proguard-rules.pro
vendored
@ -15,7 +15,17 @@
|
|||||||
# Uncomment this to preserve the line number information for
|
# Uncomment this to preserve the line number information for
|
||||||
# debugging stack traces.
|
# debugging stack traces.
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
-dontobfuscate
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
-keep class org.mosad.teapod.util.** { <fields>; }
|
||||||
|
|
||||||
|
#Gson
|
||||||
|
-keepattributes Signature
|
||||||
|
-dontwarn sun.misc.**
|
||||||
|
|
||||||
|
#misc
|
||||||
|
-dontwarn java.lang.instrument.ClassFileTransformer
|
||||||
|
-dontwarn java.lang.ClassValue
|
||||||
|
@ -10,21 +10,26 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme.Light">
|
||||||
<activity
|
<activity
|
||||||
android:name=".PlayerActivity"
|
android:name=".SplashActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:configChanges="orientation|screenSize|layoutDirection"
|
android:theme="@style/SplashTheme"
|
||||||
android:theme="@style/AppTheme.MaterialComponents.Light.NoActionBar.FullScreen" />
|
android:screenOrientation="portrait">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".player.PlayerActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/PlayerTheme"
|
||||||
|
android:configChanges="orientation|screenSize|layoutDirection" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="portrait">
|
android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
BIN
app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 13 KiB |
@ -30,32 +30,38 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.mosad.teapod.databinding.ActivityMainBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
|
import org.mosad.teapod.player.PlayerActivity
|
||||||
import org.mosad.teapod.preferences.EncryptedPreferences
|
import org.mosad.teapod.preferences.EncryptedPreferences
|
||||||
import org.mosad.teapod.ui.fragments.MediaFragment
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.ui.fragments.AccountFragment
|
|
||||||
import org.mosad.teapod.ui.components.LoginDialog
|
import org.mosad.teapod.ui.components.LoginDialog
|
||||||
import org.mosad.teapod.ui.fragments.HomeFragment
|
import org.mosad.teapod.ui.fragments.*
|
||||||
import org.mosad.teapod.ui.fragments.LibraryFragment
|
import org.mosad.teapod.util.DataTypes
|
||||||
import org.mosad.teapod.ui.fragments.SearchFragment
|
|
||||||
import org.mosad.teapod.ui.fragments.LoadingFragment
|
|
||||||
import org.mosad.teapod.util.StorageController
|
import org.mosad.teapod.util.StorageController
|
||||||
import org.mosad.teapod.util.TMDBApiController
|
import org.mosad.teapod.util.TMDBApiController
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
|
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
private var activeBaseFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
|
private var activeBaseFragment: Fragment = HomeFragment() // the currently active fragment, home at the start
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var wasInitialized = false
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
nav_view.setOnNavigationItemSelectedListener(this)
|
|
||||||
|
|
||||||
load()
|
if (!wasInitialized) { load() }
|
||||||
|
theme.applyStyle(getThemeResource(), true)
|
||||||
|
|
||||||
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
binding.navView.setOnNavigationItemSelectedListener(this)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
supportFragmentManager.commit {
|
supportFragmentManager.commit {
|
||||||
replace(R.id.nav_host_fragment, activeBaseFragment, activeBaseFragment.javaClass.simpleName)
|
replace(R.id.nav_host_fragment, activeBaseFragment, activeBaseFragment.javaClass.simpleName)
|
||||||
@ -67,7 +73,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
|||||||
supportFragmentManager.popBackStack()
|
supportFragmentManager.popBackStack()
|
||||||
} else {
|
} else {
|
||||||
if (activeBaseFragment !is HomeFragment) {
|
if (activeBaseFragment !is HomeFragment) {
|
||||||
nav_view.selectedItemId = R.id.navigation_home
|
binding.navView.selectedItemId = R.id.navigation_home
|
||||||
} else {
|
} else {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
@ -106,69 +112,40 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getThemeResource(): Int {
|
||||||
|
return when (Preferences.theme) {
|
||||||
|
DataTypes.Theme.DARK -> R.style.AppTheme_Dark
|
||||||
|
else -> R.style.AppTheme_Light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun load() {
|
private fun load() {
|
||||||
// running login and list in parallel does not bring any speed improvements
|
// running login and list in parallel does not bring any speed improvements
|
||||||
val time = measureTimeMillis {
|
val time = measureTimeMillis {
|
||||||
|
Preferences.load(this)
|
||||||
|
|
||||||
// make sure credentials are set
|
// make sure credentials are set
|
||||||
EncryptedPreferences.readCredentials(this)
|
EncryptedPreferences.readCredentials(this)
|
||||||
if (EncryptedPreferences.password.isEmpty()) {
|
if (EncryptedPreferences.password.isEmpty()) {
|
||||||
showLoginDialog(true)
|
showLoginDialog(true)
|
||||||
} else {
|
} else {
|
||||||
// try to login in, as most sites can only bee loaded once loged in
|
// try to login in, as most sites can only bee loaded once loged in
|
||||||
if (!AoDParser().login()) showLoginDialog(false)
|
if (!AoDParser.login()) showLoginDialog(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageController.load(this)
|
StorageController.load(this)
|
||||||
|
AoDParser.initialLoading()
|
||||||
|
|
||||||
// initially load all media
|
wasInitialized = true
|
||||||
AoDParser().listAnimes()
|
|
||||||
|
|
||||||
// TODO load home screen, can be parallel to listAnimes
|
|
||||||
}
|
}
|
||||||
Log.i(javaClass.name, "login and list in $time ms")
|
Log.i(javaClass.name, "login and list in $time ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the media fragment for the selected media.
|
|
||||||
* While loading show the loading fragment.
|
|
||||||
* The loading and media fragment are not stored in activeBaseFragment,
|
|
||||||
* as the don't replace a fragment but are added on top of one.
|
|
||||||
*/
|
|
||||||
fun showMediaFragment(mediaId: Int) = GlobalScope.launch {
|
|
||||||
val loadingFragment = LoadingFragment()
|
|
||||||
supportFragmentManager.commit {
|
|
||||||
add(R.id.nav_host_fragment, loadingFragment, "MediaFragment")
|
|
||||||
show(loadingFragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the streams for the selected media
|
|
||||||
val media = AoDParser().getMediaById(mediaId)
|
|
||||||
val tmdb = TMDBApiController().search(media.info.title, media.type)
|
|
||||||
|
|
||||||
val mediaFragment = MediaFragment(media, tmdb)
|
|
||||||
supportFragmentManager.commit {
|
|
||||||
add(R.id.nav_host_fragment, mediaFragment, "MediaFragment")
|
|
||||||
addToBackStack(null)
|
|
||||||
show(mediaFragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
supportFragmentManager.commit {
|
|
||||||
remove(loadingFragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startPlayer(streamUrl: String) {
|
|
||||||
val intent = Intent(this, PlayerActivity::class.java).apply {
|
|
||||||
putExtra(getString(R.string.intent_stream_url), streamUrl)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showLoginDialog(firstTry: Boolean) {
|
private fun showLoginDialog(firstTry: Boolean) {
|
||||||
LoginDialog(this, firstTry).positiveButton {
|
LoginDialog(this, firstTry).positiveButton {
|
||||||
EncryptedPreferences.saveCredentials(login, password, context)
|
EncryptedPreferences.saveCredentials(login, password, context)
|
||||||
|
|
||||||
if (!AoDParser().login()) {
|
if (!AoDParser.login()) {
|
||||||
showLoginDialog(false)
|
showLoginDialog(false)
|
||||||
Log.w(javaClass.name, "Login failed, please try again.")
|
Log.w(javaClass.name, "Login failed, please try again.")
|
||||||
}
|
}
|
||||||
@ -177,4 +154,38 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
|||||||
finish()
|
finish()
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the media fragment for the selected media.
|
||||||
|
* The media fragment is not stored in activeBaseFragment,
|
||||||
|
* as it doesn't replace a fragment but is added on top of one.
|
||||||
|
*/
|
||||||
|
fun showMediaFragment(mediaId: Int) = GlobalScope.launch {
|
||||||
|
val mediaFragment = MediaFragment(mediaId)
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
add(R.id.nav_host_fragment, mediaFragment, "MediaFragment")
|
||||||
|
addToBackStack(null)
|
||||||
|
show(mediaFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startPlayer(mediaId: Int, episodeId: Int) {
|
||||||
|
val intent = Intent(this, PlayerActivity::class.java).apply {
|
||||||
|
putExtra(getString(R.string.intent_media_id), mediaId)
|
||||||
|
putExtra(getString(R.string.intent_episode_id), episodeId)
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use custom restart instead of recreate(), since it has animations
|
||||||
|
*/
|
||||||
|
fun restart() {
|
||||||
|
val restartIntent = intent
|
||||||
|
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
|
||||||
|
finish()
|
||||||
|
startActivity(restartIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,153 +0,0 @@
|
|||||||
package org.mosad.teapod
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
|
||||||
import android.view.WindowInsets
|
|
||||||
import android.view.WindowInsetsController
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
|
||||||
import com.google.android.exoplayer2.MediaItem
|
|
||||||
import com.google.android.exoplayer2.Player
|
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
|
||||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
|
||||||
import com.google.android.exoplayer2.util.Util
|
|
||||||
import kotlinx.android.synthetic.main.activity_player.*
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
private lateinit var player: SimpleExoPlayer
|
|
||||||
private lateinit var dataSourceFactory: DataSource.Factory
|
|
||||||
|
|
||||||
private var streamUrl = ""
|
|
||||||
|
|
||||||
private var playWhenReady = true
|
|
||||||
private var currentWindow = 0
|
|
||||||
private var playbackPosition: Long = 0
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_player)
|
|
||||||
hideBars() // Initial hide the bars
|
|
||||||
|
|
||||||
savedInstanceState?.let {
|
|
||||||
currentWindow = it.getInt(getString(R.string.state_resume_window))
|
|
||||||
playbackPosition = it.getLong(getString(R.string.state_resume_position))
|
|
||||||
playWhenReady = it.getBoolean(getString(R.string.state_is_playing))
|
|
||||||
}
|
|
||||||
|
|
||||||
streamUrl = intent.getStringExtra(getString(R.string.intent_stream_url)).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
if (Util.SDK_INT > 23) {
|
|
||||||
initPlayer()
|
|
||||||
if (video_view != null) video_view.onResume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
if (Util.SDK_INT <= 23) {
|
|
||||||
initPlayer()
|
|
||||||
if (video_view != null) video_view.onResume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
if (Util.SDK_INT <= 23) {
|
|
||||||
if (video_view != null) video_view.onPause()
|
|
||||||
releasePlayer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
if (Util.SDK_INT > 23) {
|
|
||||||
if (video_view != null) video_view.onPause()
|
|
||||||
releasePlayer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
outState.putInt(getString(R.string.state_resume_window), currentWindow)
|
|
||||||
outState.putLong(getString(R.string.state_resume_position), playbackPosition)
|
|
||||||
outState.putBoolean(getString(R.string.state_is_playing), playWhenReady)
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initPlayer() {
|
|
||||||
if (streamUrl.isEmpty()) {
|
|
||||||
Log.e(javaClass.name, "No stream url was set.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
player = SimpleExoPlayer.Builder(this).build()
|
|
||||||
dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, "Teapod"))
|
|
||||||
|
|
||||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
|
|
||||||
.createMediaSource(MediaItem.fromUri(Uri.parse(streamUrl)))
|
|
||||||
|
|
||||||
player.playWhenReady = playWhenReady
|
|
||||||
player.setMediaSource(mediaSource)
|
|
||||||
player.seekTo(playbackPosition)
|
|
||||||
player.prepare()
|
|
||||||
|
|
||||||
|
|
||||||
player.addListener(object : Player.EventListener {
|
|
||||||
override fun onPlaybackStateChanged(state: Int) {
|
|
||||||
super.onPlaybackStateChanged(state)
|
|
||||||
|
|
||||||
loading.visibility = when (state) {
|
|
||||||
ExoPlayer.STATE_READY -> View.GONE
|
|
||||||
ExoPlayer.STATE_BUFFERING -> View.VISIBLE
|
|
||||||
else -> View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// when the player controls get hidden, hide the bars too
|
|
||||||
video_view.setControllerVisibilityListener {
|
|
||||||
if (it == View.GONE) hideBars()
|
|
||||||
}
|
|
||||||
|
|
||||||
video_view.player = player
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun releasePlayer(){
|
|
||||||
playbackPosition = player.currentPosition
|
|
||||||
currentWindow = player.currentWindowIndex
|
|
||||||
playWhenReady = player.playWhenReady
|
|
||||||
player.release()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hide the status and navigation bar
|
|
||||||
*/
|
|
||||||
private fun hideBars() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
window.setDecorFitsSystemWindows(false)
|
|
||||||
window.insetsController?.apply {
|
|
||||||
hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
|
||||||
systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
@Suppress("deprecation")
|
|
||||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
||||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
||||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
17
app/src/main/java/org/mosad/teapod/SplashActivity.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package org.mosad.teapod
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
|
||||||
|
class SplashActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Teapod
|
||||||
|
*
|
||||||
|
* Copyright 2020 <seil0@mosad.xyz>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
package org.mosad.teapod.parser
|
package org.mosad.teapod.parser
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -6,32 +28,26 @@ import kotlinx.coroutines.*
|
|||||||
import org.jsoup.Connection
|
import org.jsoup.Connection
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.mosad.teapod.preferences.EncryptedPreferences
|
import org.mosad.teapod.preferences.EncryptedPreferences
|
||||||
|
import org.mosad.teapod.util.*
|
||||||
import org.mosad.teapod.util.DataTypes.MediaType
|
import org.mosad.teapod.util.DataTypes.MediaType
|
||||||
import org.mosad.teapod.util.Episode
|
|
||||||
import org.mosad.teapod.util.ItemMedia
|
|
||||||
import org.mosad.teapod.util.Media
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
object AoDParser {
|
||||||
* maybe AoDParser as object would be useful
|
|
||||||
*/
|
|
||||||
class AoDParser {
|
|
||||||
|
|
||||||
private val baseUrl = "https://www.anime-on-demand.de"
|
private const val baseUrl = "https://www.anime-on-demand.de"
|
||||||
private val loginPath = "/users/sign_in"
|
private const val loginPath = "/users/sign_in"
|
||||||
private val libraryPath = "/animes"
|
private const val libraryPath = "/animes"
|
||||||
|
|
||||||
private val userAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0"
|
private const val userAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0"
|
||||||
|
|
||||||
companion object {
|
|
||||||
private var csrfToken: String = ""
|
|
||||||
private var sessionCookies = mutableMapOf<String, String>()
|
private var sessionCookies = mutableMapOf<String, String>()
|
||||||
|
private var csrfToken: String = ""
|
||||||
private var loginSuccess = false
|
private var loginSuccess = false
|
||||||
|
|
||||||
val mediaList = arrayListOf<Media>()
|
private val mediaList = arrayListOf<Media>()
|
||||||
val itemMediaList = arrayListOf<ItemMedia>()
|
val itemMediaList = arrayListOf<ItemMedia>()
|
||||||
}
|
val newEpisodesList = arrayListOf<ItemMedia>()
|
||||||
|
|
||||||
fun login(): Boolean = runBlocking {
|
fun login(): Boolean = runBlocking {
|
||||||
|
|
||||||
@ -57,6 +73,7 @@ class AoDParser {
|
|||||||
|
|
||||||
val resLogin = Jsoup.connect(baseUrl + loginPath)
|
val resLogin = Jsoup.connect(baseUrl + loginPath)
|
||||||
.method(Connection.Method.POST)
|
.method(Connection.Method.POST)
|
||||||
|
.timeout(60000) // login can take some time
|
||||||
.data(data)
|
.data(data)
|
||||||
.postDataCharset("UTF-8")
|
.postDataCharset("UTF-8")
|
||||||
.cookies(authCookies)
|
.cookies(authCookies)
|
||||||
@ -73,9 +90,62 @@ class AoDParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list all animes from the website
|
* initially load all media and home screen data
|
||||||
|
* -> blocking
|
||||||
*/
|
*/
|
||||||
fun listAnimes(): ArrayList<Media> = runBlocking {
|
fun initialLoading() = runBlocking {
|
||||||
|
val newEPJob = GlobalScope.async {
|
||||||
|
listNewEpisodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
val listJob = GlobalScope.async {
|
||||||
|
listAnimes()
|
||||||
|
}
|
||||||
|
|
||||||
|
newEPJob.await()
|
||||||
|
listJob.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a media by it's ID (int)
|
||||||
|
* @return Media
|
||||||
|
*/
|
||||||
|
suspend fun getMediaById(mediaId: Int): Media {
|
||||||
|
val media = mediaList.first { it.id == mediaId }
|
||||||
|
|
||||||
|
if (media.episodes.isEmpty()) {
|
||||||
|
loadStreams(media).join()
|
||||||
|
}
|
||||||
|
|
||||||
|
return media
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO don't use jsoup here
|
||||||
|
fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val headers = mutableMapOf(
|
||||||
|
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
|
||||||
|
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
|
||||||
|
Pair("Accept-Encoding", "gzip, deflate, br"),
|
||||||
|
Pair("X-CSRF-Token", csrfToken),
|
||||||
|
Pair("X-Requested-With", "XMLHttpRequest"),
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
Jsoup.connect(baseUrl + callbackPath)
|
||||||
|
.ignoreContentType(true)
|
||||||
|
.cookies(sessionCookies)
|
||||||
|
.headers(headers)
|
||||||
|
.execute()
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
Log.e(javaClass.name, "Callback for $callbackPath failed.", ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load all media from aod into itemMediaList and mediaList
|
||||||
|
*/
|
||||||
|
private fun listAnimes() = runBlocking {
|
||||||
if (sessionCookies.isEmpty()) login()
|
if (sessionCookies.isEmpty()) login()
|
||||||
|
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
@ -85,6 +155,7 @@ class AoDParser {
|
|||||||
|
|
||||||
//println(resAnimes)
|
//println(resAnimes)
|
||||||
|
|
||||||
|
itemMediaList.clear()
|
||||||
mediaList.clear()
|
mediaList.clear()
|
||||||
resAnimes.select("div.animebox").forEach {
|
resAnimes.select("div.animebox").forEach {
|
||||||
val type = if (it.select("p.animebox-link").select("a").text().toLowerCase(Locale.ROOT) == "zur serie") {
|
val type = if (it.select("p.animebox-link").select("a").text().toLowerCase(Locale.ROOT) == "zur serie") {
|
||||||
@ -107,41 +178,116 @@ class AoDParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log.i(javaClass.name, "Total library size is: ${mediaList.size}")
|
Log.i(javaClass.name, "Total library size is: ${mediaList.size}")
|
||||||
|
|
||||||
return@withContext mediaList
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMediaById(mediaId: Int): Media {
|
/**
|
||||||
val media = mediaList.first { it.id == mediaId }
|
* load all new episodes from AoD into newEpisodesList
|
||||||
|
*/
|
||||||
|
private fun listNewEpisodes() = runBlocking {
|
||||||
|
if (sessionCookies.isEmpty()) login()
|
||||||
|
|
||||||
if (media.episodes.isEmpty()) {
|
withContext(Dispatchers.Default) {
|
||||||
loadStreams(media)
|
val resHome = Jsoup.connect(baseUrl)
|
||||||
|
.cookies(sessionCookies)
|
||||||
|
.get()
|
||||||
|
|
||||||
|
newEpisodesList.clear()
|
||||||
|
resHome.select("div.jcarousel-container-new").select("li").forEach {
|
||||||
|
if (it.select("span").hasClass("neweps")) {
|
||||||
|
val mediaId = it.select("a.thumbs").attr("href")
|
||||||
|
.substringAfterLast("/").toInt()
|
||||||
|
val mediaImage = it.select("a.thumbs > img").attr("src")
|
||||||
|
val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}"
|
||||||
|
|
||||||
|
newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return media
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load streams for the media path, movies have one episode
|
* load streams for the media path, movies have one episode
|
||||||
* @param media is used as call ba reference
|
* @param media is used as call ba reference
|
||||||
*/
|
*/
|
||||||
private fun loadStreams(media: Media) = runBlocking {
|
private suspend fun loadStreams(media: Media) = GlobalScope.launch(Dispatchers.IO) {
|
||||||
if (sessionCookies.isEmpty()) login()
|
if (sessionCookies.isEmpty()) login()
|
||||||
|
|
||||||
if (!loginSuccess) {
|
if (!loginSuccess) {
|
||||||
Log.w(javaClass.name, "Login, was not successful.")
|
Log.w(javaClass.name, "Login, was not successful.")
|
||||||
return@runBlocking
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Default) {
|
// get the media page
|
||||||
|
|
||||||
val res = Jsoup.connect(baseUrl + media.link)
|
val res = Jsoup.connect(baseUrl + media.link)
|
||||||
.cookies(sessionCookies)
|
.cookies(sessionCookies)
|
||||||
.get()
|
.get()
|
||||||
|
|
||||||
//println(res)
|
//println(res)
|
||||||
|
|
||||||
|
if (csrfToken.isEmpty()) {
|
||||||
|
csrfToken = res.select("meta[name=csrf-token]").attr("content")
|
||||||
|
//Log.i(javaClass.name, "New csrf token is $csrfToken")
|
||||||
|
}
|
||||||
|
|
||||||
|
val pl = res.select("input.streamstarter_html5").first()
|
||||||
|
val primary = pl.attr("data-playlist")
|
||||||
|
val secondary = pl.attr("data-otherplaylist")
|
||||||
|
val secondaryIsOmU = secondary.contains("OmU", true)
|
||||||
|
|
||||||
|
// load primary and secondary playlist
|
||||||
|
val primaryPlaylist = parsePlaylistAsync(primary)
|
||||||
|
val secondaryPlaylist = parsePlaylistAsync(secondary)
|
||||||
|
|
||||||
|
primaryPlaylist.await().playlist.forEach { ep ->
|
||||||
|
val epNumber = if (media.type == MediaType.TVSHOW) {
|
||||||
|
ep.title.substringAfter(", Ep. ").toInt()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
media.episodes.add(
|
||||||
|
Episode(
|
||||||
|
id = ep.mediaid,
|
||||||
|
priStreamUrl = ep.sources.first().file,
|
||||||
|
posterUrl = ep.image,
|
||||||
|
title = ep.title,
|
||||||
|
description = ep.description,
|
||||||
|
number = epNumber
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Log.i(javaClass.name, "Loading primary playlist finished")
|
||||||
|
|
||||||
|
secondaryPlaylist.await().playlist.forEach { ep ->
|
||||||
|
val episode = media.episodes.firstOrNull { it.id == ep.mediaid }
|
||||||
|
|
||||||
|
if (episode != null) {
|
||||||
|
episode.secStreamUrl = ep.sources.first().file
|
||||||
|
episode.secStreamOmU = secondaryIsOmU
|
||||||
|
} else {
|
||||||
|
val epNumber = if (media.type == MediaType.TVSHOW) {
|
||||||
|
ep.title.substringAfter(", Ep. ").toInt()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
media.episodes.add(
|
||||||
|
Episode(
|
||||||
|
id = ep.mediaid,
|
||||||
|
secStreamUrl = ep.sources.first().file,
|
||||||
|
secStreamOmU = secondaryIsOmU,
|
||||||
|
posterUrl = ep.image,
|
||||||
|
title = ep.title,
|
||||||
|
description = ep.description,
|
||||||
|
number = epNumber
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(javaClass.name, "Loading secondary playlist finished")
|
||||||
|
|
||||||
// parse additional info from the media page
|
// parse additional info from the media page
|
||||||
res.select("table.vertical-table").select("tr").forEach { row ->
|
res.select("table.vertical-table").select("tr").forEach { row ->
|
||||||
when (row.select("th").text().toLowerCase(Locale.ROOT)) {
|
when (row.select("th").text().toLowerCase(Locale.ROOT)) {
|
||||||
@ -156,47 +302,35 @@ class AoDParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse additional information for tv shows
|
// parse additional information for tv shows the episode title (description) is loaded from the "api"
|
||||||
media.episodes = when (media.type) {
|
if (media.type == MediaType.TVSHOW) {
|
||||||
MediaType.MOVIE -> listOf(Episode())
|
res.select("div.three-box-container > div.episodebox").forEach { episodebox ->
|
||||||
MediaType.TVSHOW -> {
|
// make sure the episode has a streaming link
|
||||||
res.select("div.three-box-container > div.episodebox").map { episodebox ->
|
if (episodebox.select("input.streamstarter_html5").isNotEmpty()) {
|
||||||
val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt()
|
val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt()
|
||||||
val episodeShortDesc = episodebox.select("p.episodebox-shorttext").text()
|
val episodeShortDesc = episodebox.select("p.episodebox-shorttext").text()
|
||||||
val episodeWatched = episodebox.select("div.episodebox-icons > div").hasClass("status-icon-orange")
|
val episodeWatched = episodebox.select("div.episodebox-icons > div").hasClass("status-icon-orange")
|
||||||
val episodeWatchedCallback = episodebox.select("input.streamstarter_html5").eachAttr("data-playlist").first()
|
val episodeWatchedCallback = episodebox.select("input.streamstarter_html5").eachAttr("data-playlist").first()
|
||||||
|
|
||||||
Episode(
|
media.episodes.firstOrNull { it.id == episodeId }?.apply {
|
||||||
id = episodeId,
|
shortDesc = episodeShortDesc
|
||||||
shortDesc = episodeShortDesc,
|
watched = episodeWatched
|
||||||
watched = episodeWatched,
|
|
||||||
watchedCallback = episodeWatchedCallback
|
watchedCallback = episodeWatchedCallback
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MediaType.OTHER -> listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (csrfToken.isEmpty()) {
|
|
||||||
csrfToken = res.select("meta[name=csrf-token]").attr("content")
|
|
||||||
//Log.i(javaClass.name, "New csrf token is $csrfToken")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO has attr data-lag (ger or jap)
|
|
||||||
val playlists = res.select("input.streamstarter_html5").eachAttr("data-playlist")
|
|
||||||
|
|
||||||
if (playlists.size > 0) {
|
|
||||||
loadPlaylist(playlists.first(), csrfToken, media.type, media.episodes)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load the playlist path and parse it, read the stream info from json
|
* don't use Gson().fromJson() as we don't have any control over the api and it may change
|
||||||
* @param episodes is used as call ba reference
|
|
||||||
*/
|
*/
|
||||||
private fun loadPlaylist(playlistPath: String, csrfToken: String, type: MediaType, episodes: List<Episode>) = runBlocking {
|
private fun parsePlaylistAsync(playlistPath: String): Deferred<AoDObject> {
|
||||||
withContext(Dispatchers.Default) {
|
if (playlistPath == "[]") {
|
||||||
|
return CompletableDeferred(AoDObject(listOf()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return GlobalScope.async(Dispatchers.IO) {
|
||||||
val headers = mutableMapOf(
|
val headers = mutableMapOf(
|
||||||
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
|
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
|
||||||
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
|
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
|
||||||
@ -213,72 +347,21 @@ class AoDParser {
|
|||||||
.headers(headers)
|
.headers(headers)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
//println(res.body())
|
//Gson().fromJson(res.body(), AoDObject::class.java)
|
||||||
|
|
||||||
when (type) {
|
return@async AoDObject(JsonParser.parseString(res.body()).asJsonObject
|
||||||
MediaType.MOVIE -> {
|
.get("playlist").asJsonArray.map {
|
||||||
val movie = JsonParser.parseString(res.body()).asJsonObject
|
Playlist(
|
||||||
.get("playlist").asJsonArray
|
sources = it.asJsonObject.get("sources").asJsonArray.map { source ->
|
||||||
.first().asJsonObject
|
Source(source.asJsonObject.get("file").asString)
|
||||||
|
},
|
||||||
movie.get("sources").asJsonArray.first().apply {
|
image = it.asJsonObject.get("image").asString,
|
||||||
episodes.first().streamUrl = this.asJsonObject.get("file").asString
|
title = it.asJsonObject.get("title").asString,
|
||||||
}
|
description = it.asJsonObject.get("description").asString,
|
||||||
}
|
mediaid = it.asJsonObject.get("mediaid").asInt
|
||||||
|
|
||||||
MediaType.TVSHOW -> {
|
|
||||||
val episodesJson = JsonParser.parseString(res.body()).asJsonObject
|
|
||||||
.get("playlist").asJsonArray
|
|
||||||
|
|
||||||
episodesJson.forEach { jsonElement ->
|
|
||||||
val episodeId = jsonElement.asJsonObject.get("mediaid")
|
|
||||||
val episodeStream = jsonElement.asJsonObject.get("sources").asJsonArray
|
|
||||||
.first().asJsonObject
|
|
||||||
.get("file").asString
|
|
||||||
val episodeTitle = jsonElement.asJsonObject.get("title").asString
|
|
||||||
val episodePoster = jsonElement.asJsonObject.get("image").asString
|
|
||||||
val episodeDescription = jsonElement.asJsonObject.get("description").asString
|
|
||||||
val episodeNumber = episodeTitle.substringAfter(", Ep. ").toInt()
|
|
||||||
|
|
||||||
episodes.first { it.id == episodeId.asInt }.apply {
|
|
||||||
this.title = episodeTitle
|
|
||||||
this.posterUrl = episodePoster
|
|
||||||
this.streamUrl = episodeStream
|
|
||||||
this.description = episodeDescription
|
|
||||||
this.number = episodeNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
Log.e(javaClass.name, "Wrong Type, please report this issue.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendCallback(callbackPath: String) = GlobalScope.launch {
|
|
||||||
val headers = mutableMapOf(
|
|
||||||
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
|
|
||||||
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
|
|
||||||
Pair("Accept-Encoding", "gzip, deflate, br"),
|
|
||||||
Pair("X-CSRF-Token", csrfToken),
|
|
||||||
Pair("X-Requested-With", "XMLHttpRequest"),
|
|
||||||
)
|
)
|
||||||
|
})
|
||||||
try {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
Jsoup.connect(baseUrl + callbackPath)
|
|
||||||
.ignoreContentType(true)
|
|
||||||
.cookies(sessionCookies)
|
|
||||||
.headers(headers)
|
|
||||||
.execute()
|
|
||||||
}
|
}
|
||||||
} catch (ex: IOException) {
|
|
||||||
Log.e(javaClass.name, "Callback for $callbackPath failed.", ex)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
447
app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
package org.mosad.teapod.player
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.*
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.GestureDetectorCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
|
import com.google.android.exoplayer2.MediaItem
|
||||||
|
import com.google.android.exoplayer2.Player
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
||||||
|
import com.google.android.exoplayer2.ui.StyledPlayerControlView
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||||
|
import com.google.android.exoplayer2.util.Util
|
||||||
|
import kotlinx.android.synthetic.main.activity_player.*
|
||||||
|
import kotlinx.android.synthetic.main.player_controls.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.mosad.teapod.R
|
||||||
|
import org.mosad.teapod.preferences.Preferences
|
||||||
|
import org.mosad.teapod.util.DataTypes
|
||||||
|
import org.mosad.teapod.util.Episode
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.concurrent.scheduleAtFixedRate
|
||||||
|
|
||||||
|
class PlayerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private val model: PlayerViewModel by viewModels()
|
||||||
|
|
||||||
|
private lateinit var player: SimpleExoPlayer
|
||||||
|
private lateinit var dataSourceFactory: DataSource.Factory
|
||||||
|
private lateinit var controller: StyledPlayerControlView
|
||||||
|
private lateinit var gestureDetector: GestureDetectorCompat
|
||||||
|
private lateinit var timerUpdates: TimerTask
|
||||||
|
|
||||||
|
private var nextEpManually = false
|
||||||
|
private var playWhenReady = true
|
||||||
|
private var currentWindow = 0
|
||||||
|
private var playbackPosition: Long = 0
|
||||||
|
private var remainingTime: Long = 0
|
||||||
|
|
||||||
|
private val rwdTime = 10000
|
||||||
|
private val fwdTime = 10000
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_player)
|
||||||
|
hideBars() // Initial hide the bars
|
||||||
|
|
||||||
|
savedInstanceState?.let {
|
||||||
|
currentWindow = it.getInt(getString(R.string.state_resume_window))
|
||||||
|
playbackPosition = it.getLong(getString(R.string.state_resume_position))
|
||||||
|
playWhenReady = it.getBoolean(getString(R.string.state_is_playing))
|
||||||
|
}
|
||||||
|
|
||||||
|
model.loadMedia(
|
||||||
|
intent.getIntExtra(getString(R.string.intent_media_id), 0),
|
||||||
|
intent.getIntExtra(getString(R.string.intent_episode_id), 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
gestureDetector = GestureDetectorCompat(this, PlayerGestureListener())
|
||||||
|
|
||||||
|
initActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
if (Util.SDK_INT > 23) {
|
||||||
|
initPlayer()
|
||||||
|
video_view?.onResume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (Util.SDK_INT <= 23) {
|
||||||
|
initPlayer()
|
||||||
|
video_view?.onResume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
if (Util.SDK_INT <= 23) {
|
||||||
|
video_view?.onPause()
|
||||||
|
releasePlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
if (Util.SDK_INT > 23) {
|
||||||
|
video_view?.onPause()
|
||||||
|
releasePlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
outState.putInt(getString(R.string.state_resume_window), currentWindow)
|
||||||
|
outState.putLong(getString(R.string.state_resume_position), playbackPosition)
|
||||||
|
outState.putBoolean(getString(R.string.state_is_playing), playWhenReady)
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initPlayer() {
|
||||||
|
if (model.mediaId <= 0) {
|
||||||
|
Log.e(javaClass.name, "No media id was set.")
|
||||||
|
this.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
initExoPlayer()
|
||||||
|
initVideoView()
|
||||||
|
initTimeUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initExoPlayer() {
|
||||||
|
player = SimpleExoPlayer.Builder(this).build()
|
||||||
|
dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, "Teapod"))
|
||||||
|
controller = video_view.findViewById(R.id.exo_controller)
|
||||||
|
|
||||||
|
controller.isAnimationEnabled = false // disable controls (time-bar) animation
|
||||||
|
|
||||||
|
player.playWhenReady = playWhenReady
|
||||||
|
player.addListener(object : Player.EventListener {
|
||||||
|
override fun onPlaybackStateChanged(state: Int) {
|
||||||
|
super.onPlaybackStateChanged(state)
|
||||||
|
|
||||||
|
loading.visibility = when (state) {
|
||||||
|
ExoPlayer.STATE_READY -> View.GONE
|
||||||
|
ExoPlayer.STATE_BUFFERING -> View.VISIBLE
|
||||||
|
else -> View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
exo_play_pause.visibility = when (loading.visibility) {
|
||||||
|
View.GONE -> View.VISIBLE
|
||||||
|
View.VISIBLE -> View.INVISIBLE
|
||||||
|
else -> View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == ExoPlayer.STATE_ENDED && model.nextEpisode != null && Preferences.autoplay) {
|
||||||
|
if (nextEpManually) {
|
||||||
|
nextEpManually = false
|
||||||
|
} else {
|
||||||
|
playNextEpisode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
playCurrentMedia(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private fun initVideoView() {
|
||||||
|
video_view.player = player
|
||||||
|
|
||||||
|
// when the player controls get hidden, hide the bars too
|
||||||
|
video_view.setControllerVisibilityListener {
|
||||||
|
when (it) {
|
||||||
|
View.GONE -> hideBars()
|
||||||
|
View.VISIBLE -> updateControls()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video_view.setOnTouchListener { _, event ->
|
||||||
|
gestureDetector.onTouchEvent(event)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initActions() {
|
||||||
|
exo_close_player.setOnClickListener { this.finish() }
|
||||||
|
rwd_10.setOnButtonClickListener { rewind() }
|
||||||
|
ffwd_10.setOnButtonClickListener { fastForward() }
|
||||||
|
button_next_ep.setOnClickListener { playNextEpisode() }
|
||||||
|
button_next_ep_c.setOnClickListener { playNextEpisode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initTimeUpdates() {
|
||||||
|
if (this::timerUpdates.isInitialized) {
|
||||||
|
timerUpdates.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
timerUpdates = Timer().scheduleAtFixedRate(0, 500) {
|
||||||
|
GlobalScope.launch {
|
||||||
|
var btnNextEpIsVisible: Boolean
|
||||||
|
var controlsVisible: Boolean
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
remainingTime = player.duration - player.currentPosition
|
||||||
|
remainingTime = if (remainingTime < 0) 0 else remainingTime
|
||||||
|
|
||||||
|
btnNextEpIsVisible = button_next_ep.isVisible
|
||||||
|
controlsVisible = controller.isVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingTime in 1..20000) {
|
||||||
|
// if the next ep button is not visible, make it visible
|
||||||
|
if (!btnNextEpIsVisible && model.nextEpisode != null && Preferences.autoplay) {
|
||||||
|
withContext(Dispatchers.Main) { showButtonNextEp() }
|
||||||
|
}
|
||||||
|
} else if (btnNextEpIsVisible) {
|
||||||
|
withContext(Dispatchers.Main) { hideButtonNextEp() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// if controls are visible, update them
|
||||||
|
if (controlsVisible) {
|
||||||
|
withContext(Dispatchers.Main) { updateControls() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun releasePlayer(){
|
||||||
|
playbackPosition = player.currentPosition
|
||||||
|
currentWindow = player.currentWindowIndex
|
||||||
|
playWhenReady = player.playWhenReady
|
||||||
|
player.release()
|
||||||
|
timerUpdates.cancel()
|
||||||
|
|
||||||
|
Log.d(javaClass.name, "Released player")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update the custom controls
|
||||||
|
*/
|
||||||
|
private fun updateControls() {
|
||||||
|
// update remaining time label
|
||||||
|
val hours = TimeUnit.MILLISECONDS.toHours(remainingTime) % 24
|
||||||
|
val minutes = TimeUnit.MILLISECONDS.toMinutes(remainingTime) % 60
|
||||||
|
val seconds = TimeUnit.MILLISECONDS.toSeconds(remainingTime) % 60
|
||||||
|
|
||||||
|
// if remaining time is below 60 minutes, don't show hours
|
||||||
|
exo_remaining.text = if (TimeUnit.MILLISECONDS.toMinutes(remainingTime) < 60) {
|
||||||
|
getString(R.string.time_min_sec, minutes, seconds)
|
||||||
|
} else {
|
||||||
|
getString(R.string.time_hour_min_sec, hours, minutes, seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO set position of rewind/fast forward indicators programmatically
|
||||||
|
*/
|
||||||
|
|
||||||
|
private fun rewind() {
|
||||||
|
player.seekTo(player.currentPosition - rwdTime)
|
||||||
|
|
||||||
|
// hide/show needed components
|
||||||
|
exo_double_tap_indicator.visibility = View.VISIBLE
|
||||||
|
ffwd_10_indicator.visibility = View.INVISIBLE
|
||||||
|
rwd_10.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
rwd_10_indicator.onAnimationEndCallback = {
|
||||||
|
exo_double_tap_indicator.visibility = View.GONE
|
||||||
|
ffwd_10_indicator.visibility = View.VISIBLE
|
||||||
|
rwd_10.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
// run animation
|
||||||
|
rwd_10_indicator.runOnClickAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fastForward() {
|
||||||
|
player.seekTo(player.currentPosition + fwdTime)
|
||||||
|
|
||||||
|
// hide/show needed components
|
||||||
|
exo_double_tap_indicator.visibility = View.VISIBLE
|
||||||
|
rwd_10_indicator.visibility = View.INVISIBLE
|
||||||
|
ffwd_10.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
ffwd_10_indicator.onAnimationEndCallback = {
|
||||||
|
exo_double_tap_indicator.visibility = View.GONE
|
||||||
|
rwd_10_indicator.visibility = View.VISIBLE
|
||||||
|
ffwd_10.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
// run animation
|
||||||
|
ffwd_10_indicator.runOnClickAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun togglePausePlay() {
|
||||||
|
if (player.isPlaying) {
|
||||||
|
player.pause()
|
||||||
|
} else {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playNextEpisode() = model.nextEpisode?.let {
|
||||||
|
model.nextEpisode() // current = next, next = new or null
|
||||||
|
hideButtonNextEp()
|
||||||
|
|
||||||
|
nextEpManually = true
|
||||||
|
playCurrentMedia(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start playing a episode
|
||||||
|
* Note: movies are episodes too!
|
||||||
|
*/
|
||||||
|
private fun playCurrentMedia(seekToPosition: Boolean) {
|
||||||
|
// update the gui
|
||||||
|
exo_text_title.text = if (model.media.type == DataTypes.MediaType.TVSHOW) {
|
||||||
|
getString(R.string.component_episode_title, model.currentEpisode.number, model.currentEpisode.description)
|
||||||
|
} else {
|
||||||
|
model.currentEpisode.title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.nextEpisode == null) {
|
||||||
|
button_next_ep_c.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
player.clearMediaItems() //remove previous item
|
||||||
|
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
|
||||||
|
MediaItem.fromUri(Uri.parse(autoSelectStream(model.currentEpisode)))
|
||||||
|
)
|
||||||
|
if (seekToPosition) player.seekTo(playbackPosition)
|
||||||
|
player.setMediaSource(mediaSource)
|
||||||
|
player.prepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If preferSecondary or priStreamUrl is empty and secondary is present (secStreamOmU),
|
||||||
|
* use the secondary stream. Else, if the primary stream is set use the primary stream.
|
||||||
|
* If no stream is present, close the activity.
|
||||||
|
*/
|
||||||
|
private fun autoSelectStream(episode: Episode): String {
|
||||||
|
return if ((Preferences.preferSecondary || episode.priStreamUrl.isEmpty()) && episode.secStreamOmU) {
|
||||||
|
episode.secStreamUrl
|
||||||
|
} else if (episode.priStreamUrl.isNotEmpty()) {
|
||||||
|
episode.priStreamUrl
|
||||||
|
} else {
|
||||||
|
Log.e(javaClass.name, "No stream url set.")
|
||||||
|
this.finish()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hide the status and navigation bar
|
||||||
|
*/
|
||||||
|
private fun hideBars() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
window.insetsController?.apply {
|
||||||
|
hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
||||||
|
systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@Suppress("deprecation")
|
||||||
|
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
|
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show the next episode button
|
||||||
|
* TODO improve the show animation
|
||||||
|
*/
|
||||||
|
private fun showButtonNextEp() {
|
||||||
|
button_next_ep.visibility = View.VISIBLE
|
||||||
|
button_next_ep.alpha = 0.0f
|
||||||
|
|
||||||
|
button_next_ep.animate()
|
||||||
|
.alpha(1.0f)
|
||||||
|
.setListener(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hide the next episode button
|
||||||
|
* TODO improve the hide animation
|
||||||
|
*/
|
||||||
|
private fun hideButtonNextEp() {
|
||||||
|
button_next_ep.animate()
|
||||||
|
.alpha(0.0f)
|
||||||
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
super.onAnimationEnd(animation)
|
||||||
|
button_next_ep.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class PlayerGestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* on single tap hide or show the controls
|
||||||
|
*/
|
||||||
|
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||||||
|
if (controller.isVisible) controller.hide() else controller.show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* on double tap rewind or forward
|
||||||
|
*/
|
||||||
|
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
||||||
|
val eventPosX = e?.x?.toInt() ?: 0
|
||||||
|
val viewCenterX = video_view.measuredWidth / 2
|
||||||
|
|
||||||
|
// if the event position is on the left side rewind, if it's on the right forward
|
||||||
|
if (eventPosX < viewCenterX) {
|
||||||
|
rewind()
|
||||||
|
} else {
|
||||||
|
fastForward()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* not used
|
||||||
|
*/
|
||||||
|
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* on long press toggle pause/play
|
||||||
|
*/
|
||||||
|
override fun onLongPress(e: MotionEvent?) {
|
||||||
|
togglePausePlay()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
62
app/src/main/java/org/mosad/teapod/player/PlayerViewModel.kt
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package org.mosad.teapod.player
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.mosad.teapod.parser.AoDParser
|
||||||
|
import org.mosad.teapod.ui.fragments.MediaFragment
|
||||||
|
import org.mosad.teapod.util.DataTypes
|
||||||
|
import org.mosad.teapod.util.Episode
|
||||||
|
import org.mosad.teapod.util.Media
|
||||||
|
|
||||||
|
class PlayerViewModel : ViewModel() {
|
||||||
|
|
||||||
|
var mediaId = 0
|
||||||
|
internal set
|
||||||
|
var episodeId = 0
|
||||||
|
internal set
|
||||||
|
|
||||||
|
var media: Media = Media(0, "", DataTypes.MediaType.OTHER)
|
||||||
|
internal set
|
||||||
|
var currentEpisode = Episode()
|
||||||
|
internal set
|
||||||
|
var nextEpisode: Episode? = null
|
||||||
|
internal set
|
||||||
|
|
||||||
|
fun loadMedia(iMediaId: Int, iEpisodeId: Int) {
|
||||||
|
mediaId = iMediaId
|
||||||
|
episodeId = iEpisodeId
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
media = AoDParser.getMediaById(mediaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentEpisode = media.episodes.first { it.id == episodeId }
|
||||||
|
nextEpisode = selectNextEpisode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update currentEpisode, episodeId, nextEpisode to new episode
|
||||||
|
* updateWatchedState for the next (now current) episode
|
||||||
|
*/
|
||||||
|
fun nextEpisode() = nextEpisode?.let { nextEp ->
|
||||||
|
currentEpisode = nextEp // set current ep to next ep
|
||||||
|
episodeId = nextEp.id
|
||||||
|
MediaFragment.instance.updateWatchedState(nextEp) // watchedCallback for next ep
|
||||||
|
|
||||||
|
nextEpisode = selectNextEpisode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on the current episodeId, get the next episode. If there is no next
|
||||||
|
* episode, return null
|
||||||
|
*/
|
||||||
|
private fun selectNextEpisode(): Episode? {
|
||||||
|
val nextEpIndex = media.episodes.indexOfFirst { it.id == currentEpisode.id } + 1
|
||||||
|
return if (nextEpIndex < (media.episodes.size)) {
|
||||||
|
media.episodes[nextEpIndex]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,22 +1,71 @@
|
|||||||
package org.mosad.teapod.preferences
|
package org.mosad.teapod.preferences
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import org.mosad.teapod.R
|
||||||
|
import org.mosad.teapod.util.DataTypes
|
||||||
|
|
||||||
object Preferences {
|
object Preferences {
|
||||||
|
|
||||||
var login = ""
|
var preferSecondary = false
|
||||||
internal set
|
internal set
|
||||||
var password = ""
|
var autoplay = true
|
||||||
|
internal set
|
||||||
|
var theme = DataTypes.Theme.LIGHT
|
||||||
internal set
|
internal set
|
||||||
|
|
||||||
|
private fun getSharedPref(context: Context): SharedPreferences {
|
||||||
fun saveCredentials(login: String, password: String) {
|
return context.getSharedPreferences(
|
||||||
this.login = login
|
context.getString(R.string.preference_file_key),
|
||||||
this.password = password
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
// TODO save
|
}
|
||||||
|
|
||||||
|
fun savePreferSecondary(context: Context, preferSecondary: Boolean) {
|
||||||
|
with(getSharedPref(context).edit()) {
|
||||||
|
putBoolean(context.getString(R.string.save_key_prefer_secondary), preferSecondary)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.preferSecondary = preferSecondary
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAutoplay(context: Context, autoplay: Boolean) {
|
||||||
|
with(getSharedPref(context).edit()) {
|
||||||
|
putBoolean(context.getString(R.string.save_key_autoplay), autoplay)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.autoplay = autoplay
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveTheme(context: Context, theme: DataTypes.Theme) {
|
||||||
|
with(getSharedPref(context).edit()) {
|
||||||
|
putString(context.getString(R.string.save_key_theme), theme.toString())
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.theme = theme
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initially load the stored values
|
||||||
|
*/
|
||||||
|
fun load(context: Context) {
|
||||||
|
val sharedPref = getSharedPref(context)
|
||||||
|
|
||||||
|
preferSecondary = sharedPref.getBoolean(
|
||||||
|
context.getString(R.string.save_key_prefer_secondary), false
|
||||||
|
)
|
||||||
|
autoplay = sharedPref.getBoolean(
|
||||||
|
context.getString(R.string.save_key_autoplay), true
|
||||||
|
)
|
||||||
|
theme = DataTypes.Theme.valueOf(
|
||||||
|
sharedPref.getString(
|
||||||
|
context.getString(R.string.save_key_theme), DataTypes.Theme.LIGHT.toString()
|
||||||
|
) ?: DataTypes.Theme.LIGHT.toString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load() {
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
@ -0,0 +1,68 @@
|
|||||||
|
package org.mosad.teapod.ui.components
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import kotlinx.android.synthetic.main.button_fast_forward.view.*
|
||||||
|
import org.mosad.teapod.R
|
||||||
|
|
||||||
|
class FastForwardButton(context: Context, attrs: AttributeSet?): FrameLayout(context, attrs) {
|
||||||
|
|
||||||
|
private val animationDuration: Long = 800
|
||||||
|
private val buttonAnimation: ObjectAnimator
|
||||||
|
private val labelAnimation: ObjectAnimator
|
||||||
|
|
||||||
|
var onAnimationEndCallback: (() -> Unit)? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflate(context, R.layout.button_fast_forward, this)
|
||||||
|
|
||||||
|
buttonAnimation = ObjectAnimator.ofFloat(imageButton, View.ROTATION, 0f, 50f).apply {
|
||||||
|
duration = animationDuration / 4
|
||||||
|
repeatCount = 1
|
||||||
|
repeatMode = ObjectAnimator.REVERSE
|
||||||
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationStart(animation: Animator?) {
|
||||||
|
imageButton.isEnabled = false // disable button
|
||||||
|
imageButton.setBackgroundResource(R.drawable.ic_baseline_forward_24)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
labelAnimation = ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, 35f).apply {
|
||||||
|
duration = animationDuration
|
||||||
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
|
// the label animation takes longer then the button animation, reset stuff in here
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
imageButton.isEnabled = true // enable button
|
||||||
|
imageButton.setBackgroundResource(R.drawable.ic_baseline_forward_10_24)
|
||||||
|
|
||||||
|
textView.visibility = View.GONE
|
||||||
|
textView.animate().translationX(0f)
|
||||||
|
|
||||||
|
onAnimationEndCallback?.invoke()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnButtonClickListener(func: FastForwardButton.() -> Unit) {
|
||||||
|
imageButton.setOnClickListener {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runOnClickAnimation() {
|
||||||
|
// run button animation
|
||||||
|
buttonAnimation.start()
|
||||||
|
|
||||||
|
// run lbl animation
|
||||||
|
textView.visibility = View.VISIBLE
|
||||||
|
labelAnimation.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package org.mosad.teapod.ui.components
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import kotlinx.android.synthetic.main.button_rewind.view.*
|
||||||
|
import org.mosad.teapod.R
|
||||||
|
|
||||||
|
class RewindButton(context: Context, attrs: AttributeSet): FrameLayout(context, attrs) {
|
||||||
|
|
||||||
|
private val animationDuration: Long = 800
|
||||||
|
private val buttonAnimation: ObjectAnimator
|
||||||
|
private val labelAnimation: ObjectAnimator
|
||||||
|
|
||||||
|
var onAnimationEndCallback: (() -> Unit)? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
inflate(context, R.layout.button_rewind, this)
|
||||||
|
|
||||||
|
buttonAnimation = ObjectAnimator.ofFloat(imageButton, View.ROTATION, 0f, -50f).apply {
|
||||||
|
duration = animationDuration / 4
|
||||||
|
repeatCount = 1
|
||||||
|
repeatMode = ObjectAnimator.REVERSE
|
||||||
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationStart(animation: Animator?) {
|
||||||
|
imageButton.isEnabled = false // disable button
|
||||||
|
imageButton.setBackgroundResource(R.drawable.ic_baseline_rewind_24)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
labelAnimation = ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, -35f).apply {
|
||||||
|
duration = animationDuration
|
||||||
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
imageButton.isEnabled = true // enable button
|
||||||
|
imageButton.setBackgroundResource(R.drawable.ic_baseline_rewind_10_24)
|
||||||
|
|
||||||
|
textView.visibility = View.GONE
|
||||||
|
textView.animate().translationX(0f)
|
||||||
|
|
||||||
|
onAnimationEndCallback?.invoke()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnButtonClickListener(func: RewindButton.() -> Unit) {
|
||||||
|
imageButton.setOnClickListener {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runOnClickAnimation() {
|
||||||
|
// run button animation
|
||||||
|
buttonAnimation.start()
|
||||||
|
|
||||||
|
// run lbl animation
|
||||||
|
textView.visibility = View.VISIBLE
|
||||||
|
labelAnimation.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,57 +7,95 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||||
import de.psdev.licensesdialog.LicensesDialog
|
import de.psdev.licensesdialog.LicensesDialog
|
||||||
import kotlinx.android.synthetic.main.fragment_account.*
|
|
||||||
import org.mosad.teapod.BuildConfig
|
import org.mosad.teapod.BuildConfig
|
||||||
|
import org.mosad.teapod.MainActivity
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
|
import org.mosad.teapod.databinding.FragmentAccountBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
import org.mosad.teapod.preferences.EncryptedPreferences
|
import org.mosad.teapod.preferences.EncryptedPreferences
|
||||||
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.ui.components.LoginDialog
|
import org.mosad.teapod.ui.components.LoginDialog
|
||||||
|
import org.mosad.teapod.util.DataTypes.Theme
|
||||||
|
|
||||||
class AccountFragment : Fragment() {
|
class AccountFragment : Fragment() {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
private lateinit var binding: FragmentAccountBinding
|
||||||
return inflater.inflate(R.layout.fragment_account, container, false)
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentAccountBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
text_account_login.text = EncryptedPreferences.login
|
binding.textAccountLogin.text = EncryptedPreferences.login
|
||||||
text_info_about_desc.text = getString(R.string.info_about_desc, BuildConfig.VERSION_NAME, getString(R.string.build_time))
|
binding.textInfoAboutDesc.text = getString(R.string.info_about_desc, BuildConfig.VERSION_NAME, getString(R.string.build_time))
|
||||||
|
binding.textThemeSelected.text = when (Preferences.theme) {
|
||||||
|
Theme.DARK -> getString(R.string.theme_dark)
|
||||||
|
else -> getString(R.string.theme_light)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.switchSecondary.isChecked = Preferences.preferSecondary
|
||||||
|
binding.switchAutoplay.isChecked = Preferences.autoplay
|
||||||
|
|
||||||
initActions()
|
initActions()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initActions() {
|
private fun initActions() {
|
||||||
linear_account_login.setOnClickListener {
|
binding.linearAccountLogin.setOnClickListener {
|
||||||
showLoginDialog(true)
|
showLoginDialog(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
linear_about.setOnClickListener {
|
binding.linearTheme.setOnClickListener {
|
||||||
|
showThemeDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.linearInfo.setOnClickListener {
|
||||||
MaterialDialog(requireContext())
|
MaterialDialog(requireContext())
|
||||||
.title(R.string.info_about)
|
.title(R.string.info_about)
|
||||||
.message(R.string.info_about_dialog)
|
.message(R.string.info_about_dialog)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
text_licenses.setOnClickListener {
|
binding.textLicenses.setOnClickListener {
|
||||||
|
|
||||||
|
val dialogCss = when (Preferences.theme) {
|
||||||
|
Theme.DARK -> R.string.license_dialog_style_dark
|
||||||
|
else -> R.string.license_dialog_style_light
|
||||||
|
}
|
||||||
|
|
||||||
|
val themeId = when (Preferences.theme) {
|
||||||
|
Theme.DARK -> R.style.LicensesDialogTheme_Dark
|
||||||
|
else -> R.style.AppTheme_Light
|
||||||
|
}
|
||||||
|
|
||||||
LicensesDialog.Builder(requireContext())
|
LicensesDialog.Builder(requireContext())
|
||||||
.setNotices(R.raw.notices)
|
.setNotices(R.raw.notices)
|
||||||
.setTitle(R.string.licenses)
|
.setTitle(R.string.licenses)
|
||||||
.setIncludeOwnLicense(true)
|
.setIncludeOwnLicense(true)
|
||||||
.setThemeResourceId(R.style.AppTheme)
|
.setThemeResourceId(themeId)
|
||||||
|
.setNoticesCssStyle(dialogCss)
|
||||||
.build()
|
.build()
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.switchSecondary.setOnClickListener {
|
||||||
|
Preferences.savePreferSecondary(requireContext(), binding.switchSecondary.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.switchAutoplay.setOnClickListener {
|
||||||
|
Preferences.saveAutoplay(requireContext(), binding.switchAutoplay.isChecked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLoginDialog(firstTry: Boolean) {
|
private fun showLoginDialog(firstTry: Boolean) {
|
||||||
LoginDialog(requireContext(), firstTry).positiveButton {
|
LoginDialog(requireContext(), firstTry).positiveButton {
|
||||||
EncryptedPreferences.saveCredentials(login, password, context)
|
EncryptedPreferences.saveCredentials(login, password, context)
|
||||||
|
|
||||||
if (!AoDParser().login()) {
|
if (!AoDParser.login()) {
|
||||||
showLoginDialog(false)
|
showLoginDialog(false)
|
||||||
Log.w(javaClass.name, "Login failed, please try again.")
|
Log.w(javaClass.name, "Login failed, please try again.")
|
||||||
}
|
}
|
||||||
@ -66,4 +104,24 @@ class AccountFragment : Fragment() {
|
|||||||
password = ""
|
password = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showThemeDialog() {
|
||||||
|
val themes = listOf(
|
||||||
|
resources.getString(R.string.theme_light),
|
||||||
|
resources.getString(R.string.theme_dark)
|
||||||
|
)
|
||||||
|
|
||||||
|
MaterialDialog(requireContext()).show {
|
||||||
|
title(R.string.theme)
|
||||||
|
listItemsSingleChoice(items = themes, initialSelection = Preferences.theme.ordinal) { _, index, _ ->
|
||||||
|
when(index) {
|
||||||
|
0 -> Preferences.saveTheme(context, Theme.LIGHT)
|
||||||
|
1 -> Preferences.saveTheme(context, Theme.DARK)
|
||||||
|
else -> Preferences.saveTheme(context, Theme.LIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
(activity as MainActivity).restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,14 +5,12 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.*
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.mosad.teapod.MainActivity
|
import org.mosad.teapod.MainActivity
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.databinding.FragmentHomeBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
import org.mosad.teapod.util.StorageController
|
import org.mosad.teapod.util.StorageController
|
||||||
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
||||||
@ -20,31 +18,32 @@ import org.mosad.teapod.util.decoration.MediaItemDecoration
|
|||||||
|
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var adapter: MediaItemAdapter
|
private lateinit var binding: FragmentHomeBinding
|
||||||
private lateinit var layoutManager: LinearLayoutManager
|
private lateinit var adapterMyList: MediaItemAdapter
|
||||||
|
private lateinit var adapterNewEpisodes: MediaItemAdapter
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
if (AoDParser.mediaList.isEmpty()) {
|
|
||||||
AoDParser().listAnimes()
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
context?.let {
|
context?.let {
|
||||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
binding.recyclerMyList.addItemDecoration(MediaItemDecoration(9))
|
||||||
recycler_my_list.layoutManager = layoutManager
|
|
||||||
recycler_my_list.addItemDecoration(MediaItemDecoration(9))
|
|
||||||
|
|
||||||
updateMyListMedia()
|
updateMyListMedia()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
adapterNewEpisodes = MediaItemAdapter(AoDParser.newEpisodesList)
|
||||||
|
binding.recyclerNewEpisodes.adapter = adapterNewEpisodes
|
||||||
|
binding.recyclerNewEpisodes.addItemDecoration(MediaItemDecoration(9))
|
||||||
|
|
||||||
|
initActions()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,11 +55,17 @@ class HomeFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = MediaItemAdapter(myListMedia)
|
adapterMyList = MediaItemAdapter(myListMedia)
|
||||||
adapter.onItemClick = { mediaId, _ ->
|
adapterMyList.onItemClick = { mediaId, _ ->
|
||||||
(activity as MainActivity).showMediaFragment(mediaId)
|
(activity as MainActivity).showMediaFragment(mediaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
recycler_my_list.adapter = adapter
|
binding.recyclerMyList.adapter = adapterMyList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initActions() {
|
||||||
|
adapterNewEpisodes.onItemClick = { mediaId, _ ->
|
||||||
|
(activity as MainActivity).showMediaFragment(mediaId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,20 +5,24 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import kotlinx.android.synthetic.main.fragment_library.*
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.mosad.teapod.MainActivity
|
import org.mosad.teapod.MainActivity
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.databinding.FragmentLibraryBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
import org.mosad.teapod.util.decoration.MediaItemDecoration
|
|
||||||
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
||||||
|
import org.mosad.teapod.util.decoration.MediaItemDecoration
|
||||||
|
|
||||||
class LibraryFragment : Fragment() {
|
class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentLibraryBinding
|
||||||
private lateinit var adapter: MediaItemAdapter
|
private lateinit var adapter: MediaItemAdapter
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
return inflater.inflate(R.layout.fragment_library, container, false)
|
binding = FragmentLibraryBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@ -26,10 +30,6 @@ class LibraryFragment : Fragment() {
|
|||||||
|
|
||||||
// init async
|
// init async
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
if (AoDParser.mediaList.isEmpty()) {
|
|
||||||
AoDParser().listAnimes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// create and set the adapter, needs context
|
// create and set the adapter, needs context
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
context?.let {
|
context?.let {
|
||||||
@ -38,8 +38,8 @@ class LibraryFragment : Fragment() {
|
|||||||
(activity as MainActivity).showMediaFragment(mediaId)
|
(activity as MainActivity).showMediaFragment(mediaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
recycler_media_library.adapter = adapter
|
binding.recyclerMediaLibrary.adapter = adapter
|
||||||
recycler_media_library.addItemDecoration(MediaItemDecoration(9))
|
binding.recyclerMediaLibrary.addItemDecoration(MediaItemDecoration(9))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package org.mosad.teapod.ui.fragments
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import org.mosad.teapod.R
|
|
||||||
|
|
||||||
class LoadingFragment : Fragment() {
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
|
||||||
return inflater.inflate(R.layout.fragment_loading, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -13,37 +13,58 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
import kotlinx.android.synthetic.main.fragment_media.*
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.teapod.MainActivity
|
import org.mosad.teapod.MainActivity
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
|
import org.mosad.teapod.databinding.FragmentMediaBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
|
import org.mosad.teapod.util.*
|
||||||
import org.mosad.teapod.util.DataTypes.MediaType
|
import org.mosad.teapod.util.DataTypes.MediaType
|
||||||
import org.mosad.teapod.util.Media
|
|
||||||
import org.mosad.teapod.util.StorageController
|
|
||||||
import org.mosad.teapod.util.TMDBResponse
|
|
||||||
import org.mosad.teapod.util.adapter.EpisodeItemAdapter
|
import org.mosad.teapod.util.adapter.EpisodeItemAdapter
|
||||||
|
|
||||||
class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) : Fragment() {
|
class MediaFragment(private val mediaId: Int) : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentMediaBinding
|
||||||
private lateinit var adapterRecEpisodes: EpisodeItemAdapter
|
private lateinit var adapterRecEpisodes: EpisodeItemAdapter
|
||||||
private lateinit var viewManager: RecyclerView.LayoutManager
|
private lateinit var viewManager: RecyclerView.LayoutManager
|
||||||
|
|
||||||
|
private lateinit var media: Media
|
||||||
|
private lateinit var tmdb: TMDBResponse
|
||||||
|
private lateinit var nextEpisode: Episode
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
companion object {
|
||||||
return inflater.inflate(R.layout.fragment_media, container, false)
|
lateinit var instance: MediaFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
instance = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentMediaBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.frameLoading.visibility = View.VISIBLE
|
||||||
|
|
||||||
initGUI()
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
// load the streams for the selected media
|
||||||
|
media = AoDParser.getMediaById(mediaId)
|
||||||
|
tmdb = TMDBApiController().search(media.info.title, media.type)
|
||||||
|
|
||||||
|
if (this@MediaFragment.isAdded) {
|
||||||
|
updateGUI()
|
||||||
initActions()
|
initActions()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if tmdb data is present, use it, else use the aod data
|
* if tmdb data is present, use it, else use the aod data
|
||||||
*/
|
*/
|
||||||
private fun initGUI() {
|
private fun updateGUI() = with(binding) {
|
||||||
// generic gui
|
// generic gui
|
||||||
val backdropUrl = if (tmdb.backdropUrl.isNotEmpty()) tmdb.backdropUrl else media.info.posterUrl
|
val backdropUrl = if (tmdb.backdropUrl.isNotEmpty()) tmdb.backdropUrl else media.info.posterUrl
|
||||||
val posterUrl = if (tmdb.posterUrl.isNotEmpty()) tmdb.posterUrl else media.info.posterUrl
|
val posterUrl = if (tmdb.posterUrl.isNotEmpty()) tmdb.posterUrl else media.info.posterUrl
|
||||||
@ -51,57 +72,69 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) :
|
|||||||
Glide.with(requireContext()).load(backdropUrl)
|
Glide.with(requireContext()).load(backdropUrl)
|
||||||
.apply(RequestOptions.placeholderOf(ColorDrawable(Color.DKGRAY)))
|
.apply(RequestOptions.placeholderOf(ColorDrawable(Color.DKGRAY)))
|
||||||
.apply(RequestOptions.bitmapTransform(BlurTransformation(20, 3)))
|
.apply(RequestOptions.bitmapTransform(BlurTransformation(20, 3)))
|
||||||
.into(image_backdrop)
|
.into(imageBackdrop)
|
||||||
|
|
||||||
Glide.with(requireContext()).load(posterUrl)
|
Glide.with(requireContext()).load(posterUrl)
|
||||||
.into(image_poster)
|
.into(imagePoster)
|
||||||
|
|
||||||
text_title.text = media.info.title
|
textTitle.text = media.info.title
|
||||||
text_year.text = media.info.year.toString()
|
textYear.text = media.info.year.toString()
|
||||||
text_age.text = media.info.age.toString()
|
textAge.text = media.info.age.toString()
|
||||||
text_overview.text = media.info.shortDesc
|
textOverview.text = media.info.shortDesc
|
||||||
if (StorageController.myList.contains(media.id)) {
|
if (StorageController.myList.contains(media.id)) {
|
||||||
Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(image_my_list_action)
|
Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(imageMyListAction)
|
||||||
} else {
|
} else {
|
||||||
Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(image_my_list_action)
|
Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(imageMyListAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// specific gui
|
// specific gui
|
||||||
if (media.type == MediaType.TVSHOW) {
|
if (media.type == MediaType.TVSHOW) {
|
||||||
adapterRecEpisodes = EpisodeItemAdapter(media.episodes)
|
adapterRecEpisodes = EpisodeItemAdapter(media.episodes)
|
||||||
viewManager = LinearLayoutManager(context)
|
viewManager = LinearLayoutManager(context)
|
||||||
recycler_episodes.layoutManager = viewManager
|
recyclerEpisodes.layoutManager = viewManager
|
||||||
recycler_episodes.adapter = adapterRecEpisodes
|
recyclerEpisodes.adapter = adapterRecEpisodes
|
||||||
|
|
||||||
text_episodes_or_runtime.text = getString(R.string.text_episodes_count, media.info.episodesCount)
|
binding.textEpisodesOrRuntime.text = getString(R.string.text_episodes_count, media.info.episodesCount)
|
||||||
|
|
||||||
|
// get next episode
|
||||||
|
nextEpisode = if (media.episodes.firstOrNull{ !it.watched } != null) {
|
||||||
|
media.episodes.first{ !it.watched }
|
||||||
|
} else {
|
||||||
|
media.episodes.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
// title is the next episodes title
|
||||||
|
textTitle.text = nextEpisode.title
|
||||||
} else if (media.type == MediaType.MOVIE) {
|
} else if (media.type == MediaType.MOVIE) {
|
||||||
recycler_episodes.visibility = View.GONE
|
recyclerEpisodes.visibility = View.GONE
|
||||||
|
|
||||||
if (tmdb.runtime > 0) {
|
if (tmdb.runtime > 0) {
|
||||||
text_episodes_or_runtime.text = getString(R.string.text_runtime, tmdb.runtime)
|
textEpisodesOrRuntime.text = getString(R.string.text_runtime, tmdb.runtime)
|
||||||
} else {
|
} else {
|
||||||
text_episodes_or_runtime.visibility = View.GONE
|
textEpisodesOrRuntime.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frameLoading.visibility = View.GONE // hide loading indicator
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initActions() {
|
private fun initActions() {
|
||||||
button_play.setOnClickListener {
|
binding.buttonPlay.setOnClickListener {
|
||||||
when (media.type) {
|
when (media.type) {
|
||||||
MediaType.MOVIE -> playStream(media.episodes.first().streamUrl)
|
MediaType.MOVIE -> playStream(media.episodes.first())
|
||||||
MediaType.TVSHOW -> playStream(media.episodes.first().streamUrl)
|
MediaType.TVSHOW -> playEpisode(nextEpisode)
|
||||||
else -> Log.e(javaClass.name, "Wrong Type: $media.type")
|
else -> Log.e(javaClass.name, "Wrong Type: $media.type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add or remove media from myList
|
// add or remove media from myList
|
||||||
linear_my_list_action.setOnClickListener {
|
binding.linearMyListAction.setOnClickListener {
|
||||||
if (StorageController.myList.contains(media.id)) {
|
if (StorageController.myList.contains(media.id)) {
|
||||||
StorageController.myList.remove(media.id)
|
StorageController.myList.remove(media.id)
|
||||||
Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(image_my_list_action)
|
Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(binding.imageMyListAction)
|
||||||
} else {
|
} else {
|
||||||
StorageController.myList.add(media.id)
|
StorageController.myList.add(media.id)
|
||||||
Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(image_my_list_action)
|
Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(binding.imageMyListAction)
|
||||||
}
|
}
|
||||||
StorageController.saveMyList(requireContext())
|
StorageController.saveMyList(requireContext())
|
||||||
|
|
||||||
@ -114,19 +147,38 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) :
|
|||||||
// set onItemClick only in adapter is initialized
|
// set onItemClick only in adapter is initialized
|
||||||
if (this::adapterRecEpisodes.isInitialized) {
|
if (this::adapterRecEpisodes.isInitialized) {
|
||||||
adapterRecEpisodes.onImageClick = { _, position ->
|
adapterRecEpisodes.onImageClick = { _, position ->
|
||||||
playStream(media.episodes[position].streamUrl)
|
playEpisode(media.episodes[position])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playEpisode(ep: Episode) {
|
||||||
|
playStream(ep)
|
||||||
|
|
||||||
// update watched state
|
// update watched state
|
||||||
AoDParser().sendCallback(media.episodes[position].watchedCallback)
|
updateWatchedState(ep)
|
||||||
adapterRecEpisodes.updateWatchedState(true, position)
|
//AoDParser.sendCallback(ep.watchedCallback)
|
||||||
|
//adapterRecEpisodes.updateWatchedState(true, media.episodes.indexOf(ep))
|
||||||
|
//adapterRecEpisodes.notifyDataSetChanged()
|
||||||
|
|
||||||
|
// update nextEpisode
|
||||||
|
nextEpisode = if (media.episodes.firstOrNull{ !it.watched } != null) {
|
||||||
|
media.episodes.first{ !it.watched }
|
||||||
|
} else {
|
||||||
|
media.episodes.first()
|
||||||
|
}
|
||||||
|
binding.textTitle.text = nextEpisode.title
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playStream(ep: Episode) {
|
||||||
|
Log.d(javaClass.name, "Starting Player with mediaId: ${media.id}")
|
||||||
|
(activity as MainActivity).startPlayer(media.id, ep.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateWatchedState(ep: Episode) {
|
||||||
|
AoDParser.sendCallback(ep.watchedCallback)
|
||||||
|
adapterRecEpisodes.updateWatchedState(true, media.episodes.indexOf(ep))
|
||||||
adapterRecEpisodes.notifyDataSetChanged()
|
adapterRecEpisodes.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun playStream(url: String) {
|
|
||||||
Log.d(javaClass.name, "Playing stream: $url")
|
|
||||||
(activity as MainActivity).startPlayer(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -6,41 +6,38 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.SearchView
|
import android.widget.SearchView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import kotlinx.android.synthetic.main.fragment_search.*
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.teapod.MainActivity
|
import org.mosad.teapod.MainActivity
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.databinding.FragmentSearchBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
import org.mosad.teapod.util.decoration.MediaItemDecoration
|
import org.mosad.teapod.util.decoration.MediaItemDecoration
|
||||||
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
||||||
|
|
||||||
class SearchFragment : Fragment() {
|
class SearchFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentSearchBinding
|
||||||
private var adapter : MediaItemAdapter? = null
|
private var adapter : MediaItemAdapter? = null
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
return inflater.inflate(R.layout.fragment_search, container, false)
|
binding = FragmentSearchBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
if (AoDParser.mediaList.isEmpty()) {
|
|
||||||
AoDParser().listAnimes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// create and set the adapter, needs context
|
// create and set the adapter, needs context
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
context?.let {
|
context?.let {
|
||||||
adapter = MediaItemAdapter(AoDParser.itemMediaList)
|
adapter = MediaItemAdapter(AoDParser.itemMediaList)
|
||||||
adapter!!.onItemClick = { mediaId, _ ->
|
adapter!!.onItemClick = { mediaId, _ ->
|
||||||
search_text.clearFocus()
|
binding.searchText.clearFocus()
|
||||||
(activity as MainActivity).showMediaFragment(mediaId)
|
(activity as MainActivity).showMediaFragment(mediaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
recycler_media_search.adapter = adapter
|
binding.recyclerMediaSearch.adapter = adapter
|
||||||
recycler_media_search.addItemDecoration(MediaItemDecoration(9))
|
binding.recyclerMediaSearch.addItemDecoration(MediaItemDecoration(9))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,7 +46,7 @@ class SearchFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initActions() {
|
private fun initActions() {
|
||||||
search_text.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
binding.searchText.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
adapter?.filter?.filter(query)
|
adapter?.filter?.filter(query)
|
||||||
adapter?.notifyDataSetChanged()
|
adapter?.notifyDataSetChanged()
|
||||||
|
@ -6,6 +6,11 @@ class DataTypes {
|
|||||||
MOVIE,
|
MOVIE,
|
||||||
TVSHOW
|
TVSHOW
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class Theme(val str: String) {
|
||||||
|
LIGHT("Light"),
|
||||||
|
DARK("Dark")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,7 +23,6 @@ data class ItemMedia(
|
|||||||
val posterUrl: String
|
val posterUrl: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO the episodes workflow could use a clean up/rework
|
* TODO the episodes workflow could use a clean up/rework
|
||||||
*/
|
*/
|
||||||
@ -27,7 +31,7 @@ data class Media(
|
|||||||
val link: String,
|
val link: String,
|
||||||
val type: DataTypes.MediaType,
|
val type: DataTypes.MediaType,
|
||||||
val info: Info = Info(),
|
val info: Info = Info(),
|
||||||
var episodes: List<Episode> = listOf()
|
var episodes: ArrayList<Episode> = arrayListOf()
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Info(
|
data class Info(
|
||||||
@ -40,10 +44,15 @@ data class Info(
|
|||||||
var episodesCount: Int = 0
|
var episodesCount: Int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if secStreamOmU == true, then a secondary stream is present
|
||||||
|
*/
|
||||||
data class Episode(
|
data class Episode(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
var title: String = "",
|
var title: String = "",
|
||||||
var streamUrl: String = "",
|
var priStreamUrl: String = "",
|
||||||
|
var secStreamUrl: String = "",
|
||||||
|
var secStreamOmU: Boolean = false,
|
||||||
var posterUrl: String = "",
|
var posterUrl: String = "",
|
||||||
var description: String = "",
|
var description: String = "",
|
||||||
var shortDesc: String = "",
|
var shortDesc: String = "",
|
||||||
@ -60,3 +69,17 @@ data class TMDBResponse(
|
|||||||
val backdropUrl: String = "",
|
val backdropUrl: String = "",
|
||||||
var runtime: Int = 0
|
var runtime: Int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class AoDObject(val playlist: List<Playlist>)
|
||||||
|
|
||||||
|
data class Playlist(
|
||||||
|
val sources: List<Source>,
|
||||||
|
val image: String,
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val mediaid: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Source(
|
||||||
|
val file: String = ""
|
||||||
|
)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package org.mosad.teapod.util
|
package org.mosad.teapod.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.JsonParser
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This controller contains the logic for permanently saved data.
|
* This controller contains the logic for permanently saved data.
|
||||||
@ -21,17 +23,21 @@ object StorageController {
|
|||||||
|
|
||||||
if (!file.exists()) runBlocking { saveMyList(context).join() }
|
if (!file.exists()) runBlocking { saveMyList(context).join() }
|
||||||
|
|
||||||
|
try {
|
||||||
myList.clear()
|
myList.clear()
|
||||||
myList.addAll(
|
myList.addAll(JsonParser.parseString(file.readText()).asJsonArray.map { it.asInt }.distinct())
|
||||||
GsonBuilder().create().fromJson(file.readText(), ArrayList<Int>().javaClass)
|
} catch (ex: Exception) {
|
||||||
)
|
myList.clear()
|
||||||
|
Log.e(javaClass.name, "Parsing of My-List failed.")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveMyList(context: Context): Job {
|
fun saveMyList(context: Context): Job {
|
||||||
val file = File(context.filesDir, fileNameMyList)
|
val file = File(context.filesDir, fileNameMyList)
|
||||||
|
|
||||||
return GlobalScope.launch(Dispatchers.IO) {
|
return GlobalScope.launch(Dispatchers.IO) {
|
||||||
file.writeText(Gson().toJson(myList))
|
file.writeText(Gson().toJson(myList.distinct()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,7 @@ package org.mosad.teapod.util
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import org.mosad.teapod.util.DataTypes.MediaType
|
import org.mosad.teapod.util.DataTypes.MediaType
|
||||||
@ -22,12 +20,12 @@ class TMDBApiController {
|
|||||||
|
|
||||||
private val imageUrl = "https://image.tmdb.org/t/p/w500"
|
private val imageUrl = "https://image.tmdb.org/t/p/w500"
|
||||||
|
|
||||||
fun search(title: String, type: MediaType): TMDBResponse {
|
suspend fun search(title: String, type: MediaType): TMDBResponse {
|
||||||
val searchTerm = title.replace("(Sub)", "").trim()
|
val searchTerm = title.replace("(Sub)", "").trim()
|
||||||
|
|
||||||
return when (type) {
|
return when (type) {
|
||||||
MediaType.MOVIE -> searchMovie(searchTerm)
|
MediaType.MOVIE -> searchMovie(searchTerm).await()
|
||||||
MediaType.TVSHOW -> searchTVShow(searchTerm)
|
MediaType.TVSHOW -> searchTVShow(searchTerm).await()
|
||||||
else -> {
|
else -> {
|
||||||
Log.e(javaClass.name, "Wrong Type: $type")
|
Log.e(javaClass.name, "Wrong Type: $type")
|
||||||
TMDBResponse()
|
TMDBResponse()
|
||||||
@ -36,14 +34,14 @@ class TMDBApiController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchTVShow(title: String) = runBlocking {
|
fun searchTVShow(title: String): Deferred<TMDBResponse> {
|
||||||
val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
||||||
|
|
||||||
GlobalScope.async {
|
return GlobalScope.async {
|
||||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||||
//println(response)
|
//println(response)
|
||||||
|
|
||||||
return@async if (response.get("total_results").asInt > 0) {
|
if (response.get("total_results").asInt > 0) {
|
||||||
response.get("results").asJsonArray.first().asJsonObject.let {
|
response.get("results").asJsonArray.first().asJsonObject.let {
|
||||||
val id = getStringNotNull(it, "id").toInt()
|
val id = getStringNotNull(it, "id").toInt()
|
||||||
val overview = getStringNotNull(it, "overview")
|
val overview = getStringNotNull(it, "overview")
|
||||||
@ -55,18 +53,17 @@ class TMDBApiController {
|
|||||||
} else {
|
} else {
|
||||||
TMDBResponse()
|
TMDBResponse()
|
||||||
}
|
}
|
||||||
}.await()
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchMovie(title: String) = runBlocking {
|
fun searchMovie(title: String): Deferred<TMDBResponse> {
|
||||||
val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
||||||
|
|
||||||
GlobalScope.async {
|
return GlobalScope.async {
|
||||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||||
//println(response)
|
//println(response)
|
||||||
|
|
||||||
return@async if (response.get("total_results").asInt > 0) {
|
if (response.get("total_results").asInt > 0) {
|
||||||
response.get("results").asJsonArray.first().asJsonObject.let {
|
response.get("results").asJsonArray.first().asJsonObject.let {
|
||||||
val id = getStringNotNull(it,"id").toInt()
|
val id = getStringNotNull(it,"id").toInt()
|
||||||
val overview = getStringNotNull(it,"overview")
|
val overview = getStringNotNull(it,"overview")
|
||||||
@ -79,9 +76,7 @@ class TMDBApiController {
|
|||||||
} else {
|
} else {
|
||||||
TMDBResponse()
|
TMDBResponse()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}.await()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,13 +87,8 @@ class TMDBApiController {
|
|||||||
|
|
||||||
GlobalScope.async {
|
GlobalScope.async {
|
||||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||||
//println(response)
|
|
||||||
|
|
||||||
val runtime = getStringNotNull(response,"runtime").toInt()
|
return@async getStringNotNull(response,"runtime").toInt()
|
||||||
println(runtime)
|
|
||||||
|
|
||||||
|
|
||||||
return@async runtime
|
|
||||||
}.await()
|
}.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,50 +1,49 @@
|
|||||||
package org.mosad.teapod.util.adapter
|
package org.mosad.teapod.util.adapter
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import jp.wasabeef.glide.transformations.RoundedCornersTransformation
|
import jp.wasabeef.glide.transformations.RoundedCornersTransformation
|
||||||
import kotlinx.android.synthetic.main.item_episode.view.*
|
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
|
import org.mosad.teapod.databinding.ItemEpisodeBinding
|
||||||
import org.mosad.teapod.util.Episode
|
import org.mosad.teapod.util.Episode
|
||||||
|
|
||||||
class EpisodeItemAdapter(private val episodes: List<Episode>) : RecyclerView.Adapter<EpisodeItemAdapter.MyViewHolder>() {
|
class EpisodeItemAdapter(private val episodes: List<Episode>) : RecyclerView.Adapter<EpisodeItemAdapter.EpisodeViewHolder>() {
|
||||||
|
|
||||||
var onItemClick: ((String, Int) -> Unit)? = null
|
|
||||||
var onImageClick: ((String, Int) -> Unit)? = null
|
var onImageClick: ((String, Int) -> Unit)? = null
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_episode, parent, false)
|
return EpisodeViewHolder(ItemEpisodeBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||||
|
|
||||||
return MyViewHolder(view)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: EpisodeViewHolder, position: Int) {
|
||||||
val context = holder.view.context
|
val context = holder.binding.root.context
|
||||||
|
val ep = episodes[position]
|
||||||
|
|
||||||
holder.view.text_episode_title.text = context.getString(
|
val titleText = if (ep.priStreamUrl.isEmpty() && ep.secStreamOmU) {
|
||||||
R.string.component_episode_title,
|
context.getString(R.string.component_episode_title_sub, ep.number, ep.description)
|
||||||
episodes[position].number,
|
} else {
|
||||||
episodes[position].description
|
context.getString(R.string.component_episode_title, ep.number, ep.description)
|
||||||
)
|
}
|
||||||
holder.view.text_episode_desc.text = episodes[position].shortDesc
|
|
||||||
|
holder.binding.textEpisodeTitle.text = titleText
|
||||||
|
holder.binding.textEpisodeDesc.text = ep.shortDesc
|
||||||
|
|
||||||
if (episodes[position].posterUrl.isNotEmpty()) {
|
if (episodes[position].posterUrl.isNotEmpty()) {
|
||||||
Glide.with(context).load(episodes[position].posterUrl)
|
Glide.with(context).load(ep.posterUrl)
|
||||||
.apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(10, 0)))
|
.apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(10, 0)))
|
||||||
.into(holder.view.image_episode)
|
.into(holder.binding.imageEpisode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (episodes[position].watched) {
|
if (ep.watched) {
|
||||||
holder.view.image_watched.setImageDrawable(
|
holder.binding.imageWatched.setImageDrawable(
|
||||||
ContextCompat.getDrawable(context, R.drawable.ic_baseline_check_circle_24)
|
ContextCompat.getDrawable(context, R.drawable.ic_baseline_check_circle_24)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
holder.view.image_watched.setImageDrawable(null)
|
holder.binding.imageWatched.setImageDrawable(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,13 +55,9 @@ class EpisodeItemAdapter(private val episodes: List<Episode>) : RecyclerView.Ada
|
|||||||
episodes[position].watched = watched
|
episodes[position].watched = watched
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
inner class EpisodeViewHolder(val binding: ItemEpisodeBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
view.setOnClickListener {
|
binding.imageEpisode.setOnClickListener {
|
||||||
onItemClick?.invoke(episodes[adapterPosition].title, adapterPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
view.image_episode.setOnClickListener {
|
|
||||||
onImageClick?.invoke(episodes[adapterPosition].title, adapterPosition)
|
onImageClick?.invoke(episodes[adapterPosition].title, adapterPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,29 @@
|
|||||||
package org.mosad.teapod.util.adapter
|
package org.mosad.teapod.util.adapter
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Filter
|
import android.widget.Filter
|
||||||
import android.widget.Filterable
|
import android.widget.Filterable
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import kotlinx.android.synthetic.main.item_media.view.*
|
import org.mosad.teapod.databinding.ItemMediaBinding
|
||||||
import org.mosad.teapod.R
|
|
||||||
import org.mosad.teapod.util.ItemMedia
|
import org.mosad.teapod.util.ItemMedia
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class MediaItemAdapter(private val media: List<ItemMedia>) : RecyclerView.Adapter<MediaItemAdapter.ViewHolder>(), Filterable {
|
class MediaItemAdapter(private val media: List<ItemMedia>) : RecyclerView.Adapter<MediaItemAdapter.MediaViewHolder>(), Filterable {
|
||||||
|
|
||||||
var onItemClick: ((Int, Int) -> Unit)? = null
|
var onItemClick: ((Int, Int) -> Unit)? = null
|
||||||
private val filter = MediaFilter()
|
private val filter = MediaFilter()
|
||||||
private var filteredMedia = media.map { it.copy() }
|
private var filteredMedia = media.map { it.copy() }
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaItemAdapter.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaItemAdapter.MediaViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_media, parent, false)
|
return MediaViewHolder(ItemMediaBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||||
|
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: MediaItemAdapter.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: MediaItemAdapter.MediaViewHolder, position: Int) {
|
||||||
holder.view.apply {
|
holder.binding.root.apply {
|
||||||
text_title.text = filteredMedia[position].title
|
holder.binding.textTitle.text = filteredMedia[position].title
|
||||||
Glide.with(context).load(filteredMedia[position].posterUrl).into(image_poster)
|
Glide.with(context).load(filteredMedia[position].posterUrl).into(holder.binding.imagePoster)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,9 +35,9 @@ class MediaItemAdapter(private val media: List<ItemMedia>) : RecyclerView.Adapte
|
|||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
inner class MediaViewHolder(val binding: ItemMediaBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
view.setOnClickListener {
|
binding.root.setOnClickListener {
|
||||||
onItemClick?.invoke(filteredMedia[adapterPosition].id, adapterPosition)
|
onItemClick?.invoke(filteredMedia[adapterPosition].id, adapterPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
app/src/main/res/color/bottom_nav_item_tint.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="?attr/colorPrimary" android:state_checked="true"/>
|
||||||
|
<item android:color="?attr/iconAction"/>
|
||||||
|
</selector>
|
@ -1,30 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
||||||
android:strokeWidth="1"
|
|
||||||
android:strokeColor="#00000000" />
|
|
||||||
</vector>
|
|
12
app/src/main/res/drawable/bg_splash.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item android:drawable="@android:color/black"/>
|
||||||
|
|
||||||
|
<item android:gravity="center" android:width="144dp" android:height="144dp">
|
||||||
|
<bitmap
|
||||||
|
android:gravity="fill_horizontal|fill_vertical"
|
||||||
|
android:src="@drawable/ic_splash_logo"/>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
</layer-list>
|
10
app/src/main/res/drawable/ic_baseline_arrow_back_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="#FFFFFF"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_baseline_autorenew_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,6v3l4,-4 -4,-4v3c-4.42,0 -8,3.58 -8,8 0,1.57 0.46,3.03 1.24,4.26L6.7,14.8c-0.45,-0.83 -0.7,-1.79 -0.7,-2.8 0,-3.31 2.69,-6 6,-6zM18.76,7.74L17.3,9.2c0.44,0.84 0.7,1.79 0.7,2.8 0,3.31 -2.69,6 -6,6v-3l-4,4 4,4v-3c4.42,0 8,-3.58 8,-8 0,-1.57 -0.46,-3.03 -1.24,-4.26z" />
|
||||||
|
</vector>
|
15
app/src/main/res/drawable/ic_baseline_forward_10_24.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M18,13c0,3.31 -2.69,6 -6,6s-6,-2.69 -6,-6s2.69,-6 6,-6v4l5,-5l-5,-5v4c-4.42,0 -8,3.58 -8,8c0,4.42 3.58,8 8,8s8,-3.58 8,-8H18z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M10.86,15.94l0,-4.27l-0.09,0l-1.77,0.63l0,0.69l1.01,-0.31l0,3.26z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12.25,13.44v0.74c0,1.9 1.31,1.82 1.44,1.82c0.14,0 1.44,0.09 1.44,-1.82v-0.74c0,-1.9 -1.31,-1.82 -1.44,-1.82C13.55,11.62 12.25,11.53 12.25,13.44zM14.29,13.32v0.97c0,0.77 -0.21,1.03 -0.59,1.03c-0.38,0 -0.6,-0.26 -0.6,-1.03v-0.97c0,-0.75 0.22,-1.01 0.59,-1.01C14.07,12.3 14.29,12.57 14.29,13.32z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_baseline_forward_24.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M18,13c0,3.31 -2.69,6 -6,6s-6,-2.69 -6,-6s2.69,-6 6,-6v4l5,-5l-5,-5v4c-4.42,0 -8,3.58 -8,8c0,4.42 3.58,8 8,8c4.42,0 8,-3.58 8,-8H18z" />
|
||||||
|
</vector>
|
15
app/src/main/res/drawable/ic_baseline_rewind_10_24.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M11.99,5V1l-5,5l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6s-6,-2.69 -6,-6h-2c0,4.42 3.58,8 8,8s8,-3.58 8,-8S16.41,5 11.99,5z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M10.89,16h-0.85v-3.26l-1.01,0.31v-0.69l1.77,-0.63h0.09V16z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M15.17,14.24c0,0.32 -0.03,0.6 -0.1,0.82s-0.17,0.42 -0.29,0.57s-0.28,0.26 -0.45,0.33s-0.37,0.1 -0.59,0.1s-0.41,-0.03 -0.59,-0.1s-0.33,-0.18 -0.46,-0.33s-0.23,-0.34 -0.3,-0.57s-0.11,-0.5 -0.11,-0.82V13.5c0,-0.32 0.03,-0.6 0.1,-0.82s0.17,-0.42 0.29,-0.57s0.28,-0.26 0.45,-0.33s0.37,-0.1 0.59,-0.1s0.41,0.03 0.59,0.1c0.18,0.07 0.33,0.18 0.46,0.33s0.23,0.34 0.3,0.57s0.11,0.5 0.11,0.82V14.24zM14.32,13.38c0,-0.19 -0.01,-0.35 -0.04,-0.48s-0.07,-0.23 -0.12,-0.31s-0.11,-0.14 -0.19,-0.17s-0.16,-0.05 -0.25,-0.05s-0.18,0.02 -0.25,0.05s-0.14,0.09 -0.19,0.17s-0.09,0.18 -0.12,0.31s-0.04,0.29 -0.04,0.48v0.97c0,0.19 0.01,0.35 0.04,0.48s0.07,0.24 0.12,0.32s0.11,0.14 0.19,0.17s0.16,0.05 0.25,0.05s0.18,-0.02 0.25,-0.05s0.14,-0.09 0.19,-0.17s0.09,-0.19 0.11,-0.32s0.04,-0.29 0.04,-0.48V13.38z"/>
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_baseline_rewind_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,5V1L7,6l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6 -6,-2.69 -6,-6H4c0,4.42 3.58,8 8,8s8,-3.58 8,-8 -3.58,-8 -8,-8z"/>
|
||||||
|
</vector>
|
@ -3,7 +3,7 @@
|
|||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24"
|
||||||
android:tint="?attr/colorControlNormal">
|
android:tint="?attr/iconAction">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||||
|
5
app/src/main/res/drawable/ic_baseline_skip_next_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_baseline_style_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M2.53,19.65l1.34,0.56v-9.03l-2.43,5.86c-0.41,1.02 0.08,2.19 1.09,2.61zM22.03,15.95L17.07,3.98c-0.31,-0.75 -1.04,-1.21 -1.81,-1.23 -0.26,0 -0.53,0.04 -0.79,0.15L7.1,5.95c-0.75,0.31 -1.21,1.03 -1.23,1.8 -0.01,0.27 0.04,0.54 0.15,0.8l4.96,11.97c0.31,0.76 1.05,1.22 1.83,1.23 0.26,0 0.52,-0.05 0.77,-0.15l7.36,-3.05c1.02,-0.42 1.51,-1.59 1.09,-2.6zM7.88,8.75c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM5.88,19.75c0,1.1 0.9,2 2,2h1.45l-3.45,-8.34v6.34z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_baseline_subtitles_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM4,12h4v2L4,14v-2zM14,18L4,18v-2h10v2zM20,18h-4v-2h4v2zM20,14L10,14v-2h10v2z"/>
|
||||||
|
</vector>
|
@ -1,10 +1,5 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
android:width="24dp"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:height="24dp"
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
android:viewportWidth="24"
|
<path android:fillColor="@android:color/white" android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM12,14.5v-9l6,4.5 -6,4.5z"/>
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM12,14.5v-9l6,4.5 -6,4.5z"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
|
19
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group android:scaleX="0.051679686"
|
||||||
|
android:scaleY="0.051679686"
|
||||||
|
android:translateX="27.54"
|
||||||
|
android:translateY="38.90954">
|
||||||
|
<path
|
||||||
|
android:pathData="m850.19,372.71c87.88,-11.01 119.04,-84.97 123.1,-99.87 4.06,-14.89 24.91,-80.57 11.92,-129.36 -12.99,-48.79 -34.36,-72.36 -58.62,-77.25 -24.25,-4.9 -50.59,10.51 -65,32.81 -14.41,22.3 -14.68,45.14 -14.78,55.29 -0.11,10.15 0.76,23.2 -3.37,33.29 -4.13,10.09 3.23,25.71 6.04,35.23 2.81,9.52 9.67,82.62 5.78,115.57 -3.89,32.95 -5.07,34.29 -5.07,34.29zM0.4,23.58C55.81,77.29 56.45,120.86 56.08,132.92c-0.36,12.06 4.77,130.59 11.47,150.76 4.42,13.3 12.11,50.16 41.78,74.48 25.51,20.91 58.65,31.38 58.65,31.38 0,0 36.42,78.46 78.83,108.64 31.56,22.46 39.61,23.74 46.5,35.55 6.18,10.6 93.56,62.62 275.1,47.23 127.29,-10.79 138.56,-44.3 138.56,-44.3 0,0 49.41,-21.9 101.15,-80.43 12.87,-14.56 4.41,-13.21 28.57,-17.79 24.16,-4.58 138.01,-45.58 170.66,-154.36C1039.99,175.32 1017.81,96.01 994.52,69.12 971.23,42.22 931.6,24.18 912.25,24.93c-18.47,0.71 -44.78,4.24 -80.21,46.87 -35.43,42.62 -28.94,37.4 -39.36,41.73 -6.82,2.83 -5.68,3.91 -26.75,-11.65 -20.23,-14.93 -28.9,-21.24 -43.38,-27.24 -7.96,-3.3 2.05,-5.55 2.59,-19.48 0.54,-13.93 2.4,-23.51 -17.32,-23.77 -19.72,-0.26 -408.02,0.21 -408.02,0.21 0,0 -18.8,-1.29 -7.79,24.82 4.2,9.94 -1.45,6.43 -33.27,25.85 -31.82,19.42 -55.58,34.4 -72.28,66.09 -8.43,16 -22.91,23.02 -27.97,8.05C153.44,141.43 125.2,48.96 105.17,23.22 85.56,-1.97 77.8,0.26 77.8,0.26Z"
|
||||||
|
android:strokeLineJoin="miter"
|
||||||
|
android:strokeWidth="0.41878"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeLineCap="butt"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
BIN
app/src/main/res/drawable/ic_splash_logo.png
Normal file
After Width: | Height: | Size: 10 KiB |
3
app/src/main/res/drawable/ripple_background.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="?attr/colorControlHighlight" />
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<solid android:color="#B0B0B0"/>
|
<solid android:color="?iconNoAction"/>
|
||||||
<corners android:radius="3dp"/>
|
<corners android:radius="3dp"/>
|
||||||
</shape>
|
</shape>
|
@ -9,9 +9,8 @@
|
|||||||
android:id="@+id/nav_view"
|
android:id="@+id/nav_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="0dp"
|
android:background="?themeSecondary"
|
||||||
android:layout_marginEnd="0dp"
|
app:itemIconTint="@color/bottom_nav_item_tint"
|
||||||
android:background="?android:attr/windowBackground"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
@ -1,25 +1,92 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout 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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/player_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:keepScreenOn="true"
|
|
||||||
android:background="#000000"
|
android:background="#000000"
|
||||||
tools:context=".PlayerActivity">
|
android:keepScreenOn="true"
|
||||||
|
tools:context=".player.PlayerActivity">
|
||||||
|
|
||||||
<com.google.android.exoplayer2.ui.PlayerView
|
<com.google.android.exoplayer2.ui.StyledPlayerView
|
||||||
android:id="@+id/video_view"
|
android:id="@+id/video_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center" />
|
android:layout_gravity="center"
|
||||||
<!-- app:controller_layout_id="@layout/player_custom_control"/>-->
|
android:animateLayoutChanges="true"
|
||||||
|
android:foreground="@drawable/ripple_background"
|
||||||
|
app:controller_layout_id="@layout/player_controls"
|
||||||
|
app:fastforward_increment="10000"
|
||||||
|
app:rewind_increment="10000" />
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.ProgressIndicator
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
android:id="@+id/loading"
|
android:id="@+id/loading"
|
||||||
style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"
|
|
||||||
android:layout_width="70dp"
|
android:layout_width="70dp"
|
||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:indicatorColor="@color/exo_white"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/exo_double_tap_indicator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<org.mosad.teapod.ui.components.RewindButton
|
||||||
|
android:id="@+id/rwd_10_indicator"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="1dp" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<org.mosad.teapod.ui.components.FastForwardButton
|
||||||
|
android:id="@+id/ffwd_10_indicator"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_next_ep"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginBottom="70dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/next_episode"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@android:color/primary_text_light"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:backgroundTint="@color/exo_white"
|
||||||
|
app:iconGravity="textStart" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
24
app/src/main/res/layout/button_fast_forward.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/imageButton"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:background="@drawable/ic_baseline_forward_10_24"
|
||||||
|
android:contentDescription="@string/forward_10" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginStart="42dp"
|
||||||
|
android:text="@string/fwd_10_s"
|
||||||
|
android:textColor="@color/exo_white"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</RelativeLayout>
|
27
app/src/main/res/layout/button_rewind.xml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/imageButton"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:background="@drawable/ic_baseline_rewind_10_24"
|
||||||
|
android:contentDescription="@string/forward_10" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginEnd="42dp"
|
||||||
|
android:text="@string/rwd_10_s"
|
||||||
|
android:textColor="@color/exo_white"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#f5f5f5"
|
android:background="?themePrimary"
|
||||||
tools:context=".ui.fragments.AccountFragment">
|
tools:context=".ui.fragments.AccountFragment">
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@ -21,7 +21,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:background="#ffffff"
|
android:background="?themeSecondary"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -31,7 +31,6 @@
|
|||||||
android:paddingStart="7dp"
|
android:paddingStart="7dp"
|
||||||
android:paddingEnd="7dp"
|
android:paddingEnd="7dp"
|
||||||
android:text="@string/account"
|
android:text="@string/account"
|
||||||
android:textColor="@android:color/primary_text_light"
|
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
@ -50,10 +49,10 @@
|
|||||||
android:contentDescription="@string/account"
|
android:contentDescription="@string/account"
|
||||||
android:minWidth="48dp"
|
android:minWidth="48dp"
|
||||||
android:minHeight="48dp"
|
android:minHeight="48dp"
|
||||||
android:padding="5dp"
|
android:padding="9dp"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
android:src="@drawable/ic_baseline_account_box_24"
|
android:src="@drawable/ic_baseline_account_box_24"
|
||||||
app:srcCompat="@drawable/ic_baseline_account_box_24" />
|
app:tint="?iconNoAction" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -66,7 +65,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/account_login_ex"
|
android:text="@string/account_login_ex"
|
||||||
android:textColor="@android:color/primary_text_light"
|
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -75,18 +73,207 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/account_login_desc"
|
android:text="@string/account_login_desc"
|
||||||
android:textColor="@android:color/secondary_text_light" />
|
android:textColor="?textSecondary" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_settings"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:background="?themeSecondary"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_settings"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="7dp"
|
||||||
|
android:paddingEnd="7dp"
|
||||||
|
android:text="@string/settings"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_settings_secondary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="7dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView3"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/settings_secondary"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:padding="9dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/ic_baseline_subtitles_24"
|
||||||
|
app:tint="?iconNoAction" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linearLayout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/switch_secondary"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_settings_secondary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/settings_secondary"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_settings_secondary_desc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="@string/settings_secondary_desc"
|
||||||
|
android:textColor="?textSecondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switch_secondary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_settings_autoplay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="7dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView4"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/settings_autoplay"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:padding="9dp"
|
||||||
|
android:src="@drawable/ic_baseline_autorenew_24"
|
||||||
|
app:tint="?iconNoAction"/>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linearLayout2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/switch_autoplay"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_settings_auoplay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/settings_autoplay"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_settings_auoplay_desc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/settings_autoplay_desc"
|
||||||
|
android:textColor="?textSecondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switch_autoplay"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_theme"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="7dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageViewTheme"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/account"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:padding="9dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/ic_baseline_style_24"
|
||||||
|
app:tint="?iconNoAction" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_theme"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/theme"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_theme_selected"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/theme_light"
|
||||||
|
android:textColor="?textSecondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/linear_info"
|
android:id="@+id/linear_info"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:background="#ffffff"
|
android:background="?themeSecondary"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -96,7 +283,6 @@
|
|||||||
android:paddingStart="7dp"
|
android:paddingStart="7dp"
|
||||||
android:paddingEnd="7dp"
|
android:paddingEnd="7dp"
|
||||||
android:text="@string/info"
|
android:text="@string/info"
|
||||||
android:textColor="@android:color/primary_text_light"
|
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
@ -115,9 +301,10 @@
|
|||||||
android:contentDescription="@string/info"
|
android:contentDescription="@string/info"
|
||||||
android:minWidth="48dp"
|
android:minWidth="48dp"
|
||||||
android:minHeight="48dp"
|
android:minHeight="48dp"
|
||||||
android:padding="5dp"
|
android:padding="9dp"
|
||||||
android:scaleType="fitXY"
|
android:scaleType="fitXY"
|
||||||
app:srcCompat="@drawable/ic_baseline_info_24" />
|
app:srcCompat="@drawable/ic_baseline_info_24"
|
||||||
|
app:tint="?iconNoAction"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -130,7 +317,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/info_about"
|
android:text="@string/info_about"
|
||||||
android:textColor="@android:color/primary_text_light"
|
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -139,7 +325,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/info_about_desc"
|
android:text="@string/info_about_desc"
|
||||||
android:textColor="@android:color/secondary_text_light" />
|
android:textColor="?textSecondary" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -157,10 +343,10 @@
|
|||||||
android:layout_margin="7dp"
|
android:layout_margin="7dp"
|
||||||
android:paddingStart="48dp"
|
android:paddingStart="48dp"
|
||||||
android:paddingEnd="48dp"
|
android:paddingEnd="48dp"
|
||||||
android:text="Licenses"
|
android:text="@string/licenses"
|
||||||
android:textColor="@android:color/primary_text_light"
|
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/ff_test"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#f5f5f5"
|
android:background="?themePrimary"
|
||||||
tools:context=".ui.fragments.HomeFragment">
|
tools:context=".ui.fragments.HomeFragment">
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@ -19,10 +20,11 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="7dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView"
|
android:id="@+id/text_my_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="10dp"
|
android:paddingStart="10dp"
|
||||||
@ -42,20 +44,34 @@
|
|||||||
tools:listitem="@layout/item_media" />
|
tools:listitem="@layout/item_media" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="7dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_new_episodes"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingTop="15dp"
|
||||||
|
android:paddingEnd="5dp"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
|
android:text="@string/new_episodes"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_new_episodes"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_media" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text_home"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textSize="20sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#f5f5f5"
|
android:background="?themePrimary"
|
||||||
tools:context=".ui.fragments.LibraryFragment">
|
tools:context=".ui.fragments.LibraryFragment">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<?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"
|
|
||||||
android:background="#f5f5f5"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true">
|
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.ProgressIndicator
|
|
||||||
android:id="@+id/progressBar2"
|
|
||||||
style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"
|
|
||||||
android:layout_width="70dp"
|
|
||||||
android:layout_height="70dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
</FrameLayout>
|
|
@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#f5f5f5"
|
android:background="?themePrimary"
|
||||||
tools:context=".ui.fragments.MediaFragment">
|
tools:context=".ui.fragments.MediaFragment">
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
@ -36,7 +36,7 @@
|
|||||||
android:layout_height="200dp"
|
android:layout_height="200dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
app:shapeAppearance="@style/ShapeAppearance.Teapod.RoundedPoster"
|
app:shapeAppearance="@style/ShapeAppearance.Teapod.RoundedPoster"
|
||||||
app:srcCompat="@drawable/ic_launcher_background" />
|
tools:src="@drawable/ic_launcher_background" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
@ -60,7 +60,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="7dp"
|
android:layout_marginStart="7dp"
|
||||||
android:background="@drawable/shape_rounden_corner"
|
android:background="@drawable/shape_rounded_corner"
|
||||||
android:paddingStart="3dp"
|
android:paddingStart="3dp"
|
||||||
android:paddingTop="2dp"
|
android:paddingTop="2dp"
|
||||||
android:paddingEnd="3dp"
|
android:paddingEnd="3dp"
|
||||||
@ -86,11 +86,12 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/button_play"
|
android:text="@string/button_play"
|
||||||
android:textAllCaps="false"
|
android:textAllCaps="false"
|
||||||
android:textColor="@android:color/primary_text_dark"
|
android:textColor="?themePrimary"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:backgroundTint="#4A4141"
|
app:backgroundTint="?buttonBackground"
|
||||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||||
app:iconGravity="textStart" />
|
app:iconGravity="textStart"
|
||||||
|
app:iconTint="?themePrimary" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_title"
|
android:id="@+id/text_title"
|
||||||
@ -134,13 +135,16 @@
|
|||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:src="@drawable/ic_baseline_add_24"
|
android:src="@drawable/ic_baseline_add_24"
|
||||||
app:tint="#4A4141" />
|
app:tint="?buttonBackground"
|
||||||
|
android:contentDescription="@string/my_list" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_my_list_action"
|
android:id="@+id/text_my_list_action"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/my_list" />
|
android:text="@string/my_list"
|
||||||
|
android:textColor="?textSecondary"
|
||||||
|
android:textSize="12sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@ -156,4 +160,21 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/frame_loading"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?themePrimary"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/loadingIndicator"
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:layout_height="70dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:indicatorColor="?colorPrimary"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -4,18 +4,19 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#f5f5f5"
|
android:background="?themePrimary"
|
||||||
tools:context=".ui.fragments.SearchFragment">
|
tools:context=".ui.fragments.SearchFragment">
|
||||||
|
|
||||||
<SearchView
|
<SearchView
|
||||||
android:id="@+id/search_text"
|
android:id="@+id/search_text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:background="#FFFFFF"
|
android:background="?themeSecondary"
|
||||||
android:elevation="8dp"
|
android:elevation="8dp"
|
||||||
android:iconifiedByDefault="false"
|
android:iconifiedByDefault="false"
|
||||||
android:paddingBottom="5dp"
|
android:paddingBottom="5dp"
|
||||||
android:queryHint="@string/search_hint"
|
android:queryHint="@string/search_hint"
|
||||||
|
android:searchIcon="@drawable/ic_baseline_search_24"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
@ -27,13 +28,13 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:padding="3dp"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:padding="3dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/search_text"
|
app:layout_constraintTop_toBottomOf="@+id/search_text"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
|
||||||
app:spanCount="2"
|
app:spanCount="2"
|
||||||
tools:listitem="@layout/item_media">
|
tools:listitem="@layout/item_media">
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
android:layout_marginStart="7dp"
|
android:layout_marginStart="7dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/component_episode_title"
|
android:text="@string/component_episode_title"
|
||||||
|
android:textColor="?textPrimary"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@ -52,13 +53,15 @@
|
|||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
android:layout_margin="2dp"
|
android:layout_margin="2dp"
|
||||||
android:contentDescription="@string/component_watched_desc"
|
android:contentDescription="@string/component_watched_desc"
|
||||||
app:srcCompat="@drawable/ic_baseline_check_circle_24" />
|
app:srcCompat="@drawable/ic_baseline_check_circle_24"
|
||||||
|
app:tint="?iconNoAction" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_episode_desc"
|
android:id="@+id/text_episode_desc"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:maxLines="2"
|
android:ellipsize="end"
|
||||||
android:ellipsize="end"/>
|
android:maxLines="3"
|
||||||
|
android:textColor="?textSecondary" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -1,9 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="195dp"
|
android:layout_width="195dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:backgroundTint="#FFFFFF"
|
android:backgroundTint="?themeSecondary"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:cardCornerRadius="7dp"
|
app:cardCornerRadius="7dp"
|
||||||
app:cardElevation="4dp">
|
app:cardElevation="4dp">
|
||||||
@ -23,7 +24,7 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:srcCompat="@color/md_disabled_text_dark_theme" />
|
tools:srcCompat="@color/md_disabled_text_dark_theme" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_title"
|
android:id="@+id/text_title"
|
||||||
|
147
app/src/main/res/layout/player_controls.xml
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout 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="#73000000">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/exo_top_controls"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exo_close_player"
|
||||||
|
style="@style/ExoStyledControls.Button.Center"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:contentDescription="@string/close_player"
|
||||||
|
android:padding="10dp"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_arrow_back_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exo_text_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="44dp"
|
||||||
|
android:text="@string/text_title_ex"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/exo_white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/exo_main_controls"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<org.mosad.teapod.ui.components.RewindButton
|
||||||
|
android:id="@+id/rwd_10"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exo_play_pause"
|
||||||
|
style="@style/ExoStyledControls.Button.Center.PlayPause"
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:contentDescription="@string/play_pause" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<org.mosad.teapod.ui.components.FastForwardButton
|
||||||
|
android:id="@+id/ffwd_10"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/exo_time_controls"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginBottom="@dimen/exo_styled_progress_margin_bottom">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/exo_progress_placeholder"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/exo_styled_progress_layout_height"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/exo_remaining"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exo_remaining"
|
||||||
|
style="@style/ExoStyledControls.TimeText.Position"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/exo_bottom_controls"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginBottom="7dp">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_next_ep_c"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/episode"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_baseline_skip_next_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_episodes"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="7dp"
|
||||||
|
android:text="@string/episodes"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_baseline_video_library_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/button_next_ep_c"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 989 B |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 10 KiB |
@ -60,4 +60,9 @@
|
|||||||
<copyright>Copyright 2020 Wasabeef</copyright>
|
<copyright>Copyright 2020 Wasabeef</copyright>
|
||||||
<license>Apache Software License 2.0</license>
|
<license>Apache Software License 2.0</license>
|
||||||
</notice>
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>The Movie Database API</name>
|
||||||
|
<url>https://www.themoviedb.org</url>
|
||||||
|
<copyright>This product uses the TMDb API but is not endorsed or certified by TMDb</copyright>
|
||||||
|
</notice>
|
||||||
</notices>
|
</notices>
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<!-- home fragment -->
|
<!-- home fragment -->
|
||||||
<string name="my_list">Meine Liste</string>
|
<string name="my_list">Meine Liste</string>
|
||||||
|
<string name="new_episodes">Neue Episoden</string>
|
||||||
|
|
||||||
<!-- search fragment -->
|
<!-- search fragment -->
|
||||||
<string name="search_hint">Suche nach Filmen und Serien</string>
|
<string name="search_hint">Suche nach Filmen und Serien</string>
|
||||||
@ -15,15 +16,33 @@
|
|||||||
<string name="button_play">Abspielen</string>
|
<string name="button_play">Abspielen</string>
|
||||||
<string name="text_episodes_count">%1$d Episoden</string>
|
<string name="text_episodes_count">%1$d Episoden</string>
|
||||||
<string name="text_runtime">%1$d Minuten</string>
|
<string name="text_runtime">%1$d Minuten</string>
|
||||||
<string name="component_episode_title">Episode %1$d %2$s</string>
|
<string name="component_episode_title">Flg. %1$d %2$s</string>
|
||||||
|
<string name="component_episode_title_sub">Flg. %1$d %2$s (OmU)</string>
|
||||||
|
|
||||||
<!-- settings fragment -->
|
<!-- settings fragment -->
|
||||||
<string name="account">Account</string>
|
<string name="account">Account</string>
|
||||||
<string name="account_login_desc">Zum bearbeiten tippen</string>
|
<string name="account_login_desc">Zum bearbeiten tippen</string>
|
||||||
<string name="info">Info</string>
|
<string name="info">Info</string>
|
||||||
<string name="info_about_desc">Version %1$s (%2$s)</string>
|
<string name="info_about_desc">Version %1$s (%2$s)</string>
|
||||||
<string name="info_about_dialog">Diese App wird unter den Bedingungen der GNU GPL 3 oder höher zur Verfügung gestellt. Weiter Informationen findest du unter: \ngit.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string>
|
<string name="info_about_dialog">Diese App wird unter den Bedingungen der GNU GPL 3 oder höher zur Verfügung gestellt. Weiter Informationen findest du unter: git.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string>
|
||||||
<string name="licenses">Lizenzen</string>
|
<string name="licenses">Lizenzen</string>
|
||||||
|
<string name="settings">Einstellungen</string>
|
||||||
|
<string name="settings_secondary">Bevorzuge alternativen Stream</string>
|
||||||
|
<string name="settings_secondary_desc">Untertitle-Stream verwenden, sofern vorhanden</string>
|
||||||
|
<string name="settings_autoplay">Autoplay</string>
|
||||||
|
<string name="settings_autoplay_desc">Nächste Episode automatisch abspielen</string>
|
||||||
|
<string name="theme">Design</string>
|
||||||
|
<string name="theme_light">Hell</string>
|
||||||
|
<string name="theme_dark">Dunkel</string>
|
||||||
|
|
||||||
|
<!-- player -->
|
||||||
|
<string name="close_player">Player schließen</string>
|
||||||
|
<string name="rewind_10">10 Sekunden zurück</string>
|
||||||
|
<string name="play_pause">Abspielen/Pause</string>
|
||||||
|
<string name="forward_10">10 Sekunden vorwärts</string>
|
||||||
|
<string name="next_episode">Nächste Folge</string>
|
||||||
|
<string name="episode">Folge</string>
|
||||||
|
<string name="episodes">Folgen</string>
|
||||||
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="save">speichern</string>
|
<string name="save">speichern</string>
|
||||||
|
10
app/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<attr format="color" name="themePrimary"/>
|
||||||
|
<attr format="color" name="themeSecondary"/>
|
||||||
|
<attr format="color" name="textPrimary"/>
|
||||||
|
<attr format="color" name="textSecondary"/>
|
||||||
|
<attr format="color" name="iconAction"/>
|
||||||
|
<attr format="color" name="iconNoAction"/>
|
||||||
|
<attr format="color" name="buttonBackground"/>
|
||||||
|
</resources>
|
@ -1,6 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="colorPrimary">#6200EE</color>
|
<!-- base theme colors -->
|
||||||
<color name="colorPrimaryDark">#3700B3</color>
|
<color name="colorPrimary">#66aa00</color>
|
||||||
<color name="colorAccent">#03DAC5</color>
|
<color name="colorPrimaryLight">#99dc45</color>
|
||||||
|
<color name="colorPrimaryDark">#317a00</color>
|
||||||
|
<color name="colorAccent">#607d8b</color>
|
||||||
|
|
||||||
|
<!-- light theme colors -->
|
||||||
|
<color name="themePrimaryLight">#f7f7f7</color>
|
||||||
|
<color name="themeSecondaryLight">#ffffff</color>
|
||||||
|
<color name="textPrimaryLight">#de000000</color>
|
||||||
|
<color name="textSecondaryLight">#99000000</color>
|
||||||
|
<color name="iconActionLight">#99000000</color>
|
||||||
|
<color name="iconNoActionLight">#66000000</color>
|
||||||
|
<color name="buttonBackgroundLight">#000000</color>
|
||||||
|
|
||||||
|
<!-- dark theme colors -->
|
||||||
|
<color name="themePrimaryDark">#000000</color>
|
||||||
|
<color name="themeSecondaryDark">#202020</color>
|
||||||
|
<color name="textPrimaryDark">#deffffff</color>
|
||||||
|
<color name="textSecondaryDark">#99ffffff</color>
|
||||||
|
<color name="iconActionDark">#99ffffff</color>
|
||||||
|
<color name="iconNoActionDark">#66ffffff</color>
|
||||||
|
<color name="buttonBackgroundDark">#ffffff</color>
|
||||||
|
|
||||||
|
<color name="ic_launcher_background">#ffffff</color>
|
||||||
</resources>
|
</resources>
|
33
app/src/main/res/values/css_style.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="license_dialog_style_light" translatable="false">
|
||||||
|
body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #000000;
|
||||||
|
font-family: sans-serif;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
padding: 1em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
</string>
|
||||||
|
|
||||||
|
<string name="license_dialog_style_dark" translatable="false">
|
||||||
|
body {
|
||||||
|
background-color: #303030;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: sans-serif;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background-color: #424242;
|
||||||
|
padding: 1em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
li a {
|
||||||
|
color: #21a3df;
|
||||||
|
}
|
||||||
|
</string>
|
||||||
|
</resources>
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<!-- home fragment -->
|
<!-- home fragment -->
|
||||||
<string name="my_list">My list</string>
|
<string name="my_list">My list</string>
|
||||||
|
<string name="new_episodes">New episodes</string>
|
||||||
|
|
||||||
<!-- search fragment -->
|
<!-- search fragment -->
|
||||||
<string name="search_hint">Search for movies and series</string>
|
<string name="search_hint">Search for movies and series</string>
|
||||||
@ -20,7 +21,8 @@
|
|||||||
<string name="text_age_ex" translatable="false">6</string>
|
<string name="text_age_ex" translatable="false">6</string>
|
||||||
<string name="text_episodes_count">%1$d episodes</string>
|
<string name="text_episodes_count">%1$d episodes</string>
|
||||||
<string name="text_runtime">%1$d Minutes</string>
|
<string name="text_runtime">%1$d Minutes</string>
|
||||||
<string name="component_episode_title">Episode %1$d %2$s</string>
|
<string name="component_episode_title">Ep. %1$d %2$s</string>
|
||||||
|
<string name="component_episode_title_sub">Ep. %1$d %2$s (Sub)</string>
|
||||||
<string name="component_poster_desc" translatable="false">episode poster</string>
|
<string name="component_poster_desc" translatable="false">episode poster</string>
|
||||||
<string name="component_watched_desc" translatable="false">already watched</string>
|
<string name="component_watched_desc" translatable="false">already watched</string>
|
||||||
|
|
||||||
@ -31,8 +33,30 @@
|
|||||||
<string name="info">Info</string>
|
<string name="info">Info</string>
|
||||||
<string name="info_about" translatable="false">Teapod by @Seil0</string>
|
<string name="info_about" translatable="false">Teapod by @Seil0</string>
|
||||||
<string name="info_about_desc">Version %1$s (%2$s)</string>
|
<string name="info_about_desc">Version %1$s (%2$s)</string>
|
||||||
<string name="info_about_dialog">This app is published under the terms and conditions of the GNU GPL 3 or later. For further information visit: \ngit.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string>
|
<string name="info_about_dialog">This app is published under the terms and conditions of the GNU GPL 3 or later. For further information visit: git.mosad.xyz/Seil0/teapod \n\n© 2020 seil0@mosad.xyz</string>
|
||||||
<string name="licenses">Licenses</string>
|
<string name="licenses">Licenses</string>
|
||||||
|
<string name="settings">Settings</string>
|
||||||
|
<string name="settings_secondary">Prefer secondary (sub) stream</string>
|
||||||
|
<string name="settings_secondary_desc">Use the subtitles stream if present</string>
|
||||||
|
<string name="settings_autoplay">Autoplay</string>
|
||||||
|
<string name="settings_autoplay_desc">Play next episode automatically</string>
|
||||||
|
<string name="theme">Theme</string>
|
||||||
|
<string name="theme_light">Light</string>
|
||||||
|
<string name="theme_dark">Dark</string>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- player -->
|
||||||
|
<string name="close_player">close player</string>
|
||||||
|
<string name="rewind_10">rewind 10 seconds</string>
|
||||||
|
<string name="play_pause">play/pause</string>
|
||||||
|
<string name="forward_10">forward 10 seconds</string>
|
||||||
|
<string name="rwd_10_s" translatable="false">- 10 s</string>
|
||||||
|
<string name="fwd_10_s" translatable="false">+ 10 s</string>
|
||||||
|
<string name="next_episode">Next Episode</string>
|
||||||
|
<string name="time_min_sec" translatable="false">%1$02d:%2$02d</string>
|
||||||
|
<string name="time_hour_min_sec" translatable="false">%1$d:%2$02d:%3$02d</string>
|
||||||
|
<string name="episode">Episode</string>
|
||||||
|
<string name="episodes">Episodes</string>
|
||||||
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="save">save</string>
|
<string name="save">save</string>
|
||||||
@ -46,11 +70,16 @@
|
|||||||
|
|
||||||
<!-- save keys -->
|
<!-- save keys -->
|
||||||
<string name="encrypted_preference_file_key" translatable="false">org.mosad.teapod.encrypted_preferences</string>
|
<string name="encrypted_preference_file_key" translatable="false">org.mosad.teapod.encrypted_preferences</string>
|
||||||
|
<string name="preference_file_key" translatable="false">org.mosad.teapod.preferences</string>
|
||||||
<string name="save_key_user_login" translatable="false">org.mosad.teapod.user_login</string>
|
<string name="save_key_user_login" translatable="false">org.mosad.teapod.user_login</string>
|
||||||
<string name="save_key_user_password" translatable="false">org.mosad.teapod.user_password</string>
|
<string name="save_key_user_password" translatable="false">org.mosad.teapod.user_password</string>
|
||||||
|
<string name="save_key_prefer_secondary" translatable="false">org.mosad.teapod.prefer_secondary</string>
|
||||||
|
<string name="save_key_autoplay" translatable="false">org.mosad.teapod.autoplay</string>
|
||||||
|
<string name="save_key_theme" translatable="false">org.mosad.teapod.theme</string>
|
||||||
|
|
||||||
<!-- intents & states -->
|
<!-- intents & states -->
|
||||||
<string name="intent_stream_url" translatable="false">intent_stream_url</string>
|
<string name="intent_media_id" translatable="false">intent_media_id</string>
|
||||||
|
<string name="intent_episode_id" translatable="false">intent_episode_id</string>
|
||||||
<string name="state_resume_window" translatable="false">state_resume_window</string>
|
<string name="state_resume_window" translatable="false">state_resume_window</string>
|
||||||
<string name="state_resume_position" translatable="false">state_resume_position</string>
|
<string name="state_resume_position" translatable="false">state_resume_position</string>
|
||||||
<string name="state_is_playing" translatable="false">state_is_playing</string>
|
<string name="state_is_playing" translatable="false">state_is_playing</string>
|
||||||
|
@ -1,17 +1,61 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<!-- Base application theme. -->
|
<!-- application themes -->
|
||||||
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.MaterialComponents.Light.NoActionBar.FullScreen" parent="@style/Theme.MaterialComponents.Light.NoActionBar">
|
<style name="AppTheme.Light" parent="AppTheme">
|
||||||
|
<item name="themePrimary">@color/themePrimaryLight</item>
|
||||||
|
<item name="themeSecondary">@color/themeSecondaryLight</item>
|
||||||
|
<item name="textPrimary">@color/textPrimaryLight</item>
|
||||||
|
<item name="textSecondary">@color/textSecondaryLight</item>
|
||||||
|
<item name="android:textColor">@color/textPrimaryLight</item>
|
||||||
|
<item name="android:textColorPrimary">@color/textPrimaryLight</item>
|
||||||
|
<item name="android:textColorHint">@color/textSecondaryLight</item>
|
||||||
|
<item name="iconAction">@color/iconActionLight</item>
|
||||||
|
<item name="iconNoAction">@color/iconNoActionLight</item>
|
||||||
|
<item name="buttonBackground">@color/buttonBackgroundLight</item>
|
||||||
|
<item name="md_background_color">@color/themeSecondaryLight</item>
|
||||||
|
<item name="md_color_content">@color/textSecondaryLight</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.Dark" parent="AppTheme">
|
||||||
|
<item name="themePrimary">@color/themePrimaryDark</item>
|
||||||
|
<item name="themeSecondary">@color/themeSecondaryDark</item>
|
||||||
|
<item name="textPrimary">@color/textPrimaryDark</item>
|
||||||
|
<item name="textSecondary">@color/textSecondaryDark</item>
|
||||||
|
<item name="android:textColor">@color/textPrimaryDark</item>
|
||||||
|
<item name="android:textColorPrimary">@color/textPrimaryDark</item>
|
||||||
|
<item name="android:textColorHint">@color/textSecondaryDark</item>
|
||||||
|
<item name="iconAction">@color/iconActionDark</item>
|
||||||
|
<item name="iconNoAction">@color/iconNoActionDark</item>
|
||||||
|
<item name="buttonBackground">@color/buttonBackgroundDark</item>
|
||||||
|
<item name="md_background_color">@color/themeSecondaryDark</item>
|
||||||
|
<item name="md_color_content">@color/textSecondaryDark</item>
|
||||||
|
|
||||||
|
<!-- without this, the unchecked single choice buttons while be black -->
|
||||||
|
<item name="md_color_widget_unchecked">@color/textSecondaryDark</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="LicensesDialogTheme.Dark" parent="Theme.AppCompat.Dialog">
|
||||||
|
<item name="android:windowBackground">@color/themeSecondaryDark</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- player theme -->
|
||||||
|
<style name="PlayerTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowFullscreen">true</item>
|
<item name="android:windowFullscreen">true</item>
|
||||||
<item name="android:windowContentOverlay">@null</item>
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- splash theme -->
|
||||||
|
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
||||||
|
<item name="android:windowBackground">@drawable/bg_splash</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- shapes -->
|
<!-- shapes -->
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.4.10"
|
ext.kotlin_version = "1.4.20"
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
342
etc/drawable_resources/icon.svg
Normal file
After Width: | Height: | Size: 548 KiB |
1
fastlane/metadata/android/de-DE/changelogs/1000.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Version 0.1 ist der erste öffentliche Release von Teapod.
|
9
fastlane/metadata/android/de-DE/changelogs/2000.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
* überarbeitetes Player Interface (#10)
|
||||||
|
* Autoplay für Serien
|
||||||
|
* Gesten für Vor-/Zurückspulen(doppelt tippen), Pause/Abspielen (lang tippen)
|
||||||
|
* verbleibende Zeit und Titel
|
||||||
|
* Unterstützung für Themes (#13)
|
||||||
|
* helles/dunkles Themes
|
||||||
|
* Primärfarbe angepasst
|
||||||
|
* Fehler beim Hinzufügen/löschen in "Meine Liste" behoben (#15)
|
||||||
|
* Fehler beim Parsen von Serien behoben
|
3
fastlane/metadata/android/de-DE/changelogs/2100.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
* Ein Fehler wurde behoben, bei dem Autoplay Folgen überspringen konnte
|
||||||
|
* Der Player zeigt nun einen Button an, um zur nächsten Folge zu springen
|
||||||
|
* Die UI wurde an einigen Stellen angepasst
|
11
fastlane/metadata/android/de-DE/full_description.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
Teapod ist eine inoffizielle App für Anime-on-Demand (AoD).
|
||||||
|
|
||||||
|
* Schau dir alle Title von AoD auf deinem Android Gerät an
|
||||||
|
* Nativer Player auf Basis des ExoPayers
|
||||||
|
* Bevorzuge die OmU Version über die App-Einstellungen
|
||||||
|
* Speicher deine lieblings Anime in "Meine Liste"
|
||||||
|
|
||||||
|
Zur Verfügung gestellt unter den Bedingungen der GNU GPL 3 oder höher.
|
||||||
|
Dieses Projekt ist in keiner Weise mit Anime-on-Demand verbunden.
|
||||||
|
|
||||||
|
Bitte melde Fehler und Probleme an support@mosad.xyz
|
After Width: | Height: | Size: 848 KiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 572 KiB |
After Width: | Height: | Size: 1.2 MiB |
1
fastlane/metadata/android/de-DE/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Android App für AoD
|
1
fastlane/metadata/android/de-DE/title.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Teapod
|
11
fastlane/metadata/android/en-US/full_description.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
Teapod is a unoffical App for Anime-on-Demand (AoD).
|
||||||
|
|
||||||
|
* Watch all animes from AoD on your Android device
|
||||||
|
* Native Player based on ExoPayer
|
||||||
|
* Prefer the OmU version via the app settings
|
||||||
|
* Save your favorite animes to "My List"
|
||||||
|
|
||||||
|
Licensed under the terms and conditions of GPL 3.
|
||||||
|
This Project is not associated with Anime-on-Demand in any way.
|
||||||
|
|
||||||
|
Please report bugs and issues to support@mosad.xyz
|
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Android App for AoD
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,5 @@
|
|||||||
#Tue Oct 13 12:04:29 CEST 2020
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
|
||||||
|
53
gradlew
vendored
@ -1,5 +1,21 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# https://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.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
##
|
||||||
## Gradle start up script for UN*X
|
## Gradle start up script for UN*X
|
||||||
@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
@ -66,6 +82,7 @@ esac
|
|||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@ -109,10 +126,11 @@ if $darwin; then
|
|||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
@ -138,19 +156,19 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -159,14 +177,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
22
gradlew.bat
vendored
@ -1,3 +1,19 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@ -13,8 +29,11 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
@ -65,6 +84,7 @@ set CMD_LINE_ARGS=%*
|
|||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|