diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d7d1b948d2..47286c196c 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -93,7 +93,7 @@
android:theme="@style/Theme.AndroidIDE" />
val lastKnownTimestamp = fileTimestamps[file.absolutePath] ?: return@forEach
val currentTimestamp = file.lastModified()
- // If the file on disk is newer.
if (currentTimestamp > lastKnownTimestamp) {
val newContent = runCatching { file.readText() }.getOrNull() ?: return@forEach
withContext(Dispatchers.Main) {
- // If the editor for the new file exists AND has no unsaved changes...
val editorView = getEditorForFile(file) ?: return@withContext
if (editorView.isModified) return@withContext
+ val ideEditor = editorView.editor ?: return@withContext
- editorView.editor?.setText(newContent)
+ ideEditor.setText(newContent)
editorView.markAsSaved()
updateTabs()
}
@@ -341,12 +347,19 @@ open class EditorHandlerActivity :
prefs.getString(PREF_KEY_OPEN_FILES_CACHE, null)
} ?: return@launch
+ if (editorViewModel.getOpenedFileCount() > 0) {
+ // Returning to an in-memory session (e.g. after onPause/onStop). Replaying the
+ // snapshot would be redundant and could interfere with dirty buffers and undo.
+ withContext(Dispatchers.IO) { prefs.putString(PREF_KEY_OPEN_FILES_CACHE, null) }
+ return@launch
+ }
+
val cache = withContext(Dispatchers.Default) {
Gson().fromJson(jsonCache, OpenedFilesCache::class.java)
}
onReadOpenedFilesCache(cache)
- // Clear the preference so it's only loaded once on startup
+ // Clear the preference so it's only loaded once per cold restore
withContext(Dispatchers.IO) { prefs.putString(PREF_KEY_OPEN_FILES_CACHE, null) }
} catch (err: Throwable) {
log.error("Failed to reopen recently opened files", err)
@@ -747,6 +760,11 @@ open class EditorHandlerActivity :
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
+ val safeContent = contentOrNull ?: return
+ for (i in 0 until safeContent.editorContainer.childCount) {
+ (safeContent.editorContainer.getChildAt(i) as? CodeEditorView)?.reapplyEditorDisplayPreferences()
+ }
+
getCurrentEditor()?.editor?.apply {
doOnNextLayout {
cursor?.let { c -> ensurePositionVisible(c.leftLine, c.leftColumn, true) }
@@ -1069,17 +1087,20 @@ open class EditorHandlerActivity :
nameBuilder.addPath(it, it.path)
}
- for (index in 0 until content.tabs.tabCount) {
- val file = files.getOrNull(index) ?: continue
+ for (tabPos in 0 until content.tabs.tabCount) {
+ if (isPluginTab(tabPos)) continue
+ val fileIndex = getFileIndexForTabPosition(tabPos)
+ if (fileIndex < 0) continue
+ val file = files.getOrNull(fileIndex) ?: continue
val count = dupliCount[file.name] ?: 0
- val isModified = getEditorAtIndex(index)?.isModified ?: false
+ val isModified = getEditorAtIndex(fileIndex)?.isModified ?: false
var name = if (count > 1) nameBuilder.getShortPath(file) else file.name
if (isModified) {
name = "*$name"
}
- names[index] = name to FileExtension.Factory.forFile(file, file.isDirectory).icon
+ names[tabPos] = name to FileExtension.Factory.forFile(file, file.isDirectory).icon
}
withContext(Dispatchers.Main) {
diff --git a/app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt
index ba03fd8f4f..e14260035f 100644
--- a/app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt
+++ b/app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt
@@ -20,12 +20,15 @@ package com.itsaky.androidide.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.GestureDetector
+import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager
-import com.itsaky.androidide.databinding.FragmentRecyclerviewBinding
+import androidx.viewbinding.ViewBinding
+import com.itsaky.androidide.R
import com.itsaky.androidide.idetooltips.TooltipManager
/**
@@ -34,7 +37,7 @@ import com.itsaky.androidide.idetooltips.TooltipManager
* @author Akash Yadav
*/
abstract class RecyclerViewFragment> :
- EmptyStateFragment(FragmentRecyclerviewBinding::inflate) {
+ EmptyStateFragment(FragmentRecyclerviewManualBinding::inflate) {
protected abstract val fragmentTooltipTag: String?
private var unsavedAdapter: A? = null
@@ -86,7 +89,7 @@ abstract class RecyclerViewFragment> :
* Sets up the recycler view in the fragment.
*/
protected open fun onSetupRecyclerView() {
- binding.root.apply {
+ binding.list.apply {
layoutManager = onCreateLayoutManager()
adapter = unsavedAdapter ?: onCreateAdapter()
}
@@ -107,7 +110,7 @@ abstract class RecyclerViewFragment> :
onSetupRecyclerView()
- binding.root.addOnItemTouchListener(touchListener)
+ binding.list.addOnItemTouchListener(touchListener)
unsavedAdapter = null
@@ -123,7 +126,7 @@ abstract class RecyclerViewFragment> :
* Set the adapter for the [RecyclerView].
*/
fun setAdapter(adapter: A) {
- _binding?.root?.let { list -> list.adapter = adapter } ?: run { unsavedAdapter = adapter }
+ _binding?.list?.let { list -> list.adapter = adapter } ?: run { unsavedAdapter = adapter }
if (isAdded && view != null) {
checkIsEmpty()
}
@@ -142,6 +145,33 @@ abstract class RecyclerViewFragment> :
private fun checkIsEmpty() {
if (!isAdded || isDetached) return
- isEmpty = _binding?.root?.adapter?.itemCount == 0
+ isEmpty = _binding?.list?.adapter?.itemCount == 0
+ }
+}
+
+/**
+ * Manual [ViewBinding] for [R.layout.fragment_recyclerview] so annotation processors (kapt) do not
+ * depend on generated `FragmentRecyclerviewBinding` during stub analysis.
+ *
+ * Public (not internal/file-private): [RecyclerViewFragment] is public and Kotlin forbids a public
+ * class from using a non-public type as a [EmptyStateFragment] type argument.
+ *
+ * [getRoot] returns [RecyclerView] (covariant override), matching generated view binding so
+ * subclasses can use `binding.root.adapter` and other [RecyclerView] APIs.
+ */
+class FragmentRecyclerviewManualBinding(
+ val list: RecyclerView,
+) : ViewBinding {
+ override fun getRoot(): RecyclerView = list
+
+ companion object {
+ fun inflate(
+ inflater: LayoutInflater,
+ parent: ViewGroup?,
+ attachToParent: Boolean,
+ ): FragmentRecyclerviewManualBinding {
+ val root = inflater.inflate(R.layout.fragment_recyclerview, parent, false) as RecyclerView
+ return FragmentRecyclerviewManualBinding(root)
+ }
}
}
diff --git a/app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt b/app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt
index f33540f1ad..3a5add1edb 100644
--- a/app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt
+++ b/app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt
@@ -567,6 +567,15 @@ class CodeEditorView(
onPinLineNumbersPrefChanged()
}
+ /**
+ * Re-applies display-related preferences (font size, typeface, flags) after a configuration change
+ * such as system font scale, so the editor activity can handle `fontScale` without being recreated.
+ */
+ fun reapplyEditorDisplayPreferences() {
+ if (_binding == null) return
+ configureEditorIfNeeded()
+ }
+
private fun onMagnifierPrefChanged() {
binding.editor.getComponent(Magnifier::class.java).isEnabled =
EditorPreferences.useMagnifier