forked from recloudstream/cloudstream
backend fixes for opening from sync urls
This commit is contained in:
parent
05dc032df6
commit
67a1c447ae
11 changed files with 134 additions and 59 deletions
|
@ -10,15 +10,14 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
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.lagradost.cloudstream3.metaproviders.SyncIdName
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
|
import com.lagradost.cloudstream3.utils.*
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
|
||||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -402,6 +401,20 @@ abstract class MainAPI {
|
||||||
open val hasMainPage = false
|
open val hasMainPage = false
|
||||||
open val hasQuickSearch = false
|
open val hasQuickSearch = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of which ids the provider can open with getLoadUrl()
|
||||||
|
* If the set contains SyncIdName.Imdb then getLoadUrl() can be started with
|
||||||
|
* an Imdb class which inherits from SyncId.
|
||||||
|
*
|
||||||
|
* getLoadUrl() is then used to get page url based on that ID.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* "tt6723592" -> getLoadUrl(ImdbSyncId("tt6723592")) -> "mainUrl/imdb/tt6723592" -> load("mainUrl/imdb/tt6723592")
|
||||||
|
*
|
||||||
|
* This is used to launch pages from personal lists or recommendations using IDs.
|
||||||
|
**/
|
||||||
|
open val supportedSyncNames = setOf<SyncIdName>()
|
||||||
|
|
||||||
open val supportedTypes = setOf(
|
open val supportedTypes = setOf(
|
||||||
TvType.Movie,
|
TvType.Movie,
|
||||||
TvType.TvSeries,
|
TvType.TvSeries,
|
||||||
|
@ -412,7 +425,6 @@ abstract class MainAPI {
|
||||||
|
|
||||||
open val vpnStatus = VPNStatus.None
|
open val vpnStatus = VPNStatus.None
|
||||||
open val providerType = ProviderType.DirectProvider
|
open val providerType = ProviderType.DirectProvider
|
||||||
|
|
||||||
open val mainPage = listOf(MainPageData("", ""))
|
open val mainPage = listOf(MainPageData("", ""))
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
@ -471,6 +483,14 @@ abstract class MainAPI {
|
||||||
open fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor? {
|
open fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the load() url based on a sync ID like IMDb or MAL.
|
||||||
|
* Only contains SyncIds based on supportedSyncUrls.
|
||||||
|
**/
|
||||||
|
open suspend fun getLoadUrl(name: SyncIdName, id: String): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Might need a different implementation for desktop*/
|
/** Might need a different implementation for desktop*/
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
package com.lagradost.cloudstream3.metaproviders
|
|
||||||
|
|
||||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
|
||||||
import com.lagradost.cloudstream3.utils.SyncUtil
|
|
||||||
|
|
||||||
object SyncRedirector {
|
|
||||||
val syncApis = SyncApis
|
|
||||||
|
|
||||||
suspend fun redirect(url: String, preferredUrl: String): String {
|
|
||||||
for (api in syncApis) {
|
|
||||||
if (url.contains(api.mainUrl)) {
|
|
||||||
val otherApi = when (api.name) {
|
|
||||||
aniListApi.name -> "anilist"
|
|
||||||
malApi.name -> "myanimelist"
|
|
||||||
else -> return url
|
|
||||||
}
|
|
||||||
|
|
||||||
return SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl ->
|
|
||||||
realUrl.contains(preferredUrl)
|
|
||||||
} ?: run {
|
|
||||||
throw ErrorLoadingException("Page does not exist on $preferredUrl")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.lagradost.cloudstream3.metaproviders
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.MainAPI
|
||||||
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||||
|
import com.lagradost.cloudstream3.utils.SyncUtil
|
||||||
|
|
||||||
|
enum class SyncIdName {
|
||||||
|
AniList,
|
||||||
|
MyAnimeList,
|
||||||
|
Trakt,
|
||||||
|
Imdb
|
||||||
|
}
|
||||||
|
|
||||||
|
object SyncRedirector {
|
||||||
|
val syncApis = SyncApis
|
||||||
|
private val syncIds =
|
||||||
|
listOf(
|
||||||
|
SyncIdName.MyAnimeList to Regex("""myanimelist\.net\/anime\/(\d+)"""),
|
||||||
|
SyncIdName.AniList to Regex("""anilist\.co\/anime\/(\d+)""")
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun redirect(url: String, providerApi: MainAPI): String {
|
||||||
|
// Tries built in ID -> ProviderUrl
|
||||||
|
for (api in syncApis) {
|
||||||
|
if (url.contains(api.mainUrl)) {
|
||||||
|
val otherApi = when (api.name) {
|
||||||
|
aniListApi.name -> "anilist"
|
||||||
|
malApi.name -> "myanimelist"
|
||||||
|
else -> return url
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl ->
|
||||||
|
realUrl.contains(providerApi.mainUrl)
|
||||||
|
}?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
// ?: run {
|
||||||
|
// throw ErrorLoadingException("Page does not exist on $preferredUrl")
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tries provider solution
|
||||||
|
return syncIds.firstNotNullOfOrNull { (syncName, syncRegex) ->
|
||||||
|
if (providerApi.supportedSyncNames.contains(syncName)) {
|
||||||
|
syncRegex.find(url)?.value?.let {
|
||||||
|
suspendSafeApiCall {
|
||||||
|
providerApi.getLoadUrl(syncName, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
} ?: url
|
||||||
|
}
|
||||||
|
}
|
|
@ -602,7 +602,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
// English title first
|
// English title first
|
||||||
this.media.title.english ?: this.media.title.romaji ?: this.media.synonyms.firstOrNull()
|
this.media.title.english ?: this.media.title.romaji ?: this.media.synonyms.firstOrNull()
|
||||||
?: "",
|
?: "",
|
||||||
this.media.id.toString(),
|
"https://anilist.co/anime/${this.media.id}/",
|
||||||
listName ?: return null,
|
listName ?: return null,
|
||||||
this.progress,
|
this.progress,
|
||||||
this.media.episodes,
|
this.media.episodes,
|
||||||
|
|
|
@ -387,7 +387,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
||||||
fun toLibraryItem(): LibraryItem {
|
fun toLibraryItem(): LibraryItem {
|
||||||
return LibraryItem(
|
return LibraryItem(
|
||||||
this.node.title,
|
this.node.title,
|
||||||
this.node.id.toString(),
|
"https://myanimelist.net/anime/${this.node.id}/",
|
||||||
this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE",
|
this.list_status?.status?.lowercase()?.capitalize()?.replace("_", " ") ?: "NONE",
|
||||||
this.list_status?.num_episodes_watched,
|
this.list_status?.num_episodes_watched,
|
||||||
this.node.num_episodes,
|
this.node.num_episodes,
|
||||||
|
|
|
@ -12,6 +12,8 @@ import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import kotlinx.android.synthetic.main.fragment_library.*
|
import kotlinx.android.synthetic.main.fragment_library.*
|
||||||
|
@ -68,12 +70,17 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
viewpager?.setPageTransformer(LibraryScrollTransformer())
|
viewpager?.setPageTransformer(LibraryScrollTransformer())
|
||||||
viewpager?.adapter =
|
viewpager?.adapter =
|
||||||
viewpager.adapter ?: ViewpagerAdapter(emptyList()) { isScrollingDown: Boolean ->
|
viewpager.adapter ?: ViewpagerAdapter(emptyList(), { isScrollingDown: Boolean ->
|
||||||
if (isScrollingDown) {
|
if (isScrollingDown) {
|
||||||
sort_fab?.shrink()
|
sort_fab?.shrink()
|
||||||
} else {
|
} else {
|
||||||
sort_fab?.extend()
|
sort_fab?.extend()
|
||||||
}
|
}
|
||||||
|
}) { searchClickCallback ->
|
||||||
|
println("SEARCH CLICK $searchClickCallback")
|
||||||
|
if (searchClickCallback.action == SEARCH_ACTION_LOAD) {
|
||||||
|
activity?.loadSearchResult(searchClickCallback.card)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
viewpager?.offscreenPageLimit = 2
|
viewpager?.offscreenPageLimit = 2
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,14 @@ import android.view.ViewGroup
|
||||||
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.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import kotlinx.android.synthetic.main.search_result_grid_expanded.view.*
|
import kotlinx.android.synthetic.main.search_result_grid_expanded.view.*
|
||||||
|
|
||||||
class PageAdapter(
|
class PageAdapter(
|
||||||
override val items: MutableList<LibraryItem>,
|
override val items: MutableList<LibraryItem>,
|
||||||
|
val clickCallback: (SearchClickCallback) -> Unit
|
||||||
) :
|
) :
|
||||||
AppUtils.DiffAdapter<LibraryItem>(items) {
|
AppUtils.DiffAdapter<LibraryItem>(items) {
|
||||||
|
|
||||||
|
@ -33,7 +35,7 @@ class PageAdapter(
|
||||||
inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
fun bind(item: LibraryItem, position: Int) {
|
fun bind(item: LibraryItem, position: Int) {
|
||||||
SearchResultBuilder.bind(
|
SearchResultBuilder.bind(
|
||||||
{ println("CLICKED ${it.action}") },
|
this@PageAdapter.clickCallback,
|
||||||
item,
|
item,
|
||||||
position,
|
position,
|
||||||
itemView,
|
itemView,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchQuality
|
import com.lagradost.cloudstream3.SearchQuality
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
import kotlinx.android.synthetic.main.library_viewpager_page.view.*
|
import kotlinx.android.synthetic.main.library_viewpager_page.view.*
|
||||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
|
@ -55,7 +56,11 @@ data class LibraryItem(
|
||||||
) : SearchResponse
|
) : SearchResponse
|
||||||
|
|
||||||
|
|
||||||
class ViewpagerAdapter(var pages: List<Page>, val scrollCallback: (isScrollingDown: Boolean) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
class ViewpagerAdapter(
|
||||||
|
var pages: List<Page>,
|
||||||
|
val scrollCallback: (isScrollingDown: Boolean) -> Unit,
|
||||||
|
val clickCallback: (SearchClickCallback) -> Unit
|
||||||
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return PageViewHolder(
|
return PageViewHolder(
|
||||||
LayoutInflater.from(parent.context)
|
LayoutInflater.from(parent.context)
|
||||||
|
@ -75,8 +80,9 @@ class ViewpagerAdapter(var pages: List<Page>, val scrollCallback: (isScrollingDo
|
||||||
RecyclerView.ViewHolder(itemViewTest) {
|
RecyclerView.ViewHolder(itemViewTest) {
|
||||||
fun bind(page: Page) {
|
fun bind(page: Page) {
|
||||||
if (itemViewTest.page_recyclerview?.adapter == null) {
|
if (itemViewTest.page_recyclerview?.adapter == null) {
|
||||||
itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList())
|
itemViewTest.page_recyclerview?.adapter = PageAdapter(page.items.toMutableList(), clickCallback)
|
||||||
itemView.page_recyclerview?.spanCount = this@PageViewHolder.itemView.context.getSpanCount() ?: 3
|
itemView.page_recyclerview?.spanCount =
|
||||||
|
this@PageViewHolder.itemView.context.getSpanCount() ?: 3
|
||||||
} else {
|
} else {
|
||||||
(itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items)
|
(itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items)
|
||||||
itemViewTest.page_recyclerview?.scrollToPosition(0)
|
itemViewTest.page_recyclerview?.scrollToPosition(0)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
import com.lagradost.cloudstream3.APIHolder.getId
|
import com.lagradost.cloudstream3.APIHolder.getId
|
||||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||||
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
||||||
|
@ -1264,12 +1265,18 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
val realRecommendations = ArrayList<SearchResponse>()
|
val realRecommendations = ArrayList<SearchResponse>()
|
||||||
// TODO: fix
|
// TODO: fix
|
||||||
//val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
|
val apiNames = apis.filter {
|
||||||
// meta.recommendations?.forEach { rec ->
|
it.name.contains("gogoanime", true) ||
|
||||||
// apiNames.forEach { name ->
|
it.name.contains("9anime", true)
|
||||||
// realRecommendations.add(rec.copy(apiName = name))
|
}.map {
|
||||||
// }
|
it.name
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
meta.recommendations?.forEach { rec ->
|
||||||
|
apiNames.forEach { name ->
|
||||||
|
realRecommendations.add(rec.copy(apiName = name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
recommendations = recommendations?.union(realRecommendations)?.toList()
|
recommendations = recommendations?.union(realRecommendations)?.toList()
|
||||||
?: realRecommendations
|
?: realRecommendations
|
||||||
|
@ -1913,7 +1920,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val validUrlResource = safeApiCall {
|
val validUrlResource = safeApiCall {
|
||||||
SyncRedirector.redirect(
|
SyncRedirector.redirect(
|
||||||
url,
|
url,
|
||||||
api.mainUrl
|
api
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// TODO: fix
|
// TODO: fix
|
||||||
|
|
|
@ -4,6 +4,7 @@ package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
//import com.lagradost.cloudstream3.animeproviders.AniflixProvider
|
//import com.lagradost.cloudstream3.animeproviders.AniflixProvider
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -78,17 +79,21 @@ object SyncUtil {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUrlsFromId(id: String, type: String = "anilist") : List<String> {
|
suspend fun getUrlsFromId(id: String, type: String = "anilist"): List<String> {
|
||||||
return arrayListOf()
|
val url =
|
||||||
// val url =
|
"https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json"
|
||||||
// "https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json"
|
val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
|
||||||
// val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
|
val pages = response.pages ?: return emptyList()
|
||||||
// val pages = response.pages ?: return emptyList()
|
val current =
|
||||||
// val current = pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url }.toMutableList()
|
pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values)
|
||||||
// if(type == "anilist") { // TODO MAKE BETTER
|
.mapNotNull { it.url }.toMutableList()
|
||||||
// current.add("${AniflixProvider().mainUrl}/anime/$id")
|
|
||||||
// }
|
if (type == "anilist") { // TODO MAKE BETTER
|
||||||
// return current
|
apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach {
|
||||||
|
current.add("${it.mainUrl}/anime/$id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SyncPage(
|
data class SyncPage(
|
||||||
|
|
|
@ -301,6 +301,7 @@
|
||||||
|
|
||||||
<style name="TabNoCaps" parent="TextAppearance.Design.Tab">
|
<style name="TabNoCaps" parent="TextAppearance.Design.Tab">
|
||||||
<item name="textAllCaps">false</item>
|
<item name="textAllCaps">false</item>
|
||||||
|
<item name="fontFamily">@font/google_sans</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTextViewStyle" parent="android:Widget.TextView">
|
<style name="AppTextViewStyle" parent="android:Widget.TextView">
|
||||||
|
|
Loading…
Reference in a new issue