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..a66f13b061 100644 --- a/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt +++ b/commons/src/main/kotlin/org/fossify/commons/activities/CustomizationActivity.kt @@ -1,8 +1,12 @@ package org.fossify.commons.activities +import android.content.ActivityNotFoundException 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 +15,21 @@ 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.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 @@ -32,12 +41,19 @@ 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_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 @@ -48,6 +64,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 +86,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,39 +461,56 @@ 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 - if (isThankYouInstalled()) { - 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) + 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 + } + + val canFontsBeSynced = isThankYouFontsSupported() + 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) + if (canFontsBeSynced) { + put(COL_FONT_TYPE, curFontType) + put(COL_FONT_NAME, curFontFileName) } - ) - } + } + ) - hasUnsavedChanges = false - if (finishAfterSave) { - finish() - } else { - refreshMenuItems() + if (curFontType == FONT_TYPE_CUSTOM && curFontFileName.isNotEmpty() && canFontsBeSynced) { + 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) { + } } } @@ -490,6 +531,8 @@ class CustomizationActivity : BaseSimpleActivity() { curPrimaryColor = baseConfig.primaryColor curAccentColor = baseConfig.accentColor curAppIconColor = baseConfig.appIconColor + curFontType = baseConfig.fontType + curFontFileName = baseConfig.fontName } private fun setupColorsPickers() { @@ -526,6 +569,8 @@ class CustomizationActivity : BaseSimpleActivity() { } } } + + setupFontPicker() } private fun hasColorChanged(old: Int, new: Int) = abs(old - new) > 1 @@ -536,6 +581,108 @@ 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.system_default) + } + } + + 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)), + 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 (_: ActivityNotFoundException) { + 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( + view = window.decorView, + typeface = FontHelper.getTypeface(this, curFontType, curFontFileName) + ) + refreshMenuItems() + } + private fun setCurrentTextColor(color: Int) { curTextColor = color updateLabelColors(color) @@ -707,6 +854,8 @@ class CustomizationActivity : BaseSimpleActivity() { binding.customizationPrimaryColorLabel, binding.customizationAccentColorLabel, binding.customizationAppIconColorLabel, + binding.customizationFontLabel, + binding.customizationFont, binding.applyToAllLabel, binding.applyToAllNote ).forEach { @@ -717,6 +866,7 @@ class CustomizationActivity : BaseSimpleActivity() { private fun updateHeaderColors(primaryColor: Int = getProperPrimaryColor()) { arrayListOf( binding.settingsThemeAndColorsLabel, + binding.settingsFontLabel, binding.settingsAllFossifyAppsLabel ).forEach { it.setTextColor(primaryColor) @@ -769,7 +919,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() } 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..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 @@ -105,7 +132,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 +158,15 @@ 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.fontName.isNotEmpty()) { + ensureFontPresentLocally(it.fontName) + } + FontHelper.clearCache() + } + if (baseConfig.appIconColor != it.appIconColor) { baseConfig.appIconColor = it.appIconColor checkAppIconColor() @@ -168,15 +208,37 @@ 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, "") ) - } catch (e: Exception) { + } catch (_: Exception) { } } } 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) { @@ -194,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/extensions/Context.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Context.kt index 61ad911ad8..4d773a2947 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,9 @@ import java.text.SimpleDateFormat 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) @@ -568,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 @@ -1351,3 +1370,18 @@ 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)) { + 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/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..ee92a0d580 --- /dev/null +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/FontHelper.kt @@ -0,0 +1,93 @@ +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 + +/** + * 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, + fontType: Int = context.baseConfig.fontType, + fontFileName: String = context.baseConfig.fontName + ): Typeface { + 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 + val fontFile = File(getFontsDir(context), fileName) + if (!fontFile.exists()) { + context.ensureFontPresentLocally(fileName) + } + + return try { + 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/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 +} 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..a22be4b02a 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,19 @@ 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() + 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" 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 +23,10 @@ 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 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..69811429ef 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,6 +12,8 @@ data class GlobalConfig( val appIconColor: Int, val showCheckmarksOnSwitches: Boolean, val lastUpdatedTS: Int = 0, + val fontType: Int = FONT_TYPE_SYSTEM_DEFAULT, + val fontName: String = "", ) fun GlobalConfig?.isGlobalThemingEnabled(): Boolean { 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..fb9fddcebf 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..b8bdf6fa3b 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,11 @@ Language Show hidden items Font size + Font + App font + 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 -