Compare commits
5 Commits
1.0.0-beta
...
7fbf639a70
Author | SHA1 | Date | |
---|---|---|---|
7fbf639a70
|
|||
ff63b3d7a4
|
|||
7d32cecd89
|
|||
72280f29d8
|
|||
cd4cfb7a0c
|
@ -6,13 +6,13 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 31
|
||||||
buildToolsVersion "30.0.3"
|
buildToolsVersion "30.0.3"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.mosad.teapod"
|
applicationId "org.mosad.teapod"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 30
|
targetSdkVersion 31
|
||||||
versionCode 9000 //00.09.000
|
versionCode 9000 //00.09.000
|
||||||
versionName "1.0.0-beta1"
|
versionName "1.0.0-beta1"
|
||||||
|
|
||||||
@ -48,18 +48,18 @@ dependencies {
|
|||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1'
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.6.0'
|
implementation 'androidx.core:core-ktx:1.7.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation 'androidx.core:core-splashscreen:1.0.0-beta02'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
|
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
|
||||||
|
implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
|
||||||
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
|
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation 'com.google.android.material:material:1.5.0'
|
||||||
implementation 'com.google.code.gson:gson:2.8.8' // TODO remove, still used by metadb
|
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-hls:2.15.0'
|
implementation 'com.google.android.exoplayer:exoplayer-hls:2.15.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-dash:2.15.0'
|
implementation 'com.google.android.exoplayer:exoplayer-dash:2.15.0'
|
||||||
|
4
app/proguard-rules.pro
vendored
@ -24,10 +24,6 @@
|
|||||||
|
|
||||||
-keep class org.json.** { *; }
|
-keep class org.json.** { *; }
|
||||||
|
|
||||||
#Gson
|
|
||||||
-keepattributes Signature
|
|
||||||
-dontwarn sun.misc.**
|
|
||||||
|
|
||||||
# kotlinx.serialization
|
# kotlinx.serialization
|
||||||
# Keep `Companion` object fields of serializable classes.
|
# Keep `Companion` object fields of serializable classes.
|
||||||
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
||||||
|
@ -11,11 +11,10 @@
|
|||||||
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.Dark">
|
android:theme="@style/Theme.App.Starting">
|
||||||
<activity
|
<activity
|
||||||
android:name="org.mosad.teapod.ui.activity.SplashActivity"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:name="org.mosad.teapod.ui.activity.main.MainActivity"
|
||||||
android:theme="@style/SplashTheme"
|
|
||||||
android:screenOrientation="portrait">
|
android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@ -23,22 +22,17 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="false"
|
||||||
android:name="org.mosad.teapod.ui.activity.onboarding.OnboardingActivity"
|
android:name="org.mosad.teapod.ui.activity.onboarding.OnboardingActivity"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:windowSoftInputMode="adjustPan">
|
android:windowSoftInputMode="adjustPan">
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="org.mosad.teapod.ui.activity.main.MainActivity"
|
android:exported="false"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:screenOrientation="portrait">
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name="org.mosad.teapod.ui.activity.player.PlayerActivity"
|
android:name="org.mosad.teapod.ui.activity.player.PlayerActivity"
|
||||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection"
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection"
|
||||||
android:autoRemoveFromRecents="true"
|
android:autoRemoveFromRecents="true"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:parentActivityName="org.mosad.teapod.ui.activity.main.MainActivity"
|
android:parentActivityName="org.mosad.teapod.ui.activity.main.MainActivity"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
|
@ -500,6 +500,12 @@ object Crunchyroll {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post the playhead to crunchy (playhead position,watched state)
|
||||||
|
*
|
||||||
|
* @param episodeId A episode ID as strings.
|
||||||
|
* @param playhead The episodes playhead in seconds.
|
||||||
|
*/
|
||||||
suspend fun postPlayheads(episodeId: String, playhead: Int) {
|
suspend fun postPlayheads(episodeId: String, playhead: Int) {
|
||||||
val playheadsEndpoint = "/content/v1/playheads/$accountID"
|
val playheadsEndpoint = "/content/v1/playheads/$accountID"
|
||||||
val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag())
|
val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag())
|
||||||
|
@ -19,6 +19,10 @@ object Preferences {
|
|||||||
var theme = DataTypes.Theme.DARK
|
var theme = DataTypes.Theme.DARK
|
||||||
internal set
|
internal set
|
||||||
|
|
||||||
|
// dev settings
|
||||||
|
var updatePlayhead = true
|
||||||
|
internal set
|
||||||
|
|
||||||
private fun getSharedPref(context: Context): SharedPreferences {
|
private fun getSharedPref(context: Context): SharedPreferences {
|
||||||
return context.getSharedPreferences(
|
return context.getSharedPreferences(
|
||||||
context.getString(R.string.preference_file_key),
|
context.getString(R.string.preference_file_key),
|
||||||
@ -71,6 +75,15 @@ object Preferences {
|
|||||||
this.theme = theme
|
this.theme = theme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveUpdatePlayhead(context: Context, updatePlayhead: Boolean) {
|
||||||
|
with(getSharedPref(context).edit()) {
|
||||||
|
putBoolean(context.getString(R.string.save_key_update_playhead), updatePlayhead)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updatePlayhead = updatePlayhead
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* initially load the stored values
|
* initially load the stored values
|
||||||
*/
|
*/
|
||||||
@ -96,6 +109,11 @@ object Preferences {
|
|||||||
context.getString(R.string.save_key_theme), DataTypes.Theme.DARK.toString()
|
context.getString(R.string.save_key_theme), DataTypes.Theme.DARK.toString()
|
||||||
) ?: DataTypes.Theme.DARK.toString()
|
) ?: DataTypes.Theme.DARK.toString()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// dev settings
|
||||||
|
updatePlayhead = sharedPref.getBoolean(
|
||||||
|
context.getString(R.string.save_key_update_playhead), true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package org.mosad.teapod.ui.activity
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import org.mosad.teapod.ui.activity.main.MainActivity
|
|
||||||
|
|
||||||
|
|
||||||
class SplashActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
|
||||||
startActivity(intent)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,6 +27,7 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
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.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
@ -44,6 +45,7 @@ import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity
|
|||||||
import org.mosad.teapod.ui.activity.player.PlayerActivity
|
import org.mosad.teapod.ui.activity.player.PlayerActivity
|
||||||
import org.mosad.teapod.ui.components.LoginDialog
|
import org.mosad.teapod.ui.components.LoginDialog
|
||||||
import org.mosad.teapod.util.DataTypes
|
import org.mosad.teapod.util.DataTypes
|
||||||
|
import org.mosad.teapod.util.metadb.MetaDBController
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
@ -62,6 +64,9 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
// Handle the splash screen transition.
|
||||||
|
installSplashScreen()
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
load() // start the initial loading
|
load() // start the initial loading
|
||||||
@ -137,6 +142,9 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
|
|||||||
Preferences.load(this)
|
Preferences.load(this)
|
||||||
EncryptedPreferences.readCredentials(this)
|
EncryptedPreferences.readCredentials(this)
|
||||||
|
|
||||||
|
// load meta db at the start, it doesn't depend on any third party
|
||||||
|
val metaJob = initMetaDB()
|
||||||
|
|
||||||
// always initialize the api token
|
// always initialize the api token
|
||||||
Crunchyroll.initBasicApiToken()
|
Crunchyroll.initBasicApiToken()
|
||||||
|
|
||||||
@ -148,14 +156,17 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
|
|||||||
) {
|
) {
|
||||||
showOnboarding()
|
showOnboarding()
|
||||||
} else {
|
} else {
|
||||||
runBlocking { initCrunchyroll().joinAll() }
|
runBlocking {
|
||||||
|
initCrunchyroll().joinAll()
|
||||||
|
metaJob.join() // meta loading should be done here
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.i(classTag, "loading in $time ms")
|
Log.i(classTag, "loading in $time ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initCrunchyroll(): List<Job> {
|
private fun initCrunchyroll(): List<Job> {
|
||||||
val scope = CoroutineScope(Dispatchers.IO + CoroutineName("InitialLoadingScope"))
|
val scope = CoroutineScope(Dispatchers.IO + CoroutineName("InitialCrunchyLoading"))
|
||||||
return listOf(
|
return listOf(
|
||||||
scope.launch { Crunchyroll.index() },
|
scope.launch { Crunchyroll.index() },
|
||||||
scope.launch { Crunchyroll.account() },
|
scope.launch { Crunchyroll.account() },
|
||||||
@ -168,6 +179,11 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initMetaDB(): Job {
|
||||||
|
val scope = CoroutineScope(Dispatchers.IO + CoroutineName("InitialMetaDBLoading"))
|
||||||
|
return scope.launch { MetaDBController.list() }
|
||||||
|
}
|
||||||
|
|
||||||
private fun showLoginDialog() {
|
private fun showLoginDialog() {
|
||||||
LoginDialog(this, false).positiveButton {
|
LoginDialog(this, false).positiveButton {
|
||||||
EncryptedPreferences.saveCredentials(login, password, context)
|
EncryptedPreferences.saveCredentials(login, password, context)
|
||||||
|
@ -107,8 +107,6 @@ class AboutFragment : Fragment() {
|
|||||||
"https://github.com/material-components/material-components-android", License.APACHE2),
|
"https://github.com/material-components/material-components-android", License.APACHE2),
|
||||||
ThirdPartyComponent("ExoPlayer", "2014 - 2020", "The Android Open Source Project",
|
ThirdPartyComponent("ExoPlayer", "2014 - 2020", "The Android Open Source Project",
|
||||||
"https://github.com/google/ExoPlayer", License.APACHE2),
|
"https://github.com/google/ExoPlayer", License.APACHE2),
|
||||||
ThirdPartyComponent("Gson", "2008", "Google Inc.",
|
|
||||||
"https://github.com/google/gson", License.APACHE2),
|
|
||||||
ThirdPartyComponent("Material design icons", "2020", "Google Inc.",
|
ThirdPartyComponent("Material design icons", "2020", "Google Inc.",
|
||||||
"https://github.com/google/material-design-icons", License.APACHE2),
|
"https://github.com/google/material-design-icons", License.APACHE2),
|
||||||
ThirdPartyComponent("Material Dialogs", "", "Aidan Follestad",
|
ThirdPartyComponent("Material Dialogs", "", "Aidan Follestad",
|
||||||
|
@ -93,6 +93,7 @@ class AccountFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.linearDevSettings.isVisible = Preferences.devSettings
|
binding.linearDevSettings.isVisible = Preferences.devSettings
|
||||||
|
binding.switchUpdatePlayhead.isChecked = Preferences.updatePlayhead
|
||||||
|
|
||||||
binding.textInfoAboutDesc.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))
|
||||||
|
|
||||||
@ -130,6 +131,10 @@ class AccountFragment : Fragment() {
|
|||||||
activity?.showFragment(AboutFragment())
|
activity?.showFragment(AboutFragment())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.switchUpdatePlayhead.setOnClickListener {
|
||||||
|
Preferences.saveUpdatePlayhead(requireContext(), binding.switchUpdatePlayhead.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
binding.linearExportData.setOnClickListener {
|
binding.linearExportData.setOnClickListener {
|
||||||
val i = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
val i = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.launch
|
|||||||
import org.mosad.teapod.parser.crunchyroll.*
|
import org.mosad.teapod.parser.crunchyroll.*
|
||||||
import org.mosad.teapod.preferences.Preferences
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.util.DataTypes.MediaType
|
import org.mosad.teapod.util.DataTypes.MediaType
|
||||||
import org.mosad.teapod.util.Meta
|
import org.mosad.teapod.util.metadb.Meta
|
||||||
import org.mosad.teapod.util.tmdb.*
|
import org.mosad.teapod.util.tmdb.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,7 +59,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
|
|||||||
).joinAll()
|
).joinAll()
|
||||||
// println("series: $seriesCrunchy")
|
// println("series: $seriesCrunchy")
|
||||||
// println("seasons: $seasonsCrunchy")
|
// println("seasons: $seasonsCrunchy")
|
||||||
println(upNextSeries)
|
// println(upNextSeries)
|
||||||
|
|
||||||
// load the preferred season (preferred language, language per season, not per stream)
|
// load the preferred season (preferred language, language per season, not per stream)
|
||||||
currentSeasonCrunchy = seasonsCrunchy.getPreferredSeason(Preferences.preferredLocale)
|
currentSeasonCrunchy = seasonsCrunchy.getPreferredSeason(Preferences.preferredLocale)
|
||||||
|
@ -425,7 +425,6 @@ class PlayerActivity : AppCompatActivity() {
|
|||||||
val seekTime = (it.openingStart + it.openingDuration) - model.player.currentPosition
|
val seekTime = (it.openingStart + it.openingDuration) - model.player.currentPosition
|
||||||
model.seekToOffset(seekTime)
|
model.seekToOffset(seekTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,8 +46,10 @@ import org.mosad.teapod.parser.crunchyroll.NoneEpisode
|
|||||||
import org.mosad.teapod.parser.crunchyroll.NoneEpisodes
|
import org.mosad.teapod.parser.crunchyroll.NoneEpisodes
|
||||||
import org.mosad.teapod.parser.crunchyroll.NonePlayback
|
import org.mosad.teapod.parser.crunchyroll.NonePlayback
|
||||||
import org.mosad.teapod.preferences.Preferences
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.util.EpisodeMeta
|
import org.mosad.teapod.util.metadb.EpisodeMeta
|
||||||
import org.mosad.teapod.util.tmdb.TMDBTVSeason
|
import org.mosad.teapod.util.metadb.Meta
|
||||||
|
import org.mosad.teapod.util.metadb.MetaDBController
|
||||||
|
import org.mosad.teapod.util.metadb.TVShowMeta
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,6 +58,7 @@ import java.util.*
|
|||||||
* the next episode will be update and the callback is handled.
|
* the next episode will be update and the callback is handled.
|
||||||
*/
|
*/
|
||||||
class PlayerViewModel(application: Application) : AndroidViewModel(application) {
|
class PlayerViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private val classTag = javaClass.name
|
||||||
|
|
||||||
val player = SimpleExoPlayer.Builder(application).build()
|
val player = SimpleExoPlayer.Builder(application).build()
|
||||||
private val dataSourceFactory = DefaultDataSourceFactory(application, Util.getUserAgent(application, "Teapod"))
|
private val dataSourceFactory = DefaultDataSourceFactory(application, Util.getUserAgent(application, "Teapod"))
|
||||||
@ -65,13 +68,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
private var currentPlayhead: Long = 0
|
private var currentPlayhead: Long = 0
|
||||||
|
|
||||||
// tmdb/meta data
|
// tmdb/meta data
|
||||||
// TODO meta data currently not implemented for cr
|
var mediaMeta: Meta? = null
|
||||||
// var mediaMeta: Meta? = null
|
|
||||||
// internal set
|
|
||||||
var tmdbTVSeason: TMDBTVSeason? =null
|
|
||||||
internal set
|
internal set
|
||||||
var currentEpisodeMeta: EpisodeMeta? = null
|
var currentEpisodeMeta: EpisodeMeta? = null
|
||||||
internal set
|
internal set
|
||||||
|
// var tmdbTVSeason: TMDBTVSeason? =null
|
||||||
|
// internal set
|
||||||
|
|
||||||
// crunchyroll episodes/playback
|
// crunchyroll episodes/playback
|
||||||
var episodes = NoneEpisodes
|
var episodes = NoneEpisodes
|
||||||
@ -108,7 +110,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
mediaSession.release()
|
mediaSession.release()
|
||||||
player.release()
|
player.release()
|
||||||
|
|
||||||
Log.d(javaClass.name, "Released player")
|
Log.d(classTag, "Released player")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -124,22 +126,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
|
|
||||||
fun loadMediaAsync(seasonId: String, episodeId: String) = viewModelScope.launch {
|
fun loadMediaAsync(seasonId: String, episodeId: String) = viewModelScope.launch {
|
||||||
episodes = Crunchyroll.episodes(seasonId)
|
episodes = Crunchyroll.episodes(seasonId)
|
||||||
|
mediaMeta = loadMediaMeta(episodes.items.first().seriesId)
|
||||||
|
|
||||||
|
Log.d(classTag, "meta: $mediaMeta")
|
||||||
|
|
||||||
setCurrentEpisode(episodeId)
|
setCurrentEpisode(episodeId)
|
||||||
playCurrentMedia(currentPlayhead)
|
playCurrentMedia(currentPlayhead)
|
||||||
|
|
||||||
// TODO reimplement for cr
|
|
||||||
// run async as it should be loaded by the time the episodes a
|
|
||||||
// viewModelScope.launch {
|
|
||||||
// // get tmdb season info, if metaDB knows the tv show
|
|
||||||
// if (media.type == DataTypes.MediaType.TVSHOW && mediaMeta != null) {
|
|
||||||
// val tvShowMeta = mediaMeta as TVShowMeta
|
|
||||||
// tmdbTVSeason = TMDBApiController().getTVSeasonDetails(tvShowMeta.tmdbId, tvShowMeta.tmdbSeasonNumber)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// currentEpisodeMeta = getEpisodeMetaByAoDMediaId(currentEpisodeAoD.mediaId)
|
|
||||||
// currentLanguage = currentEpisodeAoD.getPreferredStream(preferredLanguage).language
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setLanguage(language: Locale) {
|
fun setLanguage(language: Locale) {
|
||||||
@ -174,6 +166,15 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
episode.id == episodeId
|
episode.id == episodeId
|
||||||
} ?: NoneEpisode
|
} ?: NoneEpisode
|
||||||
|
|
||||||
|
// update current episode meta
|
||||||
|
currentEpisodeMeta = if (mediaMeta is TVShowMeta && currentEpisode.episodeNumber != null) {
|
||||||
|
(mediaMeta as TVShowMeta)
|
||||||
|
.seasons[currentEpisode.seasonNumber - 1]
|
||||||
|
.episodes[currentEpisode.episodeNumber!! - 1]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
// update player gui (title, next ep button) after currentEpisode has changed
|
// update player gui (title, next ep button) after currentEpisode has changed
|
||||||
currentEpisodeChangedListener.forEach { it() }
|
currentEpisodeChangedListener.forEach { it() }
|
||||||
|
|
||||||
@ -195,9 +196,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
println("loaded playback ${currentEpisode.playback}")
|
Log.i(classTag, "playback: ${currentEpisode.playback}")
|
||||||
|
|
||||||
// TODO update metadata and language (it should not be needed to update the language here!)
|
|
||||||
|
|
||||||
if (startPlayback) {
|
if (startPlayback) {
|
||||||
playCurrentMedia()
|
playCurrentMedia()
|
||||||
@ -227,7 +226,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
currentPlayback.streams.adaptive_hls.entries.first().value.url
|
currentPlayback.streams.adaptive_hls.entries.first().value.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println("stream url: $url")
|
Log.d(classTag, "stream url: $url")
|
||||||
|
|
||||||
// create the media source object
|
// create the media source object
|
||||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
|
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
|
||||||
@ -266,25 +265,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
return episodes.items.lastOrNull()?.id == currentEpisode.id
|
return episodes.items.lastOrNull()?.id == currentEpisode.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO reimplement for cr
|
private suspend fun loadMediaMeta(crSeriesId: String): Meta? {
|
||||||
// fun getEpisodeMetaByAoDMediaId(aodMediaId: Int): EpisodeMeta? {
|
return MetaDBController.getTVShowMetadata(crSeriesId)
|
||||||
// val meta = mediaMeta
|
}
|
||||||
// return if (meta is TVShowMeta) {
|
|
||||||
// meta.episodes.firstOrNull { it.aodMediaId == aodMediaId }
|
|
||||||
// } else {
|
|
||||||
// null
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private suspend fun loadMediaMeta(aodId: Int): Meta? {
|
|
||||||
// return if (media.type == DataTypes.MediaType.TVSHOW) {
|
|
||||||
// MetaDBController().getTVShowMetadata(aodId)
|
|
||||||
// } else {
|
|
||||||
// null
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return null
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the playhead of the current episode, if currentPosition > 1000ms.
|
* Update the playhead of the current episode, if currentPosition > 1000ms.
|
||||||
@ -292,7 +275,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
private fun updatePlayhead() {
|
private fun updatePlayhead() {
|
||||||
val playhead = (player.currentPosition / 1000)
|
val playhead = (player.currentPosition / 1000)
|
||||||
|
|
||||||
if (playhead > 0) {
|
if (playhead > 0 && Preferences.updatePlayhead) {
|
||||||
viewModelScope.launch { Crunchyroll.postPlayheads(currentEpisode.id, playhead.toInt()) }
|
viewModelScope.launch { Crunchyroll.postPlayheads(currentEpisode.id, playhead.toInt()) }
|
||||||
Log.i(javaClass.name, "Set playhead for episode ${currentEpisode.id} to $playhead sec.")
|
Log.i(javaClass.name, "Set playhead for episode ${currentEpisode.id} to $playhead sec.")
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class EpisodesListPlayer @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
model?.let {
|
model?.let {
|
||||||
adapterRecEpisodes = PlayerEpisodeItemAdapter(model.episodes, model.tmdbTVSeason?.episodes)
|
adapterRecEpisodes = PlayerEpisodeItemAdapter(model.episodes)
|
||||||
adapterRecEpisodes.onImageClick = {_, episodeId ->
|
adapterRecEpisodes.onImageClick = {_, episodeId ->
|
||||||
(this.parent as ViewGroup).removeView(this)
|
(this.parent as ViewGroup).removeView(this)
|
||||||
model.setCurrentEpisode(episodeId, startPlayback = true)
|
model.setCurrentEpisode(episodeId, startPlayback = true)
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
/**
|
|
||||||
* Teapod
|
|
||||||
*
|
|
||||||
* Copyright 2020-2022 <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.util
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO remove gson usage
|
|
||||||
*/
|
|
||||||
class MetaDBController {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val repoUrl = "https://gitlab.com/Seil0/teapodmetadb/-/raw/main/aod/"
|
|
||||||
|
|
||||||
var mediaList = MediaList(listOf())
|
|
||||||
private var metaCacheList = arrayListOf<Meta>()
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
suspend fun list() = withContext(Dispatchers.IO) {
|
|
||||||
val url = URL("$repoUrl/list.json")
|
|
||||||
val json = url.readText()
|
|
||||||
|
|
||||||
mediaList = Gson().fromJson(json, MediaList::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the meta data for a movie from MetaDB
|
|
||||||
* @param aodId The AoD id of the media
|
|
||||||
* @return A meta movie object, or null if not found
|
|
||||||
*/
|
|
||||||
suspend fun getMovieMetadata(aodId: Int): MovieMeta? {
|
|
||||||
return metaCacheList.firstOrNull {
|
|
||||||
it.aodId == aodId
|
|
||||||
} as MovieMeta? ?: getMovieMetadataFromDB(aodId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the meta data for a tv show from MetaDB
|
|
||||||
* @param aodId The AoD id of the media
|
|
||||||
* @return A meta tv show object, or null if not found
|
|
||||||
*/
|
|
||||||
suspend fun getTVShowMetadata(aodId: Int): TVShowMeta? {
|
|
||||||
return metaCacheList.firstOrNull {
|
|
||||||
it.aodId == aodId
|
|
||||||
} as TVShowMeta? ?: getTVShowMetadataFromDB(aodId)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
private suspend fun getMovieMetadataFromDB(aodId: Int): MovieMeta? = withContext(Dispatchers.IO) {
|
|
||||||
val url = URL("$repoUrl/movie/$aodId/media.json")
|
|
||||||
return@withContext try {
|
|
||||||
val json = url.readText()
|
|
||||||
val meta = Gson().fromJson(json, MovieMeta::class.java)
|
|
||||||
metaCacheList.add(meta)
|
|
||||||
|
|
||||||
meta
|
|
||||||
} catch (ex: FileNotFoundException) {
|
|
||||||
Log.w(javaClass.name, "Waring: The requested file was not found. Requested ID: $aodId", ex)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
private suspend fun getTVShowMetadataFromDB(aodId: Int): TVShowMeta? = withContext(Dispatchers.IO) {
|
|
||||||
val url = URL("$repoUrl/tv/$aodId/media.json")
|
|
||||||
return@withContext try {
|
|
||||||
val json = url.readText()
|
|
||||||
val meta = Gson().fromJson(json, TVShowMeta::class.java)
|
|
||||||
metaCacheList.add(meta)
|
|
||||||
|
|
||||||
meta
|
|
||||||
} catch (ex: FileNotFoundException) {
|
|
||||||
Log.w(javaClass.name, "Waring: The requested file was not found. Requested ID: $aodId", ex)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// class representing the media list json object
|
|
||||||
data class MediaList(
|
|
||||||
val media: List<Int>
|
|
||||||
)
|
|
||||||
|
|
||||||
// abstract class used for meta data objects (tv, movie)
|
|
||||||
abstract class Meta {
|
|
||||||
abstract val id: Int
|
|
||||||
abstract val aodId: Int
|
|
||||||
abstract val tmdbId: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// class representing the movie json object
|
|
||||||
data class MovieMeta(
|
|
||||||
override val id: Int,
|
|
||||||
@SerializedName("aod_id")
|
|
||||||
override val aodId: Int,
|
|
||||||
@SerializedName("tmdb_id")
|
|
||||||
override val tmdbId: Int
|
|
||||||
): Meta()
|
|
||||||
|
|
||||||
// class representing the tv show json object
|
|
||||||
data class TVShowMeta(
|
|
||||||
override val id: Int,
|
|
||||||
@SerializedName("aod_id")
|
|
||||||
override val aodId: Int,
|
|
||||||
@SerializedName("tmdb_id")
|
|
||||||
override val tmdbId: Int,
|
|
||||||
@SerializedName("tmdb_season_id")
|
|
||||||
val tmdbSeasonId: Int,
|
|
||||||
@SerializedName("tmdb_season_number")
|
|
||||||
val tmdbSeasonNumber: Int,
|
|
||||||
@SerializedName("episodes")
|
|
||||||
val episodes: List<EpisodeMeta>
|
|
||||||
): Meta()
|
|
||||||
|
|
||||||
// class used in TVShowMeta, part of the tv show json object
|
|
||||||
data class EpisodeMeta(
|
|
||||||
val id: Int,
|
|
||||||
@SerializedName("aod_media_id")
|
|
||||||
val aodMediaId: Int,
|
|
||||||
@SerializedName("tmdb_id")
|
|
||||||
val tmdbId: Int,
|
|
||||||
@SerializedName("tmdb_number")
|
|
||||||
val tmdbNumber: Int,
|
|
||||||
@SerializedName("opening_start")
|
|
||||||
val openingStart: Long,
|
|
||||||
@SerializedName("opening_duration")
|
|
||||||
val openingDuration: Long,
|
|
||||||
@SerializedName("ending_start")
|
|
||||||
val endingStart: Long,
|
|
||||||
@SerializedName("ending_duration")
|
|
||||||
val endingDuration: Long
|
|
||||||
)
|
|
@ -12,7 +12,7 @@ import org.mosad.teapod.databinding.ItemEpisodePlayerBinding
|
|||||||
import org.mosad.teapod.parser.crunchyroll.Episodes
|
import org.mosad.teapod.parser.crunchyroll.Episodes
|
||||||
import org.mosad.teapod.util.tmdb.TMDBTVEpisode
|
import org.mosad.teapod.util.tmdb.TMDBTVEpisode
|
||||||
|
|
||||||
class PlayerEpisodeItemAdapter(private val episodes: Episodes, private val tmdbEpisodes: List<TMDBTVEpisode>?) : RecyclerView.Adapter<PlayerEpisodeItemAdapter.EpisodeViewHolder>() {
|
class PlayerEpisodeItemAdapter(private val episodes: Episodes) : RecyclerView.Adapter<PlayerEpisodeItemAdapter.EpisodeViewHolder>() {
|
||||||
|
|
||||||
var onImageClick: ((seasonId: String, episodeId: String) -> Unit)? = null
|
var onImageClick: ((seasonId: String, episodeId: String) -> Unit)? = null
|
||||||
var currentSelected: Int = -1 // -1, since position should never be < 0
|
var currentSelected: Int = -1 // -1, since position should never be < 0
|
||||||
@ -39,8 +39,6 @@ class PlayerEpisodeItemAdapter(private val episodes: Episodes, private val tmdbE
|
|||||||
holder.binding.textEpisodeTitle2.text = titleText
|
holder.binding.textEpisodeTitle2.text = titleText
|
||||||
holder.binding.textEpisodeDesc2.text = if (ep.description.isNotEmpty()) {
|
holder.binding.textEpisodeDesc2.text = if (ep.description.isNotEmpty()) {
|
||||||
ep.description
|
ep.description
|
||||||
} else if (tmdbEpisodes != null && position < tmdbEpisodes.size){
|
|
||||||
tmdbEpisodes[position].overview
|
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
57
app/src/main/java/org/mosad/teapod/util/metadb/DatTypes.kt
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package org.mosad.teapod.util.metadb
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
// class representing the media list json object
|
||||||
|
@Serializable
|
||||||
|
data class MediaList(
|
||||||
|
@SerialName("media") val media: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
// abstract class used for meta data objects (tv, movie)
|
||||||
|
abstract class Meta {
|
||||||
|
abstract val id: Int
|
||||||
|
abstract val tmdbId: Int
|
||||||
|
abstract val crSeriesId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
// class representing the movie json object
|
||||||
|
@Serializable
|
||||||
|
data class MovieMeta(
|
||||||
|
@SerialName("id") override val id: Int,
|
||||||
|
@SerialName("tmdb_id") override val tmdbId: Int,
|
||||||
|
@SerialName("cr_series_id") override val crSeriesId: String,
|
||||||
|
): Meta()
|
||||||
|
|
||||||
|
// class representing the tv show json object
|
||||||
|
@Serializable
|
||||||
|
data class TVShowMeta(
|
||||||
|
@SerialName("id") override val id: Int,
|
||||||
|
@SerialName("tmdb_id") override val tmdbId: Int,
|
||||||
|
@SerialName("cr_series_id") override val crSeriesId: String,
|
||||||
|
@SerialName("seasons") val seasons: List<SeasonMeta>,
|
||||||
|
): Meta()
|
||||||
|
|
||||||
|
// class used in TVShowMeta, part of the tv show json object
|
||||||
|
@Serializable
|
||||||
|
data class SeasonMeta(
|
||||||
|
@SerialName("id") val id: Int,
|
||||||
|
@SerialName("tmdb_season_id") val tmdbSeasonId: Int,
|
||||||
|
@SerialName("tmdb_season_number") val tmdbSeasonNumber: Int,
|
||||||
|
@SerialName("cr_season_ids") val crSeasonIds: List<String>,
|
||||||
|
@SerialName("episodes") val episodes: List<EpisodeMeta>,
|
||||||
|
)
|
||||||
|
|
||||||
|
// class used in TVShowMeta, part of the tv show json object
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeMeta(
|
||||||
|
@SerialName("id") val id: Int,
|
||||||
|
@SerialName("tmdb_episode_id") val tmdbEpisodeId: Int,
|
||||||
|
@SerialName("tmdb_episode_number") val tmdbEpisodeNumber: Int,
|
||||||
|
@SerialName("cr_episode_ids") val crEpisodeIds: List<String>,
|
||||||
|
@SerialName("opening_start") val openingStart: Long,
|
||||||
|
@SerialName("opening_duration") val openingDuration: Long,
|
||||||
|
@SerialName("ending_start") val endingStart: Long,
|
||||||
|
@SerialName("ending_duration") val endingDuration: Long
|
||||||
|
)
|
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Teapod
|
||||||
|
*
|
||||||
|
* Copyright 2020-2022 <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.util.metadb
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.features.*
|
||||||
|
import io.ktor.client.features.json.*
|
||||||
|
import io.ktor.client.features.json.serializer.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
object MetaDBController {
|
||||||
|
private val TAG = javaClass.name
|
||||||
|
|
||||||
|
private const val repoUrl = "https://gitlab.com/Seil0/teapodmetadb/-/raw/main/crunchy/"
|
||||||
|
|
||||||
|
private val client = HttpClient {
|
||||||
|
install(JsonFeature) {
|
||||||
|
serializer = KotlinxSerializer(Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mediaList = MediaList(listOf())
|
||||||
|
private var metaCacheList = arrayListOf<Meta>()
|
||||||
|
|
||||||
|
suspend fun list() = withContext(Dispatchers.IO) {
|
||||||
|
val raw: String = client.get("$repoUrl/list.json")
|
||||||
|
mediaList = Json.decodeFromString(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the meta data for a movie from MetaDB
|
||||||
|
* @param crSeriesId The crunchyroll media id
|
||||||
|
* @return A meta object, or null if not found
|
||||||
|
*/
|
||||||
|
suspend fun getTVShowMetadata(crSeriesId: String): TVShowMeta? {
|
||||||
|
return if (mediaList.media.contains(crSeriesId)) {
|
||||||
|
metaCacheList.firstOrNull {
|
||||||
|
it.crSeriesId == crSeriesId
|
||||||
|
} as TVShowMeta? ?: getTVShowMetadataFromDB(crSeriesId)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getTVShowMetadataFromDB(crSeriesId: String): TVShowMeta? = withContext(Dispatchers.IO) {
|
||||||
|
return@withContext try {
|
||||||
|
val raw: String = client.get("$repoUrl/tv/$crSeriesId/media.json")
|
||||||
|
val meta: TVShowMeta = Json.decodeFromString(raw)
|
||||||
|
metaCacheList.add(meta)
|
||||||
|
|
||||||
|
meta
|
||||||
|
} catch (ex: ClientRequestException) {
|
||||||
|
when (ex.response.status) {
|
||||||
|
HttpStatusCode.NotFound -> Log.w(TAG, "The requested file was not found. Series ID: $crSeriesId", ex)
|
||||||
|
else -> Log.e(TAG, "Error while requesting meta data. Series ID: $crSeriesId", ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
null // todo return none object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
<?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>
|
|
19
app/src/main/res/drawable/ic_splash_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.03158203"
|
||||||
|
android:scaleY="0.03158203"
|
||||||
|
android:translateX="37.83"
|
||||||
|
android:translateY="44.778053">
|
||||||
|
<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>
|
Before Width: | Height: | Size: 10 KiB |
@ -243,6 +243,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:checked="true"
|
android:checked="true"
|
||||||
|
android:contentDescription="@string/settings_prefer_subbed"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
@ -304,6 +305,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:checked="true"
|
android:checked="true"
|
||||||
|
android:contentDescription="@string/settings_autoplay"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
@ -378,6 +380,69 @@
|
|||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_update_playhead"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView5"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/update_playhead"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:padding="9dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/ic_baseline_access_time_24"
|
||||||
|
app:tint="?iconColor" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linearLayout4"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/switch_update_playhead"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_update_playhead"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/update_playhead"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_update_playhead_desc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/update_playhead_desc"
|
||||||
|
android:textColor="?textSecondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switch_update_playhead"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="true"
|
||||||
|
android:contentDescription="@string/update_playhead"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/linear_export_data"
|
android:id="@+id/linear_export_data"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -385,7 +450,8 @@
|
|||||||
android:foreground="?android:selectableItemBackground"
|
android:foreground="?android:selectableItemBackground"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="7dp">
|
android:padding="7dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/image_export_data"
|
android:id="@+id/image_export_data"
|
||||||
@ -430,7 +496,8 @@
|
|||||||
android:foreground="?android:selectableItemBackground"
|
android:foreground="?android:selectableItemBackground"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="7dp">
|
android:padding="7dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/image_import_data"
|
android:id="@+id/image_import_data"
|
||||||
|
5
app/src/main/res/mipmap-anydpi-v26/ic_splash_round.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_splash_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_splash_foreground"/>
|
||||||
|
</adaptive-icon>
|
BIN
app/src/main/res/mipmap-hdpi/ic_splash_round.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_splash_round.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_splash_round.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_splash_round.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_splash_round.png
Normal file
After Width: | Height: | Size: 10 KiB |
@ -50,6 +50,8 @@
|
|||||||
<string name="theme_light">Hell</string>
|
<string name="theme_light">Hell</string>
|
||||||
<string name="theme_dark">Dunkel</string>
|
<string name="theme_dark">Dunkel</string>
|
||||||
<string name="dev_settings">Entwickler Einstellungen</string>
|
<string name="dev_settings">Entwickler Einstellungen</string>
|
||||||
|
<string name="update_playhead">Playhead Updates</string>
|
||||||
|
<string name="update_playhead_desc">Fortschritt bei Episoden auf cr updaten</string>
|
||||||
<string name="export_data">Daten exportieren</string>
|
<string name="export_data">Daten exportieren</string>
|
||||||
<string name="export_data_desc">Speichere "Meine Liste" in eine Datei</string>
|
<string name="export_data_desc">Speichere "Meine Liste" in eine Datei</string>
|
||||||
<string name="import_data">Daten importieren</string>
|
<string name="import_data">Daten importieren</string>
|
||||||
|
@ -26,4 +26,5 @@
|
|||||||
<color name="controlHighlightDark">#11ffffff</color>
|
<color name="controlHighlightDark">#11ffffff</color>
|
||||||
|
|
||||||
<color name="ic_launcher_background">#ffffff</color>
|
<color name="ic_launcher_background">#ffffff</color>
|
||||||
|
<color name="ic_splash_background">#ffffff</color>
|
||||||
</resources>
|
</resources>
|
@ -59,6 +59,8 @@
|
|||||||
<string name="theme_light">Light</string>
|
<string name="theme_light">Light</string>
|
||||||
<string name="theme_dark">Dark</string>
|
<string name="theme_dark">Dark</string>
|
||||||
<string name="dev_settings">Developer Settings</string>
|
<string name="dev_settings">Developer Settings</string>
|
||||||
|
<string name="update_playhead">Playhead updates</string>
|
||||||
|
<string name="update_playhead_desc">Update episode playhead on cr</string>
|
||||||
<string name="export_data">export data</string>
|
<string name="export_data">export data</string>
|
||||||
<string name="export_data_desc">export "My list" to a file</string>
|
<string name="export_data_desc">export "My list" to a file</string>
|
||||||
<string name="import_data">import data</string>
|
<string name="import_data">import data</string>
|
||||||
@ -137,6 +139,8 @@
|
|||||||
<string name="save_key_autoplay" translatable="false">org.mosad.teapod.autoplay</string>
|
<string name="save_key_autoplay" translatable="false">org.mosad.teapod.autoplay</string>
|
||||||
<string name="save_key_dev_settings" translatable="false">org.mosad.teapod.dev.settings</string>
|
<string name="save_key_dev_settings" translatable="false">org.mosad.teapod.dev.settings</string>
|
||||||
<string name="save_key_theme" translatable="false">org.mosad.teapod.theme</string>
|
<string name="save_key_theme" translatable="false">org.mosad.teapod.theme</string>
|
||||||
|
<!-- dev settings -->
|
||||||
|
<string name="save_key_update_playhead" translatable="false">org.mosad.teapod.update_playhead</string>
|
||||||
|
|
||||||
<!-- intents & states -->
|
<!-- intents & states -->
|
||||||
<string name="intent_media_id" translatable="false">intent_media_id</string>
|
<string name="intent_media_id" translatable="false">intent_media_id</string>
|
||||||
|
@ -71,10 +71,20 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- splash theme -->
|
<!-- splash theme -->
|
||||||
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
|
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
|
||||||
<item name="android:windowBackground">@drawable/bg_splash</item>
|
<!-- Set the splash screen background, animated icon, and animation duration. -->
|
||||||
|
<item name="windowSplashScreenBackground">@android:color/black</item>
|
||||||
|
|
||||||
|
<!-- Use windowSplashScreenAnimatedIcon to add either a drawable or an -->
|
||||||
|
<!-- animated drawable. One of these is required. -->
|
||||||
|
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_splash_round</item>
|
||||||
|
<item name="windowSplashScreenAnimationDuration">200</item>
|
||||||
|
|
||||||
|
<!-- Set the theme of the Activity that directly follows your splash screen. -->
|
||||||
|
<item name="postSplashScreenTheme">@style/AppTheme.Dark</item> # Required.
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<!-- shapes -->
|
<!-- shapes -->
|
||||||
<style name="ShapeAppearance.Teapod.RoundedPoster" parent="ShapeAppearance.MaterialComponents.LargeComponent">
|
<style name="ShapeAppearance.Teapod.RoundedPoster" parent="ShapeAppearance.MaterialComponents.LargeComponent">
|
||||||
<item name="cornerFamily">rounded</item>
|
<item name="cornerFamily">rounded</item>
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|