This commit is contained in:
C10udburst 2022-08-17 15:52:05 +02:00
commit 4546370dc4
15 changed files with 367 additions and 267 deletions

View file

@ -11,6 +11,9 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -33,6 +36,7 @@ import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.initAll import com.lagradost.cloudstream3.APIHolder.initAll
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity.currentToast
import com.lagradost.cloudstream3.CommonActivity.loadThemes import com.lagradost.cloudstream3.CommonActivity.loadThemes
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
@ -85,7 +89,11 @@ import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event
import kotlinx.coroutines.delay
import java.lang.ref.WeakReference
import java.net.URI
const val VLC_PACKAGE = "org.videolan.vlc" const val VLC_PACKAGE = "org.videolan.vlc"
@ -293,7 +301,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (VLC_REQUEST_CODE == requestCode) { if (requestCode == VLC_REQUEST_CODE) {
if (resultCode == RESULT_OK && data != null) { if (resultCode == RESULT_OK && data != null) {
val pos: Long = val pos: Long =
data.getLongExtra( data.getLongExtra(
@ -368,7 +376,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} }
} }
} }
} else if (str.contains(appStringRepo)) { } else if (URI(str).scheme == appStringRepo) {
val url = str.replaceFirst(appStringRepo, "https") val url = str.replaceFirst(appStringRepo, "https")
loadRepository(url) loadRepository(url)
} else { } else {
@ -444,6 +452,28 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} }
PluginManager.loadAllLocalPlugins(this@MainActivity) PluginManager.loadAllLocalPlugins(this@MainActivity)
// Load cloned sites after plugins have been loaded since clones depend on plugins.
try {
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
list.forEach { custom ->
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
?.let {
allProviders.add(it.javaClass.newInstance().apply {
name = custom.name
lang = custom.lang
mainUrl = custom.url.trimEnd('/')
canBeOverridden = false
})
}
}
}
apis = allProviders.distinctBy { it }
APIHolder.apiMap = null
} catch (e: Exception) {
logError(e)
}
afterPluginsLoadedEvent.invoke(true) afterPluginsLoadedEvent.invoke(true)
} }
@ -480,26 +510,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
initAll() initAll()
// No duplicates (which can happen by registerMainAPI) // No duplicates (which can happen by registerMainAPI)
apis = allProviders.distinctBy { it } apis = allProviders.distinctBy { it }
try {
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
list.forEach { custom ->
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
?.let {
allProviders.add(it.javaClass.newInstance().apply {
name = custom.name
lang = custom.lang
mainUrl = custom.url.trimEnd('/')
canBeOverridden = false
})
}
}
}
apis = allProviders.distinctBy { it }
APIHolder.apiMap = null
} catch (e: Exception) {
logError(e)
}
} }
// val navView: BottomNavigationView = findViewById(R.id.nav_view) // val navView: BottomNavigationView = findViewById(R.id.nav_view)
@ -677,13 +687,25 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
&& PluginManager.getPluginsLocal().isEmpty() && PluginManager.getPluginsLocal().isEmpty()
// && PREBUILT_REPOSITORIES.isNotEmpty() // && PREBUILT_REPOSITORIES.isNotEmpty()
) { ) {
navController.navigate(R.id.navigation_setup_extensions, SetupFragmentExtensions.newInstance(false)) navController.navigate(
R.id.navigation_setup_extensions,
SetupFragmentExtensions.newInstance(false)
)
} }
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} finally { } finally {
setKey(HAS_DONE_SETUP_KEY, true) setKey(HAS_DONE_SETUP_KEY, true)
} }
// Used to check current focus for TV
// main {
// while (true) {
// delay(1000)
// println("Current focus: $currentFocus")
// }
// }
/* /*
val relativePath = (Environment.DIRECTORY_DOWNLOADS) + File.separatorChar val relativePath = (Environment.DIRECTORY_DOWNLOADS) + File.separatorChar

View file

@ -11,8 +11,10 @@ import android.webkit.WebViewClient
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
import kotlinx.android.synthetic.main.fragment_webview.* import kotlinx.android.synthetic.main.fragment_webview.*
import java.net.URI
class WebviewFragment : Fragment() { class WebviewFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -27,10 +29,17 @@ class WebviewFragment : Fragment() {
request: WebResourceRequest? request: WebResourceRequest?
): Boolean { ): Boolean {
val requestUrl = request?.url.toString() val requestUrl = request?.url.toString()
if (requestUrl.startsWith("https://cs.repo")) {
val realUrl = "https://" + requestUrl.substringAfter("?") val repoUrl = if (requestUrl.startsWith("https://cs.repo")) {
println("Repository url: $realUrl :::: $requestUrl") "https://" + requestUrl.substringAfter("?")
activity?.loadRepository(realUrl) } else if (URI(requestUrl).scheme == appStringRepo) {
"https://" + requestUrl.replaceFirst(appStringRepo, "https")
} else {
null
}
if (repoUrl != null) {
activity?.loadRepository(repoUrl)
findNavController().popBackStack() findNavController().popBackStack()
return true return true
} }

View file

@ -60,8 +60,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllResumeStateIds
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
@ -455,8 +454,9 @@ class HomeFragment : Fragment() {
homeViewModel.loadStoredData(list) homeViewModel.loadStoredData(list)
} }
private fun loadHomePage(successful: Boolean = true) { private fun loadHomePage(successful: Boolean = false) {
val apiName = context?.getKey<String>(HOMEPAGE_API) val apiName = context?.getKey<String>(USER_SELECTED_HOMEPAGE_API)
if (homeViewModel.apiName.value != apiName || apiName == null) { if (homeViewModel.apiName.value != apiName || apiName == null) {
//println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
homeViewModel.loadAndCancel(apiName) homeViewModel.loadAndCancel(apiName)
@ -512,7 +512,7 @@ class HomeFragment : Fragment() {
observe(homeViewModel.apiName) { apiName -> observe(homeViewModel.apiName) { apiName ->
currentApiName = apiName currentApiName = apiName
setKey(HOMEPAGE_API, apiName) // setKey(USER_SELECTED_HOMEPAGE_API, apiName)
home_api_fab?.text = apiName home_api_fab?.text = apiName
home_provider_name?.text = apiName home_provider_name?.text = apiName
try { try {

View file

@ -31,7 +31,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.HOMEPAGE_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -227,7 +227,8 @@ class HomeViewModel : ViewModel() {
expandable.clear() expandable.clear()
data.value.forEach { home -> data.value.forEach { home ->
home?.items?.forEach { list -> home?.items?.forEach { list ->
val filteredList = context?.filterHomePageListByFilmQuality(list) ?: list val filteredList =
context?.filterHomePageListByFilmQuality(list) ?: list
expandable[list.name] = expandable[list.name] =
ExpandableHomepageList(filteredList, 1, home.hasNext) ExpandableHomepageList(filteredList, 1, home.hasNext)
} }
@ -244,7 +245,9 @@ class HomeViewModel : ViewModel() {
.toList() .toList()
if (currentList.isNotEmpty()) { if (currentList.isNotEmpty()) {
val randomItems = context?.filterSearchResultByFilmQuality(currentList.shuffled()) ?: currentList.shuffled() val randomItems =
context?.filterSearchResultByFilmQuality(currentList.shuffled())
?: currentList.shuffled()
_randomItems.postValue(randomItems) _randomItems.postValue(randomItems)
} }
@ -266,18 +269,22 @@ class HomeViewModel : ViewModel() {
fun loadAndCancel(preferredApiName: String?) = viewModelScope.launch { fun loadAndCancel(preferredApiName: String?) = viewModelScope.launch {
val api = getApiFromNameNull(preferredApiName) val api = getApiFromNameNull(preferredApiName)
if (preferredApiName == noneApi.name) if (preferredApiName == noneApi.name){
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
loadAndCancel(noneApi) loadAndCancel(noneApi)
}
else if (preferredApiName == randomApi.name || api == null) { else if (preferredApiName == randomApi.name || api == null) {
val validAPIs = context?.filterProviderByPreferredMedia() val validAPIs = context?.filterProviderByPreferredMedia()
if (validAPIs.isNullOrEmpty()) { if (validAPIs.isNullOrEmpty()) {
// Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded
loadAndCancel(noneApi) loadAndCancel(noneApi)
} else { } else {
val apiRandom = validAPIs.random() val apiRandom = validAPIs.random()
loadAndCancel(apiRandom) loadAndCancel(apiRandom)
setKey(HOMEPAGE_API, apiRandom.name) setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name)
} }
} else { } else {
setKey(USER_SELECTED_HOMEPAGE_API, api.name)
loadAndCancel(api) loadAndCancel(api)
} }
} }

View file

@ -39,6 +39,7 @@ class CustomDecoder : SubtitleDecoder {
private var overrideEncoding: String? = null private var overrideEncoding: String? = null
var regexSubtitlesToRemoveCaptions = false var regexSubtitlesToRemoveCaptions = false
var regexSubtitlesToRemoveBloat = false var regexSubtitlesToRemoveBloat = false
var uppercaseSubtitles = false
val bloatRegex = val bloatRegex =
listOf( listOf(
Regex( Regex(
@ -193,6 +194,9 @@ class CustomDecoder : SubtitleDecoder {
bloatRegex.forEach { rgx -> bloatRegex.forEach { rgx ->
str = str.replace(rgx, "\n") str = str.replace(rgx, "\n")
} }
if (uppercaseSubtitles) {
str = str.uppercase()
}
} }
inputBuffer.setSubtitleText(str) inputBuffer.setSubtitleText(str)
} }

View file

@ -9,6 +9,7 @@ import com.google.android.exoplayer2.util.MimeTypes
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.regexSubtitlesToRemoveBloat import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.regexSubtitlesToRemoveBloat
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.regexSubtitlesToRemoveCaptions import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.regexSubtitlesToRemoveCaptions
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.uppercaseSubtitles
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.fromSaveToStyle import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.fromSaveToStyle
import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UIHelper.toPx
@ -87,6 +88,7 @@ class PlayerSubtitleHelper {
fun setSubStyle(style: SaveCaptionStyle) { fun setSubStyle(style: SaveCaptionStyle) {
regexSubtitlesToRemoveBloat = style.removeBloat regexSubtitlesToRemoveBloat = style.removeBloat
uppercaseSubtitles = style.upperCase
regexSubtitlesToRemoveCaptions = style.removeCaptions regexSubtitlesToRemoveCaptions = style.removeCaptions
subtitleView?.context?.let { ctx -> subtitleView?.context?.let { ctx ->
subStyle = style subStyle = style

View file

@ -16,7 +16,7 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.HOMEPAGE_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
@ -116,7 +116,7 @@ class SettingsLang : PreferenceFragmentCompat() {
.putInt(getString(R.string.prefer_media_type_key), prefValues[it]) .putInt(getString(R.string.prefer_media_type_key), prefValues[it])
.apply() .apply()
removeKey(HOMEPAGE_API) removeKey(USER_SELECTED_HOMEPAGE_API)
// (context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } // (context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) }
} }
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true

View file

@ -11,7 +11,7 @@ import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.HOMEPAGE_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_setup_media.* import kotlinx.android.synthetic.main.fragment_setup_media.*
@ -52,7 +52,7 @@ class SetupFragmentMedia : Fragment() {
.apply() .apply()
// Regenerate set homepage // Regenerate set homepage
removeKey(HOMEPAGE_API) removeKey(USER_SELECTED_HOMEPAGE_API)
} }
next_btt?.setOnClickListener { next_btt?.setOnClickListener {

View file

@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import kotlinx.android.synthetic.main.subtitle_settings.* import kotlinx.android.synthetic.main.subtitle_settings.*
import kotlinx.android.synthetic.main.toast.view.*
import java.io.File import java.io.File
const val SUBTITLE_KEY = "subtitle_settings" const val SUBTITLE_KEY = "subtitle_settings"
@ -60,6 +61,8 @@ data class SaveCaptionStyle(
@JsonProperty("fixedTextSize") var fixedTextSize: Float?, @JsonProperty("fixedTextSize") var fixedTextSize: Float?,
@JsonProperty("removeCaptions") var removeCaptions: Boolean = false, @JsonProperty("removeCaptions") var removeCaptions: Boolean = false,
@JsonProperty("removeBloat") var removeBloat: Boolean = true, @JsonProperty("removeBloat") var removeBloat: Boolean = true,
/** Apply caps lock to the text **/
@JsonProperty("upperCase") var upperCase: Boolean = false,
) )
const val DEF_SUBS_ELEVATION = 20 const val DEF_SUBS_ELEVATION = 20
@ -182,6 +185,19 @@ class SubtitlesFragment : Fragment() {
private fun Context.updateState() { private fun Context.updateState() {
subtitle_text?.setStyle(fromSaveToStyle(state)) subtitle_text?.setStyle(fromSaveToStyle(state))
val text = subtitle_text.context.getString(R.string.subtitles_example_text)
val fixedText = if (state.upperCase) text.uppercase() else text
subtitle_text?.setCues(
listOf(
Cue.Builder()
.setTextSize(
getPixels(TypedValue.COMPLEX_UNIT_SP, 25.0f).toFloat(),
Cue.TEXT_SIZE_TYPE_ABSOLUTE
)
.setText(fixedText)
.build()
)
)
} }
private fun getColor(id: Int): Int { private fun getColor(id: Int): Int {
@ -222,7 +238,6 @@ class SubtitlesFragment : Fragment() {
context?.getExternalFilesDir(null)?.absolutePath.toString() + "/Fonts" context?.getExternalFilesDir(null)?.absolutePath.toString() + "/Fonts"
) )
context?.fixPaddingStatusbar(subs_root) context?.fixPaddingStatusbar(subs_root)
state = getCurrentSavedStyle() state = getCurrentSavedStyle()
@ -404,6 +419,12 @@ class SubtitlesFragment : Fragment() {
subtitles_remove_bloat?.setOnCheckedChangeListener { _, b -> subtitles_remove_bloat?.setOnCheckedChangeListener { _, b ->
state.removeBloat = b state.removeBloat = b
} }
subtitles_uppercase?.isChecked = state.upperCase
subtitles_uppercase?.setOnCheckedChangeListener { _, b ->
state.upperCase = b
context?.updateState()
}
subtitles_remove_captions?.isChecked = state.removeCaptions subtitles_remove_captions?.isChecked = state.removeCaptions
subtitles_remove_captions?.setOnCheckedChangeListener { _, b -> subtitles_remove_captions?.setOnCheckedChangeListener { _, b ->
state.removeCaptions = b state.removeCaptions = b
@ -418,9 +439,11 @@ class SubtitlesFragment : Fragment() {
//Fetch current value from preference //Fetch current value from preference
context?.let { ctx -> context?.let { ctx ->
subtitles_filter_sub_lang?.isChecked = PreferenceManager.getDefaultSharedPreferences(ctx) subtitles_filter_sub_lang?.isChecked =
.getBoolean(getString(R.string.filter_sub_lang_key), false) PreferenceManager.getDefaultSharedPreferences(ctx)
.getBoolean(getString(R.string.filter_sub_lang_key), false)
} }
subtitles_filter_sub_lang?.setOnCheckedChangeListener { _, b -> subtitles_filter_sub_lang?.setOnCheckedChangeListener { _, b ->
context?.let { ctx -> context?.let { ctx ->
PreferenceManager.getDefaultSharedPreferences(ctx) PreferenceManager.getDefaultSharedPreferences(ctx)
@ -553,17 +576,5 @@ class SubtitlesFragment : Fragment() {
it.context.fromSaveToStyle(state) it.context.fromSaveToStyle(state)
activity?.popCurrentPage() activity?.popCurrentPage()
} }
subtitle_text.setCues(
listOf(
Cue.Builder()
.setTextSize(
getPixels(TypedValue.COMPLEX_UNIT_SP, 25.0f).toFloat(),
Cue.TEXT_SIZE_TYPE_ABSOLUTE
)
.setText(subtitle_text.context.getString(R.string.subtitles_example_text))
.build()
)
)
} }
} }

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.Activity.RESULT_CANCELED
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -21,6 +22,7 @@ import android.provider.MediaStore
import android.text.Spanned import android.text.Spanned
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -41,13 +43,11 @@ import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.wrappers.Wrappers import com.google.android.gms.common.wrappers.Wrappers
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.isMovieType
import com.lagradost.cloudstream3.mapper
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.plugins.RepositoryManager
import com.lagradost.cloudstream3.ui.WebviewFragment import com.lagradost.cloudstream3.ui.WebviewFragment
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
@ -239,6 +239,7 @@ object AppUtils {
) )
} }
} }
fun Activity.loadRepository(url: String) { fun Activity.loadRepository(url: String) {
ioSafe { ioSafe {
val repo = RepositoryManager.parseRepository(url) ?: return@ioSafe val repo = RepositoryManager.parseRepository(url) ?: return@ioSafe
@ -259,6 +260,18 @@ object AppUtils {
} }
} }
private fun Context.hasWebView(): Boolean {
return this.packageManager.hasSystemFeature("android.software.webview")
}
private fun openWebView(fragment: Fragment?, url: String) {
if (fragment?.context?.hasWebView() == true)
normalSafeApiCall {
fragment
.findNavController()
.navigate(R.id.navigation_webview, WebviewFragment.newInstance(url))
}
}
/** /**
* If fallbackWebview is true and a fragment is supplied then it will open a webview with the url if the browser fails. * If fallbackWebview is true and a fragment is supplied then it will open a webview with the url if the browser fails.
@ -266,23 +279,34 @@ object AppUtils {
fun Context.openBrowser( fun Context.openBrowser(
url: String, url: String,
fallbackWebview: Boolean = false, fallbackWebview: Boolean = false,
fragment: Fragment? = null fragment: Fragment? = null,
) { ) {
try { try {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url) intent.data = Uri.parse(url)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
ContextCompat.startActivity(this, intent, null)
// activityResultRegistry is used to fall back to webview if a browser is missing
// On older versions the startActivity just crashes, but on newer android versions
// You need to check the result to make sure it failed
val activityResultRegistry = fragment?.activity?.activityResultRegistry
if (activityResultRegistry != null) {
activityResultRegistry.register(
url,
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_CANCELED && fallbackWebview) {
openWebView(fragment, url)
}
}.launch(intent)
} else {
ContextCompat.startActivity(this, intent, null)
}
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
if (fallbackWebview) { if (fallbackWebview) {
try { openWebView(fragment, url)
fragment
?.findNavController()
?.navigate(R.id.navigation_webview, WebviewFragment.newInstance(url))
} catch (e: Exception) {
logError(e)
}
} }
} }
} }

View file

@ -13,7 +13,7 @@ const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
//const val WATCH_HEADER_CACHE = "watch_header_cache" //const val WATCH_HEADER_CACHE = "watch_header_cache"
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache" const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha_key" const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha_key"
const val HOMEPAGE_API = "home_api_used" const val USER_SELECTED_HOMEPAGE_API = "home_api_used"
const val USER_PROVIDER_API = "user_custom_sites" const val USER_PROVIDER_API = "user_custom_sites"
const val PREFERENCES_NAME = "rebuild_preference" const val PREFERENCES_NAME = "rebuild_preference"

View file

@ -163,7 +163,7 @@
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/add_repo_button" android:id="@+id/add_repo_button"
android:nextFocusUp="@id/list_repositories" android:foreground="@drawable/outline_drawable"
android:nextFocusDown="@id/plugin_storage_appbar" android:nextFocusDown="@id/plugin_storage_appbar"
style="@style/ExtendedFloatingActionButton" style="@style/ExtendedFloatingActionButton"
android:text="@string/add_repository" android:text="@string/add_repository"

View file

@ -6,6 +6,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:nextFocusRight="@id/action_button"
android:background="@drawable/outline_drawable"
android:padding="20dp"> android:padding="20dp">
<ImageView <ImageView
@ -86,11 +88,16 @@
tools:visibility="visible" /> tools:visibility="visible" />
<ImageView <ImageView
android:padding="10dp"
android:background="@drawable/outline_drawable"
android:nextFocusLeft="@id/repository_item_root"
android:clickable="true"
android:id="@+id/action_button" android:id="@+id/action_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
tools:src="@drawable/ic_baseline_add_24" /> tools:src="@drawable/ic_baseline_add_24"
android:focusable="true" />
</LinearLayout> </LinearLayout>

View file

@ -1,235 +1,248 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/subs_root" android:id="@+id/subs_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/primaryBlackBackground"> android:background="?attr/primaryBlackBackground">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:paddingStart="20dp" android:paddingStart="20dp"
android:paddingEnd="20dp" android:paddingEnd="20dp"
android:text="@string/subtitles_settings" android:text="@string/subtitles_settings"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="75sp"> android:layout_height="75sp">
<ImageView <ImageView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:contentDescription="@string/preview_background_img_des" android:contentDescription="@string/preview_background_img_des"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/subtitles_preview_background" /> android:src="@drawable/subtitles_preview_background" />
<com.google.android.exoplayer2.ui.SubtitleView <com.google.android.exoplayer2.ui.SubtitleView
android:id="@+id/subtitle_text" android:id="@+id/subtitle_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:foregroundGravity="center" /> android:foregroundGravity="center" />
</FrameLayout> </FrameLayout>
<TextView <TextView
android:id="@+id/subs_font" android:id="@+id/subs_font"
style="@style/SettingsItem" style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt" android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt" android:nextFocusRight="@id/cancel_btt"
android:nextFocusDown="@id/subs_font_size" android:nextFocusDown="@id/subs_font_size"
android:text="@string/subs_font" /> android:text="@string/subs_font" />
<TextView <TextView
android:id="@+id/subs_font_size" android:id="@+id/subs_font_size"
style="@style/SettingsItem" style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_font"
android:nextFocusDown="@id/subs_text_color"
android:text="@string/subs_font_size" />
<TextView
android:id="@+id/subs_text_color"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_font_size"
android:nextFocusDown="@id/subs_outline_color"
android:text="@string/subs_text_color" />
<TextView
android:id="@+id/subs_outline_color"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_text_color"
android:nextFocusDown="@id/subs_background_color"
android:text="@string/subs_outline_color" />
<TextView
android:id="@+id/subs_background_color"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_outline_color"
android:nextFocusDown="@id/subs_window_color"
android:text="@string/subs_background_color" />
<TextView
android:id="@+id/subs_window_color"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_background_color"
android:nextFocusDown="@id/subs_edge_type"
android:text="@string/subs_window_color" />
<TextView
android:id="@+id/subs_edge_type"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_window_color"
android:nextFocusDown="@id/subs_subtitle_elevation"
android:text="@string/subs_edge_type" />
<TextView
android:id="@+id/subs_subtitle_elevation"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_edge_type"
android:nextFocusDown="@id/subs_auto_select_language"
android:text="@string/subs_subtitle_elevation" />
<TextView
android:id="@+id/subs_auto_select_language"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_subtitle_elevation"
android:nextFocusDown="@id/subs_download_languages"
android:text="@string/subs_auto_select_language" />
<TextView
android:id="@+id/subs_download_languages"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_auto_select_language"
android:nextFocusDown="@id/subtitles_remove_bloat"
android:text="@string/subs_download_languages" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:nextFocusUp="@id/subs_download_languages"
android:nextFocusDown="@id/subtitles_remove_captions"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:fontFamily="@font/google_sans"
style="@style/SettingsItem"
app:drawableEndCompat="@null"
android:id="@+id/subtitles_remove_bloat"
android:text="@string/subtitles_remove_bloat"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:nextFocusUp="@id/subtitles_remove_bloat"
android:nextFocusDown="@id/apply_btt"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:fontFamily="@font/google_sans"
style="@style/SettingsItem"
app:drawableEndCompat="@null"
android:id="@+id/subtitles_remove_captions"
android:text="@string/subtitles_remove_captions"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:nextFocusUp="@id/subtitles_remove_captions"
android:nextFocusDown="@id/apply_btt"
android:nextFocusLeft="@id/apply_btt" android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt" android:nextFocusRight="@id/cancel_btt"
android:fontFamily="@font/google_sans"
android:nextFocusUp="@id/subs_font"
android:nextFocusDown="@id/subs_text_color"
android:text="@string/subs_font_size" />
<TextView
android:id="@+id/subs_text_color"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_font_size"
android:nextFocusDown="@id/subs_outline_color"
android:text="@string/subs_text_color" />
<TextView
android:id="@+id/subs_outline_color"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_text_color"
android:nextFocusDown="@id/subs_background_color"
android:text="@string/subs_outline_color" />
<TextView
android:id="@+id/subs_background_color"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_outline_color"
android:nextFocusDown="@id/subs_window_color"
android:text="@string/subs_background_color" />
<TextView
android:id="@+id/subs_window_color"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_background_color"
android:nextFocusDown="@id/subs_edge_type"
android:text="@string/subs_window_color" />
<TextView
android:id="@+id/subs_edge_type"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_window_color"
android:nextFocusDown="@id/subs_subtitle_elevation"
android:text="@string/subs_edge_type" />
<TextView
android:id="@+id/subs_subtitle_elevation"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_edge_type"
android:nextFocusDown="@id/subs_auto_select_language"
android:text="@string/subs_subtitle_elevation" />
<TextView
android:id="@+id/subs_auto_select_language"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_subtitle_elevation"
android:nextFocusDown="@id/subs_download_languages"
android:text="@string/subs_auto_select_language" />
<TextView
android:id="@+id/subs_download_languages"
style="@style/SettingsItem"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_auto_select_language"
android:nextFocusDown="@id/subtitles_remove_bloat"
android:text="@string/subs_download_languages" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/subtitles_remove_bloat"
style="@style/SettingsItem" style="@style/SettingsItem"
app:drawableEndCompat="@null"
android:id="@+id/subtitles_filter_sub_lang"
android:text="@string/subtitles_filter_lang"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:fontFamily="@font/google_sans"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subs_download_languages"
android:nextFocusDown="@id/subtitles_remove_captions"
android:text="@string/subtitles_remove_bloat"
app:drawableEndCompat="@null" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/subtitles_remove_captions"
style="@style/SettingsItem"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="@font/google_sans"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subtitles_remove_bloat"
android:nextFocusDown="@id/subtitles_filter_sub_lang"
android:text="@string/subtitles_remove_captions"
app:drawableEndCompat="@null" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/subtitles_filter_sub_lang"
style="@style/SettingsItem"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="@font/google_sans"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subtitles_remove_captions"
android:nextFocusDown="@id/subtitles_uppercase"
android:text="@string/subtitles_filter_lang"
app:drawableEndCompat="@null" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/subtitles_uppercase"
style="@style/SettingsItem"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="@font/google_sans"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subtitles_filter_sub_lang"
android:nextFocusDown="@id/apply_btt"
android:text="@string/uppercase_all_subtitles"
app:drawableEndCompat="@null" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:gravity="center" android:gravity="center"
android:text="@string/subs_hold_to_reset_to_default" android:text="@string/subs_hold_to_reset_to_default"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
android:textSize="14sp" /> android:textSize="14sp" />
<TextView <TextView
android:id="@+id/subs_import_text" android:id="@+id/subs_import_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:gravity="center" android:gravity="center"
android:text="@string/subs_import_text" android:text="@string/subs_import_text"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
android:textSize="14sp" /> android:textSize="14sp" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="60dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:gravity="bottom|end" android:gravity="bottom|end"
android:orientation="horizontal"> android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/apply_btt" android:id="@+id/apply_btt"
style="@style/WhiteButton" style="@style/WhiteButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:nextFocusRight="@id/cancel_btt" android:nextFocusRight="@id/cancel_btt"
android:nextFocusUp="@id/subtitles_remove_captions" android:nextFocusUp="@id/subtitles_remove_captions"
android:text="@string/sort_apply" android:text="@string/sort_apply"
android:visibility="visible"> android:visibility="visible">
<requestFocus /> <requestFocus />
</com.google.android.material.button.MaterialButton> </com.google.android.material.button.MaterialButton>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/cancel_btt" android:id="@+id/cancel_btt"
style="@style/BlackButton" style="@style/BlackButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:nextFocusLeft="@id/apply_btt" android:nextFocusLeft="@id/apply_btt"
android:nextFocusUp="@id/subtitles_remove_captions" android:nextFocusUp="@id/subtitles_remove_captions"
android:text="@string/sort_cancel" /> android:text="@string/sort_cancel" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View file

@ -601,4 +601,5 @@
<string name="blank_repo_message">Add a repository to install site extensions</string> <string name="blank_repo_message">Add a repository to install site extensions</string>
<string name="view_public_repositories_button">View community repositories</string> <string name="view_public_repositories_button">View community repositories</string>
<string name="view_public_repositories_button_short">Public list</string> <string name="view_public_repositories_button_short">Public list</string>
<string name="uppercase_all_subtitles">Uppercase all subtitles</string>
</resources> </resources>