forked from recloudstream/cloudstream
updated syncapi and possible bug fixed
This commit is contained in:
parent
1628ec56c2
commit
a16cd3ef9a
17 changed files with 710 additions and 398 deletions
|
@ -1,4 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#6A32D3</color>
|
<!--#6A32D3-->
|
||||||
|
<color name="ic_launcher_background">@color/colorPrimaryPurple</color>
|
||||||
</resources>
|
</resources>
|
|
@ -54,7 +54,7 @@ object APIHolder {
|
||||||
)
|
)
|
||||||
|
|
||||||
val restrictedApis = arrayListOf(
|
val restrictedApis = arrayListOf(
|
||||||
TrailersToProvider(), // be aware that this is fuckery
|
// TrailersToProvider(), // be aware that this is fuckery
|
||||||
// NyaaProvider(), // torrents in cs3 is wack
|
// NyaaProvider(), // torrents in cs3 is wack
|
||||||
// ThenosProvider(), // ddos protection and wacked links
|
// ThenosProvider(), // ddos protection and wacked links
|
||||||
AsiaFlixProvider(),
|
AsiaFlixProvider(),
|
||||||
|
@ -314,7 +314,7 @@ interface SearchResponse {
|
||||||
val name: String
|
val name: String
|
||||||
val url: String
|
val url: String
|
||||||
val apiName: String
|
val apiName: String
|
||||||
val type: TvType
|
val type: TvType?
|
||||||
val posterUrl: String?
|
val posterUrl: String?
|
||||||
val id: Int?
|
val id: Int?
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,9 @@ import com.lagradost.cloudstream3.APIHolder.restrictedApis
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.network.initRequestClient
|
import com.lagradost.cloudstream3.network.initRequestClient
|
||||||
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.OAuth2Apis
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.OAuth2Apis
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.OAuth2accountApis
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.OAuth2accountApis
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
||||||
|
@ -45,6 +45,7 @@ import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
|
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
|
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
|
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
|
||||||
|
@ -447,6 +448,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
nav_view.setupWithNavController(navController)
|
nav_view.setupWithNavController(navController)
|
||||||
|
|
||||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||||
|
this.hideKeyboard()
|
||||||
// nav_view.hideKeyboard()
|
// nav_view.hideKeyboard()
|
||||||
/*if (destination.id != R.id.navigation_player) {
|
/*if (destination.id != R.id.navigation_player) {
|
||||||
requestedOrientation = if (settingsManager?.getBoolean("force_landscape", false) == true) {
|
requestedOrientation = if (settingsManager?.getBoolean("force_landscape", false) == true) {
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.lagradost.cloudstream3.syncproviders
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
|
|
||||||
|
abstract class AccountManager(private val defIndex: Int) : OAuth2API {
|
||||||
|
// don't change this as all keys depend on it
|
||||||
|
open val idPrefix: String
|
||||||
|
get() {
|
||||||
|
throw(NotImplementedError())
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountIndex = defIndex
|
||||||
|
protected val accountId get() = "${idPrefix}_account_$accountIndex"
|
||||||
|
private val accountActiveKey get() = "${idPrefix}_active"
|
||||||
|
|
||||||
|
// int array of all accounts indexes
|
||||||
|
private val accountsKey get() = "${idPrefix}_accounts"
|
||||||
|
|
||||||
|
protected fun Context.removeAccountKeys() {
|
||||||
|
this.removeKeys(accountId)
|
||||||
|
val accounts = getAccounts(this).toMutableList()
|
||||||
|
accounts.remove(accountIndex)
|
||||||
|
this.setKey(accountsKey, accounts.toIntArray())
|
||||||
|
|
||||||
|
init(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAccounts(context: Context): IntArray {
|
||||||
|
return context.getKey(accountsKey, intArrayOf())!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
accountIndex = context.getKey(accountActiveKey, defIndex)!!
|
||||||
|
val accounts = getAccounts(context)
|
||||||
|
if (accounts.isNotEmpty() && this.loginInfo(context) == null) {
|
||||||
|
accountIndex = accounts.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun Context.switchToNewAccount() {
|
||||||
|
val accounts = getAccounts(this)
|
||||||
|
accountIndex = (accounts.maxOrNull() ?: 0) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun Context.registerAccount() {
|
||||||
|
this.setKey(accountActiveKey, accountIndex)
|
||||||
|
val accounts = getAccounts(this).toMutableList()
|
||||||
|
if (!accounts.contains(accountIndex)) {
|
||||||
|
accounts.add(accountIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setKey(accountsKey, accounts.toIntArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeAccount(context: Context, index: Int) {
|
||||||
|
accountIndex = index
|
||||||
|
context.setKey(accountActiveKey, index)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.lagradost.cloudstream3.syncproviders
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.providers.AniListApi
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.providers.MALApi
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
interface OAuth2API {
|
||||||
|
val key: String
|
||||||
|
val name: String
|
||||||
|
val redirectUrl: String
|
||||||
|
|
||||||
|
fun handleRedirect(context: Context, url: String)
|
||||||
|
fun authenticate(context: Context)
|
||||||
|
|
||||||
|
fun loginInfo(context: Context): LoginInfo?
|
||||||
|
fun logOut(context: Context)
|
||||||
|
|
||||||
|
class LoginInfo(
|
||||||
|
val profilePicture: String?,
|
||||||
|
val name: String?,
|
||||||
|
|
||||||
|
val accountIndex: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val malApi = MALApi(0)
|
||||||
|
val aniListApi = AniListApi(0)
|
||||||
|
|
||||||
|
// used to login via app intent
|
||||||
|
val OAuth2Apis
|
||||||
|
get() = listOf<OAuth2API>(
|
||||||
|
malApi, aniListApi
|
||||||
|
)
|
||||||
|
|
||||||
|
// this needs init with context and can be accessed in settings
|
||||||
|
val OAuth2accountApis
|
||||||
|
get() = listOf<AccountManager>(
|
||||||
|
malApi, aniListApi
|
||||||
|
)
|
||||||
|
|
||||||
|
// used for active syncing
|
||||||
|
val SyncApis
|
||||||
|
get() = listOf<SyncAPI>(
|
||||||
|
malApi, aniListApi
|
||||||
|
)
|
||||||
|
|
||||||
|
const val appString = "cloudstreamapp"
|
||||||
|
|
||||||
|
val unixTime: Long
|
||||||
|
get() = System.currentTimeMillis() / 1000L
|
||||||
|
|
||||||
|
const val maxStale = 60 * 10
|
||||||
|
|
||||||
|
fun secondsToReadable(seconds: Int, completedValue: String): String {
|
||||||
|
var secondsLong = seconds.toLong()
|
||||||
|
val days = TimeUnit.SECONDS
|
||||||
|
.toDays(secondsLong)
|
||||||
|
secondsLong -= TimeUnit.DAYS.toSeconds(days)
|
||||||
|
|
||||||
|
val hours = TimeUnit.SECONDS
|
||||||
|
.toHours(secondsLong)
|
||||||
|
secondsLong -= TimeUnit.HOURS.toSeconds(hours)
|
||||||
|
|
||||||
|
val minutes = TimeUnit.SECONDS
|
||||||
|
.toMinutes(secondsLong)
|
||||||
|
secondsLong -= TimeUnit.MINUTES.toSeconds(minutes)
|
||||||
|
if (minutes < 0) {
|
||||||
|
return completedValue
|
||||||
|
}
|
||||||
|
//println("$days $hours $minutes")
|
||||||
|
return "${if (days != 0L) "$days" + "d " else ""}${if (hours != 0L) "$hours" + "h " else ""}${minutes}m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,127 +0,0 @@
|
||||||
package com.lagradost.cloudstream3.syncproviders
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
interface OAuth2Interface {
|
|
||||||
val key: String
|
|
||||||
val name: String
|
|
||||||
val redirectUrl: String
|
|
||||||
|
|
||||||
fun handleRedirect(context: Context, url: String)
|
|
||||||
fun authenticate(context: Context)
|
|
||||||
|
|
||||||
fun loginInfo(context: Context): LoginInfo?
|
|
||||||
fun logOut(context: Context)
|
|
||||||
|
|
||||||
class LoginInfo(
|
|
||||||
val profilePicture: String?,
|
|
||||||
val name: String?,
|
|
||||||
|
|
||||||
val accountIndex: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
abstract class AccountManager(private val defIndex: Int) : OAuth2Interface {
|
|
||||||
// don't change this as all keys depend on it
|
|
||||||
open val idPrefix: String
|
|
||||||
get() {
|
|
||||||
throw(NotImplementedError())
|
|
||||||
}
|
|
||||||
|
|
||||||
var accountIndex = defIndex
|
|
||||||
protected val accountId get() = "${idPrefix}_account_$accountIndex"
|
|
||||||
private val accountActiveKey get() = "${idPrefix}_active"
|
|
||||||
|
|
||||||
// int array of all accounts indexes
|
|
||||||
private val accountsKey get() = "${idPrefix}_accounts"
|
|
||||||
|
|
||||||
protected fun Context.removeAccountKeys() {
|
|
||||||
this.removeKeys(accountId)
|
|
||||||
val accounts = getAccounts(this).toMutableList()
|
|
||||||
accounts.remove(accountIndex)
|
|
||||||
this.setKey(accountsKey, accounts.toIntArray())
|
|
||||||
|
|
||||||
init(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAccounts(context: Context): IntArray {
|
|
||||||
return context.getKey(accountsKey, intArrayOf())!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun init(context: Context) {
|
|
||||||
accountIndex = context.getKey(accountActiveKey, defIndex)!!
|
|
||||||
val accounts = getAccounts(context)
|
|
||||||
if (accounts.isNotEmpty() && this.loginInfo(context) == null) {
|
|
||||||
accountIndex = accounts.first()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun Context.switchToNewAccount() {
|
|
||||||
val accounts = getAccounts(this)
|
|
||||||
accountIndex = (accounts.maxOrNull() ?: 0) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun Context.registerAccount() {
|
|
||||||
this.setKey(accountActiveKey, accountIndex)
|
|
||||||
val accounts = getAccounts(this).toMutableList()
|
|
||||||
if (!accounts.contains(accountIndex)) {
|
|
||||||
accounts.add(accountIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setKey(accountsKey, accounts.toIntArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun changeAccount(context: Context, index: Int) {
|
|
||||||
accountIndex = index
|
|
||||||
context.setKey(accountActiveKey, index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val malApi = MALApi(0)
|
|
||||||
val aniListApi = AniListApi(0)
|
|
||||||
|
|
||||||
val OAuth2Apis
|
|
||||||
get() = listOf<OAuth2Interface>(
|
|
||||||
malApi, aniListApi
|
|
||||||
)
|
|
||||||
|
|
||||||
// this needs init with context
|
|
||||||
val OAuth2accountApis
|
|
||||||
get() = listOf<AccountManager>(
|
|
||||||
malApi, aniListApi
|
|
||||||
)
|
|
||||||
|
|
||||||
const val appString = "cloudstreamapp"
|
|
||||||
|
|
||||||
val unixTime: Long
|
|
||||||
get() = System.currentTimeMillis() / 1000L
|
|
||||||
val unixTimeMS: Long
|
|
||||||
get() = System.currentTimeMillis()
|
|
||||||
|
|
||||||
const val maxStale = 60 * 10
|
|
||||||
|
|
||||||
fun secondsToReadable(seconds: Int, completedValue: String): String {
|
|
||||||
var secondsLong = seconds.toLong()
|
|
||||||
val days = TimeUnit.SECONDS
|
|
||||||
.toDays(secondsLong)
|
|
||||||
secondsLong -= TimeUnit.DAYS.toSeconds(days)
|
|
||||||
|
|
||||||
val hours = TimeUnit.SECONDS
|
|
||||||
.toHours(secondsLong)
|
|
||||||
secondsLong -= TimeUnit.HOURS.toSeconds(hours)
|
|
||||||
|
|
||||||
val minutes = TimeUnit.SECONDS
|
|
||||||
.toMinutes(secondsLong)
|
|
||||||
secondsLong -= TimeUnit.MINUTES.toSeconds(minutes)
|
|
||||||
if (minutes < 0) {
|
|
||||||
return completedValue
|
|
||||||
}
|
|
||||||
//println("$days $hours $minutes")
|
|
||||||
return "${if (days != 0L) "$days" + "d " else ""}${if (hours != 0L) "$hours" + "h " else ""}${minutes}m"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.lagradost.cloudstream3.syncproviders
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.lagradost.cloudstream3.ShowStatus
|
||||||
|
|
||||||
|
interface SyncAPI {
|
||||||
|
data class SyncSearchResult(
|
||||||
|
val name: String,
|
||||||
|
val syncApiName: String,
|
||||||
|
val id: String,
|
||||||
|
val url: String?,
|
||||||
|
val posterUrl: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SyncNextAiring(
|
||||||
|
val episode: Int,
|
||||||
|
val unixTime: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SyncActor(
|
||||||
|
val name: String,
|
||||||
|
val posterUrl: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SyncCharacter(
|
||||||
|
val name: String,
|
||||||
|
val posterUrl: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SyncStatus(
|
||||||
|
val status: Int,
|
||||||
|
/** 1-10 */
|
||||||
|
val score: Int?,
|
||||||
|
val watchedEpisodes: Int?,
|
||||||
|
var isFavorite: Boolean? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SyncResult(
|
||||||
|
/**Used to verify*/
|
||||||
|
var id: String,
|
||||||
|
|
||||||
|
var totalEpisodes: Int? = null,
|
||||||
|
|
||||||
|
var title: String? = null,
|
||||||
|
/**1-1000*/
|
||||||
|
var publicScore: Int? = null,
|
||||||
|
/**In minutes*/
|
||||||
|
var duration: Int? = null,
|
||||||
|
var synopsis: String? = null,
|
||||||
|
var airStatus: ShowStatus? = null,
|
||||||
|
var nextAiring: SyncNextAiring? = null,
|
||||||
|
var studio: String? = null,
|
||||||
|
var genres: List<String>? = null,
|
||||||
|
var trailerUrl: String? = null,
|
||||||
|
|
||||||
|
/** In unixtime */
|
||||||
|
var startDate: Long? = null,
|
||||||
|
/** In unixtime */
|
||||||
|
var endDate: Long? = null,
|
||||||
|
var recommendations: List<SyncSearchResult>? = null,
|
||||||
|
var nextSeason: SyncSearchResult? = null,
|
||||||
|
var prevSeason: SyncSearchResult? = null,
|
||||||
|
var actors: List<SyncActor>? = null,
|
||||||
|
var characters: List<SyncCharacter>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
val icon : Int
|
||||||
|
|
||||||
|
val mainUrl: String
|
||||||
|
fun search(context: Context, name: String): List<SyncSearchResult>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
-1 -> None
|
||||||
|
0 -> Watching
|
||||||
|
1 -> Completed
|
||||||
|
2 -> OnHold
|
||||||
|
3 -> Dropped
|
||||||
|
4 -> PlanToWatch
|
||||||
|
5 -> ReWatching
|
||||||
|
*/
|
||||||
|
fun score(context: Context, id: String, status : SyncStatus): Boolean
|
||||||
|
|
||||||
|
fun getStatus(context: Context, id : String) : SyncStatus?
|
||||||
|
|
||||||
|
fun getResult(context: Context, id: String): SyncResult?
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.lagradost.cloudstream3.syncproviders
|
package com.lagradost.cloudstream3.syncproviders.providers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
@ -6,12 +6,16 @@ import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.network.post
|
import com.lagradost.cloudstream3.network.post
|
||||||
import com.lagradost.cloudstream3.network.text
|
import com.lagradost.cloudstream3.network.text
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString
|
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.maxStale
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.unixTime
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.maxStale
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
@ -21,9 +25,8 @@ import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = "AniList"
|
get() = "AniList"
|
||||||
override val key: String
|
override val key: String
|
||||||
|
@ -32,11 +35,19 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
get() = "anilistlogin"
|
get() = "anilistlogin"
|
||||||
override val idPrefix: String
|
override val idPrefix: String
|
||||||
get() = "anilist"
|
get() = "anilist"
|
||||||
|
override val mainUrl: String
|
||||||
|
get() = "https://anilist.co"
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_anilist_icon
|
||||||
|
|
||||||
override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? {
|
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||||
// context.getUser(true)?.
|
// context.getUser(true)?.
|
||||||
context.getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.let { user ->
|
context.getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.let { user ->
|
||||||
return OAuth2Interface.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex)
|
return OAuth2API.LoginInfo(
|
||||||
|
profilePicture = user.picture,
|
||||||
|
name = user.name,
|
||||||
|
accountIndex = accountIndex
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -71,7 +82,60 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun search(context: Context, name: String): List<SyncAPI.SyncSearchResult>? {
|
||||||
|
val data = searchShows(name) ?: return null
|
||||||
|
return data.data.Page.media.map {
|
||||||
|
SyncAPI.SyncSearchResult(
|
||||||
|
it.title.romaji,
|
||||||
|
this.name,
|
||||||
|
it.id.toString(),
|
||||||
|
"$mainUrl/anime/${it.id}",
|
||||||
|
it.bannerImage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getResult(context: Context, id: String): SyncAPI.SyncResult? {
|
||||||
|
val internalId = id.toIntOrNull() ?: return null
|
||||||
|
val season = getSeason(internalId)?.data?.Media ?: return null
|
||||||
|
|
||||||
|
return SyncAPI.SyncResult(
|
||||||
|
season.id.toString(),
|
||||||
|
nextAiring = season.nextAiringEpisode?.let {
|
||||||
|
SyncAPI.SyncNextAiring(
|
||||||
|
it.episode,
|
||||||
|
it.timeUntilAiring + unixTime
|
||||||
|
)
|
||||||
|
},
|
||||||
|
//TODO REST
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatus(context: Context, id: String): SyncAPI.SyncStatus? {
|
||||||
|
val internalId = id.toIntOrNull() ?: return null
|
||||||
|
val data = context.getDataAboutId(internalId) ?: return null
|
||||||
|
|
||||||
|
return SyncAPI.SyncStatus(
|
||||||
|
score = data.score,
|
||||||
|
watchedEpisodes = data.episodes,
|
||||||
|
status = data.type.value,
|
||||||
|
isFavorite = data.isFavourite,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun score(context: Context, id: String, status: SyncAPI.SyncStatus): Boolean {
|
||||||
|
return context.postDataAboutId(
|
||||||
|
id.toIntOrNull() ?: return false,
|
||||||
|
fromIntToAnimeStatus(status.status),
|
||||||
|
status.score,
|
||||||
|
status.watchedEpisodes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||||
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
||||||
|
|
||||||
private val aniListStatusString = arrayOf("CURRENT", "COMPLETED", "PAUSED", "DROPPED", "PLANNING", "REPEATING")
|
private val aniListStatusString = arrayOf("CURRENT", "COMPLETED", "PAUSED", "DROPPED", "PLANNING", "REPEATING")
|
||||||
|
|
||||||
const val ANILIST_UNIXTIME_KEY: String = "anilist_unixtime" // When token expires
|
const val ANILIST_UNIXTIME_KEY: String = "anilist_unixtime" // When token expires
|
||||||
|
@ -79,81 +143,6 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
const val ANILIST_USER_KEY: String = "anilist_user" // user data like profile
|
const val ANILIST_USER_KEY: String = "anilist_user" // user data like profile
|
||||||
const val ANILIST_CACHED_LIST: String = "anilist_cached_list"
|
const val ANILIST_CACHED_LIST: String = "anilist_cached_list"
|
||||||
const val ANILIST_SHOULD_UPDATE_LIST: String = "anilist_should_update_list"
|
const val ANILIST_SHOULD_UPDATE_LIST: String = "anilist_should_update_list"
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private val mapper = JsonMapper.builder().addModule(KotlinModule())
|
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
|
||||||
|
|
||||||
|
|
||||||
// Changing names of these will show up in UI
|
|
||||||
enum class AniListStatusType(var value: Int) {
|
|
||||||
Watching(0),
|
|
||||||
Completed(1),
|
|
||||||
Paused(2),
|
|
||||||
Dropped(3),
|
|
||||||
Planning(4),
|
|
||||||
Rewatching(5),
|
|
||||||
None(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp }
|
|
||||||
return when (inp) {
|
|
||||||
-1 -> AniListStatusType.None
|
|
||||||
0 -> AniListStatusType.Watching
|
|
||||||
1 -> AniListStatusType.Completed
|
|
||||||
2 -> AniListStatusType.Paused
|
|
||||||
3 -> AniListStatusType.Dropped
|
|
||||||
4 -> AniListStatusType.Planning
|
|
||||||
5 -> AniListStatusType.Rewatching
|
|
||||||
else -> AniListStatusType.None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun convertAnilistStringToStatus(string: String): AniListStatusType {
|
|
||||||
return fromIntToAnimeStatus(aniListStatusString.indexOf(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.initGetUser() {
|
|
||||||
if (getKey<String>(accountId, ANILIST_TOKEN_KEY, null) == null) return
|
|
||||||
ioSafe {
|
|
||||||
getUser()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Context.checkToken(): Boolean {
|
|
||||||
if (unixTime > getKey(accountId,
|
|
||||||
ANILIST_UNIXTIME_KEY, 0L
|
|
||||||
)!!
|
|
||||||
) {
|
|
||||||
/*getCurrentActivity()?.runOnUiThread {
|
|
||||||
val alertDialog: AlertDialog? = activity?.let {
|
|
||||||
val builder = AlertDialog.Builder(it, R.style.AlertDialogCustom)
|
|
||||||
builder.apply {
|
|
||||||
setPositiveButton(
|
|
||||||
"Login"
|
|
||||||
) { dialog, id ->
|
|
||||||
authenticateAniList()
|
|
||||||
}
|
|
||||||
setNegativeButton(
|
|
||||||
"Cancel"
|
|
||||||
) { dialog, id ->
|
|
||||||
// User cancelled the dialog
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Set other dialog properties
|
|
||||||
builder.setTitle("AniList token has expired")
|
|
||||||
|
|
||||||
// Create the AlertDialog
|
|
||||||
builder.create()
|
|
||||||
}
|
|
||||||
alertDialog?.show()
|
|
||||||
}*/
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fixName(name: String): String {
|
private fun fixName(name: String): String {
|
||||||
return name.toLowerCase(Locale.ROOT).replace(" ", "").replace("[^a-zA-Z0-9]".toRegex(), "")
|
return name.toLowerCase(Locale.ROOT).replace(" ", "").replace("[^a-zA-Z0-9]".toRegex(), "")
|
||||||
|
@ -217,7 +206,16 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
val data =
|
val data =
|
||||||
mapOf("query" to query, "variables" to mapper.writeValueAsString(mapOf("search" to name, "page" to 1, "type" to "ANIME")) )
|
mapOf(
|
||||||
|
"query" to query,
|
||||||
|
"variables" to mapper.writeValueAsString(
|
||||||
|
mapOf(
|
||||||
|
"search" to name,
|
||||||
|
"page" to 1,
|
||||||
|
"type" to "ANIME"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val res = post(
|
val res = post(
|
||||||
"https://graphql.anilist.co/",
|
"https://graphql.anilist.co/",
|
||||||
|
@ -267,40 +265,120 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
return filtered?.firstOrNull()
|
return filtered?.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.postApi(url: String, q: String, cache: Boolean = false): String {
|
// Changing names of these will show up in UI
|
||||||
return try {
|
enum class AniListStatusType(var value: Int) {
|
||||||
if (!checkToken()) {
|
Watching(0),
|
||||||
// println("VARS_ " + vars)
|
Completed(1),
|
||||||
post(
|
Paused(2),
|
||||||
"https://graphql.anilist.co/",
|
Dropped(3),
|
||||||
headers = mapOf(
|
Planning(4),
|
||||||
"Authorization" to "Bearer " + getKey(
|
Rewatching(5),
|
||||||
accountId,
|
None(-1)
|
||||||
ANILIST_TOKEN_KEY,
|
|
||||||
""
|
|
||||||
)!!,
|
|
||||||
if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
|
|
||||||
),
|
|
||||||
cacheTime = 0,
|
|
||||||
data = mapOf("query" to q),//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars))
|
|
||||||
timeout = 5 // REASONABLE TIMEOUT
|
|
||||||
).text.replace("\\/", "/")
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||||
""
|
return when (inp) {
|
||||||
|
-1 -> AniListStatusType.None
|
||||||
|
0 -> AniListStatusType.Watching
|
||||||
|
1 -> AniListStatusType.Completed
|
||||||
|
2 -> AniListStatusType.Paused
|
||||||
|
3 -> AniListStatusType.Dropped
|
||||||
|
4 -> AniListStatusType.Planning
|
||||||
|
5 -> AniListStatusType.Rewatching
|
||||||
|
else -> AniListStatusType.None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MediaRecommendation(
|
fun convertAnilistStringToStatus(string: String): AniListStatusType {
|
||||||
@JsonProperty("id") val id: Int,
|
return fromIntToAnimeStatus(aniListStatusString.indexOf(string))
|
||||||
@JsonProperty("title") val title: Title,
|
}
|
||||||
@JsonProperty("idMal") val idMal: Int?,
|
|
||||||
@JsonProperty("coverImage") val coverImage: CoverImage,
|
|
||||||
@JsonProperty("averageScore") val averageScore: Int?
|
private fun getSeason(id: Int): SeasonResponse? {
|
||||||
)
|
val q: String = """
|
||||||
|
query (${'$'}id: Int = $id) {
|
||||||
|
Media (id: ${'$'}id, type: ANIME) {
|
||||||
|
id
|
||||||
|
idMal
|
||||||
|
relations {
|
||||||
|
edges {
|
||||||
|
id
|
||||||
|
relationType(version: 2)
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
format
|
||||||
|
nextAiringEpisode {
|
||||||
|
timeUntilAiring
|
||||||
|
episode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextAiringEpisode {
|
||||||
|
timeUntilAiring
|
||||||
|
episode
|
||||||
|
}
|
||||||
|
format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val data = post(
|
||||||
|
"https://graphql.anilist.co",
|
||||||
|
data = mapOf("query" to q),
|
||||||
|
cacheTime = 0,
|
||||||
|
).text
|
||||||
|
if (data == "") return null
|
||||||
|
return try {
|
||||||
|
mapper.readValue(data)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.initGetUser() {
|
||||||
|
if (getKey<String>(accountId, ANILIST_TOKEN_KEY, null) == null) return
|
||||||
|
ioSafe {
|
||||||
|
getUser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.checkToken(): Boolean {
|
||||||
|
if (unixTime > getKey(
|
||||||
|
accountId,
|
||||||
|
ANILIST_UNIXTIME_KEY, 0L
|
||||||
|
)!!
|
||||||
|
) {
|
||||||
|
/*getCurrentActivity()?.runOnUiThread {
|
||||||
|
val alertDialog: AlertDialog? = activity?.let {
|
||||||
|
val builder = AlertDialog.Builder(it, R.style.AlertDialogCustom)
|
||||||
|
builder.apply {
|
||||||
|
setPositiveButton(
|
||||||
|
"Login"
|
||||||
|
) { dialog, id ->
|
||||||
|
authenticateAniList()
|
||||||
|
}
|
||||||
|
setNegativeButton(
|
||||||
|
"Cancel"
|
||||||
|
) { dialog, id ->
|
||||||
|
// User cancelled the dialog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set other dialog properties
|
||||||
|
builder.setTitle("AniList token has expired")
|
||||||
|
|
||||||
|
// Create the AlertDialog
|
||||||
|
builder.create()
|
||||||
|
}
|
||||||
|
alertDialog?.show()
|
||||||
|
}*/
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.getDataAboutId(id: Int): AniListTitleHolder? {
|
fun Context.getDataAboutId(id: Int): AniListTitleHolder? {
|
||||||
val q =
|
val q =
|
||||||
|
@ -361,6 +439,41 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Context.postApi(url: String, q: String, cache: Boolean = false): String {
|
||||||
|
return try {
|
||||||
|
if (!checkToken()) {
|
||||||
|
// println("VARS_ " + vars)
|
||||||
|
post(
|
||||||
|
"https://graphql.anilist.co/",
|
||||||
|
headers = mapOf(
|
||||||
|
"Authorization" to "Bearer " + getKey(
|
||||||
|
accountId,
|
||||||
|
ANILIST_TOKEN_KEY,
|
||||||
|
""
|
||||||
|
)!!,
|
||||||
|
if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
|
||||||
|
),
|
||||||
|
cacheTime = 0,
|
||||||
|
data = mapOf("query" to q),//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars))
|
||||||
|
timeout = 5 // REASONABLE TIMEOUT
|
||||||
|
).text.replace("\\/", "/")
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MediaRecommendation(
|
||||||
|
@JsonProperty("id") val id: Int,
|
||||||
|
@JsonProperty("title") val title: Title,
|
||||||
|
@JsonProperty("idMal") val idMal: Int?,
|
||||||
|
@JsonProperty("coverImage") val coverImage: CoverImage,
|
||||||
|
@JsonProperty("averageScore") val averageScore: Int?
|
||||||
|
)
|
||||||
|
|
||||||
data class FullAnilistList(
|
data class FullAnilistList(
|
||||||
@JsonProperty("data") val data: Data
|
@JsonProperty("data") val data: Data
|
||||||
)
|
)
|
||||||
|
@ -530,7 +643,7 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
return data != ""
|
return data != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.postDataAboutId(id: Int, type: AniListStatusType, score: Int, progress: Int): Boolean {
|
private fun Context.postDataAboutId(id: Int, type: AniListStatusType, score: Int?, progress: Int?): Boolean {
|
||||||
try {
|
try {
|
||||||
val q =
|
val q =
|
||||||
"""mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
|
"""mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
|
||||||
|
@ -538,7 +651,7 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
0,
|
0,
|
||||||
type.value
|
type.value
|
||||||
)]
|
)]
|
||||||
}, ${'$'}scoreRaw: Int = ${score * 10}, ${'$'}progress: Int = $progress) {
|
}, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) {
|
||||||
SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
|
SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
|
||||||
id
|
id
|
||||||
status
|
status
|
||||||
|
@ -597,49 +710,6 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSeason(id: Int): SeasonResponse? {
|
|
||||||
val q: String = """
|
|
||||||
query (${'$'}id: Int = $id) {
|
|
||||||
Media (id: ${'$'}id, type: ANIME) {
|
|
||||||
id
|
|
||||||
idMal
|
|
||||||
relations {
|
|
||||||
edges {
|
|
||||||
id
|
|
||||||
relationType(version: 2)
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
format
|
|
||||||
nextAiringEpisode {
|
|
||||||
timeUntilAiring
|
|
||||||
episode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nextAiringEpisode {
|
|
||||||
timeUntilAiring
|
|
||||||
episode
|
|
||||||
}
|
|
||||||
format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
val data = post(
|
|
||||||
"https://graphql.anilist.co",
|
|
||||||
data = mapOf("query" to q),
|
|
||||||
cacheTime = 0,
|
|
||||||
).text
|
|
||||||
if (data == "") return null
|
|
||||||
return try {
|
|
||||||
mapper.readValue(data)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAllSeasons(id: Int): List<SeasonResponse?> {
|
fun getAllSeasons(id: Int): List<SeasonResponse?> {
|
||||||
val seasons = mutableListOf<SeasonResponse?>()
|
val seasons = mutableListOf<SeasonResponse?>()
|
||||||
fun getSeasonRecursive(id: Int) {
|
fun getSeasonRecursive(id: Int) {
|
||||||
|
@ -662,27 +732,6 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
return seasons.toList()
|
return seasons.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun secondsToReadable(seconds: Int, completedValue: String): String {
|
|
||||||
var secondsLong = seconds.toLong()
|
|
||||||
val days = TimeUnit.SECONDS
|
|
||||||
.toDays(secondsLong)
|
|
||||||
secondsLong -= TimeUnit.DAYS.toSeconds(days)
|
|
||||||
|
|
||||||
val hours = TimeUnit.SECONDS
|
|
||||||
.toHours(secondsLong)
|
|
||||||
secondsLong -= TimeUnit.HOURS.toSeconds(hours)
|
|
||||||
|
|
||||||
val minutes = TimeUnit.SECONDS
|
|
||||||
.toMinutes(secondsLong)
|
|
||||||
secondsLong -= TimeUnit.MINUTES.toSeconds(minutes)
|
|
||||||
if (minutes < 0) {
|
|
||||||
return completedValue
|
|
||||||
}
|
|
||||||
//println("$days $hours $minutes")
|
|
||||||
return "${if (days != 0L) "$days" + "d " else ""}${if (hours != 0L) "$hours" + "h " else ""}${minutes}m"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
data class SeasonResponse(
|
data class SeasonResponse(
|
||||||
@JsonProperty("data") val data: SeasonData,
|
@JsonProperty("data") val data: SeasonData,
|
||||||
)
|
)
|
||||||
|
@ -725,9 +774,9 @@ class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
data class SeasonNode(
|
data class SeasonNode(
|
||||||
@JsonProperty("id") val id: Int,
|
@JsonProperty("id") val id: Int,
|
||||||
@JsonProperty("format") val format: String?,
|
@JsonProperty("format") val format: String?,
|
||||||
@JsonProperty("title") val title: AniListApi.Title,
|
@JsonProperty("title") val title: Title,
|
||||||
@JsonProperty("idMal") val idMal: Int?,
|
@JsonProperty("idMal") val idMal: Int?,
|
||||||
@JsonProperty("coverImage") val coverImage: AniListApi.CoverImage,
|
@JsonProperty("coverImage") val coverImage: CoverImage,
|
||||||
@JsonProperty("averageScore") val averageScore: Int?
|
@JsonProperty("averageScore") val averageScore: Int?
|
||||||
// @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?,
|
// @JsonProperty("nextAiringEpisode") val nextAiringEpisode: SeasonNextAiringEpisode?,
|
||||||
)
|
)
|
|
@ -1,9 +1,10 @@
|
||||||
package com.lagradost.cloudstream3.syncproviders
|
package com.lagradost.cloudstream3.syncproviders.providers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||||
|
|
||||||
//TODO dropbox sync
|
//TODO dropbox sync
|
||||||
class Dropbox : OAuth2Interface {
|
class Dropbox : OAuth2API {
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = "Dropbox"
|
get() = "Dropbox"
|
||||||
override val key: String
|
override val key: String
|
||||||
|
@ -23,7 +24,7 @@ class Dropbox : OAuth2Interface {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? {
|
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.lagradost.cloudstream3.syncproviders
|
package com.lagradost.cloudstream3.syncproviders.providers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
@ -7,14 +7,18 @@ import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.network.get
|
import com.lagradost.cloudstream3.network.get
|
||||||
import com.lagradost.cloudstream3.network.post
|
import com.lagradost.cloudstream3.network.post
|
||||||
import com.lagradost.cloudstream3.network.put
|
import com.lagradost.cloudstream3.network.put
|
||||||
import com.lagradost.cloudstream3.network.text
|
import com.lagradost.cloudstream3.network.text
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString
|
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.secondsToReadable
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.unixTime
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.secondsToReadable
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
@ -27,7 +31,10 @@ import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
/** max 100 via https://myanimelist.net/apiconfig/references/api/v2#tag/anime */
|
||||||
|
const val MAL_MAX_SEARCH_LIMIT = 25
|
||||||
|
|
||||||
|
class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = "MAL"
|
get() = "MAL"
|
||||||
override val key: String
|
override val key: String
|
||||||
|
@ -36,19 +43,70 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
get() = "mallogin"
|
get() = "mallogin"
|
||||||
override val idPrefix: String
|
override val idPrefix: String
|
||||||
get() = "mal"
|
get() = "mal"
|
||||||
|
override val mainUrl: String
|
||||||
|
get() = "https://myanimelist.net"
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.mal_logo
|
||||||
|
|
||||||
override fun logOut(context: Context) {
|
override fun logOut(context: Context) {
|
||||||
context.removeAccountKeys()
|
context.removeAccountKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? {
|
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||||
//context.getMalUser(true)?
|
//context.getMalUser(true)?
|
||||||
context.getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user ->
|
context.getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user ->
|
||||||
return OAuth2Interface.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex)
|
return OAuth2API.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun search(context: Context, name: String): List<SyncAPI.SyncSearchResult> {
|
||||||
|
val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
|
||||||
|
val res = get(
|
||||||
|
url, headers = mapOf(
|
||||||
|
"Authorization" to "Bearer " + context.getKey<String>(
|
||||||
|
accountId,
|
||||||
|
MAL_TOKEN_KEY
|
||||||
|
)!!,
|
||||||
|
), cacheTime = 0
|
||||||
|
).text
|
||||||
|
return mapper.readValue<MalSearch>(res).data.map {
|
||||||
|
SyncAPI.SyncSearchResult(
|
||||||
|
it.title,
|
||||||
|
this.name,
|
||||||
|
it.id.toString(),
|
||||||
|
"$mainUrl/anime/${it.id}/",
|
||||||
|
it.main_picture?.large ?: it.main_picture?.medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun score(context: Context, id: String, status : SyncAPI.SyncStatus): Boolean {
|
||||||
|
return context.setScoreRequest(
|
||||||
|
id.toIntOrNull() ?: return false,
|
||||||
|
fromIntToAnimeStatus(status.status),
|
||||||
|
status.score,
|
||||||
|
status.watchedEpisodes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getResult(context: Context, id: String): SyncAPI.SyncResult? {
|
||||||
|
val internalId = id.toIntOrNull() ?: return null
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatus(context: Context, id: String): SyncAPI.SyncStatus? {
|
||||||
|
val internalId = id.toIntOrNull() ?: return null
|
||||||
|
|
||||||
|
val data = context.getDataAboutMalId(internalId)?.my_list_status ?: return null
|
||||||
|
return SyncAPI.SyncStatus(
|
||||||
|
score = data.score,
|
||||||
|
status = malStatusAsString.indexOf(data.status),
|
||||||
|
isFavorite = null,
|
||||||
|
watchedEpisodes = data.num_episodes_watched,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val malStatusAsString = arrayOf("watching", "completed", "on_hold", "dropped", "plan_to_watch")
|
private val malStatusAsString = arrayOf("watching", "completed", "on_hold", "dropped", "plan_to_watch")
|
||||||
|
|
||||||
|
@ -60,7 +118,7 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api
|
const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleRedirect(context: Context,url: String) {
|
override fun handleRedirect(context: Context, url: String) {
|
||||||
try {
|
try {
|
||||||
val sanitizer =
|
val sanitizer =
|
||||||
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR
|
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR
|
||||||
|
@ -293,7 +351,7 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getDataAboutMalId(id: Int): MalAnime? {
|
private fun Context.getDataAboutMalId(id: Int): MalAnime? {
|
||||||
return try {
|
return try {
|
||||||
// https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get
|
// https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get
|
||||||
val url = "https://api.myanimelist.net/v2/anime/$id?fields=id,title,num_episodes,my_list_status"
|
val url = "https://api.myanimelist.net/v2/anime/$id?fields=id,title,num_episodes,my_list_status"
|
||||||
|
@ -419,7 +477,7 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
None(-1)
|
None(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp }
|
private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp }
|
||||||
return when (inp) {
|
return when (inp) {
|
||||||
-1 -> MalStatusType.None
|
-1 -> MalStatusType.None
|
||||||
0 -> MalStatusType.Watching
|
0 -> MalStatusType.Watching
|
||||||
|
@ -534,12 +592,23 @@ class MALApi(index : Int) : OAuth2Interface.AccountManager(index) {
|
||||||
@JsonProperty("picture") val picture: String,
|
@JsonProperty("picture") val picture: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class MalMainPicture(
|
||||||
|
@JsonProperty("large") val large: String?,
|
||||||
|
@JsonProperty("medium") val medium: String?,
|
||||||
|
)
|
||||||
|
|
||||||
// Used for getDataAboutId()
|
// Used for getDataAboutId()
|
||||||
data class MalAnime(
|
data class MalAnime(
|
||||||
@JsonProperty("id") val id: Int,
|
@JsonProperty("id") val id: Int,
|
||||||
@JsonProperty("title") val title: String,
|
@JsonProperty("title") val title: String,
|
||||||
@JsonProperty("num_episodes") val num_episodes: Int,
|
@JsonProperty("num_episodes") val num_episodes: Int,
|
||||||
@JsonProperty("my_list_status") val my_list_status: MalStatus?
|
@JsonProperty("my_list_status") val my_list_status: MalStatus?,
|
||||||
|
@JsonProperty("main_picture") val main_picture: MalMainPicture?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MalSearch(
|
||||||
|
@JsonProperty("data") val data: List<MalAnime>,
|
||||||
|
//paging
|
||||||
)
|
)
|
||||||
|
|
||||||
data class MalTitleHolder(
|
data class MalTitleHolder(
|
|
@ -26,7 +26,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
||||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||||
|
@ -430,7 +430,7 @@ class HomeFragment : Fragment() {
|
||||||
// nice profile pic on homepage
|
// nice profile pic on homepage
|
||||||
home_profile_picture_holder?.isVisible = false
|
home_profile_picture_holder?.isVisible = false
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
for (syncApi in OAuth2Interface.OAuth2Apis) {
|
for (syncApi in OAuth2API.OAuth2Apis) {
|
||||||
val login = syncApi.loginInfo(ctx)
|
val login = syncApi.loginInfo(ctx)
|
||||||
val pic = login?.profilePicture
|
val pic = login?.profilePicture
|
||||||
if (pic != null) {
|
if (pic != null) {
|
||||||
|
|
|
@ -8,13 +8,13 @@ import android.widget.TextView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
|
||||||
class AccountClickCallback(val action: Int, val view : View, val card: OAuth2Interface.LoginInfo)
|
class AccountClickCallback(val action: Int, val view : View, val card: OAuth2API.LoginInfo)
|
||||||
|
|
||||||
class AccountAdapter(
|
class AccountAdapter(
|
||||||
val cardList: List<OAuth2Interface.LoginInfo>,
|
val cardList: List<OAuth2API.LoginInfo>,
|
||||||
val layout: Int = R.layout.account_single,
|
val layout: Int = R.layout.account_single,
|
||||||
private val clickCallback: (AccountClickCallback) -> Unit
|
private val clickCallback: (AccountClickCallback) -> Unit
|
||||||
) :
|
) :
|
||||||
|
@ -48,7 +48,7 @@ class AccountAdapter(
|
||||||
private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!!
|
private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!!
|
||||||
private val accountName: TextView = itemView.findViewById(R.id.account_name)!!
|
private val accountName: TextView = itemView.findViewById(R.id.account_name)!!
|
||||||
|
|
||||||
fun bind(card: OAuth2Interface.LoginInfo) {
|
fun bind(card: OAuth2API.LoginInfo) {
|
||||||
// just in case name is null account index will show, should never happened
|
// just in case name is null account index will show, should never happened
|
||||||
accountName.text = card.name ?: "%s %d".format(accountName.context.getString(R.string.account), card.accountIndex)
|
accountName.text = card.name ?: "%s %d".format(accountName.context.getString(R.string.account), card.accountIndex)
|
||||||
if(card.profilePicture.isNullOrEmpty()) {
|
if(card.profilePicture.isNullOrEmpty()) {
|
||||||
|
|
|
@ -30,9 +30,10 @@ import com.lagradost.cloudstream3.MainActivity.Companion.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.network.initRequestClient
|
import com.lagradost.cloudstream3.network.initRequestClient
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface
|
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.aniListApi
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.malApi
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
|
@ -120,7 +121,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
Triple("🇹🇷", "Turkish", "tr")
|
Triple("🇹🇷", "Turkish", "tr")
|
||||||
).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top
|
).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top
|
||||||
|
|
||||||
private fun showAccountSwitch(context: Context, api: OAuth2Interface.AccountManager) {
|
private fun showAccountSwitch(context: Context, api: AccountManager) {
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch)
|
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch)
|
||||||
val dialog = builder.show()
|
val dialog = builder.show()
|
||||||
|
@ -132,7 +133,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
val ogIndex = api.accountIndex
|
val ogIndex = api.accountIndex
|
||||||
|
|
||||||
val items = ArrayList<OAuth2Interface.LoginInfo>()
|
val items = ArrayList<OAuth2API.LoginInfo>()
|
||||||
|
|
||||||
for (index in accounts) {
|
for (index in accounts) {
|
||||||
api.accountIndex = index
|
api.accountIndex = index
|
||||||
|
@ -150,7 +151,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
list?.adapter = adapter
|
list?.adapter = adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showLoginInfo(context: Context, api: OAuth2Interface.AccountManager, info: OAuth2Interface.LoginInfo) {
|
private fun showLoginInfo(context: Context, api: AccountManager, info: OAuth2API.LoginInfo) {
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_managment)
|
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_managment)
|
||||||
val dialog = builder.show()
|
val dialog = builder.show()
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.lagradost.cloudstream3.mapper
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.network.get
|
||||||
|
import com.lagradost.cloudstream3.network.text
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
object SyncUtil {
|
||||||
|
/** first. Mal, second. Anilist,
|
||||||
|
* valid sites are: Gogoanime, Twistmoe and 9anime*/
|
||||||
|
fun getIdsFromSlug(slug: String, site : String = "Gogoanime"): Pair<String?, String?>? {
|
||||||
|
try {
|
||||||
|
//Gogoanime, Twistmoe and 9anime
|
||||||
|
val url =
|
||||||
|
"https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/pages/$site/$slug.json"
|
||||||
|
val response = get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).text
|
||||||
|
val mapped = mapper.readValue<MalSyncPage?>(response)
|
||||||
|
|
||||||
|
val overrideMal = mapped?.malId ?: mapped?.Mal?.id ?: mapped?.Anilist?.malId
|
||||||
|
val overrideAnilist = mapped?.aniId ?: mapped?.Anilist?.id
|
||||||
|
|
||||||
|
if (overrideMal != null) {
|
||||||
|
return overrideMal.toString() to overrideAnilist?.toString()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MalSyncPage(
|
||||||
|
@JsonProperty("identifier") val identifier: String?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("page") val page: String?,
|
||||||
|
@JsonProperty("title") val title: String?,
|
||||||
|
@JsonProperty("url") val url: String?,
|
||||||
|
@JsonProperty("image") val image: String?,
|
||||||
|
@JsonProperty("hentai") val hentai: Boolean?,
|
||||||
|
@JsonProperty("sticky") val sticky: Boolean?,
|
||||||
|
@JsonProperty("active") val active: Boolean?,
|
||||||
|
@JsonProperty("actor") val actor: String?,
|
||||||
|
@JsonProperty("malId") val malId: Int?,
|
||||||
|
@JsonProperty("aniId") val aniId: Int?,
|
||||||
|
@JsonProperty("createdAt") val createdAt: String?,
|
||||||
|
@JsonProperty("updatedAt") val updatedAt: String?,
|
||||||
|
@JsonProperty("deletedAt") val deletedAt: String?,
|
||||||
|
@JsonProperty("Mal") val Mal: Mal?,
|
||||||
|
@JsonProperty("Anilist") val Anilist: Anilist?,
|
||||||
|
@JsonProperty("malUrl") val malUrl: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Anilist(
|
||||||
|
// @JsonProperty("altTitle") val altTitle: List<String>?,
|
||||||
|
// @JsonProperty("externalLinks") val externalLinks: List<String>?,
|
||||||
|
@JsonProperty("id") val id: Int?,
|
||||||
|
@JsonProperty("malId") val malId: Int?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("title") val title: String?,
|
||||||
|
@JsonProperty("url") val url: String?,
|
||||||
|
@JsonProperty("image") val image: String?,
|
||||||
|
@JsonProperty("category") val category: String?,
|
||||||
|
@JsonProperty("hentai") val hentai: Boolean?,
|
||||||
|
@JsonProperty("createdAt") val createdAt: String?,
|
||||||
|
@JsonProperty("updatedAt") val updatedAt: String?,
|
||||||
|
@JsonProperty("deletedAt") val deletedAt: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Mal(
|
||||||
|
// @JsonProperty("altTitle") val altTitle: List<String>?,
|
||||||
|
@JsonProperty("id") val id: Int?,
|
||||||
|
@JsonProperty("type") val type: String?,
|
||||||
|
@JsonProperty("title") val title: String?,
|
||||||
|
@JsonProperty("url") val url: String?,
|
||||||
|
@JsonProperty("image") val image: String?,
|
||||||
|
@JsonProperty("category") val category: String?,
|
||||||
|
@JsonProperty("hentai") val hentai: Boolean?,
|
||||||
|
@JsonProperty("createdAt") val createdAt: String?,
|
||||||
|
@JsonProperty("updatedAt") val updatedAt: String?,
|
||||||
|
@JsonProperty("deletedAt") val deletedAt: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -67,11 +67,16 @@ object UIHelper {
|
||||||
|
|
||||||
fun Fragment.hideKeyboard() {
|
fun Fragment.hideKeyboard() {
|
||||||
activity?.window?.decorView?.clearFocus()
|
activity?.window?.decorView?.clearFocus()
|
||||||
view.let {
|
view?.let {
|
||||||
if (it != null) {
|
|
||||||
hideKeyboard(it)
|
hideKeyboard(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Activity.hideKeyboard() {
|
||||||
|
window?.decorView?.clearFocus()
|
||||||
|
this.findViewById<View>(android.R.id.content)?.rootView?.let {
|
||||||
|
hideKeyboard(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity?.navigate(@IdRes navigation: Int, arguments: Bundle? = null) {
|
fun Activity?.navigate(@IdRes navigation: Int, arguments: Bundle? = null) {
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
<string name="type_dropped">Dropped</string>
|
<string name="type_dropped">Dropped</string>
|
||||||
<string name="type_plan_to_watch">Plan to Watch</string>
|
<string name="type_plan_to_watch">Plan to Watch</string>
|
||||||
<string name="type_none">None</string>
|
<string name="type_none">None</string>
|
||||||
|
<string name="type_re_watching">ReWatching</string>
|
||||||
|
|
||||||
<string name="play_movie_button">Play Movie</string>
|
<string name="play_movie_button">Play Movie</string>
|
||||||
<string name="play_torrent_button">Stream Torrent</string>
|
<string name="play_torrent_button">Stream Torrent</string>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#3DDC84</color>
|
<!--#3DDC84-->
|
||||||
|
<color name="ic_launcher_background">@color/colorPrimaryDark</color>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue