parent
a51001ec2e
commit
e8bf63a666
|
@ -76,6 +76,10 @@ dependencies {
|
||||||
implementation 'com.github.kittinunf.fuel:fuel-android:2.3.1'
|
implementation 'com.github.kittinunf.fuel:fuel-android:2.3.1'
|
||||||
implementation 'com.github.kittinunf.fuel:fuel-json:2.3.1'
|
implementation 'com.github.kittinunf.fuel:fuel-json:2.3.1'
|
||||||
|
|
||||||
|
// TODO replace fuel with ktor
|
||||||
|
implementation "io.ktor:ktor-client-core:$ktor_version"
|
||||||
|
implementation "io.ktor:ktor-client-android:$ktor_version"
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
|
|
@ -4,10 +4,13 @@ import android.util.Log
|
||||||
import com.github.kittinunf.fuel.Fuel
|
import com.github.kittinunf.fuel.Fuel
|
||||||
import com.github.kittinunf.fuel.core.FuelError
|
import com.github.kittinunf.fuel.core.FuelError
|
||||||
import com.github.kittinunf.fuel.core.Parameters
|
import com.github.kittinunf.fuel.core.Parameters
|
||||||
import com.github.kittinunf.fuel.core.extensions.jsonBody
|
|
||||||
import com.github.kittinunf.fuel.json.FuelJson
|
import com.github.kittinunf.fuel.json.FuelJson
|
||||||
import com.github.kittinunf.fuel.json.responseJson
|
import com.github.kittinunf.fuel.json.responseJson
|
||||||
import com.github.kittinunf.result.Result
|
import com.github.kittinunf.result.Result
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
@ -20,7 +23,9 @@ import org.mosad.teapod.util.concatenate
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
object Crunchyroll {
|
object Crunchyroll {
|
||||||
|
private val TAG = javaClass.name
|
||||||
|
|
||||||
|
private val client = HttpClient()
|
||||||
private const val baseUrl = "https://beta-api.crunchyroll.com"
|
private const val baseUrl = "https://beta-api.crunchyroll.com"
|
||||||
|
|
||||||
private var accessToken = ""
|
private var accessToken = ""
|
||||||
|
@ -80,7 +85,7 @@ object Crunchyroll {
|
||||||
// println("response: $response")
|
// println("response: $response")
|
||||||
// println("response: $result")
|
// println("response: $result")
|
||||||
|
|
||||||
Log.i(javaClass.name, "login complete with code ${response.statusCode}")
|
Log.i(TAG, "login complete with code ${response.statusCode}")
|
||||||
success = (response.statusCode == 200)
|
success = (response.statusCode == 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,16 +124,46 @@ object Crunchyroll {
|
||||||
private suspend fun requestPost(
|
private suspend fun requestPost(
|
||||||
endpoint: String,
|
endpoint: String,
|
||||||
params: Parameters = listOf(),
|
params: Parameters = listOf(),
|
||||||
body: String
|
requestBody: String
|
||||||
) = coroutineScope {
|
) = coroutineScope {
|
||||||
val path = "$baseUrl$endpoint"
|
val path = "$baseUrl$endpoint"
|
||||||
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
|
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Fuel.post(path, params)
|
val response: HttpResponse = client.request(path) {
|
||||||
.header("Authorization", "$tokenType $accessToken")
|
method = HttpMethod.Post
|
||||||
.jsonBody(body)
|
body = requestBody
|
||||||
.response() // without a response, crunchy doesn't accept the request
|
header("Authorization", "$tokenType $accessToken")
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
params.forEach {
|
||||||
|
parameter(it.first, it.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Response status: ${response.status}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requestPatch(
|
||||||
|
endpoint: String,
|
||||||
|
params: Parameters = listOf(),
|
||||||
|
requestBody: String
|
||||||
|
) = coroutineScope {
|
||||||
|
val path = "$baseUrl$endpoint"
|
||||||
|
if (System.currentTimeMillis() > tokenValidUntil) refreshToken()
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val response: HttpResponse = client.request(path) {
|
||||||
|
method = HttpMethod.Patch
|
||||||
|
body = requestBody
|
||||||
|
header("Authorization", "$tokenType $accessToken")
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
params.forEach {
|
||||||
|
parameter(it.first, it.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Response status: ${response.status}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,4 +529,13 @@ object Crunchyroll {
|
||||||
} ?: NoneProfile
|
} ?: NoneProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun postPrefSubLanguage(languageTag: String) {
|
||||||
|
val profileEndpoint = "/accounts/v1/me/profile"
|
||||||
|
val json = buildJsonObject {
|
||||||
|
put("preferred_content_subtitle_language", languageTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPatch(profileEndpoint, requestBody = json.toString())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.mosad.teapod.BuildConfig
|
import org.mosad.teapod.BuildConfig
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
import org.mosad.teapod.databinding.FragmentAccountBinding
|
import org.mosad.teapod.databinding.FragmentAccountBinding
|
||||||
|
@ -26,12 +27,13 @@ import org.mosad.teapod.ui.activity.main.MainActivity
|
||||||
import org.mosad.teapod.ui.components.LoginDialog
|
import org.mosad.teapod.ui.components.LoginDialog
|
||||||
import org.mosad.teapod.util.DataTypes.Theme
|
import org.mosad.teapod.util.DataTypes.Theme
|
||||||
import org.mosad.teapod.util.showFragment
|
import org.mosad.teapod.util.showFragment
|
||||||
|
import org.mosad.teapod.util.toDisplayString
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class AccountFragment : Fragment() {
|
class AccountFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var binding: FragmentAccountBinding
|
private lateinit var binding: FragmentAccountBinding
|
||||||
private val profile: Deferred<Profile> = lifecycleScope.async {
|
private var profile: Deferred<Profile> = lifecycleScope.async {
|
||||||
Crunchyroll.profile()
|
Crunchyroll.profile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +109,7 @@ class AccountFragment : Fragment() {
|
||||||
//startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(AoDParser.getSubscriptionUrl())))
|
//startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(AoDParser.getSubscriptionUrl())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
binding.linearSettingsContentLanguage.setOnClickListener {
|
binding.linearSettingsContentLanguage.setOnClickListener {
|
||||||
showContentLanguageSelection()
|
showContentLanguageSelection()
|
||||||
}
|
}
|
||||||
|
@ -161,24 +164,45 @@ class AccountFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showContentLanguageSelection() {
|
private fun showContentLanguageSelection() {
|
||||||
|
// we should be able to use the index of supportedLocals for language selection, items is GUI only
|
||||||
val items = supportedLocals.map {
|
val items = supportedLocals.map {
|
||||||
if (it.displayLanguage.isNotEmpty() && it.displayCountry.isNotEmpty()) {
|
it.toDisplayString(getString(R.string.settings_content_language_none))
|
||||||
"${it.displayLanguage} (${it.displayCountry})"
|
|
||||||
} else if (it.displayCountry.isNotEmpty()) {
|
|
||||||
it.displayLanguage
|
|
||||||
} else {
|
|
||||||
getString(R.string.settings_content_language_none)
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
|
var initialSelection: Int
|
||||||
|
// profile should be completed here, therefore blocking
|
||||||
|
runBlocking {
|
||||||
|
initialSelection = supportedLocals.indexOf(Locale.forLanguageTag(
|
||||||
|
profile.await().preferredContentSubtitleLanguage))
|
||||||
|
if (initialSelection < 0) initialSelection = supportedLocals.lastIndex
|
||||||
|
}
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.settings_content_language)
|
.setTitle(R.string.settings_content_language)
|
||||||
.setSingleChoiceItems(items, 0){ _, which ->
|
.setSingleChoiceItems(items, initialSelection){ dialog, which ->
|
||||||
// TODO
|
updatePrefContentLanguage(supportedLocals[which].toLanguageTag())
|
||||||
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
private fun updatePrefContentLanguage(languageTag: String) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
Crunchyroll.postPrefSubLanguage(languageTag)
|
||||||
|
|
||||||
|
}.invokeOnCompletion {
|
||||||
|
// update profile since the language selection might have changed
|
||||||
|
profile = lifecycleScope.async { Crunchyroll.profile() }
|
||||||
|
profile.invokeOnCompletion {
|
||||||
|
// update language once loading profile is completed
|
||||||
|
binding.textSettingsContentLanguageDesc.text = Locale.forLanguageTag(
|
||||||
|
profile.getCompleted().preferredContentSubtitleLanguage
|
||||||
|
).displayLanguage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun showThemeDialog() {
|
private fun showThemeDialog() {
|
||||||
val items = arrayOf(
|
val items = arrayOf(
|
||||||
resources.getString(R.string.theme_light),
|
resources.getString(R.string.theme_light),
|
||||||
|
|
|
@ -3,8 +3,8 @@ package org.mosad.teapod.util
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import org.mosad.teapod.parser.crunchyroll.Collection
|
import org.mosad.teapod.parser.crunchyroll.Collection
|
||||||
import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem
|
import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem
|
||||||
import org.mosad.teapod.parser.crunchyroll.ContinueWatchingList
|
|
||||||
import org.mosad.teapod.parser.crunchyroll.Item
|
import org.mosad.teapod.parser.crunchyroll.Item
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
fun TextView.setDrawableTop(drawable: Int) {
|
fun TextView.setDrawableTop(drawable: Int) {
|
||||||
this.setCompoundDrawablesWithIntrinsicBounds(0, drawable, 0, 0)
|
this.setCompoundDrawablesWithIntrinsicBounds(0, drawable, 0, 0)
|
||||||
|
@ -27,3 +27,13 @@ fun Collection<ContinueWatchingItem>.toItemMediaList(): List<ItemMedia> {
|
||||||
ItemMedia(it.panel.episodeMetadata.seriesId, it.panel.title, it.panel.images.thumbnail[0][0].source)
|
ItemMedia(it.panel.episodeMetadata.seriesId, it.panel.title, it.panel.images.thumbnail[0][0].source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Locale.toDisplayString(fallback: String): String {
|
||||||
|
return if (this.displayLanguage.isNotEmpty() && this.displayCountry.isNotEmpty()) {
|
||||||
|
"${this.displayLanguage} (${this.displayCountry})"
|
||||||
|
} else if (this.displayCountry.isNotEmpty()) {
|
||||||
|
this.displayLanguage
|
||||||
|
} else {
|
||||||
|
fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.6.10"
|
ext.kotlin_version = "1.6.10"
|
||||||
|
ext.ktor_version = "1.6.7"
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
Loading…
Reference in New Issue