diff --git a/.gitignore b/.gitignore index 5fc9f0870b6..513e3349be1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ /captures .cxx .kotlin/* +.jdks/ +# local jdk17 for compilation # Created by https://www.toptal.com/developers/gitignore/api/kotlin,java,android,androidstudio,visualstudiocode # Edit at https://www.toptal.com/developers/gitignore?templates=kotlin,java,android,androidstudio,visualstudiocode diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ae530192998..474c1ee9ad7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -255,6 +255,7 @@ dependencies { // Extensions & Other Libs implementation(libs.jsoup) // HTML Parser implementation(libs.rhino) // Run JavaScript + implementation(libs.woff2.android) implementation(libs.fuzzywuzzy) // Library/Ext Searching with Levenshtein Distance implementation(libs.safefile) // To Prevent the URI File Fu*kery coreLibraryDesugaring(libs.desugar.jdk.libs.nio) // NIO Flavor Needed for NewPipeExtractor diff --git a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt index a9cd9c01edd..1673fbb9d29 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -22,6 +22,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser import com.lagradost.cloudstream3.utils.AppDebug +import com.lagradost.cloudstream3.utils.AppFontManager import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKeys @@ -84,6 +85,7 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory { } AppDebug.isDebug = BuildConfig.DEBUG + registerActivityLifecycleCallbacks(AppFontManager.activityLifecycleCallbacks) } override fun attachBaseContext(base: Context?) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt index 72955e7cf8c..1817230aeb3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt @@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setSystemBarsPadding +import com.lagradost.cloudstream3.utils.AppFontManager import com.lagradost.cloudstream3.utils.txt /** @@ -75,6 +76,7 @@ private interface BaseFragmentHelper { */ fun onViewReady(view: View, savedInstanceState: Bundle?) { fixLayout(view) + AppFontManager.applyToViewTree(view) binding?.let { onBindingCreated(it, savedInstanceState) } } @@ -269,6 +271,7 @@ abstract class BasePreferenceFragmentCompat() : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setSystemBarsPadding() + AppFontManager.applyToViewTree(view) } override fun onConfigurationChanged(newConfig: Configuration) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index f4c522bf981..e1560cacb16 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -1,15 +1,23 @@ package com.lagradost.cloudstream3.ui.settings +import android.graphics.Typeface import android.os.Build import android.os.Bundle import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.ArrayAdapter import androidx.core.content.edit +import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceManager import androidx.preference.SeekBarPreference +import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity +import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchQuality +import com.lagradost.cloudstream3.databinding.BottomAppFontDialogBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.BasePreferenceFragmentCompat import com.lagradost.cloudstream3.ui.clear @@ -25,13 +33,138 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar +import com.lagradost.cloudstream3.utils.AppFontManager import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.toPx +import kotlinx.coroutines.launch class SettingsUI : BasePreferenceFragmentCompat() { + private fun updateAppFontSummary() { + context?.let { + getPref(R.string.app_font_key)?.summary = AppFontManager.getSummary(it) + } + } + + private fun showAppFontDialog() { + val activity = activity ?: return + val binding = BottomAppFontDialogBinding.inflate(layoutInflater) + val dialog = BottomSheetDialog(activity) + val defaultValue = getString(R.string.app_font_default) + val customOption = getString(R.string.app_font_custom_option) + val currentFont = AppFontManager.getSelectedFont(activity) + val suggestedFonts = AppFontManager.getSuggestedFonts(activity) + val suggestions = listOf(defaultValue, customOption) + suggestedFonts + + dialog.setContentView(binding.root) + + binding.text1.text = getString(R.string.app_font_picker_title) + val previewCache = mutableMapOf() + val adapter = object : ArrayAdapter( + activity, + R.layout.app_font_dropdown_item, + suggestions + ) { + private fun applyPreview(textView: TextView, fontName: String) { + val isSpecial = fontName == defaultValue || fontName == customOption + val typeface = if (isSpecial) { + Typeface.DEFAULT + } else { + previewCache.getOrPut(fontName) { + AppFontManager.getPreviewTypeface(activity, fontName) + } ?: Typeface.DEFAULT + } + textView.typeface = typeface + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = super.getView(position, convertView, parent) + (view as? TextView)?.let { applyPreview(it, getItem(position).orEmpty()) } + return view + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = super.getDropDownView(position, convertView, parent) + (view as? TextView)?.let { applyPreview(it, getItem(position).orEmpty()) } + return view + } + } + + binding.appFontSuggestions.setAdapter(adapter) + + val initialSuggestion = when { + currentFont == null -> defaultValue + suggestedFonts.any { it.equals(currentFont, true) } -> currentFont + else -> customOption + } + val initialInput = currentFont.orEmpty() + binding.appFontSuggestions.setText(initialSuggestion, false) + binding.appFontInput.setText(if (initialSuggestion == defaultValue) "" else initialInput) + + fun updateCustomState(isCustom: Boolean) { + binding.appFontInputLayout.isEnabled = isCustom + binding.appFontInputLayout.alpha = if (isCustom) 1f else 0.7f + } + updateCustomState(initialSuggestion == customOption) + + binding.appFontSuggestions.setOnItemClickListener { _, _, position, _ -> + val selected = suggestions.getOrNull(position) ?: return@setOnItemClickListener + when (selected) { + defaultValue -> { + updateCustomState(false) + binding.appFontInput.setText("") + } + customOption -> { + updateCustomState(true) + if (binding.appFontInput.text.isNullOrBlank()) { + binding.appFontInput.setText(currentFont.orEmpty()) + } + binding.appFontInput.requestFocus() + } + else -> { + updateCustomState(false) + binding.appFontInput.setText(selected) + } + } + } + binding.applyBtt.setOnClickListener { + val typedFont = binding.appFontInput.text?.toString()?.trim().orEmpty() + val selectedSuggestion = binding.appFontSuggestions.text?.toString()?.trim().orEmpty() + val selectedFont = when { + selectedSuggestion == defaultValue -> null + selectedSuggestion == customOption -> typedFont.takeIf { it.isNotEmpty() } + selectedSuggestion.isNotEmpty() -> selectedSuggestion + typedFont.isNotEmpty() -> typedFont + else -> null + } + + binding.applyBtt.isEnabled = false + binding.cancelBtt.isEnabled = false + viewLifecycleOwner.lifecycleScope.launch { + val result = AppFontManager.setSelectedFont(activity, selectedFont) + binding.applyBtt.isEnabled = true + binding.cancelBtt.isEnabled = true + result.onSuccess { + updateAppFontSummary() + AppFontManager.refresh(activity) + dialog.dismiss() + }.onFailure { + showToast(activity, it.message ?: getString(R.string.app_font_invalid)) + } + } + } + binding.cancelBtt.setOnClickListener { + dialog.dismiss() + } + + dialog.setOnShowListener { + AppFontManager.applyToViewTree(binding.root) + } + dialog.show() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_ui) @@ -43,6 +176,7 @@ class SettingsUI : BasePreferenceFragmentCompat() { hideKeyboard() setPreferencesFromResource(R.xml.settings_ui, rootKey) val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) + updateAppFontSummary() (getPref(R.string.overscan_key)?.hideOn(PHONE or EMULATOR) as? SeekBarPreference)?.setOnPreferenceChangeListener { pref, newValue -> val padding = (newValue as? Int)?.toPx ?: return@setOnPreferenceChangeListener true @@ -121,6 +255,11 @@ class SettingsUI : BasePreferenceFragmentCompat() { return@setOnPreferenceClickListener true } + getPref(R.string.app_font_key)?.setOnPreferenceClickListener { + showAppFontDialog() + true + } + getPref(R.string.app_theme_key)?.setOnPreferenceClickListener { val prefNames = resources.getStringArray(R.array.themes_names).toMutableList() val prefValues = resources.getStringArray(R.array.themes_names_values).toMutableList() @@ -248,4 +387,4 @@ class SettingsUI : BasePreferenceFragmentCompat() { return@setOnPreferenceClickListener true } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppFontManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppFontManager.kt new file mode 100644 index 00000000000..b5f028296c2 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppFontManager.kt @@ -0,0 +1,358 @@ +package com.lagradost.cloudstream3.utils + +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.res.Configuration +import android.graphics.Typeface +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.activity.ComponentActivity +import androidx.core.content.edit +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.RecyclerView +import com.github.khoben.woff2android.Woff2Typeface +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.app +import java.io.File +import java.text.Normalizer +import java.util.Locale +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.launch + +object AppFontManager { + private const val cacheFolder = "app_fonts" + private val faceRegex = + Regex( + """/\*\s*([^*]+?)\s*\*/\s*@font-face\s*\{.*?src:\s*url\(([^)]+)\)""", + setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE) + ) + private val lock = Any() + private var cachedFont: Pair? = null + + private val recyclerListener = object : RecyclerView.OnChildAttachStateChangeListener { + override fun onChildViewAttachedToWindow(view: View) { + applyToViewTree(view) + } + + override fun onChildViewDetachedFromWindow(view: View) = Unit + } + + val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + if (activity is FragmentActivity) { + activity.supportFragmentManager.registerFragmentLifecycleCallbacks( + fragmentLifecycleCallbacks, + true + ) + } + if (activity is ComponentActivity) { + activity.lifecycleScope.launch { + warmUp(activity) + refresh(activity) + } + } else { + val decorView = activity.window?.decorView + decorView?.post { + applyToViewTree(decorView) + } + } + } + + override fun onActivityStarted(activity: Activity) = Unit + override fun onActivityResumed(activity: Activity) { + refresh(activity) + } + override fun onActivityPaused(activity: Activity) = Unit + override fun onActivityStopped(activity: Activity) = Unit + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit + override fun onActivityDestroyed(activity: Activity) = Unit + } + + private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentViewCreated( + fm: FragmentManager, + f: Fragment, + v: View, + savedInstanceState: Bundle? + ) { + v.post { + applyToViewTree(v) + } + } + } + + suspend fun setSelectedFont(context: Context, rawName: String?): Result { + val fontName = rawName?.trim()?.takeIf { it.isNotEmpty() } + return runCatching { + if (fontName == null) { + PreferenceManager.getDefaultSharedPreferences(context).edit { + remove(context.getString(R.string.app_font_key)) + } + synchronized(lock) { + cachedFont = null + } + null + } else { + val key = cacheKey(fontName, currentLocale(context.resources.configuration)) + val typeface = loadTypeface(context, fontName) + ?: throw IllegalArgumentException(context.getString(R.string.app_font_invalid)) + synchronized(lock) { + cachedFont = key to typeface + } + PreferenceManager.getDefaultSharedPreferences(context).edit { + putString(context.getString(R.string.app_font_key), fontName) + putString( + context.getString(R.string.app_font_recent_key), + updatedRecentFonts(context, fontName).joinToString("\n") + ) + } + fontName + } + } + } + + fun getSelectedFont(context: Context): String? { + return PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.app_font_key), null) + ?.trim() + ?.takeIf { it.isNotEmpty() } + } + + fun getRecentFonts(context: Context): List { + return PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.app_font_recent_key), null) + ?.split('\n') + ?.mapNotNull { it.trim().takeIf(String::isNotEmpty) } + ?: emptyList() + } + + fun getSuggestedFonts(context: Context): List { + return buildList { + addAll(getRecentFonts(context)) + addAll(context.resources.getStringArray(R.array.app_font_suggestions)) + }.distinctBy { it.lowercase(Locale.ROOT) } + } + + fun getSummary(context: Context): String { + return getSelectedFont(context) ?: context.getString(R.string.app_font_default) + } + + fun refresh(activity: Activity) { + val decorView = activity.window?.decorView + decorView?.post { + applyToViewTree(decorView) + } + } + + fun applyToViewTree(root: View) { + val baseTypeface = getCachedTypeface(root.context) ?: return + val selectedFont = getSelectedFont(root.context) ?: return + val key = cacheKey(selectedFont, currentLocale(root.context.resources.configuration)) + applyInternal(root, baseTypeface, key) + } + + fun getPreviewTypeface(context: Context, fontName: String): Typeface? { + val locale = currentLocale(context.resources.configuration) + val key = cacheKey(fontName, locale) + synchronized(lock) { + cachedFont?.takeIf { it.first == key }?.let { return it.second } + } + val file = getFontFile(context, fontName, preferredSubset(locale)) + if (!file.exists()) { + return null + } + return runCatching { createTypeface(file) }.getOrNull() + } + + private fun applyInternal(view: View, baseTypeface: Typeface, key: String) { + if (view.isSubtitleView()) { + return + } + + if (view is TextView) { + val currentKey = view.getTag(R.id.app_font_tag) as? String + if (currentKey != key) { + val style = view.typeface?.style ?: Typeface.NORMAL + view.typeface = Typeface.create(baseTypeface, style) + view.setTag(R.id.app_font_tag, key) + } + } + + if (view is RecyclerView && view.getTag(R.id.app_font_recycler_listener_tag) == null) { + view.addOnChildAttachStateChangeListener(recyclerListener) + view.setTag(R.id.app_font_recycler_listener_tag, recyclerListener) + } + + if (view is ViewGroup) { + for (index in 0 until view.childCount) { + applyInternal(view.getChildAt(index), baseTypeface, key) + } + } + } + + private suspend fun warmUp(context: Context) { + if (getSelectedFont(context) == null) { + return + } + loadTypeface(context, getSelectedFont(context)) + } + + private fun getCachedTypeface(context: Context): Typeface? { + val fontName = getSelectedFont(context) ?: return null + val key = cacheKey(fontName, currentLocale(context.resources.configuration)) + synchronized(lock) { + cachedFont?.takeIf { it.first == key }?.let { return it.second } + } + val file = getFontFile(context, fontName, preferredSubset(currentLocale(context.resources.configuration))) + if (!file.exists()) { + return null + } + return runCatching { + val typeface = createTypeface(file) + synchronized(lock) { + cachedFont = key to typeface + } + typeface + }.getOrNull() + } + + private suspend fun loadTypeface(context: Context, fontName: String?): Typeface? { + fontName ?: return null + getCachedTypeface(context)?.let { return it } + val locale = currentLocale(context.resources.configuration) + val subset = preferredSubset(locale) + val file = getFontFile(context, fontName, subset) + if (!file.exists()) { + val css = app.get( + "https://api.fonts.coollabs.io/css2?family=${encodeFamily(fontName)}&display=swap", + cacheTime = 7, + cacheUnit = TimeUnit.DAYS + ).text + val downloadUrl = resolveDownloadUrl(css, locale) ?: return null + file.parentFile?.mkdirs() + val fontBytes = app.get( + downloadUrl, + cacheTime = 30, + cacheUnit = TimeUnit.DAYS + ).okhttpResponse.body.bytes() + file.writeBytes(fontBytes) + } + return runCatching { + val typeface = createTypeface(file) + synchronized(lock) { + cachedFont = cacheKey(fontName, locale) to typeface + } + typeface + }.getOrNull() + } + + private fun resolveDownloadUrl(css: String, locale: Locale): String? { + val fontFaces = faceRegex.findAll(css).map { + it.groupValues[1].trim() to it.groupValues[2].trim().removePrefix("\"").removeSuffix("\"") + }.toList() + if (fontFaces.isEmpty()) { + return null + } + val preferred = preferredSubsets(locale) + for (subset in preferred) { + fontFaces.firstOrNull { it.first.equals(subset, true) }?.let { return it.second } + } + return fontFaces.firstOrNull()?.second + } + + private fun preferredSubsets(locale: Locale): List { + return buildList { + add(preferredSubset(locale)) + add("latin-ext") + add("latin") + }.distinct() + } + + private fun preferredSubset(locale: Locale): String { + val language = locale.language.lowercase(Locale.ROOT) + val script = locale.script.lowercase(Locale.ROOT) + return when { + language == "vi" -> "vietnamese" + language == "ja" -> "japanese" + language == "ko" -> "korean" + language == "zh" && locale.country.equals("TW", true) -> "chinese-traditional" + language == "zh" -> "chinese-simplified" + language == "he" || language == "iw" -> "hebrew" + language == "ar" || script == "arab" -> "arabic" + language == "hi" || script == "deva" -> "devanagari" + language == "th" -> "thai" + language in setOf("ru", "uk", "bg", "be", "mk", "sr", "kk", "ky", "mn") || script == "cyrl" -> "cyrillic" + language in setOf("el") || script == "grek" -> "greek" + else -> "latin" + } + } + + private fun createTypeface(file: File): Typeface { + return Woff2Typeface.get().createFromFile(file) + } + + private fun currentLocale(configuration: Configuration): Locale { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + configuration.locales[0] + } else { + @Suppress("DEPRECATION") + configuration.locale + } + } + + private fun updatedRecentFonts(context: Context, fontName: String): List { + val suggested = context.resources.getStringArray(R.array.app_font_suggestions) + val shouldStore = suggested.any { it.equals(fontName, true) } + if (!shouldStore) { + return getRecentFonts(context) + } + return buildList { + add(fontName) + addAll(getRecentFonts(context)) + }.distinctBy { it.lowercase(Locale.ROOT) }.take(8) + } + + private fun getFontFile(context: Context, fontName: String, subset: String): File { + return context.cacheDir.resolve(cacheFolder).resolve("${safeName(fontName)}-$subset.woff2") + } + + private fun safeName(value: String): String { + return Normalizer.normalize(value, Normalizer.Form.NFKD) + .replace(Regex("[^A-Za-z0-9]+"), "-") + .trim('-') + .lowercase(Locale.ROOT) + .ifEmpty { "font" } + } + + private fun cacheKey(fontName: String, locale: Locale): String { + return "${safeName(fontName)}-${preferredSubset(locale)}" + } + + private fun encodeFamily(fontName: String): String { + return Uri.encode(fontName.trim()) + } + + private fun View.isSubtitleView(): Boolean { + val subtitleClasses = setOf( + "androidx.media3.ui.SubtitleView", + "com.google.android.exoplayer2.ui.SubtitleView" + ) + var currentClass: Class<*>? = javaClass + while (currentClass != null) { + if (subtitleClasses.contains(currentClass.name)) { + return true + } + currentClass = currentClass.superclass + } + return false + } +} diff --git a/app/src/main/res/drawable/brand_family_24px.xml b/app/src/main/res/drawable/brand_family_24px.xml new file mode 100644 index 00000000000..b30c16d3f2a --- /dev/null +++ b/app/src/main/res/drawable/brand_family_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/app_font_dropdown_item.xml b/app/src/main/res/layout/app_font_dropdown_item.xml new file mode 100644 index 00000000000..1c606cbd2c2 --- /dev/null +++ b/app/src/main/res/layout/app_font_dropdown_item.xml @@ -0,0 +1,13 @@ + + diff --git a/app/src/main/res/layout/bottom_app_font_dialog.xml b/app/src/main/res/layout/bottom_app_font_dialog.xml new file mode 100644 index 00000000000..90677cd4c0f --- /dev/null +++ b/app/src/main/res/layout/bottom_app_font_dialog.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index d14dca768c5..e2cec05a103 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -229,6 +229,35 @@ 2 + + Roboto + Inter + Open Sans + Lato + Montserrat + Poppins + Nunito Sans + Noto Sans + Oswald + Playfair Display + Roboto Condensed + Source Sans 3 + Work Sans + Fira Sans + Rubik + Raleway + Quicksand + Manrope + DM Sans + PT Sans + Ubuntu + Nunito + Merriweather + Libre Baskerville + Cabin + Mukta + + @string/automatic HW+SW diff --git a/app/src/main/res/values/donottranslate-strings.xml b/app/src/main/res/values/donottranslate-strings.xml index 6a4c8271341..6cf24066119 100644 --- a/app/src/main/res/values/donottranslate-strings.xml +++ b/app/src/main/res/values/donottranslate-strings.xml @@ -65,6 +65,8 @@ show_logcat_key bottom_title_key poster_ui_key + app_font_key + app_font_recent_key overscan_key poster_size_key subtitles_encoding_key diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 00000000000..ce72cdad09a --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31cf951cf5f..af4565c9312 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -194,6 +194,15 @@ Show trailers Show posters from Kitsu Show cast panel + App font + Use a custom font across the app UI + Default + This font could not be loaded + App font + Suggested fonts + Custom font family + Custom font family + Select a suggestion or choose Custom and type any Google Fonts family from CoolLabs Hide selected video quality in search results Automatic plugin updates Automatically download plugins diff --git a/app/src/main/res/xml/settings_ui.xml b/app/src/main/res/xml/settings_ui.xml index 1b516ffa304..6191db3c437 100644 --- a/app/src/main/res/xml/settings_ui.xml +++ b/app/src/main/res/xml/settings_ui.xml @@ -15,6 +15,11 @@ android:icon="@drawable/ic_baseline_tv_24" android:key="@string/app_layout_key" android:title="@string/app_layout" /> + - \ No newline at end of file + diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 00000000000..6c1139ec06a --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,12 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73bcfb608d1fde9fb62e462f834a3299/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/846ee0d876d26a26f37aa1ce8de73224/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9482ddec596298c84656d31d16652665/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/39701d92e1756bb2f141eb67cd4c660e/redirect +toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a97145c3f81..e0c6d1790bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,6 +47,7 @@ tmdbJava = "2.13.0" torrentserver = "7861970" tvprovider = "1.1.0" video = "1.0.0" +woff2Android = "0.0.2" workRuntimeKtx = "2.11.2" zipline = "1.27.0" @@ -114,6 +115,7 @@ tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJa torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } video = { module = "com.google.android.mediahome:video", version.ref = "video" } +woff2-android = { module = "io.github.khoben.woff2-android:typeface", version.ref = "woff2Android" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } zipline = { module = "app.cash.zipline:zipline-android", version.ref = "zipline" }