From a11c4607b4e9360dade9c1d97a0f7e765bd0dd29 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Fri, 27 Oct 2023 20:04:41 +0530 Subject: [PATCH] fix: improve options menu inflation and invalidation in editor activity This commits improves the options menu inflation and invalidation logic in the EditorActivity. The options menu is now properly invalidated in onPrepareOptionsMenu and inflated only when required. Also, the options menu is now lazily invalidated, cancelling all the previous invalidation calls in order to prevent re-inflating the options menu frequently. --- .../internal/DefaultActionsRegistry.kt | 25 +++-- .../activities/editor/BaseEditorActivity.kt | 20 ++++ .../editor/EditorHandlerActivity.kt | 93 +++++++++++++------ .../projects/models/ActiveDocument.kt | 6 +- 4 files changed, 97 insertions(+), 47 deletions(-) diff --git a/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt b/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt index 1b3c18da1..05e3d9a9e 100644 --- a/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt +++ b/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt @@ -16,7 +16,6 @@ */ package com.itsaky.androidide.actions.internal -import android.content.Context import android.view.Menu import android.view.MenuItem import com.google.auto.service.AutoService @@ -167,24 +166,24 @@ class DefaultActionsRegistry : ActionsRegistry() { item.icon = action.icon?.apply { colorFilter = action.createColorFilter(data) + alpha = if (action.enabled) 255 else 76 } - if (item.icon != null) { - item.icon!!.alpha = if (action.enabled) 255 else 76 - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) - } else { - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) - } - - val showAsAction = action.getShowAsActionFlags(data) - if (showAsAction != -1) { - item.setShowAsAction(showAsAction) + var showAsAction = action.getShowAsActionFlags(data) + if (showAsAction == -1) { + showAsAction = if (action.icon != null) { + MenuItem.SHOW_AS_ACTION_IF_ROOM + } else { + MenuItem.SHOW_AS_ACTION_NEVER + } } if (!action.enabled) { - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + showAsAction = MenuItem.SHOW_AS_ACTION_NEVER } + item.setShowAsAction(showAsAction) + action.createActionView(data)?.let { item.actionView = it } if (action !is ActionMenu) { @@ -195,7 +194,7 @@ class DefaultActionsRegistry : ActionsRegistry() { } /** Executes the given action item with the given */ - fun executeAction(action: ActionItem, data: ActionData) : Job { + fun executeAction(action: ActionItem, data: ActionData): Job { val onMainThread = action.requiresUIThread val context = if (onMainThread) Dispatchers.Main.immediate else Dispatchers.Default return actionsCoroutineScope.launch(context) { diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt index ef5513193..c26706b7f 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt @@ -134,8 +134,12 @@ abstract class BaseEditorActivity : } } + private var optionsMenuInvalidator: Runnable? = null + companion object { + private const val OPTIONS_MENU_INVALIDATION_DELAY = 150L + const val EDITOR_CONTAINER_SCALE_FACTOR = 0.87f const val KEY_BOTTOM_SHEET_SHOWN = "editor_bottomSheetShown" const val KEY_PROJECT_PATH = "saved_projectPath" @@ -154,6 +158,12 @@ abstract class BaseEditorActivity : internal abstract fun doConfirmProjectClose() protected open fun preDestroy() { + optionsMenuInvalidator?.also { + ThreadUtils.getMainHandler().removeCallbacks(it) + } + + optionsMenuInvalidator = null + installationCallback?.destroy() installationCallback = null @@ -199,6 +209,8 @@ abstract class BaseEditorActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + this.optionsMenuInvalidator = Runnable { super.invalidateOptionsMenu() } + registerLanguageServers() if (savedInstanceState != null && savedInstanceState.containsKey(KEY_PROJECT_PATH)) { @@ -260,6 +272,14 @@ abstract class BaseEditorActivity : super.onSaveInstanceState(outState) } + override fun invalidateOptionsMenu() { + val mainHandler = ThreadUtils.getMainHandler() + optionsMenuInvalidator?.also { + mainHandler.removeCallbacks(it) + mainHandler.postDelayed(it, OPTIONS_MENU_INVALIDATION_DELAY) + } + } + override fun onTabSelected(tab: Tab) { val position = tab.position editorViewModel.displayedFileIndex = position diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt index 0bfee85ef..87afa2f6e 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt @@ -23,6 +23,7 @@ import android.os.Bundle import android.text.TextUtils import android.util.SparseArray import android.view.Menu +import android.view.MenuItem import android.view.ViewGroup.LayoutParams import androidx.appcompat.view.menu.MenuBuilder import androidx.core.util.forEach @@ -64,8 +65,6 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import java.io.File import java.util.concurrent.atomic.AtomicBoolean -import kotlin.collections.component1 -import kotlin.collections.component2 import kotlin.collections.set /** @@ -138,14 +137,6 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } } - @SuppressLint("RestrictedApi") - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - if (menu is MenuBuilder) { - menu.setOptionalIconsVisible(true) - } - return true - } - override fun onPause() { super.onPause() @@ -210,10 +201,72 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } override fun onPrepareOptionsMenu(menu: Menu): Boolean { - ensureToolbarMenu(menu) + prepareOptionsMenu(menu) return true } + @SuppressLint("RestrictedApi") + override fun onCreateOptionsMenu(menu: Menu): Boolean { + if (menu is MenuBuilder) { + menu.setOptionalIconsVisible(true) + } + + val data = createToolbarActionData() + getInstance().fillMenu(FillMenuParams(data, EDITOR_TOOLBAR, menu)) + return true + } + + open fun prepareOptionsMenu(menu: Menu) { + val data = createToolbarActionData() + val actions = getInstance().getActions(EDITOR_TOOLBAR) + actions.forEach { (_, action) -> + menu.findItem(action.itemId)?.let { item -> + action.prepare(data) + + item.isVisible = action.visible + item.isEnabled = action.enabled + item.title = action.label + + item.icon = action.icon?.apply { + colorFilter = action.createColorFilter(data) + alpha = if (action.enabled) 255 else 76 + } + + var showAsAction = action.getShowAsActionFlags(data) + if (showAsAction == -1) { + showAsAction = if (action.icon != null) { + MenuItem.SHOW_AS_ACTION_IF_ROOM + } else { + MenuItem.SHOW_AS_ACTION_NEVER + } + } + + if (!action.enabled) { + showAsAction = MenuItem.SHOW_AS_ACTION_NEVER + } + + item.setShowAsAction(showAsAction) + + action.createActionView(data)?.let { item.actionView = it } + } + } + binding.editorToolbar.updateMenuDisplay() + } + + private fun createToolbarActionData(): ActionData { + val data = ActionData() + val currentEditor = getCurrentEditor() + + data.put(Context::class.java, this) + data.put(CodeEditorView::class.java, currentEditor) + + if (currentEditor != null) { + data.put(IDEEditor::class.java, currentEditor.editor) + data.put(File::class.java, currentEditor.file) + } + return data + } + override fun getCurrentEditor(): CodeEditorView? { return if (editorViewModel.getCurrentFileIndex() != -1) { getEditorAtIndex(editorViewModel.getCurrentFileIndex()) @@ -485,24 +538,6 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } } - open fun ensureToolbarMenu(menu: Menu) { - menu.clear() - - val data = ActionData() - val currentEditor = getCurrentEditor() - - data.put(Context::class.java, this) - data.put(CodeEditorView::class.java, currentEditor) - - if (currentEditor != null) { - data.put(IDEEditor::class.java, currentEditor.editor) - data.put(File::class.java, currentEditor.file) - } - - getInstance().fillMenu(FillMenuParams(data, EDITOR_TOOLBAR, menu)) - binding.editorToolbar.updateMenuDisplay() - } - override fun closeAll(runAfter: () -> Unit) { val count = editorViewModel.getOpenedFileCount() val unsavedFiles = diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/models/ActiveDocument.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/models/ActiveDocument.kt index 53ff36aee..5f23eeb68 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/models/ActiveDocument.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/models/ActiveDocument.kt @@ -17,15 +17,10 @@ package com.itsaky.androidide.projects.models -import com.itsaky.androidide.eventbus.events.editor.ChangeType -import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent -import com.itsaky.androidide.utils.ILogger import java.io.BufferedInputStream import java.io.BufferedReader -import java.io.Closeable import java.nio.file.Path import java.time.Instant -import java.util.concurrent.Semaphore /** * A document that is opened in the editor. @@ -38,6 +33,7 @@ open class ActiveDocument( var modified: Instant, content: String = "" ) { + var content: String = content internal set