Compare commits
No commits in common. "1.0.0-beta3" and "1.0.0-beta2" have entirely different histories.
1.0.0-beta
...
1.0.0-beta
|
@ -5,15 +5,15 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 31
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.mosad.teapod"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 32
|
||||
versionCode 9020 //00.09.020
|
||||
versionName "1.0.0-beta3"
|
||||
targetSdkVersion 31
|
||||
versionCode 9010 //00.09.010
|
||||
versionName "1.0.0-beta2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resValue "string", "build_time", buildTime()
|
||||
|
@ -48,36 +48,33 @@ android {
|
|||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3'
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.2'
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0-rc01'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
|
||||
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
|
||||
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation "com.google.android.exoplayer:exoplayer-core:$exo_version"
|
||||
implementation "com.google.android.exoplayer:exoplayer-hls:$exo_version"
|
||||
implementation "com.google.android.exoplayer:exoplayer-dash:$exo_version"
|
||||
implementation "com.google.android.exoplayer:exoplayer-ui:$exo_version"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:$exo_version"
|
||||
|
||||
implementation 'com.facebook.shimmer:shimmer:0.5.0'
|
||||
|
||||
implementation 'com.github.bumptech.glide:glide:4.13.2'
|
||||
implementation 'com.github.bumptech.glide:glide:4.13.1'
|
||||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||
|
||||
implementation "io.ktor:ktor-client-core:$ktor_version"
|
||||
implementation "io.ktor:ktor-client-android:$ktor_version"
|
||||
implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
|
||||
implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
|
||||
implementation "io.ktor:ktor-client-serialization:$ktor_version"
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
|
|
|
@ -25,13 +25,13 @@ package org.mosad.teapod.parser.crunchyroll
|
|||
import android.util.Log
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
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.client.request.forms.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
|
@ -41,14 +41,14 @@ import kotlinx.serialization.json.put
|
|||
import org.mosad.teapod.preferences.EncryptedPreferences
|
||||
import org.mosad.teapod.preferences.Preferences
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
object Crunchyroll {
|
||||
private val TAG = javaClass.name
|
||||
|
||||
private val client = HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(json)
|
||||
}
|
||||
}
|
||||
private const val baseUrl = "https://beta-api.crunchyroll.com"
|
||||
|
@ -61,7 +61,6 @@ object Crunchyroll {
|
|||
private val tokenRefreshContext = newSingleThreadContext("TokenRefreshContext")
|
||||
|
||||
private var accountID = ""
|
||||
private var externalID = ""
|
||||
|
||||
private var policy = ""
|
||||
private var signature = ""
|
||||
|
@ -77,7 +76,7 @@ object Crunchyroll {
|
|||
*/
|
||||
fun initBasicApiToken() = runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
basicApiToken = client.get { url(basicApiTokenUrl) }.bodyAsText()
|
||||
basicApiToken = (client.get(basicApiTokenUrl) as HttpResponse).readText()
|
||||
Log.i(TAG, "basic auth token: $basicApiToken")
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +106,7 @@ object Crunchyroll {
|
|||
val response: HttpResponse = client.submitForm("$baseUrl$tokenEndpoint", formParameters = formData) {
|
||||
header("Authorization", "Basic $basicApiToken")
|
||||
}
|
||||
token = response.body()
|
||||
token = response.receive()
|
||||
tokenValidUntil = System.currentTimeMillis() + (token.expiresIn * 1000)
|
||||
response.status
|
||||
} catch (ex: ClientRequestException) {
|
||||
|
@ -155,10 +154,10 @@ object Crunchyroll {
|
|||
|
||||
// for json set body and content type
|
||||
if (bodyObject is JsonObject) {
|
||||
setBody(bodyObject)
|
||||
body = bodyObject
|
||||
contentType(ContentType.Application.Json)
|
||||
}
|
||||
}.body()
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
@ -246,7 +245,6 @@ object Crunchyroll {
|
|||
}
|
||||
|
||||
accountID = account.accountId
|
||||
externalID = account.externalId
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -333,7 +331,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(searchEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in search(), with query = \"$query\".", ex)
|
||||
NoneSearchResult
|
||||
}
|
||||
|
@ -357,7 +355,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(episodesEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in objects().", ex)
|
||||
NoneCollection
|
||||
}
|
||||
|
@ -373,7 +371,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(seasonListEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in seasonList().", ex)
|
||||
NoneDiscSeasonList
|
||||
}
|
||||
|
@ -397,7 +395,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(seriesEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in series().", ex)
|
||||
NoneSeries
|
||||
}
|
||||
|
@ -418,7 +416,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(upNextSeriesEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in upNextSeries().", ex)
|
||||
NoneUpNextSeriesItem
|
||||
}
|
||||
|
@ -442,7 +440,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(seasonsEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in seasons().", ex)
|
||||
NoneSeasons
|
||||
}
|
||||
|
@ -466,7 +464,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(episodesEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in episodes().", ex)
|
||||
NoneEpisodes
|
||||
}
|
||||
|
@ -481,7 +479,7 @@ object Crunchyroll {
|
|||
suspend fun playback(url: String): Playback {
|
||||
return try {
|
||||
requestGet("", url = url)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in playback(), with url = $url.", ex)
|
||||
NonePlayback
|
||||
}
|
||||
|
@ -504,7 +502,7 @@ object Crunchyroll {
|
|||
return try {
|
||||
(requestGet(watchlistSeriesEndpoint, parameters) as JsonObject)
|
||||
.containsKey(seriesId)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in isWatchlist() with seriesId = $seriesId", ex)
|
||||
false
|
||||
}
|
||||
|
@ -600,7 +598,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(similarToEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in similarTo().", ex)
|
||||
NoneSimilarToResult
|
||||
}
|
||||
|
@ -625,7 +623,7 @@ object Crunchyroll {
|
|||
|
||||
val list: ContinueWatchingList = try {
|
||||
requestGet(watchlistEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in watchlist().", ex)
|
||||
NoneContinueWatchingList
|
||||
}
|
||||
|
@ -649,7 +647,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(watchlistEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in upNextAccount().", ex)
|
||||
NoneContinueWatchingList
|
||||
}
|
||||
|
@ -666,7 +664,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(recommendationsEndpoint, parameters)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in recommendations().", ex)
|
||||
NoneRecommendationsList
|
||||
}
|
||||
|
@ -686,7 +684,7 @@ object Crunchyroll {
|
|||
|
||||
return try {
|
||||
requestGet(profileEndpoint)
|
||||
} catch (ex: SerializationException) {
|
||||
}catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in profile().", ex)
|
||||
NoneProfile
|
||||
}
|
||||
|
@ -706,20 +704,4 @@ object Crunchyroll {
|
|||
requestPatch(profileEndpoint, bodyObject = json)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional profile (benefits) information for the currently logged in account.
|
||||
*
|
||||
* * @return A **[Profile]** object
|
||||
*/
|
||||
suspend fun benefits(): Benefits {
|
||||
val profileEndpoint = "/subs/v1/subscriptions/$externalID/benefits"
|
||||
|
||||
return try {
|
||||
requestGet(profileEndpoint)
|
||||
} catch (ex: SerializationException) {
|
||||
Log.e(TAG, "SerializationException in benefits().", ex)
|
||||
NoneBenefits
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -125,7 +125,6 @@ typealias DiscSeasonList = Collection<SeasonListItem>
|
|||
typealias Watchlist = Collection<Item>
|
||||
typealias ContinueWatchingList = Collection<ContinueWatchingItem>
|
||||
typealias RecommendationsList = Collection<Item>
|
||||
typealias Benefits = Collection<Benefit>
|
||||
|
||||
@Serializable
|
||||
data class UpNextSeriesItem(
|
||||
|
@ -227,7 +226,6 @@ val NoneSimilarToResult = SimilarToResult(0, emptyList())
|
|||
val NoneDiscSeasonList = DiscSeasonList(0, emptyList())
|
||||
val NoneContinueWatchingList = ContinueWatchingList(0, emptyList())
|
||||
val NoneRecommendationsList = RecommendationsList(0, emptyList())
|
||||
val NoneBenefits = Benefits(0, emptyList())
|
||||
|
||||
val NoneUpNextSeriesItem = UpNextSeriesItem(
|
||||
playhead = 0,
|
||||
|
@ -382,9 +380,9 @@ data class Streams(
|
|||
|
||||
@Serializable
|
||||
data class Stream(
|
||||
@SerialName("hardsub_locale") val hardsubLocale: String = "", // default/nullable value since might be optional
|
||||
@SerialName("url") val url: String = "", // default/nullable value since optional
|
||||
@SerialName("vcodec") val vcodec: String = "", // default/nullable value since optional
|
||||
@SerialName("hardsub_locale") val hardsubLocale: String,
|
||||
@SerialName("url") val url: String,
|
||||
@SerialName("vcodec") val vcodec: String,
|
||||
)
|
||||
|
||||
val NonePlayback = Playback(
|
||||
|
@ -414,16 +412,3 @@ val NoneProfile = Profile(
|
|||
preferredContentSubtitleLanguage = "",
|
||||
username = ""
|
||||
)
|
||||
|
||||
/**
|
||||
* benefit data class
|
||||
*/
|
||||
@Serializable
|
||||
data class Benefit(
|
||||
@SerialName("benefit") val benefit: String,
|
||||
@SerialName("source") val source: String,
|
||||
)
|
||||
val NoneBenefit = Benefit(
|
||||
benefit = "",
|
||||
source = ""
|
||||
)
|
||||
|
|
|
@ -26,7 +26,6 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.addCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.fragment.app.Fragment
|
||||
|
@ -79,14 +78,16 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
|
|||
supportFragmentManager.commit {
|
||||
replace(R.id.nav_host_fragment, activeBaseFragment, activeBaseFragment.javaClass.simpleName)
|
||||
}
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback {
|
||||
if (supportFragmentManager.backStackEntryCount > 0) {
|
||||
supportFragmentManager.popBackStack()
|
||||
override fun onBackPressed() {
|
||||
if (supportFragmentManager.backStackEntryCount > 0) {
|
||||
supportFragmentManager.popBackStack()
|
||||
} else {
|
||||
if (activeBaseFragment !is HomeFragment) {
|
||||
binding.navView.selectedItemId = R.id.navigation_home
|
||||
} else {
|
||||
if (activeBaseFragment !is HomeFragment) {
|
||||
binding.navView.selectedItemId = R.id.navigation_home
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import kotlinx.coroutines.runBlocking
|
|||
import org.mosad.teapod.BuildConfig
|
||||
import org.mosad.teapod.R
|
||||
import org.mosad.teapod.databinding.FragmentAccountBinding
|
||||
import org.mosad.teapod.parser.crunchyroll.Benefits
|
||||
import org.mosad.teapod.parser.crunchyroll.Crunchyroll
|
||||
import org.mosad.teapod.parser.crunchyroll.Profile
|
||||
import org.mosad.teapod.parser.crunchyroll.supportedLocals
|
||||
|
@ -34,9 +33,6 @@ class AccountFragment : Fragment() {
|
|||
private var profile: Deferred<Profile> = lifecycleScope.async {
|
||||
Crunchyroll.profile()
|
||||
}
|
||||
private var benefits: Deferred<Benefits> = lifecycleScope.async {
|
||||
Crunchyroll.benefits()
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = FragmentAccountBinding.inflate(inflater, container, false)
|
||||
|
@ -48,18 +44,14 @@ class AccountFragment : Fragment() {
|
|||
|
||||
binding.textAccountLogin.text = EncryptedPreferences.login
|
||||
|
||||
// load account status and tier (async) info before anything else
|
||||
// TODO reimplement for cr, if possible (maybe account status would be better? (premium))
|
||||
// load subscription (async) info before anything else
|
||||
binding.textAccountSubscription.text = getString(R.string.account_subscription, getString(R.string.loading))
|
||||
lifecycleScope.launch {
|
||||
benefits.await().apply {
|
||||
this.items.firstOrNull { it.benefit == "cr_premium" }?.let {
|
||||
binding.textAccountSubscription.text = getString(R.string.account_premium)
|
||||
}
|
||||
|
||||
this.items.firstOrNull { it.benefit == "cr_fan_pack" }?.let {
|
||||
binding.textAccountSubscriptionDesc.text =
|
||||
getString(R.string.account_tier, getString(R.string.account_tier_mega_fan))
|
||||
}
|
||||
}
|
||||
binding.textAccountSubscription.text = getString(
|
||||
R.string.account_subscription,
|
||||
"TODO"
|
||||
)
|
||||
}
|
||||
|
||||
// add preferred subtitles
|
||||
|
@ -88,6 +80,12 @@ class AccountFragment : Fragment() {
|
|||
showLoginDialog()
|
||||
}
|
||||
|
||||
binding.linearAccountSubscription.setOnClickListener {
|
||||
// TODO
|
||||
//startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(AoDParser.getSubscriptionUrl())))
|
||||
}
|
||||
|
||||
|
||||
binding.linearSettingsContentLanguage.setOnClickListener {
|
||||
showContentLanguageSelection()
|
||||
}
|
||||
|
|
|
@ -27,15 +27,12 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.bumptech.glide.Glide
|
||||
import com.facebook.shimmer.ShimmerFrameLayout
|
||||
import kotlinx.coroutines.launch
|
||||
import org.mosad.teapod.R
|
||||
import org.mosad.teapod.databinding.FragmentHomeBinding
|
||||
|
@ -164,44 +161,10 @@ class HomeFragment : Fragment() {
|
|||
binding.textHighlightInfo.setOnClickListener {
|
||||
activity?.showFragment(MediaFragment(uiState.highlightItem.id))
|
||||
}
|
||||
|
||||
// disable the shimmer effect and hide the shimmer layouts
|
||||
binding.shimmerLayoutHighlight.apply {
|
||||
stopShimmer()
|
||||
isVisible = false
|
||||
}
|
||||
binding.shimmerLayoutUpNext.apply {
|
||||
stopShimmer()
|
||||
isVisible = false
|
||||
}
|
||||
binding.shimmerLayoutWatchlist.apply {
|
||||
stopShimmer()
|
||||
isVisible = false
|
||||
}
|
||||
binding.shimmerLayoutRecommendations.apply {
|
||||
stopShimmer()
|
||||
isVisible = false
|
||||
}
|
||||
binding.shimmerLayoutNewTitles.apply {
|
||||
stopShimmer()
|
||||
isVisible = false
|
||||
}
|
||||
binding.shimmerLayoutTopTen.apply {
|
||||
stopShimmer()
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
// make highlights layout visible again
|
||||
binding.linearHighlight.isVisible = true
|
||||
}
|
||||
|
||||
private fun bindUiStateLoading() {
|
||||
// hide highlights layout
|
||||
binding.linearHighlight.isVisible = false
|
||||
binding.root.children.filter { it is ShimmerFrameLayout }.forEach {
|
||||
it as ShimmerFrameLayout
|
||||
it.startShimmer()
|
||||
}
|
||||
// currently not used
|
||||
}
|
||||
|
||||
private fun bindUiStateError(uiState: HomeViewModel.UiState.Error) {
|
||||
|
|
|
@ -3,14 +3,13 @@ package org.mosad.teapod.ui.activity.onboarding
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.addCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import org.mosad.teapod.databinding.ActivityOnboardingBinding
|
||||
import org.mosad.teapod.ui.activity.main.MainActivity
|
||||
import org.mosad.teapod.databinding.ActivityOnboardingBinding
|
||||
|
||||
class OnboardingActivity : AppCompatActivity() {
|
||||
|
||||
|
@ -36,11 +35,13 @@ class OnboardingActivity : AppCompatActivity() {
|
|||
if (fragments.size <= 1) {
|
||||
binding.tabLayout.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback {
|
||||
if (binding.viewPager.currentItem != 0) {
|
||||
binding.viewPager.currentItem = binding.viewPager.currentItem - 1
|
||||
}
|
||||
override fun onBackPressed() {
|
||||
if (binding.viewPager.currentItem == 0) {
|
||||
super.onBackPressed()
|
||||
} else {
|
||||
binding.viewPager.currentItem = binding.viewPager.currentItem - 1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
|
||||
private lateinit var controller: StyledPlayerControlView
|
||||
private lateinit var gestureDetector: GestureDetectorCompat
|
||||
private lateinit var controlsUpdates: TimerTask
|
||||
private lateinit var timerUpdates: TimerTask
|
||||
|
||||
private var wasInPiP = false
|
||||
private var remainingTime: Long = 0
|
||||
|
@ -85,6 +85,8 @@ class PlayerActivity : AppCompatActivity() {
|
|||
hideBars() // Initial hide the bars
|
||||
|
||||
playerBinding = ActivityPlayerBinding.bind(findViewById(R.id.player_root))
|
||||
|
||||
println(findViewById(R.id.player_controls_root))
|
||||
controlsBinding = PlayerControlsBinding.bind(findViewById(R.id.player_controls_root))
|
||||
|
||||
model.loadMediaAsync(
|
||||
|
@ -192,11 +194,9 @@ class PlayerActivity : AppCompatActivity() {
|
|||
|
||||
override fun onPictureInPictureModeChanged(
|
||||
isInPictureInPictureMode: Boolean,
|
||||
newConfig: Configuration
|
||||
newConfig: Configuration?
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||
}
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||
|
||||
// Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
|
||||
playerBinding.videoView.useController = !isInPictureInPictureMode
|
||||
|
@ -229,11 +229,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
else -> View.GONE
|
||||
}
|
||||
|
||||
// don't use isVisible to hide exoPlayPause, as it will set the visibility to GONE
|
||||
controlsBinding.exoPlayPause.visibility = when(playerBinding.loading.isVisible) {
|
||||
true -> View.INVISIBLE
|
||||
false -> View.VISIBLE
|
||||
}
|
||||
controlsBinding.exoPlayPause.isVisible = !playerBinding.loading.isVisible
|
||||
|
||||
if (state == ExoPlayer.STATE_ENDED && hasNextEpisode() && Preferences.autoplay) {
|
||||
playNextEpisode()
|
||||
|
@ -288,11 +284,11 @@ class PlayerActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun initTimeUpdates() {
|
||||
if (this::controlsUpdates.isInitialized) {
|
||||
controlsUpdates.cancel()
|
||||
if (this::timerUpdates.isInitialized) {
|
||||
timerUpdates.cancel()
|
||||
}
|
||||
|
||||
controlsUpdates = Timer().scheduleAtFixedRate(0, 500) {
|
||||
timerUpdates = Timer().scheduleAtFixedRate(0, 500) {
|
||||
lifecycleScope.launch {
|
||||
val currentPosition = model.player.currentPosition
|
||||
val btnNextEpIsVisible = playerBinding.buttonNextEp.isVisible
|
||||
|
@ -302,14 +298,12 @@ class PlayerActivity : AppCompatActivity() {
|
|||
if (model.player.duration > 0) {
|
||||
remainingTime = model.player.duration - currentPosition
|
||||
remainingTime = if (remainingTime < 0) 0 else remainingTime
|
||||
} else {
|
||||
remainingTime = 0
|
||||
}
|
||||
|
||||
// TODO add metaDB ending_start support
|
||||
// if remaining time > 1 and < 20 sec, a next ep is set, autoplay is enabled
|
||||
// and not in pip: show next ep button
|
||||
if (remainingTime in 1000..20000) {
|
||||
// if remaining time < 20 sec, a next ep is set, autoplay is enabled and not in pip:
|
||||
// show next ep button
|
||||
if (remainingTime in 1..20000) {
|
||||
if (!btnNextEpIsVisible && hasNextEpisode() && Preferences.autoplay && !isInPiPMode()) {
|
||||
showButtonNextEp()
|
||||
}
|
||||
|
@ -343,7 +337,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
private fun onPauseOnStop() {
|
||||
playerBinding.videoView.onPause()
|
||||
model.player.pause()
|
||||
controlsUpdates.cancel()
|
||||
timerUpdates.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -430,16 +424,8 @@ class PlayerActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun playNextEpisode() {
|
||||
// disable the next episode buttons, so a user can't double click it
|
||||
playerBinding.buttonNextEp.isClickable = false
|
||||
controlsBinding.buttonNextEpC.isClickable = false
|
||||
|
||||
hideButtonNextEp()
|
||||
model.playNextEpisode()
|
||||
|
||||
// enable the next episode buttons when playNextEpisode() has returned
|
||||
playerBinding.buttonNextEp.isClickable = true
|
||||
controlsBinding.buttonNextEpC.isClickable = true
|
||||
hideButtonNextEp()
|
||||
}
|
||||
|
||||
private fun skipOpening() {
|
||||
|
@ -471,7 +457,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
playerBinding.buttonNextEp.animate()
|
||||
.alpha(0.0f)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
super.onAnimationEnd(animation)
|
||||
playerBinding.buttonNextEp.isVisible = false
|
||||
}
|
||||
|
@ -491,7 +477,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
playerBinding.buttonSkipOp.animate()
|
||||
.alpha(0.0f)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
super.onAnimationEnd(animation)
|
||||
playerBinding.buttonSkipOp.isVisible = false
|
||||
}
|
||||
|
@ -523,7 +509,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
/**
|
||||
* on single tap hide or show the controls
|
||||
*/
|
||||
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
||||
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||||
if (!isInPiPMode()) {
|
||||
if (controller.isVisible) controller.hide() else controller.show()
|
||||
}
|
||||
|
@ -534,8 +520,8 @@ class PlayerActivity : AppCompatActivity() {
|
|||
/**
|
||||
* on double tap rewind or forward
|
||||
*/
|
||||
override fun onDoubleTap(e: MotionEvent): Boolean {
|
||||
val eventPosX = e.x.toInt()
|
||||
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
||||
val eventPosX = e?.x?.toInt() ?: 0
|
||||
val viewCenterX = playerBinding.videoView.measuredWidth / 2
|
||||
|
||||
// if the event position is on the left side rewind, if it's on the right forward
|
||||
|
@ -547,14 +533,14 @@ class PlayerActivity : AppCompatActivity() {
|
|||
/**
|
||||
* not used
|
||||
*/
|
||||
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
|
||||
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* on long press toggle pause/play
|
||||
*/
|
||||
override fun onLongPress(e: MotionEvent) {
|
||||
override fun onLongPress(e: MotionEvent?) {
|
||||
model.togglePausePlay()
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,10 @@ import com.google.android.exoplayer2.ExoPlayer
|
|||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.mosad.teapod.R
|
||||
import org.mosad.teapod.parser.crunchyroll.*
|
||||
import org.mosad.teapod.preferences.Preferences
|
||||
|
@ -41,7 +44,6 @@ 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 kotlin.concurrent.scheduleAtFixedRate
|
||||
|
||||
/**
|
||||
* PlayerViewModel handles all stuff related to media/episodes.
|
||||
|
@ -53,7 +55,6 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||
|
||||
val player = ExoPlayer.Builder(application).build()
|
||||
private val mediaSession = MediaSessionCompat(application, "TEAPOD_PLAYER_SESSION")
|
||||
private val playheadAutoUpdate: TimerTask
|
||||
|
||||
val currentEpisodeChangedListener = ArrayList<() -> Unit>()
|
||||
private var currentPlayhead: Long = 0
|
||||
|
@ -95,14 +96,6 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||
if (!isPlaying) updatePlayhead()
|
||||
}
|
||||
})
|
||||
|
||||
playheadAutoUpdate = Timer().scheduleAtFixedRate(0, 30000) {
|
||||
viewModelScope.launch {
|
||||
if (player.isPlaying){
|
||||
updatePlayhead()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
@ -135,6 +128,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||
currentPlayheads = Crunchyroll.playheads(episodeIDs)
|
||||
}
|
||||
).joinAll()
|
||||
|
||||
|
||||
Log.d(classTag, "meta: $mediaMeta")
|
||||
|
||||
setCurrentEpisode(episodeId)
|
||||
|
@ -281,8 +276,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||
val playhead = (player.currentPosition / 1000)
|
||||
|
||||
if (playhead > 0 && Preferences.updatePlayhead) {
|
||||
// don't use viewModelScope here. This task may needs to finish, when ViewModel will be cleared
|
||||
CoroutineScope(Dispatchers.IO).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.")
|
||||
}
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@ class EpisodeListDialogFragment : DialogFragment() {
|
|||
EpisodeItemAdapter.ViewType.PLAYER
|
||||
)
|
||||
|
||||
// get the position/index of the currently playing episode
|
||||
adapterRecEpisodes.currentSelected = model.episodes.items.indexOfFirst { it.id == model.currentEpisode.id }
|
||||
// episodeNumber starts at 1, we need the episode index -> - 1
|
||||
adapterRecEpisodes.currentSelected = model.currentEpisode.episodeNumber?.minus(1) ?: 0
|
||||
|
||||
binding.recyclerEpisodesPlayer.adapter = adapterRecEpisodes
|
||||
binding.recyclerEpisodesPlayer.scrollToPosition(adapterRecEpisodes.currentSelected)
|
||||
|
|
|
@ -28,7 +28,7 @@ class FastForwardButton(context: Context, attrs: AttributeSet?): FrameLayout(con
|
|||
repeatCount = 1
|
||||
repeatMode = ObjectAnimator.REVERSE
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationStart(animation: Animator) {
|
||||
override fun onAnimationStart(animation: Animator?) {
|
||||
binding.imageButton.isEnabled = false // disable button
|
||||
binding.imageButton.setBackgroundResource(R.drawable.ic_baseline_forward_24)
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class FastForwardButton(context: Context, attrs: AttributeSet?): FrameLayout(con
|
|||
duration = animationDuration
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
// the label animation takes longer then the button animation, reset stuff in here
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
binding.imageButton.isEnabled = true // enable button
|
||||
binding.imageButton.setBackgroundResource(R.drawable.ic_baseline_forward_10_24)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class RewindButton(context: Context, attrs: AttributeSet): FrameLayout(context,
|
|||
repeatCount = 1
|
||||
repeatMode = ObjectAnimator.REVERSE
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationStart(animation: Animator) {
|
||||
override fun onAnimationStart(animation: Animator?) {
|
||||
binding.imageButton.isEnabled = false // disable button
|
||||
binding.imageButton.setBackgroundResource(R.drawable.ic_baseline_rewind_24)
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class RewindButton(context: Context, attrs: AttributeSet): FrameLayout(context,
|
|||
labelAnimation = ObjectAnimator.ofFloat(binding.textView, View.TRANSLATION_X, -35f).apply {
|
||||
duration = animationDuration
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
binding.imageButton.isEnabled = true // enable button
|
||||
binding.imageButton.setBackgroundResource(R.drawable.ic_baseline_rewind_10_24)
|
||||
|
||||
|
|
|
@ -24,12 +24,11 @@ package org.mosad.teapod.util.metadb
|
|||
|
||||
import android.util.Log
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
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 io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.decodeFromString
|
||||
|
@ -41,8 +40,8 @@ object MetaDBController {
|
|||
private const val repoUrl = "https://gitlab.com/Seil0/teapodmetadb/-/raw/main/crunchy/"
|
||||
|
||||
private val client = HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(Json)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +49,7 @@ object MetaDBController {
|
|||
private var metaCacheList = arrayListOf<Meta>()
|
||||
|
||||
suspend fun list() = withContext(Dispatchers.IO) {
|
||||
val raw: String = client.get("$repoUrl/list.json").body()
|
||||
val raw: String = client.get("$repoUrl/list.json")
|
||||
mediaList = Json.decodeFromString(raw)
|
||||
}
|
||||
|
||||
|
@ -71,7 +70,7 @@ object MetaDBController {
|
|||
|
||||
private suspend fun getTVShowMetadataFromDB(crSeriesId: String): TVShowMeta? = withContext(Dispatchers.IO) {
|
||||
return@withContext try {
|
||||
val raw: String = client.get("$repoUrl/tv/$crSeriesId/media.json").body()
|
||||
val raw: String = client.get("$repoUrl/tv/$crSeriesId/media.json")
|
||||
val meta: TVShowMeta = Json.decodeFromString(raw)
|
||||
metaCacheList.add(meta)
|
||||
|
||||
|
|
|
@ -25,10 +25,10 @@ package org.mosad.teapod.util.tmdb
|
|||
import android.util.Log
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.invoke
|
||||
|
@ -46,11 +46,10 @@ import org.mosad.teapod.util.concatenate
|
|||
class TMDBApiController {
|
||||
private val classTag = javaClass.name
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
private val client = HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
install(JsonFeature) {
|
||||
serializer = KotlinxSerializer(json)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +78,7 @@ class TMDBApiController {
|
|||
}
|
||||
}
|
||||
|
||||
response.body<T>()
|
||||
response.receive<T>()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?shapeTextBackground"/>
|
||||
<size
|
||||
android:width="1920px"
|
||||
android:height="1080px"/>
|
||||
</shape>
|
|
@ -112,7 +112,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/loading"
|
||||
android:text="@string/account_subscription"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
|
@ -120,7 +120,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/account_tier"
|
||||
android:text="@string/account_subscription_desc"
|
||||
android:textColor="?textSecondary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -17,16 +17,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/shimmer_layout_highlight"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:visibility="gone">
|
||||
|
||||
<include layout="@layout/item_highlight_shimmer" />
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_highlight"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -136,23 +126,6 @@
|
|||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/shimmer_layout_up_next"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
</LinearLayout>
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_up_next"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -181,23 +154,6 @@
|
|||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/shimmer_layout_watchlist"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
</LinearLayout>
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_watchlist"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -226,23 +182,6 @@
|
|||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/shimmer_layout_recommendations"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
</LinearLayout>
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_recommendations"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -271,23 +210,6 @@
|
|||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/shimmer_layout_new_titles"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
</LinearLayout>
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_new_titles"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -316,23 +238,6 @@
|
|||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/shimmer_layout_top_ten"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
<include layout="@layout/item_media_shimmer" />
|
||||
</LinearLayout>
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_top_ten"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?themePrimary">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/shimmer_image_highlight"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:src="@drawable/placeholder_image"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/shimmer_linear_highlight"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?themePrimary"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="7dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/shimmer_image_highlight">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/shimmer_text_highlight_title"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="7dp"
|
||||
android:background="?shapeTextBackground"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="7dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/shimmer_text_highlight_my_list"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textSize="12sp"
|
||||
app:drawableTint="?shapeTextBackground"
|
||||
app:drawableTopCompat="@drawable/ic_baseline_add_24" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/shimmer_button_play_highlight"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="?shapeTextBackground" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/shimmer_text_highlight_info"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
app:drawableTint="?shapeTextBackground"
|
||||
app:drawableTopCompat="@drawable/ic_outline_info_24" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -2,16 +2,16 @@
|
|||
<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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="195dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="?themeSecondary"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="7dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintWidth_max="195dp">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/frame_image_progress"
|
||||
|
@ -21,8 +21,7 @@
|
|||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth="195dp">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_poster"
|
||||
|
@ -54,7 +53,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/text_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:lines="2"
|
||||
|
@ -63,8 +62,6 @@
|
|||
android:text="@string/text_title_ex"
|
||||
android:textAlignment="center"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="3dp"
|
||||
android:backgroundTint="?themeSecondary"
|
||||
app:cardCornerRadius="7dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintWidth_max="195dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/frame_image_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text_title"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth="195dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_poster"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?shapeTextBackground"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_title"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="11dp"
|
||||
android:background="?shapeTextBackground"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
|
@ -37,8 +37,6 @@
|
|||
<string name="account_login_desc">Zum bearbeiten tippen</string>
|
||||
<string name="account_subscription">Abo %1$s</string>
|
||||
<string name="account_subscription_desc">Zum verlängern tippen</string>
|
||||
<string name="account_premium">Premium Mitglied</string>
|
||||
<string name="account_tier">Typ: %1$s</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="info_about_desc">Version %1$s (%2$s)</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
|
|
|
@ -49,11 +49,6 @@
|
|||
<string name="account_login_desc">Tap to edit</string>
|
||||
<string name="account_subscription">Subscription %1$s</string>
|
||||
<string name="account_subscription_desc">Tap to extend</string>
|
||||
<string name="account_premium">Premium member</string>
|
||||
<string name="account_tier">Tier: %1$s</string>
|
||||
<string name="account_tier_fan" translatable="false">Fan</string>
|
||||
<string name="account_tier_mega_fan" translatable="false">Mega Fan</string>
|
||||
<string name="account_tier_ultimate_fan" translatable="false">Ultimate Fan</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="settings_content_language">Preferred content language</string>
|
||||
<string name="settings_content_language_desc">English</string>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package org.mosad.teapod
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package org.mosad.teapod.parser.crunchyroll
|
||||
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class DataTypesTest {
|
||||
|
||||
@Test
|
||||
fun testTokenType() {
|
||||
val testToken = javaClass.getResource("/token.json")!!.readText()
|
||||
val token: Token = Json.decodeFromString(testToken)
|
||||
|
||||
Assert.assertEquals("TestAccessToken-1_TestAccessToken", token.accessToken)
|
||||
Assert.assertEquals("00000000-0000-0000-0000-000000000000", token.refreshToken)
|
||||
Assert.assertEquals(300, token.expiresIn)
|
||||
Assert.assertEquals("Bearer", token.tokenType)
|
||||
Assert.assertEquals("account content offline_access reviews talkbox", token.scope)
|
||||
Assert.assertEquals("DE", token.country)
|
||||
Assert.assertEquals("00000000-0000-0000-0000-000000000000", token.accountId)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"access_token":"TestAccessToken-1_TestAccessToken",
|
||||
"refresh_token":"00000000-0000-0000-0000-000000000000",
|
||||
"expires_in":300,
|
||||
"token_type":"Bearer",
|
||||
"scope":"account content offline_access reviews talkbox",
|
||||
"country":"DE",
|
||||
"account_id":"00000000-0000-0000-0000-000000000000"
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.7.10"
|
||||
ext.ktor_version = "2.1.1"
|
||||
ext.kotlin_version = "1.6.21"
|
||||
ext.ktor_version = "1.6.8"
|
||||
ext.exo_version = "2.17.1"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath 'com.android.tools.build:gradle:7.2.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
Dies ist der dritte beta Release von Teapod 1.0.0 mit Unterstützung für Cunchyroll.
|
||||
|
||||
* Diverse UI/UX Verbesserungen
|
||||
* Playhead Updates werden nun alle 30 Sekunden durchgeführt
|
||||
* Fehlende Playhead Updates beim schließen des Players behoben (#62)
|
||||
* Abo Status und Stufe zum Accountscreen hinzugefügt
|
||||
* Das Verhalten des "Nächste Episode" Buttons wurde verbessert (#53)
|
||||
|
||||
Alle Änderungen https://git.mosad.xyz/Seil0/teapod/compare/1.0.0-beta2...1.0.0-beta3
|
|
@ -1,9 +0,0 @@
|
|||
This is the third beta release of Teapod 1.0.0 with support for crunchyroll.
|
||||
|
||||
* UI/UX improvements
|
||||
* Playhead is now updated every 30 seconds
|
||||
* Fixed missing playhead updates when closing the player (#62)
|
||||
* Add subscription status and tier info to the account screen
|
||||
* Improved the behaviour of the "next episde" button (#53)
|
||||
|
||||
Full changelog https://git.mosad.xyz/Seil0/teapod/compare/1.0.0-beta2...1.0.0-beta3
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
Loading…
Reference in New Issue