From 01336738240f5c965533e2d19ba13e266369702a Mon Sep 17 00:00:00 2001 From: Naveen Singh Date: Sat, 20 Dec 2025 22:46:59 +0530 Subject: [PATCH 1/7] feat: add ability to use custom fonts in all apps Refs: https://github.com/FossifyOrg/General-Discussion/issues/323 --- .../commons/activities/BaseSimpleActivity.kt | 25 ++++ .../activities/CustomizationActivity.kt | 133 ++++++++++++++++++ .../components/LinkifyTextComponent.kt | 3 + .../fossify/commons/compose/theme/Theme.kt | 47 +++++++ .../fossify/commons/extensions/Activity.kt | 37 ++++- .../commons/extensions/Context-styling.kt | 26 +++- .../org/fossify/commons/extensions/Context.kt | 19 +++ .../org/fossify/commons/extensions/Cursor.kt | 5 + .../org/fossify/commons/extensions/String.kt | 2 + .../org/fossify/commons/helpers/BaseConfig.kt | 9 ++ .../org/fossify/commons/helpers/Constants.kt | 7 + .../org/fossify/commons/helpers/FontHelper.kt | 88 ++++++++++++ .../commons/helpers/MyContentProvider.kt | 10 +- .../fossify/commons/models/GlobalConfig.kt | 42 +++++- .../fossify/commons/views/MyAppBarLayout.kt | 9 ++ .../commons/views/MyAppCompatCheckbox.kt | 17 ++- .../org/fossify/commons/views/MyButton.kt | 17 ++- .../commons/views/MyCompatRadioButton.kt | 17 ++- .../org/fossify/commons/views/MyEditText.kt | 17 ++- .../fossify/commons/views/MyMaterialSwitch.kt | 14 +- .../commons/views/MyTextInputLayout.kt | 19 ++- .../org/fossify/commons/views/MyTextView.kt | 17 ++- .../res/layout/activity_customization.xml | 41 ++++++ commons/src/main/res/values/strings.xml | 17 ++- commons/src/main/res/values/styles.xml | 34 +++-- 25 files changed, 618 insertions(+), 54 deletions(-) create mode 100644 commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/BaseSimpleActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/BaseSimpleActivity.kt index 34eb2722b9..b9f640ec42 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/BaseSimpleActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/BaseSimpleActivity.kt @@ -18,11 +18,14 @@ import android.provider.DocumentsContract import android.provider.MediaStore import android.provider.Settings import android.telecom.TelecomManager +import android.util.AttributeSet +import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.EditText import android.widget.ImageView +import android.widget.TextView import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.activity.addCallback @@ -30,6 +33,7 @@ import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import androidx.core.net.toUri import androidx.core.util.Pair +import androidx.core.view.LayoutInflaterCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.get @@ -44,6 +48,7 @@ import org.fossify.commons.dialogs.WritePermissionDialog import org.fossify.commons.dialogs.WritePermissionDialog.WritePermissionDialogMode import org.fossify.commons.extensions.adjustAlpha import org.fossify.commons.extensions.applyColorFilter +import org.fossify.commons.extensions.applyFontToTextView import org.fossify.commons.extensions.baseConfig import org.fossify.commons.extensions.buildDocumentUriSdk30 import org.fossify.commons.extensions.canManageMedia @@ -183,6 +188,7 @@ abstract class BaseSimpleActivity : EdgeToEdgeActivity() { setTheme(getThemeId(showTransparentTop = true)) } + installFontInflaterFactory() super.onCreate(savedInstanceState) WindowCompat.enableEdgeToEdge(window) registerBackPressedCallback() @@ -194,6 +200,25 @@ abstract class BaseSimpleActivity : EdgeToEdgeActivity() { } } + private fun installFontInflaterFactory() { + val inflater = layoutInflater + if (inflater.factory2 != null) return + + val appCompatDelegate = delegate + LayoutInflaterCompat.setFactory2(inflater, object : LayoutInflater.Factory2 { + override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? { + val view = appCompatDelegate.createView(parent, name, context, attrs) + val textView = view as? TextView ?: return view + applyFontToTextView(textView) + return view + } + + override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { + return onCreateView(null, name, context, attrs) + } + }) + } + override fun onResume() { super.onResume() if (useDynamicTheme) { diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt index cfffccba9b..79b45faa9d 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt @@ -2,7 +2,10 @@ package org.fossify.commons.activities import android.content.ContentValues import android.graphics.Color +import android.graphics.Typeface +import android.net.Uri import android.os.Bundle +import androidx.activity.result.contract.ActivityResultContracts import org.fossify.commons.R import org.fossify.commons.databinding.ActivityCustomizationBinding import org.fossify.commons.dialogs.ColorPickerDialog @@ -11,16 +14,19 @@ import org.fossify.commons.dialogs.ConfirmationDialog import org.fossify.commons.dialogs.LineColorPickerDialog import org.fossify.commons.dialogs.PurchaseThankYouDialog import org.fossify.commons.dialogs.RadioGroupDialog +import org.fossify.commons.extensions.applyFontToViewRecursively import org.fossify.commons.extensions.baseConfig import org.fossify.commons.extensions.beVisibleIf import org.fossify.commons.extensions.canAccessGlobalConfig import org.fossify.commons.extensions.checkAppIconColor import org.fossify.commons.extensions.getColoredMaterialStatusBarColor import org.fossify.commons.extensions.getContrastColor +import org.fossify.commons.extensions.getFilenameFromUri import org.fossify.commons.extensions.getProperPrimaryColor import org.fossify.commons.extensions.getProperTextColor import org.fossify.commons.extensions.getThemeId import org.fossify.commons.extensions.isDynamicTheme +import org.fossify.commons.extensions.isFontFile import org.fossify.commons.extensions.isSystemInDarkMode import org.fossify.commons.extensions.isThankYouInstalled import org.fossify.commons.extensions.setFillWithStroke @@ -32,9 +38,16 @@ import org.fossify.commons.extensions.withGlobalConfig import org.fossify.commons.helpers.APP_ICON_IDS import org.fossify.commons.helpers.APP_LAUNCHER_NAME import org.fossify.commons.helpers.DARK_GREY +import org.fossify.commons.helpers.FONT_TYPE_CUSTOM +import org.fossify.commons.helpers.FONT_TYPE_MONOSPACE +import org.fossify.commons.helpers.FONT_TYPE_SYSTEM_DEFAULT +import org.fossify.commons.helpers.FontHelper import org.fossify.commons.helpers.MyContentProvider.COL_ACCENT_COLOR import org.fossify.commons.helpers.MyContentProvider.COL_APP_ICON_COLOR import org.fossify.commons.helpers.MyContentProvider.COL_BACKGROUND_COLOR +import org.fossify.commons.helpers.MyContentProvider.COL_FONT_DATA +import org.fossify.commons.helpers.MyContentProvider.COL_FONT_NAME +import org.fossify.commons.helpers.MyContentProvider.COL_FONT_TYPE import org.fossify.commons.helpers.MyContentProvider.COL_PRIMARY_COLOR import org.fossify.commons.helpers.MyContentProvider.COL_TEXT_COLOR import org.fossify.commons.helpers.MyContentProvider.COL_THEME_TYPE @@ -48,6 +61,7 @@ import org.fossify.commons.models.GlobalConfig import org.fossify.commons.models.MyTheme import org.fossify.commons.models.RadioItem import org.fossify.commons.models.isGlobalThemingEnabled +import java.io.File import kotlin.math.abs class CustomizationActivity : BaseSimpleActivity() { @@ -69,12 +83,19 @@ class CustomizationActivity : BaseSimpleActivity() { private var curAppIconColor = 0 private var curSelectedThemeId = 0 private var originalAppIconColor = 0 + private var curFontType = 0 + private var curFontFileName = "" private var lastSavePromptTS = 0L private var hasUnsavedChanges = false private val predefinedThemes = LinkedHashMap() private var curPrimaryLineColorPicker: LineColorPickerDialog? = null private var globalConfig: GlobalConfig? = null + private val fontFilePicker = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + uri?.let { handleFontFileSelected(it) } + } + override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList() override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: "" @@ -437,12 +458,16 @@ class CustomizationActivity : BaseSimpleActivity() { primaryColor = curPrimaryColor accentColor = curAccentColor appIconColor = curAppIconColor + fontType = curFontType + fontName = curFontFileName } if (didAppIconColorChange) { checkAppIconColor() } + FontHelper.clearCache() + baseConfig.isGlobalThemeEnabled = binding.applyToAllSwitch.isChecked baseConfig.isSystemThemeEnabled = curSelectedThemeId == THEME_SYSTEM @@ -461,6 +486,13 @@ class CustomizationActivity : BaseSimpleActivity() { put(COL_PRIMARY_COLOR, curPrimaryColor) put(COL_ACCENT_COLOR, curAccentColor) put(COL_APP_ICON_COLOR, curAppIconColor) + put(COL_FONT_TYPE, curFontType) + put(COL_FONT_NAME, curFontFileName) + if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty()) { + FontHelper.getFontData(this@CustomizationActivity, curFontFileName)?.let { + put(COL_FONT_DATA, it) + } + } } ) } @@ -490,6 +522,8 @@ class CustomizationActivity : BaseSimpleActivity() { curPrimaryColor = baseConfig.primaryColor curAccentColor = baseConfig.accentColor curAppIconColor = baseConfig.appIconColor + curFontType = baseConfig.fontType + curFontFileName = baseConfig.fontName } private fun setupColorsPickers() { @@ -526,6 +560,8 @@ class CustomizationActivity : BaseSimpleActivity() { } } } + + setupFontPicker() } private fun hasColorChanged(old: Int, new: Int) = abs(old - new) > 1 @@ -536,6 +572,100 @@ class CustomizationActivity : BaseSimpleActivity() { refreshMenuItems() } + private fun setupFontPicker() { + updateFontDisplay() + binding.customizationFontHolder.setOnClickListener { + fontPickerClicked() + } + } + + private fun updateFontDisplay() { + binding.customizationFont.text = when (curFontType) { + FONT_TYPE_MONOSPACE -> getString(R.string.font_monospace) + FONT_TYPE_CUSTOM -> curFontFileName.ifEmpty { getString(R.string.select_font_file) } + else -> getString(R.string.font_system_default) + } + } + + private fun fontPickerClicked() { + val items = arrayListOf( + RadioItem(FONT_TYPE_SYSTEM_DEFAULT, getString(R.string.font_system_default)), + RadioItem(FONT_TYPE_MONOSPACE, getString(R.string.font_monospace)), + RadioItem(FONT_TYPE_CUSTOM, getString(R.string.select_font_file)) + ) + + RadioGroupDialog(this, items, curFontType) { selected -> + val selectedType = selected as Int + if (selectedType == FONT_TYPE_CUSTOM) { + openFontFilePicker() + } else { + curFontType = selectedType + curFontFileName = "" + fontChanged() + } + } + } + + private fun openFontFilePicker() { + try { + fontFilePicker.launch( + arrayOf( + "font/ttf", + "font/otf", + "application/x-font-ttf", + "application/x-font-otf", + "*/*" + ) + ) + } catch (e: Exception) { + toast(R.string.system_service_disabled) + } + } + + private fun handleFontFileSelected(uri: Uri) { + try { + val fileName = getFilenameFromUri(uri) + if (fileName.isEmpty() || !fileName.isFontFile()) { + toast(R.string.invalid_font_file) + return + } + + val fontData = contentResolver.openInputStream(uri)?.use { it.readBytes() } + if (fontData == null) { + toast(R.string.invalid_font_file) + return + } + + val tempFile = File(cacheDir, fileName) + tempFile.writeBytes(fontData) + try { + Typeface.createFromFile(tempFile) + } catch (_: Exception) { + tempFile.delete() + toast(R.string.invalid_font_file) + return + } + tempFile.delete() + + if (FontHelper.saveFontData(this, fontData, fileName)) { + curFontType = FONT_TYPE_CUSTOM + curFontFileName = fileName + fontChanged() + } else { + toast(R.string.invalid_font_file) + } + } catch (_: Exception) { + toast(R.string.invalid_font_file) + } + } + + private fun fontChanged() { + hasUnsavedChanges = true + updateFontDisplay() + applyFontToViewRecursively(window.decorView) + refreshMenuItems() + } + private fun setCurrentTextColor(color: Int) { curTextColor = color updateLabelColors(color) @@ -707,6 +837,8 @@ class CustomizationActivity : BaseSimpleActivity() { binding.customizationPrimaryColorLabel, binding.customizationAccentColorLabel, binding.customizationAppIconColorLabel, + binding.customizationFontLabel, + binding.customizationFont, binding.applyToAllLabel, binding.applyToAllNote ).forEach { @@ -717,6 +849,7 @@ class CustomizationActivity : BaseSimpleActivity() { private fun updateHeaderColors(primaryColor: Int = getProperPrimaryColor()) { arrayListOf( binding.settingsThemeAndColorsLabel, + binding.settingsFontLabel, binding.settingsAllFossifyAppsLabel ).forEach { it.setTextColor(primaryColor) diff --git a/commons/src/main/kotlin/org/fossify/commons/compose/components/LinkifyTextComponent.kt b/commons/src/main/kotlin/org/fossify/commons/compose/components/LinkifyTextComponent.kt index 824b1d710e..2f6f15f3d2 100644 --- a/commons/src/main/kotlin/org/fossify/commons/compose/components/LinkifyTextComponent.kt +++ b/commons/src/main/kotlin/org/fossify/commons/compose/components/LinkifyTextComponent.kt @@ -16,6 +16,7 @@ import org.fossify.commons.R import org.fossify.commons.compose.extensions.MyDevices import org.fossify.commons.compose.theme.AppThemeSurface import org.fossify.commons.compose.theme.SimpleTheme +import org.fossify.commons.extensions.applyFontToTextView import org.fossify.commons.extensions.fromHtml import org.fossify.commons.extensions.removeUnderlines @@ -40,6 +41,8 @@ fun LinkifyTextComponent( textView.textAlignment = textAlignment textView.textSize = fontSize.value textView.movementMethod = LinkMovementMethod.getInstance() + context.applyFontToTextView(textView) + if (removeUnderlines) { customLinkifyTextView.removeUnderlines() } diff --git a/commons/src/main/kotlin/org/fossify/commons/compose/theme/Theme.kt b/commons/src/main/kotlin/org/fossify/commons/compose/theme/Theme.kt index 849f7492a1..fba0e913c9 100644 --- a/commons/src/main/kotlin/org/fossify/commons/compose/theme/Theme.kt +++ b/commons/src/main/kotlin/org/fossify/commons/compose/theme/Theme.kt @@ -1,5 +1,6 @@ package org.fossify.commons.compose.theme +import android.content.Context import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.* import androidx.compose.runtime.* @@ -7,10 +8,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.Typeface import org.fossify.commons.compose.extensions.config import org.fossify.commons.compose.theme.model.Theme import org.fossify.commons.compose.theme.model.Theme.Companion.systemDefaultMaterialYou import org.fossify.commons.extensions.getContrastColor +import org.fossify.commons.helpers.FONT_TYPE_CUSTOM +import org.fossify.commons.helpers.FONT_TYPE_MONOSPACE +import org.fossify.commons.helpers.FontHelper import org.fossify.commons.helpers.isSPlus @Composable @@ -84,9 +90,16 @@ internal fun Theme( } val dimensions = CommonDimensions + + val customTypography = if (!view.isInEditMode) { + getCustomTypography(context) + } else { + Typography() + } MaterialTheme( colorScheme = colorScheme, + typography = customTypography, shapes = Shapes, content = { CompositionLocalProvider( @@ -100,6 +113,40 @@ internal fun Theme( ) } +@Composable +private fun getCustomTypography(context: Context): Typography { + val baseConfig = context.config + val fontType = baseConfig.fontType + + val fontFamily = when (fontType) { + FONT_TYPE_MONOSPACE -> FontFamily.Monospace + FONT_TYPE_CUSTOM -> { + val typeface = FontHelper.getTypeface(context) + FontFamily(Typeface(typeface)) + } + else -> FontFamily.Default + } + + val defaultTypography = Typography() + return Typography( + displayLarge = defaultTypography.displayLarge.copy(fontFamily = fontFamily), + displayMedium = defaultTypography.displayMedium.copy(fontFamily = fontFamily), + displaySmall = defaultTypography.displaySmall.copy(fontFamily = fontFamily), + headlineLarge = defaultTypography.headlineLarge.copy(fontFamily = fontFamily), + headlineMedium = defaultTypography.headlineMedium.copy(fontFamily = fontFamily), + headlineSmall = defaultTypography.headlineSmall.copy(fontFamily = fontFamily), + titleLarge = defaultTypography.titleLarge.copy(fontFamily = fontFamily), + titleMedium = defaultTypography.titleMedium.copy(fontFamily = fontFamily), + titleSmall = defaultTypography.titleSmall.copy(fontFamily = fontFamily), + bodyLarge = defaultTypography.bodyLarge.copy(fontFamily = fontFamily), + bodyMedium = defaultTypography.bodyMedium.copy(fontFamily = fontFamily), + bodySmall = defaultTypography.bodySmall.copy(fontFamily = fontFamily), + labelLarge = defaultTypography.labelLarge.copy(fontFamily = fontFamily), + labelMedium = defaultTypography.labelMedium.copy(fontFamily = fontFamily), + labelSmall = defaultTypography.labelSmall.copy(fontFamily = fontFamily), + ) +} + val LocalTheme: ProvidableCompositionLocal = staticCompositionLocalOf { Theme.Custom(1, 1, 1, 1) } diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt index e195fb6265..e1385b772b 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Activity.kt @@ -37,6 +37,7 @@ import androidx.biometric.BiometricPrompt import androidx.biometric.auth.AuthPromptCallback import androidx.biometric.auth.AuthPromptHost import androidx.biometric.auth.Class2BiometricAuthPrompt +import androidx.core.net.toUri import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.FragmentActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -90,7 +91,6 @@ import java.io.FileOutputStream import java.io.IOException import java.io.OutputStream import java.util.TreeSet -import androidx.core.net.toUri fun Activity.appLaunched(appId: String) { baseConfig.internalStoragePath = getInternalStoragePath() @@ -1557,9 +1557,20 @@ fun Activity.setupDialogStuff( if (!isFinishing) { show() } - getButton(Dialog.BUTTON_POSITIVE)?.setTextColor(primaryColor) - getButton(Dialog.BUTTON_NEGATIVE)?.setTextColor(primaryColor) - getButton(Dialog.BUTTON_NEUTRAL)?.setTextColor(primaryColor) + getButton(Dialog.BUTTON_POSITIVE)?.apply { + setTextColor(primaryColor) + applyFontToTextView(this) + } + getButton(Dialog.BUTTON_NEGATIVE)?.apply { + setTextColor(primaryColor) + applyFontToTextView(this) + } + getButton(Dialog.BUTTON_NEUTRAL)?.apply { + setTextColor(primaryColor) + applyFontToTextView(this) + } + + applyFontToViewRecursively(view) callback?.invoke(this) } } else { @@ -1573,6 +1584,7 @@ fun Activity.setupDialogStuff( setText(titleId) } setTextColor(textColor) + applyFontToTextView(this) } } @@ -1591,9 +1603,20 @@ fun Activity.setupDialogStuff( if (!isFinishing) { show() } - getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(dialogButtonColor) - getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(dialogButtonColor) - getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(dialogButtonColor) + getButton(AlertDialog.BUTTON_POSITIVE)?.apply { + setTextColor(dialogButtonColor) + applyFontToTextView(this) + } + getButton(AlertDialog.BUTTON_NEGATIVE)?.apply { + setTextColor(dialogButtonColor) + applyFontToTextView(this) + } + getButton(AlertDialog.BUTTON_NEUTRAL)?.apply { + setTextColor(dialogButtonColor) + applyFontToTextView(this) + } + + applyFontToViewRecursively(view) val bgDrawable = when { isBlackAndWhiteTheme() -> resources.getDrawable(R.drawable.black_dialog_background, theme) diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt index 28c9ac3283..2208af2fbb 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt @@ -105,7 +105,11 @@ fun Context.getDatePickerDialogTheme() = when { fun Context.getPopupMenuTheme(): Int { return if (isDynamicTheme()) { - R.style.AppTheme_YouPopupMenuStyle + if (isSystemInDarkMode()) { + R.style.AppTheme_YouPopupMenuStyle + } else { + R.style.AppTheme_YouPopupMenuStyle_Light + } } else if (isWhiteTheme()) { R.style.AppTheme_PopupMenuLightStyle } else { @@ -127,6 +131,19 @@ fun Context.syncGlobalConfig(callback: (() -> Unit)? = null) { primaryColor = it.primaryColor accentColor = it.accentColor + if (it.fontType >= 0 && (fontType != it.fontType || fontName != it.fontName)) { + fontType = it.fontType + fontName = it.fontName + if (it.fontType == FONT_TYPE_CUSTOM && it.fontData != null) { + FontHelper.saveFontData( + context = this@syncGlobalConfig, + fontData = it.fontData, + fileName = it.fontName + ) + } + FontHelper.clearCache() + } + if (baseConfig.appIconColor != it.appIconColor) { baseConfig.appIconColor = it.appIconColor checkAppIconColor() @@ -168,9 +185,12 @@ fun Context.getGlobalConfig(cursorLoader: CursorLoader): GlobalConfig? { accentColor = cursor.getIntValue(MyContentProvider.COL_ACCENT_COLOR), appIconColor = cursor.getIntValue(MyContentProvider.COL_APP_ICON_COLOR), showCheckmarksOnSwitches = cursor.getIntValue(MyContentProvider.COL_SHOW_CHECKMARKS_ON_SWITCHES) != 0, - lastUpdatedTS = cursor.getIntValue(MyContentProvider.COL_LAST_UPDATED_TS) + lastUpdatedTS = cursor.getIntValue(MyContentProvider.COL_LAST_UPDATED_TS), + fontType = cursor.getIntValueOr(MyContentProvider.COL_FONT_TYPE, -1), + fontName = cursor.getStringValueOr(MyContentProvider.COL_FONT_NAME, ""), + fontData = cursor.getBlobValueOrNull(MyContentProvider.COL_FONT_DATA) ) - } catch (e: Exception) { + } catch (_: Exception) { } } } diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt index 61ad911ad8..7388dd97e6 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt @@ -20,6 +20,7 @@ import android.content.res.Configuration import android.database.Cursor import android.graphics.BitmapFactory import android.graphics.Point +import android.graphics.Typeface import android.media.MediaMetadataRetriever import android.media.RingtoneManager import android.net.Uri @@ -43,6 +44,7 @@ import android.provider.Settings import android.telecom.TelecomManager import android.telephony.PhoneNumberUtils import android.view.View +import android.view.ViewGroup import android.view.WindowManager import android.widget.ImageView import android.widget.TextView @@ -130,6 +132,7 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import androidx.core.net.toUri +import org.fossify.commons.helpers.FontHelper import kotlin.math.roundToInt fun Context.getSharedPrefs() = getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE) @@ -1351,3 +1354,19 @@ fun Context.formatWithDeprecatedBadge( @StringRes labelRes: Int, vararg labelArgs: Any ): CharSequence = formatWithBadge(labelRes, R.string.badge_deprecated, *labelArgs) + +fun Context.applyFontToTextView(textView: TextView, typeface: Typeface = FontHelper.getTypeface(this)) { + if (typeface == Typeface.DEFAULT) return + val existingStyle = textView.typeface?.style ?: Typeface.NORMAL + textView.setTypeface(typeface, existingStyle) +} + +fun Context.applyFontToViewRecursively(view: View?, typeface: Typeface = FontHelper.getTypeface(this)) { + if (view == null) return + if (view is TextView) applyFontToTextView(view, typeface) + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + applyFontToViewRecursively(view.getChildAt(i), typeface) + } + } +} diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt index 81c47d6a44..44346f9316 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt @@ -22,6 +22,11 @@ fun Cursor.getLongValueOrNull(key: String): Long? { fun Cursor.getBlobValue(key: String) = getBlob(getColumnIndexOrThrow(key)) +fun Cursor.getBlobValueOrNull(key: String): ByteArray? { + val index = getColumnIndex(key) + return if (index != -1 && !isNull(index)) getBlob(index) else null +} + fun Cursor.getStringValueOr(key: String, defaultValue: String): String { val index = getColumnIndex(key) return if (index != -1 && !isNull(index)) getString(index) else defaultValue diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/String.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/String.kt index 4f0424c199..33d5843b0b 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/String.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/String.kt @@ -114,6 +114,8 @@ fun String.isImageSlow() = isImageFast() || getMimeType().startsWith("image") || fun String.isVideoSlow() = isVideoFast() || getMimeType().startsWith("video") || startsWith(MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString()) fun String.isAudioSlow() = isAudioFast() || getMimeType().startsWith("audio") || startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString()) +fun String.isFontFile() = endsWith(".ttf", true) || endsWith(".otf", true) + fun String.canModifyEXIF() = extensionsSupportingEXIF.any { endsWith(it, true) } fun String.getCompressionFormat() = when (getFilenameExtension().lowercase(Locale.getDefault())) { diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/BaseConfig.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/BaseConfig.kt index 2c3d50a572..66dfae5d42 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/BaseConfig.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/BaseConfig.kt @@ -608,6 +608,15 @@ open class BaseConfig(val context: Context) { var showCheckmarksOnSwitchesFlow = ::showCheckmarksOnSwitches.asFlowNonNull() + // Font customization + var fontType: Int + get() = prefs.getInt(CUSTOM_FONT_TYPE, FONT_TYPE_SYSTEM_DEFAULT) + set(customFontType) = prefs.edit().putInt(CUSTOM_FONT_TYPE, customFontType).apply() + + var fontName: String + get() = prefs.getString(CUSTOM_FONT_FILE_NAME, "") ?: "" + set(customFontFileName) = prefs.edit().putString(CUSTOM_FONT_FILE_NAME, customFontFileName).apply() + protected fun KProperty0.asFlow(emitOnCollect: Boolean = false): Flow = prefs.run { sharedPreferencesCallback(sendOnCollect = emitOnCollect) { this@asFlow.get() } } diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt index e2816b4cb9..0813dd5715 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt @@ -201,6 +201,8 @@ const val LAST_UNLOCK_TIMESTAMP_MS = "last_unlock_timestamp_ms" const val UNLOCK_TIMEOUT_DURATION_MS = "unlock_timeout_duration_ms" const val SHOW_CHECKMARKS_ON_SWITCHES = "show_checkmarks_on_switches" const val FIRST_DAY_OF_WEEK = "first_day_of_week" +const val CUSTOM_FONT_TYPE = "custom_font_type" +const val CUSTOM_FONT_FILE_NAME = "custom_font_file_name" const val MAX_PASSWORD_RETRY_COUNT = 3 const val DEFAULT_PASSWORD_COUNTDOWN = 5 @@ -356,6 +358,11 @@ const val FONT_SIZE_MEDIUM = 1 const val FONT_SIZE_LARGE = 2 const val FONT_SIZE_EXTRA_LARGE = 3 +// font types +const val FONT_TYPE_SYSTEM_DEFAULT = 0 +const val FONT_TYPE_MONOSPACE = 1 +const val FONT_TYPE_CUSTOM = 2 + const val MONDAY_BIT = 1 const val TUESDAY_BIT = 2 const val WEDNESDAY_BIT = 4 diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt new file mode 100644 index 0000000000..9f93d9d870 --- /dev/null +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt @@ -0,0 +1,88 @@ +package org.fossify.commons.helpers + +import android.content.Context +import android.graphics.Typeface +import org.fossify.commons.extensions.baseConfig +import java.io.File + +/** + * Helper for loading and caching custom fonts. + */ +object FontHelper { + private var cachedTypeface: Typeface? = null + private var cachedFontType: Int = -1 + private var cachedFontFileName: String = "" + + fun getTypeface(context: Context): Typeface { + val config = context.baseConfig + val fontType = config.fontType + val fontFileName = config.fontName + + if (fontType == cachedFontType && fontFileName == cachedFontFileName && cachedTypeface != null) { + return cachedTypeface!! + } + + cachedFontType = fontType + cachedFontFileName = fontFileName + cachedTypeface = when (fontType) { + FONT_TYPE_MONOSPACE -> Typeface.MONOSPACE + FONT_TYPE_CUSTOM -> loadCustomFont(context, fontFileName) + else -> Typeface.DEFAULT + } + return cachedTypeface!! + } + + private fun loadCustomFont(context: Context, fileName: String): Typeface { + if (fileName.isEmpty()) return Typeface.DEFAULT + return try { + val fontFile = File(getFontsDir(context), fileName) + if (fontFile.exists()) Typeface.createFromFile(fontFile) else Typeface.DEFAULT + } catch (_: Exception) { + Typeface.DEFAULT + } + } + + fun clearCache() { + cachedTypeface = null + cachedFontType = -1 + cachedFontFileName = "" + } + + fun getFontsDir(context: Context): File { + return File(context.filesDir, "fonts").apply { + if (!exists()) mkdirs() + } + } + + fun saveFontData(context: Context, fontData: ByteArray, fileName: String): Boolean { + return try { + val fontFile = File(getFontsDir(context), fileName) + fontFile.writeBytes(fontData) + true + } catch (_: Exception) { + false + } + } + + fun getFontData(context: Context, fileName: String): ByteArray? { + if (fileName.isEmpty()) { + return null + } + + return try { + val fontFile = File(getFontsDir(context), fileName) + if (fontFile.exists()) fontFile.readBytes() else null + } catch (_: Exception) { + null + } + } + + fun deleteFont(context: Context, fileName: String): Boolean { + return try { + val fontFile = File(getFontsDir(context), fileName) + if (fontFile.exists()) fontFile.delete() else true + } catch (_: Exception) { + false + } + } +} diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt index 79228c9540..9e5a1bade0 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt @@ -1,15 +1,18 @@ package org.fossify.commons.helpers import android.net.Uri +import androidx.core.net.toUri object MyContentProvider { private const val AUTHORITY = "org.fossify.android.provider" - val MY_CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/settings") + val MY_CONTENT_URI: Uri = "content://$AUTHORITY/settings".toUri() const val ACTION_GLOBAL_CONFIG_UPDATED = "org.fossify.android.GLOBAL_CONFIG_UPDATED" const val PERMISSION_WRITE_GLOBAL_SETTINGS = "org.fossify.android.permission.WRITE_GLOBAL_SETTINGS" const val COL_ID = "_id" // used in Fossify Thank You + + // Color customization const val COL_THEME_TYPE = "theme_type" const val COL_TEXT_COLOR = "text_color" const val COL_BACKGROUND_COLOR = "background_color" @@ -19,6 +22,11 @@ object MyContentProvider { const val COL_SHOW_CHECKMARKS_ON_SWITCHES = "show_checkmarks_on_switches" const val COL_LAST_UPDATED_TS = "last_updated_ts" + // Font customization + const val COL_FONT_TYPE = "font_type" + const val COL_FONT_NAME = "font_name" + const val COL_FONT_DATA = "font_data" + const val GLOBAL_THEME_DISABLED = 0 const val GLOBAL_THEME_SYSTEM = 1 const val GLOBAL_THEME_CUSTOM = 2 diff --git a/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt b/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt index dded20b152..c4b1e500f7 100644 --- a/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt +++ b/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt @@ -1,5 +1,6 @@ package org.fossify.commons.models +import org.fossify.commons.helpers.FONT_TYPE_SYSTEM_DEFAULT import org.fossify.commons.helpers.MyContentProvider data class GlobalConfig( @@ -11,7 +12,46 @@ data class GlobalConfig( val appIconColor: Int, val showCheckmarksOnSwitches: Boolean, val lastUpdatedTS: Int = 0, -) + val fontType: Int = FONT_TYPE_SYSTEM_DEFAULT, + val fontName: String = "", + val fontData: ByteArray? = null, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GlobalConfig + + if (themeType != other.themeType) return false + if (textColor != other.textColor) return false + if (backgroundColor != other.backgroundColor) return false + if (primaryColor != other.primaryColor) return false + if (accentColor != other.accentColor) return false + if (appIconColor != other.appIconColor) return false + if (showCheckmarksOnSwitches != other.showCheckmarksOnSwitches) return false + if (lastUpdatedTS != other.lastUpdatedTS) return false + if (fontType != other.fontType) return false + if (fontName != other.fontName) return false + if (!fontData.contentEquals(other.fontData)) return false + + return true + } + + override fun hashCode(): Int { + var result = themeType + result = 31 * result + textColor + result = 31 * result + backgroundColor + result = 31 * result + primaryColor + result = 31 * result + accentColor + result = 31 * result + appIconColor + result = 31 * result + showCheckmarksOnSwitches.hashCode() + result = 31 * result + lastUpdatedTS + result = 31 * result + fontType + result = 31 * result + fontName.hashCode() + result = 31 * result + (fontData?.contentHashCode() ?: 0) + return result + } +} fun GlobalConfig?.isGlobalThemingEnabled(): Boolean { return this != null && themeType != MyContentProvider.GLOBAL_THEME_DISABLED diff --git a/commons/src/main/kotlin/org/fossify/commons/views/MyAppBarLayout.kt b/commons/src/main/kotlin/org/fossify/commons/views/MyAppBarLayout.kt index a9664b915b..8315233234 100644 --- a/commons/src/main/kotlin/org/fossify/commons/views/MyAppBarLayout.kt +++ b/commons/src/main/kotlin/org/fossify/commons/views/MyAppBarLayout.kt @@ -9,6 +9,7 @@ import androidx.core.view.WindowInsetsCompat.Type import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.MaterialToolbar import org.fossify.commons.R +import org.fossify.commons.extensions.applyFontToViewRecursively import org.fossify.commons.extensions.updatePaddingWithBase open class MyAppBarLayout @JvmOverloads constructor( @@ -58,6 +59,7 @@ open class MyAppBarLayout @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() ViewCompat.requestApplyInsets(this) + applyFontToToolbar() } override fun onViewAdded(child: View) { @@ -83,4 +85,11 @@ open class MyAppBarLayout @JvmOverloads constructor( fun requireToolbar(): MaterialToolbar = toolbar ?: error("MyAppBarLayout requires a Toolbar/MaterialToolbar child") + + fun applyFontToToolbar() { + if (isInEditMode) return + if (toolbar != null) { + context.applyFontToViewRecursively(toolbar) + } + } } diff --git a/commons/src/main/kotlin/org/fossify/commons/views/MyAppCompatCheckbox.kt b/commons/src/main/kotlin/org/fossify/commons/views/MyAppCompatCheckbox.kt index de1622cac9..a5255e15bb 100644 --- a/commons/src/main/kotlin/org/fossify/commons/views/MyAppCompatCheckbox.kt +++ b/commons/src/main/kotlin/org/fossify/commons/views/MyAppCompatCheckbox.kt @@ -5,13 +5,24 @@ import android.content.res.ColorStateList import android.util.AttributeSet import androidx.appcompat.widget.AppCompatCheckBox import org.fossify.commons.extensions.adjustAlpha +import org.fossify.commons.extensions.applyFontToTextView open class MyAppCompatCheckbox : AppCompatCheckBox { - constructor(context: Context) : super(context) + constructor(context: Context) : super(context) { + init() + } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + init() + } + + private fun init() { + if (!isInEditMode) context.applyFontToTextView(this) + } fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) { setTextColor(textColor) diff --git a/commons/src/main/kotlin/org/fossify/commons/views/MyButton.kt b/commons/src/main/kotlin/org/fossify/commons/views/MyButton.kt index da0182e5a7..4e3c716bfb 100644 --- a/commons/src/main/kotlin/org/fossify/commons/views/MyButton.kt +++ b/commons/src/main/kotlin/org/fossify/commons/views/MyButton.kt @@ -3,13 +3,24 @@ package org.fossify.commons.views import android.content.Context import android.util.AttributeSet import android.widget.Button +import org.fossify.commons.extensions.applyFontToTextView open class MyButton : Button { - constructor(context: Context) : super(context) + constructor(context: Context) : super(context) { + init() + } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + init() + } + + private fun init() { + if (!isInEditMode) context.applyFontToTextView(this) + } fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) { setTextColor(textColor) diff --git a/commons/src/main/kotlin/org/fossify/commons/views/MyCompatRadioButton.kt b/commons/src/main/kotlin/org/fossify/commons/views/MyCompatRadioButton.kt index ccf2058f42..775e9e6a4e 100644 --- a/commons/src/main/kotlin/org/fossify/commons/views/MyCompatRadioButton.kt +++ b/commons/src/main/kotlin/org/fossify/commons/views/MyCompatRadioButton.kt @@ -5,13 +5,24 @@ import android.content.res.ColorStateList import android.util.AttributeSet import androidx.appcompat.widget.AppCompatRadioButton import org.fossify.commons.extensions.adjustAlpha +import org.fossify.commons.extensions.applyFontToTextView open class MyCompatRadioButton : AppCompatRadioButton { - constructor(context: Context) : super(context) + constructor(context: Context) : super(context) { + init() + } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + init() + } + + private fun init() { + if (!isInEditMode) context.applyFontToTextView(this) + } fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) { setTextColor(textColor) diff --git a/commons/src/main/kotlin/org/fossify/commons/views/MyEditText.kt b/commons/src/main/kotlin/org/fossify/commons/views/MyEditText.kt index 22a9ec0803..db7532bc81 100644 --- a/commons/src/main/kotlin/org/fossify/commons/views/MyEditText.kt +++ b/commons/src/main/kotlin/org/fossify/commons/views/MyEditText.kt @@ -5,14 +5,25 @@ import android.util.AttributeSet import androidx.appcompat.widget.AppCompatEditText import org.fossify.commons.extensions.adjustAlpha import org.fossify.commons.extensions.applyColorFilter +import org.fossify.commons.extensions.applyFontToTextView import org.fossify.commons.helpers.MEDIUM_ALPHA open class MyEditText : AppCompatEditText { - constructor(context: Context) : super(context) + constructor(context: Context) : super(context) { + init() + } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + init() + } + + private fun init() { + if (!isInEditMode) context.applyFontToTextView(this) + } fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) { background?.mutate()?.applyColorFilter(accentColor) diff --git a/commons/src/main/kotlin/org/fossify/commons/views/MyMaterialSwitch.kt b/commons/src/main/kotlin/org/fossify/commons/views/MyMaterialSwitch.kt index 5ed53a845d..1790aa337b 100644 --- a/commons/src/main/kotlin/org/fossify/commons/views/MyMaterialSwitch.kt +++ b/commons/src/main/kotlin/org/fossify/commons/views/MyMaterialSwitch.kt @@ -7,6 +7,7 @@ import androidx.appcompat.content.res.AppCompatResources import com.google.android.material.materialswitch.MaterialSwitch import org.fossify.commons.R import org.fossify.commons.extensions.adjustAlpha +import org.fossify.commons.extensions.applyFontToTextView import org.fossify.commons.extensions.baseConfig import org.fossify.commons.extensions.getContrastColor @@ -19,18 +20,17 @@ open class MyMaterialSwitch : MaterialSwitch { init { setShowCheckmark(context.baseConfig.showCheckmarksOnSwitches) + if (!isInEditMode) context.applyFontToTextView(this) } fun setShowCheckmark(showCheckmark: Boolean) { if (showCheckmark) { setOnCheckedChangeListener { _, isChecked -> - setThumbIconDrawable( - if (isChecked) { - AppCompatResources.getDrawable(context, R.drawable.ic_check_vector) - } else { - null - } - ) + thumbIconDrawable = if (isChecked) { + AppCompatResources.getDrawable(context, R.drawable.ic_check_vector) + } else { + null + } } } else { setOnCheckedChangeListener(null) diff --git a/commons/src/main/kotlin/org/fossify/commons/views/MyTextInputLayout.kt b/commons/src/main/kotlin/org/fossify/commons/views/MyTextInputLayout.kt index 73f1d8a3cd..15b9589b7c 100644 --- a/commons/src/main/kotlin/org/fossify/commons/views/MyTextInputLayout.kt +++ b/commons/src/main/kotlin/org/fossify/commons/views/MyTextInputLayout.kt @@ -6,15 +6,28 @@ import android.util.AttributeSet import com.google.android.material.textfield.TextInputLayout import org.fossify.commons.extensions.adjustAlpha import org.fossify.commons.extensions.value +import org.fossify.commons.helpers.FontHelper import org.fossify.commons.helpers.HIGHER_ALPHA import org.fossify.commons.helpers.MEDIUM_ALPHA open class MyTextInputLayout : TextInputLayout { - constructor(context: Context) : super(context) + constructor(context: Context) : super(context) { + applyCustomFont() + } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + applyCustomFont() + } - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + applyCustomFont() + } + + private fun applyCustomFont() { + if (isInEditMode) return + val customTypeface = FontHelper.getTypeface(context) + typeface = customTypeface + } // we need to use reflection to make some colors work well fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) { diff --git a/commons/src/main/kotlin/org/fossify/commons/views/MyTextView.kt b/commons/src/main/kotlin/org/fossify/commons/views/MyTextView.kt index 1935786d40..ca97a0aa9a 100644 --- a/commons/src/main/kotlin/org/fossify/commons/views/MyTextView.kt +++ b/commons/src/main/kotlin/org/fossify/commons/views/MyTextView.kt @@ -3,13 +3,24 @@ package org.fossify.commons.views import android.content.Context import android.util.AttributeSet import android.widget.TextView +import org.fossify.commons.extensions.applyFontToTextView open class MyTextView : TextView { - constructor(context: Context) : super(context) + constructor(context: Context) : super(context) { + init() + } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + init() + } + + private fun init() { + if (!isInEditMode) context.applyFontToTextView(this) + } fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) { setTextColor(textColor) diff --git a/commons/src/main/res/layout/activity_customization.xml b/commons/src/main/res/layout/activity_customization.xml index db495e7e65..f981abd4ab 100644 --- a/commons/src/main/res/layout/activity_customization.xml +++ b/commons/src/main/res/layout/activity_customization.xml @@ -206,6 +206,47 @@ + + + + + + + + + + + diff --git a/commons/src/main/res/values/strings.xml b/commons/src/main/res/values/strings.xml index c59ad67fb2..511e5725d7 100644 --- a/commons/src/main/res/values/strings.xml +++ b/commons/src/main/res/values/strings.xml @@ -374,7 +374,7 @@ Use default Default Change color - Theme + App theme Changing a color will make it switch to Custom theme Save Discard @@ -382,7 +382,7 @@ Are you sure you want to undo your changes? This action cannot be undone. You have unsaved changes. Save before exit? - Apply colors to all Fossify Apps + Apply theme to all Fossify Apps Caution: Certain launchers may not handle app icon customization correctly. If the icon disappears, attempt launching the app through Google Play or a widget, if accessible. After launching, restore the default green icon color #106D1F. In extreme cases, reinstalling the app may be necessary. The current theme has been successfully applied to all Fossify apps. Any future changes to this theme will be synced automatically. @@ -697,10 +697,9 @@ Requires Fossify Thank You Enables more customization General - Color customization - Improved color customization - Customize colors - Theme and colors + Look & feel + Customize appearance + Theme & colors Locked Customize widget colors Customize notifications @@ -709,6 +708,12 @@ Language Show hidden items Font size + Font + App font + System default + Monospace + Select font file… + Invalid font file Small Medium Large diff --git a/commons/src/main/res/values/styles.xml b/commons/src/main/res/values/styles.xml index 23dd2c3313..d733c94745 100644 --- a/commons/src/main/res/values/styles.xml +++ b/commons/src/main/res/values/styles.xml @@ -59,6 +59,7 @@ @style/ActionMenuOverflowIcon @style/MyRadioButtonStyle @style/MyRadioButtonStyle + @style/AppToolbar @@ -93,6 +94,10 @@ none + + - - - - + + - @@ -364,7 +376,7 @@ @style/AppTheme.PopupMenuDark - @@ -373,7 +385,7 @@ @style/AppTheme.PopupMenuLight - From 223872a7afb3763007c354040cf9e876aeb760b8 Mon Sep 17 00:00:00 2001 From: Naveen Singh Date: Tue, 30 Dec 2025 00:39:59 +0530 Subject: [PATCH 2/7] fix: don't save font data as blob --- .../activities/CustomizationActivity.kt | 22 ++++-- .../commons/extensions/Context-styling.kt | 68 +++++++++++++++---- .../org/fossify/commons/helpers/FontHelper.kt | 7 +- .../commons/helpers/MyContentProvider.kt | 2 +- .../fossify/commons/models/GlobalConfig.kt | 39 +---------- 5 files changed, 78 insertions(+), 60 deletions(-) diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt index 79b45faa9d..5d87a01253 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt @@ -45,12 +45,12 @@ import org.fossify.commons.helpers.FontHelper import org.fossify.commons.helpers.MyContentProvider.COL_ACCENT_COLOR import org.fossify.commons.helpers.MyContentProvider.COL_APP_ICON_COLOR import org.fossify.commons.helpers.MyContentProvider.COL_BACKGROUND_COLOR -import org.fossify.commons.helpers.MyContentProvider.COL_FONT_DATA import org.fossify.commons.helpers.MyContentProvider.COL_FONT_NAME import org.fossify.commons.helpers.MyContentProvider.COL_FONT_TYPE import org.fossify.commons.helpers.MyContentProvider.COL_PRIMARY_COLOR import org.fossify.commons.helpers.MyContentProvider.COL_TEXT_COLOR import org.fossify.commons.helpers.MyContentProvider.COL_THEME_TYPE +import org.fossify.commons.helpers.MyContentProvider.FONTS_URI import org.fossify.commons.helpers.MyContentProvider.GLOBAL_THEME_CUSTOM import org.fossify.commons.helpers.MyContentProvider.GLOBAL_THEME_DISABLED import org.fossify.commons.helpers.MyContentProvider.GLOBAL_THEME_SYSTEM @@ -488,13 +488,21 @@ class CustomizationActivity : BaseSimpleActivity() { put(COL_APP_ICON_COLOR, curAppIconColor) put(COL_FONT_TYPE, curFontType) put(COL_FONT_NAME, curFontFileName) - if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty()) { - FontHelper.getFontData(this@CustomizationActivity, curFontFileName)?.let { - put(COL_FONT_DATA, it) - } - } } ) + + if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty()) { + FontHelper.getFontData(this, curFontFileName)?.let { fontData -> + val fontUri = FONTS_URI.buildUpon() + .appendPath(curFontFileName) + .build() + try { + contentResolver.openOutputStream(fontUri, "w") + ?.use { it.write(fontData) } + } catch (_: Exception) { + } + } + } } hasUnsavedChanges = false @@ -560,7 +568,7 @@ class CustomizationActivity : BaseSimpleActivity() { } } } - + setupFontPicker() } diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt index 2208af2fbb..7d8d2f8e5f 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt @@ -4,23 +4,50 @@ import android.annotation.SuppressLint import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager +import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED +import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED import android.content.res.Configuration import android.graphics.Color import android.view.ViewGroup import androidx.loader.content.CursorLoader import com.google.android.material.color.MaterialColors import org.fossify.commons.R -import org.fossify.commons.helpers.* +import org.fossify.commons.helpers.DARK_GREY +import org.fossify.commons.helpers.FONT_TYPE_CUSTOM +import org.fossify.commons.helpers.FontHelper +import org.fossify.commons.helpers.MyContentProvider import org.fossify.commons.helpers.MyContentProvider.GLOBAL_THEME_SYSTEM +import org.fossify.commons.helpers.appIconColorStrings +import org.fossify.commons.helpers.ensureBackgroundThread +import org.fossify.commons.helpers.isSPlus import org.fossify.commons.models.GlobalConfig import org.fossify.commons.models.isGlobalThemingEnabled -import org.fossify.commons.views.* +import org.fossify.commons.views.MyAppCompatCheckbox +import org.fossify.commons.views.MyAppCompatSpinner +import org.fossify.commons.views.MyAutoCompleteTextView +import org.fossify.commons.views.MyButton +import org.fossify.commons.views.MyCompatRadioButton +import org.fossify.commons.views.MyEditText +import org.fossify.commons.views.MyFloatingActionButton +import org.fossify.commons.views.MyMaterialSwitch +import org.fossify.commons.views.MySeekBar +import org.fossify.commons.views.MyTextInputLayout +import org.fossify.commons.views.MyTextView +import java.io.File fun Context.isDynamicTheme() = isSPlus() && baseConfig.isSystemThemeEnabled -fun Context.isBlackAndWhiteTheme() = baseConfig.textColor == Color.WHITE && baseConfig.primaryColor == Color.BLACK && baseConfig.backgroundColor == Color.BLACK +fun Context.isBlackAndWhiteTheme(): Boolean { + return baseConfig.textColor == Color.WHITE + && baseConfig.primaryColor == Color.BLACK + && baseConfig.backgroundColor == Color.BLACK +} -fun Context.isWhiteTheme() = baseConfig.textColor == DARK_GREY && baseConfig.primaryColor == Color.WHITE && baseConfig.backgroundColor == Color.WHITE +fun Context.isWhiteTheme(): Boolean { + return baseConfig.textColor == DARK_GREY + && baseConfig.primaryColor == Color.WHITE + && baseConfig.backgroundColor == Color.WHITE +} fun Context.isSystemInDarkMode() = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES != 0 @@ -134,12 +161,8 @@ fun Context.syncGlobalConfig(callback: (() -> Unit)? = null) { if (it.fontType >= 0 && (fontType != it.fontType || fontName != it.fontName)) { fontType = it.fontType fontName = it.fontName - if (it.fontType == FONT_TYPE_CUSTOM && it.fontData != null) { - FontHelper.saveFontData( - context = this@syncGlobalConfig, - fontData = it.fontData, - fileName = it.fontName - ) + if (it.fontType == FONT_TYPE_CUSTOM && it.fontName.isNotEmpty()) { + ensureFontPresentLocally(it.fontName) } FontHelper.clearCache() } @@ -187,8 +210,7 @@ fun Context.getGlobalConfig(cursorLoader: CursorLoader): GlobalConfig? { showCheckmarksOnSwitches = cursor.getIntValue(MyContentProvider.COL_SHOW_CHECKMARKS_ON_SWITCHES) != 0, lastUpdatedTS = cursor.getIntValue(MyContentProvider.COL_LAST_UPDATED_TS), fontType = cursor.getIntValueOr(MyContentProvider.COL_FONT_TYPE, -1), - fontName = cursor.getStringValueOr(MyContentProvider.COL_FONT_NAME, ""), - fontData = cursor.getBlobValueOrNull(MyContentProvider.COL_FONT_DATA) + fontName = cursor.getStringValueOr(MyContentProvider.COL_FONT_NAME, "") ) } catch (_: Exception) { } @@ -197,6 +219,26 @@ fun Context.getGlobalConfig(cursorLoader: CursorLoader): GlobalConfig? { return null } +fun Context.ensureFontPresentLocally(fontName: String): Boolean { + if (fontName.isEmpty()) return false + val localFile = File(FontHelper.getFontsDir(this), fontName) + if (localFile.exists()) return true + + val fontUri = MyContentProvider.FONTS_URI.buildUpon() + .appendPath(fontName) + .build() + + return try { + contentResolver.openInputStream(fontUri)?.use { input -> + localFile.outputStream().use { output -> + input.copyTo(output) + } + } != null + } catch (_: Exception) { + false + } +} + fun Context.checkAppIconColor() { val appId = baseConfig.appId if (appId.isNotEmpty() && baseConfig.lastIconColor != baseConfig.appIconColor) { @@ -214,7 +256,7 @@ fun Context.checkAppIconColor() { fun Context.toggleAppIconColor(appId: String, colorIndex: Int, color: Int, enable: Boolean) { val className = "${appId.removeSuffix(".debug")}.activities.SplashActivity${appIconColorStrings[colorIndex]}" - val state = if (enable) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED + val state = if (enable) COMPONENT_ENABLED_STATE_ENABLED else COMPONENT_ENABLED_STATE_DISABLED try { packageManager.setComponentEnabledSetting(ComponentName(appId, className), state, PackageManager.DONT_KILL_APP) if (enable) { diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt index 9f93d9d870..fc6dead570 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt @@ -3,6 +3,7 @@ package org.fossify.commons.helpers import android.content.Context import android.graphics.Typeface import org.fossify.commons.extensions.baseConfig +import org.fossify.commons.extensions.ensureFontPresentLocally import java.io.File /** @@ -34,8 +35,12 @@ object FontHelper { private fun loadCustomFont(context: Context, fileName: String): Typeface { if (fileName.isEmpty()) return Typeface.DEFAULT + val fontFile = File(getFontsDir(context), fileName) + if (!fontFile.exists()) { + context.ensureFontPresentLocally(fileName) + } + return try { - val fontFile = File(getFontsDir(context), fileName) if (fontFile.exists()) Typeface.createFromFile(fontFile) else Typeface.DEFAULT } catch (_: Exception) { Typeface.DEFAULT diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt index 9e5a1bade0..a22be4b02a 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt @@ -6,6 +6,7 @@ import androidx.core.net.toUri object MyContentProvider { private const val AUTHORITY = "org.fossify.android.provider" val MY_CONTENT_URI: Uri = "content://$AUTHORITY/settings".toUri() + val FONTS_URI: Uri = "content://$AUTHORITY/fonts".toUri() const val ACTION_GLOBAL_CONFIG_UPDATED = "org.fossify.android.GLOBAL_CONFIG_UPDATED" const val PERMISSION_WRITE_GLOBAL_SETTINGS = "org.fossify.android.permission.WRITE_GLOBAL_SETTINGS" @@ -25,7 +26,6 @@ object MyContentProvider { // Font customization const val COL_FONT_TYPE = "font_type" const val COL_FONT_NAME = "font_name" - const val COL_FONT_DATA = "font_data" const val GLOBAL_THEME_DISABLED = 0 const val GLOBAL_THEME_SYSTEM = 1 diff --git a/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt b/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt index c4b1e500f7..69811429ef 100644 --- a/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt +++ b/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt @@ -14,44 +14,7 @@ data class GlobalConfig( val lastUpdatedTS: Int = 0, val fontType: Int = FONT_TYPE_SYSTEM_DEFAULT, val fontName: String = "", - val fontData: ByteArray? = null, -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GlobalConfig - - if (themeType != other.themeType) return false - if (textColor != other.textColor) return false - if (backgroundColor != other.backgroundColor) return false - if (primaryColor != other.primaryColor) return false - if (accentColor != other.accentColor) return false - if (appIconColor != other.appIconColor) return false - if (showCheckmarksOnSwitches != other.showCheckmarksOnSwitches) return false - if (lastUpdatedTS != other.lastUpdatedTS) return false - if (fontType != other.fontType) return false - if (fontName != other.fontName) return false - if (!fontData.contentEquals(other.fontData)) return false - - return true - } - - override fun hashCode(): Int { - var result = themeType - result = 31 * result + textColor - result = 31 * result + backgroundColor - result = 31 * result + primaryColor - result = 31 * result + accentColor - result = 31 * result + appIconColor - result = 31 * result + showCheckmarksOnSwitches.hashCode() - result = 31 * result + lastUpdatedTS - result = 31 * result + fontType - result = 31 * result + fontName.hashCode() - result = 31 * result + (fontData?.contentHashCode() ?: 0) - return result - } -} +) fun GlobalConfig?.isGlobalThemingEnabled(): Boolean { return this != null && themeType != MyContentProvider.GLOBAL_THEME_DISABLED From d7d7848c092fd179d0b58deee47bd9ee6bb06e80 Mon Sep 17 00:00:00 2001 From: Naveen Singh Date: Tue, 30 Dec 2025 01:04:24 +0530 Subject: [PATCH 3/7] refactor: fix detekt issues --- .../activities/CustomizationActivity.kt | 72 +++++++++---------- .../org/fossify/commons/extensions/Cursor.kt | 7 -- 2 files changed, 34 insertions(+), 45 deletions(-) diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt index 5d87a01253..0d4508b00b 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt @@ -1,5 +1,6 @@ package org.fossify.commons.activities +import android.content.ActivityNotFoundException import android.content.ContentValues import android.graphics.Color import android.graphics.Typeface @@ -467,50 +468,45 @@ class CustomizationActivity : BaseSimpleActivity() { } FontHelper.clearCache() - baseConfig.isGlobalThemeEnabled = binding.applyToAllSwitch.isChecked baseConfig.isSystemThemeEnabled = curSelectedThemeId == THEME_SYSTEM - if (isThankYouInstalled()) { - val globalThemeType = when { - baseConfig.isGlobalThemeEnabled.not() -> GLOBAL_THEME_DISABLED - baseConfig.isSystemThemeEnabled -> GLOBAL_THEME_SYSTEM - else -> GLOBAL_THEME_CUSTOM + if (isThankYouInstalled()) saveThankYouChanges() + hasUnsavedChanges = false + if (finishAfterSave) finish() else refreshMenuItems() + } + + private fun saveThankYouChanges() { + val globalThemeType = when { + baseConfig.isGlobalThemeEnabled.not() -> GLOBAL_THEME_DISABLED + baseConfig.isSystemThemeEnabled -> GLOBAL_THEME_SYSTEM + else -> GLOBAL_THEME_CUSTOM + } + + updateGlobalConfig( + ContentValues().apply { + put(COL_THEME_TYPE, globalThemeType) + put(COL_TEXT_COLOR, curTextColor) + put(COL_BACKGROUND_COLOR, curBackgroundColor) + put(COL_PRIMARY_COLOR, curPrimaryColor) + put(COL_ACCENT_COLOR, curAccentColor) + put(COL_APP_ICON_COLOR, curAppIconColor) + put(COL_FONT_TYPE, curFontType) + put(COL_FONT_NAME, curFontFileName) } + ) - updateGlobalConfig( - ContentValues().apply { - put(COL_THEME_TYPE, globalThemeType) - put(COL_TEXT_COLOR, curTextColor) - put(COL_BACKGROUND_COLOR, curBackgroundColor) - put(COL_PRIMARY_COLOR, curPrimaryColor) - put(COL_ACCENT_COLOR, curAccentColor) - put(COL_APP_ICON_COLOR, curAppIconColor) - put(COL_FONT_TYPE, curFontType) - put(COL_FONT_NAME, curFontFileName) - } - ) - - if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty()) { - FontHelper.getFontData(this, curFontFileName)?.let { fontData -> - val fontUri = FONTS_URI.buildUpon() - .appendPath(curFontFileName) - .build() - try { - contentResolver.openOutputStream(fontUri, "w") - ?.use { it.write(fontData) } - } catch (_: Exception) { - } - } + if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty()) { + val fontData = FontHelper.getFontData(this, curFontFileName) ?: return + val fontUri = FONTS_URI.buildUpon() + .appendPath(curFontFileName) + .build() + try { + contentResolver.openOutputStream(fontUri, "w") + ?.use { it.write(fontData) } + } catch (_: Exception) { } } - - hasUnsavedChanges = false - if (finishAfterSave) { - finish() - } else { - refreshMenuItems() - } } private fun resetColors() { @@ -625,7 +621,7 @@ class CustomizationActivity : BaseSimpleActivity() { "*/*" ) ) - } catch (e: Exception) { + } catch (_: ActivityNotFoundException) { toast(R.string.system_service_disabled) } } diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt index 44346f9316..39ff12f897 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt @@ -20,13 +20,6 @@ fun Cursor.getLongValueOrNull(key: String): Long? { return if (isNull(getColumnIndexOrThrow(key))) null else getLong(getColumnIndexOrThrow(key)) } -fun Cursor.getBlobValue(key: String) = getBlob(getColumnIndexOrThrow(key)) - -fun Cursor.getBlobValueOrNull(key: String): ByteArray? { - val index = getColumnIndex(key) - return if (index != -1 && !isNull(index)) getBlob(index) else null -} - fun Cursor.getStringValueOr(key: String, defaultValue: String): String { val index = getColumnIndex(key) return if (index != -1 && !isNull(index)) getString(index) else defaultValue From 7ab0a53aa109e620ec9f160efcbfa09a20e3a5f8 Mon Sep 17 00:00:00 2001 From: Naveen Singh Date: Tue, 30 Dec 2025 04:21:49 +0530 Subject: [PATCH 4/7] fix: reflect selected typeface immediately in UI --- .../commons/activities/CustomizationActivity.kt | 9 ++++++--- .../kotlin/org/fossify/commons/extensions/Context.kt | 1 - .../kotlin/org/fossify/commons/extensions/Cursor.kt | 2 ++ .../kotlin/org/fossify/commons/helpers/FontHelper.kt | 10 +++++----- commons/src/main/res/layout/activity_customization.xml | 2 +- commons/src/main/res/values/strings.xml | 1 - 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt index 0d4508b00b..62363c69e2 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt @@ -587,13 +587,13 @@ class CustomizationActivity : BaseSimpleActivity() { binding.customizationFont.text = when (curFontType) { FONT_TYPE_MONOSPACE -> getString(R.string.font_monospace) FONT_TYPE_CUSTOM -> curFontFileName.ifEmpty { getString(R.string.select_font_file) } - else -> getString(R.string.font_system_default) + else -> getString(R.string.system_default) } } private fun fontPickerClicked() { val items = arrayListOf( - RadioItem(FONT_TYPE_SYSTEM_DEFAULT, getString(R.string.font_system_default)), + RadioItem(FONT_TYPE_SYSTEM_DEFAULT, getString(R.string.system_default)), RadioItem(FONT_TYPE_MONOSPACE, getString(R.string.font_monospace)), RadioItem(FONT_TYPE_CUSTOM, getString(R.string.select_font_file)) ) @@ -666,7 +666,10 @@ class CustomizationActivity : BaseSimpleActivity() { private fun fontChanged() { hasUnsavedChanges = true updateFontDisplay() - applyFontToViewRecursively(window.decorView) + applyFontToViewRecursively( + view = window.decorView, + typeface = FontHelper.getTypeface(this, curFontType, curFontFileName) + ) refreshMenuItems() } diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt index 7388dd97e6..51c9a4e7ea 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt @@ -1356,7 +1356,6 @@ fun Context.formatWithDeprecatedBadge( ): CharSequence = formatWithBadge(labelRes, R.string.badge_deprecated, *labelArgs) fun Context.applyFontToTextView(textView: TextView, typeface: Typeface = FontHelper.getTypeface(this)) { - if (typeface == Typeface.DEFAULT) return val existingStyle = textView.typeface?.style ?: Typeface.NORMAL textView.setTypeface(typeface, existingStyle) } diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt index 39ff12f897..81c47d6a44 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Cursor.kt @@ -20,6 +20,8 @@ fun Cursor.getLongValueOrNull(key: String): Long? { return if (isNull(getColumnIndexOrThrow(key))) null else getLong(getColumnIndexOrThrow(key)) } +fun Cursor.getBlobValue(key: String) = getBlob(getColumnIndexOrThrow(key)) + fun Cursor.getStringValueOr(key: String, defaultValue: String): String { val index = getColumnIndex(key) return if (index != -1 && !isNull(index)) getString(index) else defaultValue diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt index fc6dead570..ee92a0d580 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt @@ -14,11 +14,11 @@ object FontHelper { private var cachedFontType: Int = -1 private var cachedFontFileName: String = "" - fun getTypeface(context: Context): Typeface { - val config = context.baseConfig - val fontType = config.fontType - val fontFileName = config.fontName - + fun getTypeface( + context: Context, + fontType: Int = context.baseConfig.fontType, + fontFileName: String = context.baseConfig.fontName + ): Typeface { if (fontType == cachedFontType && fontFileName == cachedFontFileName && cachedTypeface != null) { return cachedTypeface!! } diff --git a/commons/src/main/res/layout/activity_customization.xml b/commons/src/main/res/layout/activity_customization.xml index f981abd4ab..fb9fddcebf 100644 --- a/commons/src/main/res/layout/activity_customization.xml +++ b/commons/src/main/res/layout/activity_customization.xml @@ -244,7 +244,7 @@ android:background="@null" android:clickable="false" android:textSize="@dimen/normal_text_size" - tools:text="@string/font_system_default" /> + tools:text="@string/system_default" /> Font size Font App font - System default Monospace Select font file… Invalid font file From 3fa89cb397b9d0edb8ec6c8be53b716b080e0743 Mon Sep 17 00:00:00 2001 From: Naveen Singh Date: Tue, 30 Dec 2025 20:22:35 +0530 Subject: [PATCH 5/7] feat: gate font change via thank you in play builds --- .../fossify/commons/activities/CustomizationActivity.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt index 62363c69e2..691c884fde 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt @@ -28,6 +28,7 @@ import org.fossify.commons.extensions.getProperTextColor import org.fossify.commons.extensions.getThemeId import org.fossify.commons.extensions.isDynamicTheme import org.fossify.commons.extensions.isFontFile +import org.fossify.commons.extensions.isOrWasThankYouInstalled import org.fossify.commons.extensions.isSystemInDarkMode import org.fossify.commons.extensions.isThankYouInstalled import org.fossify.commons.extensions.setFillWithStroke @@ -592,6 +593,11 @@ class CustomizationActivity : BaseSimpleActivity() { } private fun fontPickerClicked() { + if (!resources.getBoolean(R.bool.hide_google_relations) && !isOrWasThankYouInstalled()) { + PurchaseThankYouDialog(this) + return + } + val items = arrayListOf( RadioItem(FONT_TYPE_SYSTEM_DEFAULT, getString(R.string.system_default)), RadioItem(FONT_TYPE_MONOSPACE, getString(R.string.font_monospace)), @@ -909,7 +915,6 @@ class CustomizationActivity : BaseSimpleActivity() { binding.applyToAllHolder.beVisibleIf(showThankYouFeatures) binding.applyToAllDivider.root.beVisibleIf(showThankYouFeatures) binding.settingsAllFossifyAppsLabel.beVisibleIf(showThankYouFeatures) - binding.settingsThemeAndColorsLabel.beVisibleIf(showThankYouFeatures) binding.applyToAllSwitch.isChecked = baseConfig.isGlobalThemeEnabled updateApplyToAllColors() } From fc77f83048035d1c66af18567670d712fa00bfb3 Mon Sep 17 00:00:00 2001 From: Naveen Singh Date: Tue, 30 Dec 2025 20:40:55 +0530 Subject: [PATCH 6/7] fix: detect if thank you supports fonts before writing to db --- .../activities/CustomizationActivity.kt | 9 ++++++--- .../org/fossify/commons/extensions/Context.kt | 18 +++++++++++++++++- .../fossify/commons/helpers/FossifyThankYou.kt | 6 ++++++ 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 commons/src/main/kotlin/org/fossify/commons/helpers/FossifyThankYou.kt diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt index 691c884fde..19529b0e5e 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt @@ -29,6 +29,7 @@ import org.fossify.commons.extensions.getThemeId import org.fossify.commons.extensions.isDynamicTheme import org.fossify.commons.extensions.isFontFile import org.fossify.commons.extensions.isOrWasThankYouInstalled +import org.fossify.commons.extensions.isThankYouFontsSupported import org.fossify.commons.extensions.isSystemInDarkMode import org.fossify.commons.extensions.isThankYouInstalled import org.fossify.commons.extensions.setFillWithStroke @@ -492,12 +493,14 @@ class CustomizationActivity : BaseSimpleActivity() { put(COL_PRIMARY_COLOR, curPrimaryColor) put(COL_ACCENT_COLOR, curAccentColor) put(COL_APP_ICON_COLOR, curAppIconColor) - put(COL_FONT_TYPE, curFontType) - put(COL_FONT_NAME, curFontFileName) + if (isThankYouFontsSupported()) { + put(COL_FONT_TYPE, curFontType) + put(COL_FONT_NAME, curFontFileName) + } } ) - if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty()) { + if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty() && isThankYouFontsSupported()) { val fontData = FontHelper.getFontData(this, curFontFileName) ?: return val fontUri = FONTS_URI.buildUpon() .appendPath(curFontFileName) diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt index 51c9a4e7ea..4d773a2947 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt @@ -133,6 +133,8 @@ import java.util.Date import java.util.Locale import androidx.core.net.toUri import org.fossify.commons.helpers.FontHelper +import org.fossify.commons.helpers.FossifyThankYou +import org.fossify.commons.helpers.isPiePlus import kotlin.math.roundToInt fun Context.getSharedPrefs() = getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE) @@ -571,7 +573,21 @@ fun Context.getUriMimeType(path: String, newUri: Uri): String { return mimeType } -fun Context.isThankYouInstalled() = isPackageInstalled("org.fossify.thankyou") +fun Context.isThankYouInstalled() = isPackageInstalled(FossifyThankYou.PACKAGE_NAME) + +fun Context.isThankYouFontsSupported(): Boolean { + return try { + val thankYouAppInfo = packageManager.getPackageInfo(FossifyThankYou.PACKAGE_NAME, 0) + if (isPiePlus()) { + thankYouAppInfo.longVersionCode >= FossifyThankYou.MIN_VERSION_CODE_FOR_FONTS + } else { + @Suppress("DEPRECATION") + thankYouAppInfo.versionCode >= FossifyThankYou.MIN_VERSION_CODE_FOR_FONTS + } + } catch (_: Exception) { + false + } +} fun Context.canAccessGlobalConfig(): Boolean { return isThankYouInstalled() && ContextCompat.checkSelfPermission(this, PERMISSION_WRITE_GLOBAL_SETTINGS) == PERMISSION_GRANTED diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/FossifyThankYou.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/FossifyThankYou.kt new file mode 100644 index 0000000000..238655b883 --- /dev/null +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/FossifyThankYou.kt @@ -0,0 +1,6 @@ +package org.fossify.commons.helpers + +object FossifyThankYou { + const val PACKAGE_NAME = "org.fossify.thankyou" + const val MIN_VERSION_CODE_FOR_FONTS = 10L +} From c94ef14c6498a2a89803d11f7069cc0aed10d22e Mon Sep 17 00:00:00 2001 From: Naveen Singh Date: Tue, 30 Dec 2025 22:23:29 +0530 Subject: [PATCH 7/7] fix: minor improvement --- .../org/fossify/commons/activities/CustomizationActivity.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt index 19529b0e5e..a66f13b061 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt @@ -485,6 +485,7 @@ class CustomizationActivity : BaseSimpleActivity() { else -> GLOBAL_THEME_CUSTOM } + val canFontsBeSynced = isThankYouFontsSupported() updateGlobalConfig( ContentValues().apply { put(COL_THEME_TYPE, globalThemeType) @@ -493,14 +494,14 @@ class CustomizationActivity : BaseSimpleActivity() { put(COL_PRIMARY_COLOR, curPrimaryColor) put(COL_ACCENT_COLOR, curAccentColor) put(COL_APP_ICON_COLOR, curAppIconColor) - if (isThankYouFontsSupported()) { + if (canFontsBeSynced) { put(COL_FONT_TYPE, curFontType) put(COL_FONT_NAME, curFontFileName) } } ) - if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty() && isThankYouFontsSupported()) { + if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty() && canFontsBeSynced) { val fontData = FontHelper.getFontData(this, curFontFileName) ?: return val fontUri = FONTS_URI.buildUpon() .appendPath(curFontFileName)