Compare commits
5 Commits
ef9a0f00d0
...
1.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
ad1e3068cd
|
|||
de1f19c2b7
|
|||
12bbc2ef5f
|
|||
0186cef79e
|
|||
bc5509cf93
|
@ -38,6 +38,9 @@ android {
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
kotlin.sourceSets.all {
|
||||
languageSettings.optIn("kotlin.RequiresOptIn")
|
||||
}
|
||||
}
|
||||
namespace 'org.mosad.teapod'
|
||||
}
|
||||
|
@ -33,8 +33,6 @@ import io.ktor.client.request.forms.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
@ -59,7 +57,8 @@ object Crunchyroll {
|
||||
|
||||
private lateinit var token: Token
|
||||
private var tokenValidUntil: Long = 0
|
||||
private val tokeRefreshMutex = Mutex()
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
private val tokenRefreshContext = newSingleThreadContext("TokenRefreshContext")
|
||||
|
||||
private var accountID = ""
|
||||
|
||||
@ -141,8 +140,7 @@ object Crunchyroll {
|
||||
params: List<Pair<String, Any?>> = listOf(),
|
||||
bodyObject: Any = Any()
|
||||
): T = coroutineScope {
|
||||
// TODO find a better way to make token refresh thread safe, currently it's blocking
|
||||
tokeRefreshMutex.withLock {
|
||||
withContext(tokenRefreshContext) {
|
||||
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
|
||||
}
|
||||
|
||||
@ -553,7 +551,10 @@ object Crunchyroll {
|
||||
return try {
|
||||
requestGet(playheadsEndpoint, parameters)
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
@ -573,7 +574,11 @@ object Crunchyroll {
|
||||
put("playhead", playhead)
|
||||
}
|
||||
|
||||
try {
|
||||
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
|
||||
*/
|
||||
|
@ -124,6 +124,7 @@ typealias SimilarToResult = Collection<Item>
|
||||
typealias DiscSeasonList = Collection<SeasonListItem>
|
||||
typealias Watchlist = Collection<Item>
|
||||
typealias ContinueWatchingList = Collection<ContinueWatchingItem>
|
||||
typealias RecommendationsList = Collection<Item>
|
||||
|
||||
@Serializable
|
||||
data class UpNextSeriesItem(
|
||||
@ -224,6 +225,7 @@ val NoneBrowseResult = BrowseResult(0, emptyList())
|
||||
val NoneSimilarToResult = SimilarToResult(0, emptyList())
|
||||
val NoneDiscSeasonList = DiscSeasonList(0, emptyList())
|
||||
val NoneContinueWatchingList = ContinueWatchingList(0, emptyList())
|
||||
val NoneRecommendationsList = RecommendationsList(0, emptyList())
|
||||
|
||||
val NoneUpNextSeriesItem = UpNextSeriesItem(
|
||||
playhead = 0,
|
||||
|
@ -61,6 +61,7 @@ class HomeFragment : Fragment() {
|
||||
|
||||
binding.recyclerUpNext.addItemDecoration(MediaItemDecoration(9))
|
||||
binding.recyclerWatchlist.addItemDecoration(MediaItemDecoration(9))
|
||||
binding.recyclerRecommendations.addItemDecoration(MediaItemDecoration(9))
|
||||
binding.recyclerNewTitles.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(
|
||||
MediaItemListAdapter.OnClickListener {
|
||||
activity?.showFragment(MediaFragment(it.id))
|
||||
@ -129,6 +136,9 @@ class HomeFragment : Fragment() {
|
||||
val adapterWatchlist = binding.recyclerWatchlist.adapter as MediaItemListAdapter
|
||||
adapterWatchlist.submitList(uiState.watchlistItems.toItemMediaList())
|
||||
|
||||
val adapterRecommendations = binding.recyclerRecommendations.adapter as MediaItemListAdapter
|
||||
adapterRecommendations.submitList(uiState.recommendationsItems.toItemMediaList())
|
||||
|
||||
val adapterNewTitles = binding.recyclerNewTitles.adapter as MediaItemListAdapter
|
||||
adapterNewTitles.submitList(uiState.recentlyAddedItems.toItemMediaList())
|
||||
|
||||
|
@ -40,6 +40,7 @@ class HomeViewModel : ViewModel() {
|
||||
data class Normal(
|
||||
val upNextItems: List<ContinueWatchingItem>,
|
||||
val watchlistItems: List<Item>,
|
||||
val recommendationsItems: List<Item>,
|
||||
val recentlyAddedItems: List<Item>,
|
||||
val topTenItems: List<Item>,
|
||||
val highlightItem: Item,
|
||||
@ -63,6 +64,9 @@ class HomeViewModel : ViewModel() {
|
||||
// run the loading in parallel to speed up the process
|
||||
val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount().items }
|
||||
val watchlistJob = viewModelScope.async { Crunchyroll.watchlist(50).items }
|
||||
val recommendationsJob = viewModelScope.async {
|
||||
Crunchyroll.recommendations(20).items
|
||||
}
|
||||
val recentlyAddedJob = viewModelScope.async {
|
||||
Crunchyroll.browse(sortBy = SortBy.NEWLY_ADDED, n = 50).items
|
||||
}
|
||||
@ -76,8 +80,9 @@ class HomeViewModel : ViewModel() {
|
||||
val highlightItemIsWatchlist = Crunchyroll.isWatchlist(highlightItem.id)
|
||||
|
||||
uiState.emit(UiState.Normal(
|
||||
upNextJob.await(), watchlistJob.await(), recentlyAddedJob.await(),
|
||||
topTenJob.await(), highlightItem, highlightItemIsWatchlist
|
||||
upNextJob.await(), watchlistJob.await(), recommendationsJob.await(),
|
||||
recentlyAddedJob.await(), topTenJob.await(), highlightItem,
|
||||
highlightItemIsWatchlist
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
uiState.emit(UiState.Error(e.message))
|
||||
|
@ -462,7 +462,6 @@ class PlayerActivity : AppCompatActivity() {
|
||||
playerBinding.buttonNextEp.isVisible = false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun showButtonSkipOp() {
|
||||
|
@ -77,7 +77,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="70dp"
|
||||
android:layout_marginBottom="72dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/next_episode"
|
||||
android:textAllCaps="false"
|
||||
@ -93,7 +93,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginBottom="70dp"
|
||||
android:layout_marginBottom="72dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/skip_opening"
|
||||
android:textAllCaps="false"
|
||||
|
@ -163,6 +163,34 @@
|
||||
tools:listitem="@layout/item_media" />
|
||||
</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
|
||||
android:id="@+id/linear_new_titles"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -9,6 +9,7 @@
|
||||
<string name="highlight_media">Highlight</string>
|
||||
<string name="up_next">Weiterschauen</string>
|
||||
<string name="my_list">Meine Liste</string>
|
||||
<string name="recommendations">Empfehlungen</string>
|
||||
<string name="new_episodes">Neue Episoden</string>
|
||||
<string name="new_simulcasts">Neue Simulcasts</string>
|
||||
<string name="new_titles">Neue Titel</string>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
</resources>
|
||||
|
@ -9,6 +9,7 @@
|
||||
<string name="highlight_media">Highlight</string>
|
||||
<string name="up_next">Up next</string>
|
||||
<string name="my_list">My list</string>
|
||||
<string name="recommendations">Recommendations</string>
|
||||
<string name="new_episodes">New episodes</string>
|
||||
<string name="new_simulcasts">New simulcasts</string>
|
||||
<string name="new_titles">New titles</string>
|
||||
|
@ -8,7 +8,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
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"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
@ -1,4 +1,10 @@
|
||||
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)
|
||||
* 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
|
||||
|
@ -1,4 +1,10 @@
|
||||
This is the second beta release of Teapod 1.0.0 with support for crunchyroll.
|
||||
|
||||
* Added support for crunchyroll (a premium account is needed)
|
||||
* Added crunchyroll metadb support
|
||||
* Support for crunchyroll (a premium account is needed)
|
||||
* 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
|
||||
|
Reference in New Issue
Block a user