@@ -24,6 +24,7 @@ import android.content.res.Configuration
2424import android.os.Bundle
2525import android.text.TextUtils
2626import android.util.Log
27+ import android.util.TypedValue
2728import android.view.KeyEvent
2829import android.view.View
2930import android.view.ViewGroup.LayoutParams
@@ -32,6 +33,8 @@ import androidx.collection.MutableIntObjectMap
3233import androidx.core.content.res.ResourcesCompat
3334import androidx.core.view.GravityCompat
3435import androidx.core.view.doOnNextLayout
36+ import androidx.fragment.app.Fragment
37+ import androidx.fragment.app.FragmentManager
3538import androidx.lifecycle.lifecycleScope
3639import com.blankj.utilcode.util.ImageUtils
3740import com.google.android.material.tabs.TabLayout
@@ -63,6 +66,7 @@ import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent
6366import com.itsaky.androidide.eventbus.events.file.FileRenameEvent
6467import com.itsaky.androidide.activities.PluginManagerActivity
6568import com.itsaky.androidide.eventbus.events.plugin.PluginCrashedEvent
69+ import com.itsaky.androidide.eventbus.events.preferences.PreferenceChangeEvent
6670import com.itsaky.androidide.idetooltips.TooltipManager
6771import com.itsaky.androidide.idetooltips.TooltipTag
6872import com.itsaky.androidide.interfaces.IEditorHandler
@@ -73,6 +77,7 @@ import com.itsaky.androidide.models.Range
7377import com.itsaky.androidide.models.SaveResult
7478import com.itsaky.androidide.plugins.manager.build.PluginBuildActionManager
7579import com.itsaky.androidide.plugins.manager.fragment.PluginFragmentFactory
80+ import com.itsaky.androidide.preferences.internal.EditorPreferences
7681import com.itsaky.androidide.plugins.manager.ui.PluginDrawableResolver
7782import com.itsaky.androidide.plugins.manager.ui.PluginEditorTabManager
7883import com.itsaky.androidide.plugins.manager.ui.PluginUiActionManager
@@ -91,6 +96,7 @@ import com.itsaky.androidide.utils.EditorSidebarActions
9196import com.itsaky.androidide.utils.IntentUtils.openImage
9297import com.itsaky.androidide.utils.UniqueNameBuilder
9398import com.itsaky.androidide.utils.flashSuccess
99+ import com.itsaky.androidide.utils.forEachViewRecursively
94100import kotlinx.coroutines.Dispatchers
95101import kotlinx.coroutines.NonCancellable
96102import kotlinx.coroutines.launch
@@ -99,6 +105,7 @@ import org.adfa.constants.CONTENT_KEY
99105import org.greenrobot.eventbus.Subscribe
100106import org.greenrobot.eventbus.ThreadMode
101107import java.io.File
108+ import java.util.WeakHashMap
102109import java.util.concurrent.ConcurrentHashMap
103110import java.util.concurrent.CopyOnWriteArrayList
104111import java.util.concurrent.atomic.AtomicBoolean
@@ -126,6 +133,22 @@ open class EditorHandlerActivity :
126133
127134 private val pluginTabIndices = mutableMapOf<String , Int >()
128135 private val tabIndexToPluginId = mutableMapOf<Int , String >()
136+ private var lastAppliedPluginFontScale = EditorPreferences .editorFontScale
137+ private val pluginTextBaseSizes = WeakHashMap <TextView , Float >()
138+
139+ private val pluginFontScalingListener = object : FragmentManager .FragmentLifecycleCallbacks () {
140+ override fun onFragmentViewCreated (
141+ mFragmentManager : FragmentManager ,
142+ mFragment : Fragment ,
143+ view : View ,
144+ savedInstanceState : Bundle ?
145+ ) {
146+ val scale = EditorPreferences .editorFontScale
147+ if (scale != 1f && isPluginFragment(mFragment)) {
148+ applyPluginFontScale(view, scale)
149+ }
150+ }
151+ }
129152 private val shortcutManager by lazy { ShortcutManager (applicationContext) }
130153
131154 private var pluginEditorProvider: EditorProviderImpl ? = null
@@ -195,6 +218,8 @@ open class EditorHandlerActivity :
195218 mBuildEventListener.setActivity(this )
196219 super .onCreate(savedInstanceState)
197220
221+ supportFragmentManager.registerFragmentLifecycleCallbacks(pluginFontScalingListener, true )
222+
198223 editorViewModel._displayedFile .observe(
199224 this ,
200225 ) { fileIndex ->
@@ -393,25 +418,38 @@ open class EditorHandlerActivity :
393418 }
394419
395420 restoreOpenedPluginTabs()
421+ syncPluginUiFontSize()
396422 }
397423
424+ /* *
425+ * Restores the plugin tabs cached from the previous session, running the
426+ * SharedPreferences IO and Gson decode off the main thread to avoid a startup UI stall.
427+ */
398428 private fun restoreOpenedPluginTabs () {
399- try {
400- val prefs = (application as BaseApplication ).prefManager
401- val json = prefs.getString(PREF_KEY_OPEN_PLUGIN_TABS , null ) ? : return
429+ lifecycleScope.launch {
430+ try {
431+ val prefs = (application as BaseApplication ).prefManager
432+ val json = withContext(Dispatchers .IO ) {
433+ prefs.getString(PREF_KEY_OPEN_PLUGIN_TABS , null )
434+ } ? : return @launch
402435
403- val tabIds = Gson ().fromJson(json, Array <String >::class .java)?.toList() ? : return
404- Log .d(" EditorHandlerActivity" , " Restoring plugin tabs: $tabIds " )
436+ // Decoding the cached JSON off the main thread avoids a UI stall on startup.
437+ val tabIds = withContext(Dispatchers .Default ) {
438+ Gson ().fromJson(json, Array <String >::class .java)?.toList()
439+ } ? : return @launch
440+ Log .d(" EditorHandlerActivity" , " Restoring plugin tabs: $tabIds " )
405441
406- tabIds.forEach { tabId ->
407- if (! pluginTabIndices.containsKey(tabId)) {
408- selectPluginTabById(tabId)
442+ // Tab selection touches UI state, so keep it on the main thread.
443+ tabIds.forEach { tabId ->
444+ if (! pluginTabIndices.containsKey(tabId)) {
445+ selectPluginTabById(tabId)
446+ }
409447 }
410- }
411448
412- prefs.putString(PREF_KEY_OPEN_PLUGIN_TABS , null )
413- } catch (e: Exception ) {
414- Log .e(" EditorHandlerActivity" , " Failed to restore plugin tabs" , e)
449+ withContext(Dispatchers .IO ) { prefs.putString(PREF_KEY_OPEN_PLUGIN_TABS , null ) }
450+ } catch (e: Exception ) {
451+ Log .e(" EditorHandlerActivity" , " Failed to restore plugin tabs" , e)
452+ }
415453 }
416454 }
417455
@@ -1122,6 +1160,49 @@ open class EditorHandlerActivity :
11221160 showPluginCrashDialog(event)
11231161 }
11241162
1163+ @Subscribe(threadMode = ThreadMode .MAIN )
1164+ fun onPreferenceChanged (event : PreferenceChangeEvent ) {
1165+ if (event.key == EditorPreferences .FONT_SIZE ) {
1166+ syncPluginUiFontSize()
1167+ }
1168+ }
1169+
1170+ private fun syncPluginUiFontSize () {
1171+ val scale = EditorPreferences .editorFontScale
1172+ if (scale == lastAppliedPluginFontScale) {
1173+ return
1174+ }
1175+ lastAppliedPluginFontScale = scale
1176+
1177+ val pluginFragments = mutableListOf<Fragment >()
1178+ collectPluginFragments(supportFragmentManager, pluginFragments)
1179+ pluginFragments.forEach { fragment ->
1180+ fragment.view?.let { applyPluginFontScale(it, scale) }
1181+ }
1182+ }
1183+
1184+ private fun isPluginFragment (fragment : Fragment ): Boolean =
1185+ fragment.javaClass.classLoader != = javaClass.classLoader
1186+
1187+ private fun applyPluginFontScale (root : View , scale : Float ) {
1188+ root.forEachViewRecursively { view ->
1189+ if (view is TextView ) {
1190+ val baseSize = pluginTextBaseSizes.getOrPut(view) { view.textSize }
1191+ view.setTextSize(TypedValue .COMPLEX_UNIT_PX , baseSize * scale)
1192+ }
1193+ }
1194+ }
1195+
1196+ private fun collectPluginFragments (manager : FragmentManager , into : MutableList <Fragment >) {
1197+ manager.fragments.forEach { fragment ->
1198+ if (isPluginFragment(fragment)) {
1199+ into.add(fragment)
1200+ } else {
1201+ collectPluginFragments(fragment.childFragmentManager, into)
1202+ }
1203+ }
1204+ }
1205+
11251206 private fun showPluginCrashDialog (event : PluginCrashedEvent ) {
11261207 val dialogView = layoutInflater.inflate(R .layout.dialog_plugin_crash, null )
11271208 dialogView.findViewById<TextView >(R .id.plugin_crash_message).text =
0 commit comments