5 Commits

Author SHA1 Message Date
ad1e3068cd update changelog for beta2 release 2022-06-06 13:33:21 +02:00
de1f19c2b7 catch exceprion in playheads() and postPlayheads() & update agp
* fix a crash, if there is no internet connection while in playback (closes #60)
* agp 7.2.0 -> 7.2.1
2022-06-06 13:14:41 +02:00
12bbc2ef5f add recommendations to home fragment 2022-05-22 11:21:49 +02:00
0186cef79e fix player progress bar skip intro/next ep button overlapping 2022-05-22 10:39:17 +02:00
bc5509cf93 use newSingleThreadContext instead of mutex for token refresh
fixes #57
2022-05-20 15:07:07 +02:00
14 changed files with 101 additions and 18 deletions

View File

@ -38,6 +38,9 @@ android {
} }
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
kotlin.sourceSets.all {
languageSettings.optIn("kotlin.RequiresOptIn")
}
} }
namespace 'org.mosad.teapod' namespace 'org.mosad.teapod'
} }

View File

@ -33,8 +33,6 @@ import io.ktor.client.request.forms.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
@ -59,7 +57,8 @@ object Crunchyroll {
private lateinit var token: Token private lateinit var token: Token
private var tokenValidUntil: Long = 0 private var tokenValidUntil: Long = 0
private val tokeRefreshMutex = Mutex() @OptIn(DelicateCoroutinesApi::class)
private val tokenRefreshContext = newSingleThreadContext("TokenRefreshContext")
private var accountID = "" private var accountID = ""
@ -141,8 +140,7 @@ object Crunchyroll {
params: List<Pair<String, Any?>> = listOf(), params: List<Pair<String, Any?>> = listOf(),
bodyObject: Any = Any() bodyObject: Any = Any()
): T = coroutineScope { ): T = coroutineScope {
// TODO find a better way to make token refresh thread safe, currently it's blocking withContext(tokenRefreshContext) {
tokeRefreshMutex.withLock {
if (System.currentTimeMillis() > tokenValidUntil) refreshToken() if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
} }
@ -552,8 +550,11 @@ object Crunchyroll {
return try { return try {
requestGet(playheadsEndpoint, parameters) requestGet(playheadsEndpoint, parameters)
}catch (ex: SerializationException) { } catch (ex: SerializationException) {
Log.e(TAG, "SerializationException in upNextSeries().", ex) Log.e(TAG, "SerializationException in playheads().", ex)
emptyMap()
} catch (ex: Throwable) {
Log.e(TAG, "Exception in playheads().", ex.cause)
emptyMap() emptyMap()
} }
} }
@ -573,7 +574,11 @@ object Crunchyroll {
put("playhead", playhead) put("playhead", playhead)
} }
try {
requestPost(playheadsEndpoint, parameters, json) requestPost(playheadsEndpoint, parameters, json)
} catch (ex: Throwable) {
Log.e(TAG, "Exception in postPlayheads()", ex.cause)
}
} }
/** /**
@ -648,6 +653,23 @@ object Crunchyroll {
} }
} }
suspend fun recommendations(n: Int = 20, start: Int = 0): RecommendationsList {
val recommendationsEndpoint = "/content/v1/$accountID/recommendations"
val parameters = listOf(
"locale" to Preferences.preferredLocale.toLanguageTag(),
"n" to n,
"start" to start,
"variant_id" to 0
)
return try {
requestGet(recommendationsEndpoint, parameters)
}catch (ex: SerializationException) {
Log.e(TAG, "SerializationException in recommendations().", ex)
NoneRecommendationsList
}
}
/** /**
* Account/Profile functions * Account/Profile functions
*/ */

View File

@ -124,6 +124,7 @@ typealias SimilarToResult = Collection<Item>
typealias DiscSeasonList = Collection<SeasonListItem> typealias DiscSeasonList = Collection<SeasonListItem>
typealias Watchlist = Collection<Item> typealias Watchlist = Collection<Item>
typealias ContinueWatchingList = Collection<ContinueWatchingItem> typealias ContinueWatchingList = Collection<ContinueWatchingItem>
typealias RecommendationsList = Collection<Item>
@Serializable @Serializable
data class UpNextSeriesItem( data class UpNextSeriesItem(
@ -224,6 +225,7 @@ val NoneBrowseResult = BrowseResult(0, emptyList())
val NoneSimilarToResult = SimilarToResult(0, emptyList()) val NoneSimilarToResult = SimilarToResult(0, emptyList())
val NoneDiscSeasonList = DiscSeasonList(0, emptyList()) val NoneDiscSeasonList = DiscSeasonList(0, emptyList())
val NoneContinueWatchingList = ContinueWatchingList(0, emptyList()) val NoneContinueWatchingList = ContinueWatchingList(0, emptyList())
val NoneRecommendationsList = RecommendationsList(0, emptyList())
val NoneUpNextSeriesItem = UpNextSeriesItem( val NoneUpNextSeriesItem = UpNextSeriesItem(
playhead = 0, playhead = 0,

View File

@ -61,6 +61,7 @@ class HomeFragment : Fragment() {
binding.recyclerUpNext.addItemDecoration(MediaItemDecoration(9)) binding.recyclerUpNext.addItemDecoration(MediaItemDecoration(9))
binding.recyclerWatchlist.addItemDecoration(MediaItemDecoration(9)) binding.recyclerWatchlist.addItemDecoration(MediaItemDecoration(9))
binding.recyclerRecommendations.addItemDecoration(MediaItemDecoration(9))
binding.recyclerNewTitles.addItemDecoration(MediaItemDecoration(9)) binding.recyclerNewTitles.addItemDecoration(MediaItemDecoration(9))
binding.recyclerTopTen.addItemDecoration(MediaItemDecoration(9)) binding.recyclerTopTen.addItemDecoration(MediaItemDecoration(9))
@ -79,6 +80,12 @@ class HomeFragment : Fragment() {
} }
) )
binding.recyclerRecommendations.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id))
}
)
binding.recyclerNewTitles.adapter = MediaItemListAdapter( binding.recyclerNewTitles.adapter = MediaItemListAdapter(
MediaItemListAdapter.OnClickListener { MediaItemListAdapter.OnClickListener {
activity?.showFragment(MediaFragment(it.id)) activity?.showFragment(MediaFragment(it.id))
@ -129,6 +136,9 @@ class HomeFragment : Fragment() {
val adapterWatchlist = binding.recyclerWatchlist.adapter as MediaItemListAdapter val adapterWatchlist = binding.recyclerWatchlist.adapter as MediaItemListAdapter
adapterWatchlist.submitList(uiState.watchlistItems.toItemMediaList()) adapterWatchlist.submitList(uiState.watchlistItems.toItemMediaList())
val adapterRecommendations = binding.recyclerRecommendations.adapter as MediaItemListAdapter
adapterRecommendations.submitList(uiState.recommendationsItems.toItemMediaList())
val adapterNewTitles = binding.recyclerNewTitles.adapter as MediaItemListAdapter val adapterNewTitles = binding.recyclerNewTitles.adapter as MediaItemListAdapter
adapterNewTitles.submitList(uiState.recentlyAddedItems.toItemMediaList()) adapterNewTitles.submitList(uiState.recentlyAddedItems.toItemMediaList())

View File

@ -40,6 +40,7 @@ class HomeViewModel : ViewModel() {
data class Normal( data class Normal(
val upNextItems: List<ContinueWatchingItem>, val upNextItems: List<ContinueWatchingItem>,
val watchlistItems: List<Item>, val watchlistItems: List<Item>,
val recommendationsItems: List<Item>,
val recentlyAddedItems: List<Item>, val recentlyAddedItems: List<Item>,
val topTenItems: List<Item>, val topTenItems: List<Item>,
val highlightItem: Item, val highlightItem: Item,
@ -63,6 +64,9 @@ class HomeViewModel : ViewModel() {
// run the loading in parallel to speed up the process // run the loading in parallel to speed up the process
val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount().items } val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount().items }
val watchlistJob = viewModelScope.async { Crunchyroll.watchlist(50).items } val watchlistJob = viewModelScope.async { Crunchyroll.watchlist(50).items }
val recommendationsJob = viewModelScope.async {
Crunchyroll.recommendations(20).items
}
val recentlyAddedJob = viewModelScope.async { val recentlyAddedJob = viewModelScope.async {
Crunchyroll.browse(sortBy = SortBy.NEWLY_ADDED, n = 50).items Crunchyroll.browse(sortBy = SortBy.NEWLY_ADDED, n = 50).items
} }
@ -76,8 +80,9 @@ class HomeViewModel : ViewModel() {
val highlightItemIsWatchlist = Crunchyroll.isWatchlist(highlightItem.id) val highlightItemIsWatchlist = Crunchyroll.isWatchlist(highlightItem.id)
uiState.emit(UiState.Normal( uiState.emit(UiState.Normal(
upNextJob.await(), watchlistJob.await(), recentlyAddedJob.await(), upNextJob.await(), watchlistJob.await(), recommendationsJob.await(),
topTenJob.await(), highlightItem, highlightItemIsWatchlist recentlyAddedJob.await(), topTenJob.await(), highlightItem,
highlightItemIsWatchlist
)) ))
} catch (e: Exception) { } catch (e: Exception) {
uiState.emit(UiState.Error(e.message)) uiState.emit(UiState.Error(e.message))

View File

@ -462,7 +462,6 @@ class PlayerActivity : AppCompatActivity() {
playerBinding.buttonNextEp.isVisible = false playerBinding.buttonNextEp.isVisible = false
} }
}) })
} }
private fun showButtonSkipOp() { private fun showButtonSkipOp() {

View File

@ -77,7 +77,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:layout_marginBottom="70dp" android:layout_marginBottom="72dp"
android:gravity="center" android:gravity="center"
android:text="@string/next_episode" android:text="@string/next_episode"
android:textAllCaps="false" android:textAllCaps="false"
@ -93,7 +93,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:layout_marginBottom="70dp" android:layout_marginBottom="72dp"
android:gravity="center" android:gravity="center"
android:text="@string/skip_opening" android:text="@string/skip_opening"
android:textAllCaps="false" android:textAllCaps="false"

View File

@ -163,6 +163,34 @@
tools:listitem="@layout/item_media" /> tools:listitem="@layout/item_media" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/linear_recommendations"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="7dp">
<TextView
android:id="@+id/text_recommendations"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingTop="15dp"
android:paddingEnd="5dp"
android:paddingBottom="5dp"
android:text="@string/recommendations"
android:textSize="16sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_recommendations"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_media" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/linear_new_titles" android:id="@+id/linear_new_titles"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -9,6 +9,7 @@
<string name="highlight_media">Highlight</string> <string name="highlight_media">Highlight</string>
<string name="up_next">Weiterschauen</string> <string name="up_next">Weiterschauen</string>
<string name="my_list">Meine Liste</string> <string name="my_list">Meine Liste</string>
<string name="recommendations">Empfehlungen</string>
<string name="new_episodes">Neue Episoden</string> <string name="new_episodes">Neue Episoden</string>
<string name="new_simulcasts">Neue Simulcasts</string> <string name="new_simulcasts">Neue Simulcasts</string>
<string name="new_titles">Neue Titel</string> <string name="new_titles">Neue Titel</string>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="player_styled_progress_layout_height">48dp</dimen> <dimen name="player_styled_progress_layout_height">28dp</dimen>
<dimen name="player_styled_progress_margin_bottom">52dp</dimen> <dimen name="player_styled_progress_margin_bottom">52dp</dimen>
</resources> </resources>

View File

@ -9,6 +9,7 @@
<string name="highlight_media">Highlight</string> <string name="highlight_media">Highlight</string>
<string name="up_next">Up next</string> <string name="up_next">Up next</string>
<string name="my_list">My list</string> <string name="my_list">My list</string>
<string name="recommendations">Recommendations</string>
<string name="new_episodes">New episodes</string> <string name="new_episodes">New episodes</string>
<string name="new_simulcasts">New simulcasts</string> <string name="new_simulcasts">New simulcasts</string>
<string name="new_titles">New titles</string> <string name="new_titles">New titles</string>

View File

@ -8,7 +8,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.0' classpath 'com.android.tools.build:gradle:7.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@ -1,4 +1,10 @@
Dies ist der zweite beta Release von Teapod 1.0.0 mit Unterstützung für Cunchyroll. Dies ist der zweite beta Release von Teapod 1.0.0 mit Unterstützung für Cunchyroll.
* Unterstützung für Crunchyroll hinzugefügt (Ein premium Account wird benötigt) * Unterstützung für Crunchyroll hinzugefügt (Ein premium Account wird benötigt)
* Crunchyroll metadb Unterstützung hinzugefügt * Crunchyroll metadb Unterstützung hinzugefügt (#54)
* Playhead Updates lassen sich nun ausschalten
* Ähnliche Titel zum Mediafragment hinzugefügt
* Empfehlungen für dich zum Homefragment hinzugefügt
* Einen Crash beim login wurde behoben
Alle Änderungen https://git.mosad.xyz/Seil0/teapod/compare/1.0.0-beta1...1.0.0-beta2

View File

@ -1,4 +1,10 @@
This is the second beta release of Teapod 1.0.0 with support for crunchyroll. This is the second beta release of Teapod 1.0.0 with support for crunchyroll.
* Added support for crunchyroll (a premium account is needed) * Support for crunchyroll (a premium account is needed)
* Added crunchyroll metadb support * Crunchyroll metadb support (#54)
* Added a option to disable playhead updates/reporting
* Show similar titles in the media fragment
* Added recommendations to the home fragment
* Fixed a crash on login, which made the app unusable
Full changelog https://git.mosad.xyz/Seil0/teapod/compare/1.0.0-beta1...1.0.0-beta2