diff --git a/.idea/ktfmt.xml b/.idea/ktfmt.xml new file mode 100644 index 000000000..82c58afc2 --- /dev/null +++ b/.idea/ktfmt.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 8e4f71cb0..30b6c7c42 100755 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -30,11 +30,14 @@ + + + @@ -127,6 +130,7 @@ + diff --git a/actions/.gitignore b/actions/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/actions/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/actions/build.gradle b/actions/build.gradle new file mode 100644 index 000000000..955c6734c --- /dev/null +++ b/actions/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion rootProject.ext.compileSdk + buildToolsVersion rootProject.ext.buildTools + + defaultConfig { + minSdkVersion rootProject.ext.minSdk + targetSdkVersion rootProject.ext.targetSdk + } + + buildFeatures.viewBinding true + + compileOptions { + sourceCompatibility rootProject.ext.javaSourceVersion + targetCompatibility rootProject.ext.javaTargetVersion + } + + buildTypes { + release { + minifyEnabled true + } + } +} + +dependencies { + implementation project(':common') + implementation common.editor + implementation common.utilcode + + implementation "androidx.core:core-ktx:1.7.0" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() +} diff --git a/actions/consumer-rules.pro b/actions/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/actions/proguard-rules.pro b/actions/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/actions/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/actions/src/androidTest/java/com/itsaky/androidide/actions/ExampleInstrumentedTest.java b/actions/src/androidTest/java/com/itsaky/androidide/actions/ExampleInstrumentedTest.java new file mode 100644 index 000000000..19f74f83b --- /dev/null +++ b/actions/src/androidTest/java/com/itsaky/androidide/actions/ExampleInstrumentedTest.java @@ -0,0 +1,43 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext () { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation ().getTargetContext (); + assertEquals ("com.itsaky.androidide.actions.test", appContext.getPackageName ()); + } +} \ No newline at end of file diff --git a/actions/src/main/AndroidManifest.xml b/actions/src/main/AndroidManifest.xml new file mode 100644 index 000000000..89537cbda --- /dev/null +++ b/actions/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/actions/src/main/java/com/itsaky/androidide/actions/ActionData.java b/actions/src/main/java/com/itsaky/androidide/actions/ActionData.java new file mode 100644 index 000000000..f305c5256 --- /dev/null +++ b/actions/src/main/java/com/itsaky/androidide/actions/ActionData.java @@ -0,0 +1,54 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @author Akash Yadav + */ +public class ActionData { + + private final Map, Object> instances = new HashMap<>(); + + public void put(@NonNull Class type, @Nullable T object) { + Objects.requireNonNull(type); + this.instances.put(type, object); + } + + @Nullable + public T get(@NonNull Class type) { + Objects.requireNonNull(type); + + final var object = this.instances.get(type); + if (object == null) { + return null; + } + + return type.cast(object); + } + + public void clear() { + this.instances.clear(); + } +} diff --git a/actions/src/main/java/com/itsaky/androidide/actions/ActionItem.kt b/actions/src/main/java/com/itsaky/androidide/actions/ActionItem.kt new file mode 100644 index 000000000..8d1ab553d --- /dev/null +++ b/actions/src/main/java/com/itsaky/androidide/actions/ActionItem.kt @@ -0,0 +1,82 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions + +import android.graphics.drawable.Drawable + +/** + * An action that can be registered using the [ActionsRegistry] + * [com.itsaky.androidide.actions.ActionsRegistry] + * @author Akash Yadav + */ +interface ActionItem { + + val id: String + var label: String + var visible: Boolean + var enabled: Boolean + var icon: Drawable? + var requiresUIThread: Boolean + var location: Location + + /** + * Prepare the action. Subclasses can modify the visual properties of this action here. + * @param data The data containing various information about the event. + */ + fun prepare(data: ActionData) + + /** + * Execute the action. The action executed in a background thread by default. + * + * @param data The data containing various information about the event. + * @return `true` if this action was executed successfully, `false` otherwise. + */ + fun execAction(data: ActionData): Boolean + + /** + * Called just after the [execAction] method executes **successfully** (i.e. returns `true`). + * Subclasses are free to do UI related work here as this method is called on UI thread. + * + * @param data The data containing various information about the event. + */ + fun postExec(data: ActionData, execResult: Boolean) {} + + /** Location where an action item will be shown. */ + enum class Location(val id: String) { + + /** Location marker for action items shown in editor activity's toolbar. */ + EDITOR_TOOLBAR("editor_toolbar"), + + /** Location marker for action items shown in editor's text action menu. */ + EDITOR_TEXT_ACTIONS("editor_text_actions"), + + /** + * Location marker for action items shown in 'Code actions' submenu in editor's text action + * menu. + */ + EDITOR_CODE_ACTIONS("editor_code_actions"); + + override fun toString(): String { + return id + } + + fun forId(id: String): Location { + return values().first { it.id == id } + } + } +} diff --git a/actions/src/main/java/com/itsaky/androidide/actions/ActionMenu.kt b/actions/src/main/java/com/itsaky/androidide/actions/ActionMenu.kt new file mode 100644 index 000000000..f151c66bb --- /dev/null +++ b/actions/src/main/java/com/itsaky/androidide/actions/ActionMenu.kt @@ -0,0 +1,36 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions + +/** + * An action menu is an action which can contain child actions. + * @author Akash Yadav + */ +interface ActionMenu : ActionItem { + + val children: MutableSet + + fun addAction(action: ActionItem) = children.add(action) + + fun removeAction(action: ActionItem) = children.remove(action) + + /** Action menus are not supposed to perform any action */ + override fun execAction(data: ActionData): Boolean { + return false + } +} diff --git a/actions/src/main/java/com/itsaky/androidide/actions/ActionsRegistry.kt b/actions/src/main/java/com/itsaky/androidide/actions/ActionsRegistry.kt new file mode 100644 index 000000000..7adc0a63c --- /dev/null +++ b/actions/src/main/java/com/itsaky/androidide/actions/ActionsRegistry.kt @@ -0,0 +1,80 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ +package com.itsaky.androidide.actions + +import android.view.Menu +import com.itsaky.androidide.actions.internal.DefaultActionsRegistry + +/** @author Akash Yadav */ +abstract class ActionsRegistry { + + companion object { + + private var instance: ActionsRegistry? = null + + @JvmStatic + fun getInstance(): ActionsRegistry { + if (instance == null) { + instance = DefaultActionsRegistry() + } + + return instance as ActionsRegistry + } + } + + /** + * Register the given action. + * + * @param action The action to register. Must not be `null`. + * @return `true` if the action was registered successfully. `false` otherwise. + */ + abstract fun registerAction(action: ActionItem): Boolean + + /** + * Unregister the given action. + * + * @param action The action to unregister. Must not be `null`. + * @return `true` if the action was unregistered successfully. `false` otherwise. + */ + abstract fun unregisterAction(action: ActionItem): Boolean + + /** + * Unregister the action with the given ID. + * + * @param id The ID of action to unregister. Must not be `null`. + * @return `true` if the action was unregistered successfully. `false` otherwise. + */ + abstract fun unregisterAction(id: String): Boolean + + /** + * Find the action with the given ID. + * + * @param id The ID of the action to find. + * @return The found action or `null`. + */ + abstract fun findAction(location: ActionItem.Location, id: String): ActionItem? + + /** + * Fill the given menu with the registered actions. + * + * Subclasses must first call [ActionItem.prepare] to update the action. + */ + abstract fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) + + /** Clear all the registered actions. */ + abstract fun clearActions(location: ActionItem.Location) +} 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 new file mode 100644 index 000000000..f4676cd7d --- /dev/null +++ b/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt @@ -0,0 +1,150 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ +package com.itsaky.androidide.actions.internal + +import android.view.Menu +import android.view.MenuItem +import com.blankj.utilcode.util.ThreadUtils +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.actions.ActionMenu +import com.itsaky.androidide.actions.ActionsRegistry +import com.itsaky.androidide.utils.Logger +import java.util.concurrent.CompletableFuture + +/** + * Default implementation for the [ActionsRegistry] + * + * @author Akash Yadav + */ +class DefaultActionsRegistry : ActionsRegistry() { + + private val log = Logger.newInstance("DefaultActionsRegistry") + private val actions: MutableMap> = mutableMapOf() + + private fun getActions(location: ActionItem.Location): MutableMap { + if (actions[location.id] == null) { + actions[location.id] = mutableMapOf() + } + + return actions[location.id]!! + } + + override fun registerAction(action: ActionItem): Boolean { + val actionsAtLocation = getActions(action.location) + actionsAtLocation[action.id] = action + return true + } + + override fun unregisterAction(action: ActionItem): Boolean { + val actions = getActions(action.location) + val older = actions.remove(action.id) + if (older != null) { + return true + } + + return false + } + + override fun unregisterAction(id: String): Boolean { + for (locations in this.actions.values) { + val older = locations.remove(id) + if (older != null) { + return true + } + } + + return false + } + + override fun findAction(location: ActionItem.Location, id: String): ActionItem? = + getActions(location)[id] + + override fun clearActions(location: ActionItem.Location) = getActions(location).clear() + + override fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) { + val actions = getActions(location) + + for (action in actions.values) { + action.prepare(data) + + if (!action.visible) { + continue + } + addActionToMenu(menu, action, data) + } + } + + private fun addActionToMenu(menu: Menu, action: ActionItem, data: ActionData) { + val item: MenuItem = + if (action is ActionMenu) { + val sub = menu.addSubMenu(action.label) + + for (subItem in action.children) { + subItem.prepare(data) + if (subItem.visible) { + addActionToMenu(sub, subItem, data) + } + } + + sub.item + } else { + menu.add(action.label) + } + + item.isEnabled = action.enabled + item.icon = action.icon + + 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) + } + + if (action !is ActionMenu) { + item.setOnMenuItemClickListener { + if (action.requiresUIThread) { + ThreadUtils.runOnUiThread { + val result = action.execAction(data) + action.postExec(data, result) + } + } else { + execInBackground(action, data, it) + } + + true + } + } + } + + private fun execInBackground(action: ActionItem, data: ActionData, it: MenuItem) { + val start = System.currentTimeMillis() + CompletableFuture.supplyAsync { action.execAction(data) }.whenComplete { result, error -> + if (result == null || !result || error != null) { + log.error( + "An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms", + error) + } else { + log.info( + "Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms") + } + + ThreadUtils.runOnUiThread { action.postExec(data, result ?: false) } + } + } +} diff --git a/actions/src/test/java/com/itsaky/androidide/actions/ExampleUnitTest.java b/actions/src/test/java/com/itsaky/androidide/actions/ExampleUnitTest.java new file mode 100644 index 000000000..ee7277306 --- /dev/null +++ b/actions/src/test/java/com/itsaky/androidide/actions/ExampleUnitTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect () { + assertEquals (4, 2 + 2); + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 32cf281de..8217f84f5 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -122,6 +122,7 @@ dependencies { implementation project(':xml-inflater') implementation project(':lexers') implementation project(':common') + implementation project(':actions') implementation project(':lsp:api') implementation project(':lsp:java') diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index db495d6df..e0fe4e33d 100755 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -21,6 +21,7 @@ package com.itsaky.androidide; import android.annotation.SuppressLint; import android.app.ProgressDialog; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.drawable.GradientDrawable; @@ -58,6 +59,7 @@ import com.blankj.utilcode.util.IntentUtils; import com.blankj.utilcode.util.KeyboardUtils; import com.blankj.utilcode.util.ResourceUtils; import com.blankj.utilcode.util.SizeUtils; +import com.blankj.utilcode.util.ThreadUtils; import com.blankj.utilcode.util.ThrowableUtils; import com.blankj.utilcode.util.ZipUtils; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -65,6 +67,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.navigation.NavigationView; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; +import com.itsaky.androidide.actions.ActionData; +import com.itsaky.androidide.actions.ActionsRegistry; import com.itsaky.androidide.adapters.DiagnosticsAdapter; import com.itsaky.androidide.adapters.EditorBottomSheetTabAdapter; import com.itsaky.androidide.adapters.SearchListAdapter; @@ -79,6 +83,7 @@ import com.itsaky.androidide.fragments.sheets.OptionsListFragment; import com.itsaky.androidide.fragments.sheets.ProgressSheet; import com.itsaky.androidide.fragments.sheets.TextSheetFragment; import com.itsaky.androidide.handlers.BuildServiceHandler; +import com.itsaky.androidide.handlers.EditorActivityActions; import com.itsaky.androidide.handlers.FileOptionsHandler; import com.itsaky.androidide.handlers.IDEHandler; import com.itsaky.androidide.interfaces.DiagnosticClickListener; @@ -154,24 +159,6 @@ public class EditorActivity extends StudioActivity private static final int ACTION_ID_OTHERS = 101; private static final int ACTION_ID_ALL = 102; private static final Logger LOG = Logger.newInstance("EditorActivity"); - /** - * MenuItem(s) that are related to the build process - * - *

These items will be disabled once the build process starts and will be enabled after the - * execution - */ - private final int[] BUILD_IDS = { - R.id.menuEditor_quickRun, - R.id.menuEditor_runDebug, - R.id.menuEditor_runRelease, - R.id.menuEditor_runBuild, - R.id.menuEditor_runBundle, - R.id.menuEditor_runClean, - R.id.menuEditor_runCleanBuild, - R.id.menuEditor_lint, - R.id.menuEditor_lintDebug, - R.id.menuEditor_lintRelease - }; private ActivityEditorBinding mBinding; private LayoutDiagnosticInfoBinding mDiagnosticInfoBinding; @@ -234,6 +221,9 @@ public class EditorActivity extends StudioActivity registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), this::onGetUIDesignerResult); + + EditorActivityActions.register(this); + ensureToolbarMenu(); } private void setupDrawerToggle() { @@ -354,14 +344,18 @@ public class EditorActivity extends StudioActivity @Override protected void onPause() { - dispatchOnPauseToEditors(); super.onPause(); + dispatchOnPauseToEditors(); + EditorActivityActions.clear(); } @Override protected void onResume() { super.onResume(); + EditorActivityActions.register(this); + ensureToolbarMenu(); + try { checkForCompilerModule(); dispatchOnResumeToEditors(); @@ -383,88 +377,90 @@ public class EditorActivity extends StudioActivity super.onDestroy(); } - @Override - @SuppressLint("AlwaysShowAction") - public boolean onPrepareOptionsMenu(Menu menu) { - for (int id : BUILD_IDS) { - MenuItem item = menu.findItem(id); - if (item != null) { - boolean enabled = getBuildService() != null && !getBuildService().isBuilding(); - item.setEnabled(enabled); - item.getIcon().setAlpha(enabled ? 255 : 76); - } - } - - MenuItem run1 = menu.findItem(R.id.menuEditor_quickRun); - MenuItem run2 = menu.findItem(R.id.menuEditor_run); - MenuItem undo = menu.findItem(R.id.menuEditor_undo); - MenuItem redo = menu.findItem(R.id.menuEditor_redo); - MenuItem save = menu.findItem(R.id.menuEditor_save); - MenuItem def = menu.findItem(R.id.menuEditor_gotoDefinition); - MenuItem ref = menu.findItem(R.id.menuEditor_findReferences); - MenuItem comment = menu.findItem(R.id.menuEditor_commentLine); - MenuItem uncomment = menu.findItem(R.id.menuEditor_uncommentLine); - MenuItem findFile = menu.findItem(R.id.menuEditor_findFile); - MenuItem viewLayout = menu.findItem(R.id.menuEditor_viewLayout); - - if (KeyboardUtils.isSoftInputVisible(this)) { - run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - undo.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - - viewLayout.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - } else { - run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - undo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - - viewLayout.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - } - - final var editor = getCurrentEditor(); - final var file = editor != null ? editor.getFile() : null; - final var notNull = editor != null && file != null; - final var isJava = notNull && file.getName().endsWith(".java"); - final var isXml = notNull && file.getName().endsWith(".xml"); - final var filesModified = notNull && mViewModel != null && mViewModel.areFilesModified(); - final var isLayout = - isXml - && file.getParentFile() != null - && Pattern.compile(FileOptionsHandler.LAYOUT_RES_PATH_REGEX) - .matcher(file.getParentFile().getAbsolutePath()) - .matches(); - final var nullableAlpha = notNull ? 255 : 76; - final var javaFileAlpha = isJava ? 255 : 76; - final var layoutFileAlpha = isLayout ? 255 : 76; - final var saveAlpha = filesModified ? 255 : 76; - - undo.setEnabled(notNull); - redo.setEnabled(notNull); - save.setEnabled(filesModified); - comment.setEnabled(notNull); - uncomment.setEnabled(notNull); - findFile.setEnabled(notNull); - def.setEnabled(isJava); - ref.setEnabled(isJava); - - undo.getIcon().setAlpha(nullableAlpha); - redo.getIcon().setAlpha(nullableAlpha); - save.getIcon().setAlpha(saveAlpha); - comment.getIcon().setAlpha(nullableAlpha); - uncomment.getIcon().setAlpha(nullableAlpha); - findFile.getIcon().setAlpha(nullableAlpha); - def.getIcon().setAlpha(javaFileAlpha); - ref.getIcon().setAlpha(javaFileAlpha); - - viewLayout.setEnabled(isLayout); - viewLayout.getIcon().setAlpha(layoutFileAlpha); - viewLayout.setShowAsActionFlags( - isLayout ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER); - - return true; - } + // @Override + // @SuppressLint("AlwaysShowAction") + // public boolean onPrepareOptionsMenu(Menu menu) { + // for (int id : BUILD_IDS) { + // MenuItem item = menu.findItem(id); + // if (item != null) { + // boolean enabled = getBuildService() != null && + // !getBuildService().isBuilding(); + // item.setEnabled(enabled); + // item.getIcon().setAlpha(enabled ? 255 : 76); + // } + // } + // + // MenuItem run1 = menu.findItem(R.id.menuEditor_quickRun); + // MenuItem run2 = menu.findItem(R.id.menuEditor_run); + // MenuItem undo = menu.findItem(R.id.menuEditor_undo); + // MenuItem redo = menu.findItem(R.id.menuEditor_redo); + // MenuItem save = menu.findItem(R.id.menuEditor_save); + // MenuItem def = menu.findItem(R.id.menuEditor_gotoDefinition); + // MenuItem ref = menu.findItem(R.id.menuEditor_findReferences); + // MenuItem comment = menu.findItem(R.id.menuEditor_commentLine); + // MenuItem uncomment = menu.findItem(R.id.menuEditor_uncommentLine); + // MenuItem findFile = menu.findItem(R.id.menuEditor_findFile); + // MenuItem viewLayout = menu.findItem(R.id.menuEditor_viewLayout); + // + // if (KeyboardUtils.isSoftInputVisible(this)) { + // run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + // run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + // undo.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + // redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + // + // viewLayout.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + // } else { + // run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + // run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + // undo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + // redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + // + // viewLayout.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + // } + // + // final var editor = getCurrentEditor(); + // final var file = editor != null ? editor.getFile() : null; + // final var notNull = editor != null && file != null; + // final var isJava = notNull && file.getName().endsWith(".java"); + // final var isXml = notNull && file.getName().endsWith(".xml"); + // final var filesModified = notNull && mViewModel != null && + // mViewModel.areFilesModified(); + // final var isLayout = + // isXml + // && file.getParentFile() != null + // && Pattern.compile(FileOptionsHandler.LAYOUT_RES_PATH_REGEX) + // .matcher(file.getParentFile().getAbsolutePath()) + // .matches(); + // final var nullableAlpha = notNull ? 255 : 76; + // final var javaFileAlpha = isJava ? 255 : 76; + // final var layoutFileAlpha = isLayout ? 255 : 76; + // final var saveAlpha = filesModified ? 255 : 76; + // + // undo.setEnabled(notNull); + // redo.setEnabled(notNull); + // save.setEnabled(filesModified); + // comment.setEnabled(notNull); + // uncomment.setEnabled(notNull); + // findFile.setEnabled(notNull); + // def.setEnabled(isJava); + // ref.setEnabled(isJava); + // + // undo.getIcon().setAlpha(nullableAlpha); + // redo.getIcon().setAlpha(nullableAlpha); + // save.getIcon().setAlpha(saveAlpha); + // comment.getIcon().setAlpha(nullableAlpha); + // uncomment.getIcon().setAlpha(nullableAlpha); + // findFile.getIcon().setAlpha(nullableAlpha); + // def.getIcon().setAlpha(javaFileAlpha); + // ref.getIcon().setAlpha(javaFileAlpha); + // + // viewLayout.setEnabled(isLayout); + // viewLayout.getIcon().setAlpha(layoutFileAlpha); + // viewLayout.setShowAsActionFlags( + // isLayout ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER); + // + // return true; + // } @SuppressLint("RestrictedApi") @Override @@ -474,77 +470,21 @@ public class EditorActivity extends StudioActivity builder.setOptionalIconsVisible(true); } - getMenuInflater().inflate(R.menu.menu_editor, menu); return true; } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (getBuildService() == null) { - LOG.error("Build service is null. Cannot perform option menu actions."); - return false; - } + public void ensureToolbarMenu() { + final var menu = mBinding.editorToolbar.getMenu(); + menu.clear(); - int id = item.getItemId(); - if (id == R.id.menuEditor_runDebug || id == R.id.menuEditor_quickRun) { - getBuildServiceHandler().assembleDebug(true); - } else if (id == R.id.menuEditor_runRelease) { - getBuildService().assembleRelease(); - } else if (id == R.id.menuEditor_runClean) { - getBuildService().clean(); - } else if (id == R.id.menuEditor_runCleanBuild) { - getBuildService().cleanAndRebuild(); - } else if (id == R.id.menuEditor_runStopDaemons) { - getBuildService().stopAllDaemons(); - } else if (id == R.id.menuEditor_runBuild) { - getBuildService().build(); - } else if (id == R.id.menuEditor_runBundle) { - getBuildService().bundle(); - } else if (id == R.id.menuEditor_lint) { - getBuildService().lint(); - } else if (id == R.id.menuEditor_lintDebug) { - getBuildService().lintDebug(); - } else if (id == R.id.menuEditor_lintRelease) { - getBuildService().lintRelease(); - } else if (id == R.id.menuEditor_save) { - saveAll(true, true); - } else if (id == R.id.menuEditor_findProject) { - AlertDialog d = getFindInProjectDialog(); - if (d != null) { - d.show(); - } - } else if (id == R.id.menuEditor_viewLayout) { - previewLayout(); - } else if (id == R.id.menuEditor_daemonStatus) { - showDaemonStatus(); - } else if (id == R.id.menuEditor_files) { - if (!mBinding.editorDrawerLayout.isDrawerOpen(GravityCompat.END)) { - mBinding.editorDrawerLayout.openDrawer(GravityCompat.END); - } - } else if (getCurrentEditor() != null) { // Should be checked at last - if (id == R.id.menuEditor_undo) { - this.getCurrentEditor().undo(); - } else if (id == R.id.menuEditor_redo) { - this.getCurrentEditor().redo(); - } else if (id == R.id.menuEditor_gotoDefinition) { - this.getCurrentEditor().findDefinition(); - } else if (id == R.id.menuEditor_findReferences) { - this.getCurrentEditor().findReferences(); - } else if (id == R.id.menuEditor_commentLine) { - this.getCurrentEditor().commentLine(); - } else if (id == R.id.menuEditor_uncommentLine) { - this.getCurrentEditor().uncommentLine(); - } else if (id == R.id.menuEditor_findFile) { - this.getCurrentEditor().beginSearch(); - } else if (id == R.id.menuEditor_formatCode) { - if (!this.getCurrentEditor().getEditor().formatCodeAsync()) { - LOG.error("Unable to start format thread to format code..."); - } - } - } - - invalidateOptionsMenu(); - return true; + final var data = new ActionData(); + data.put(Context.class, EditorActivity.this); + data.put(CodeEditorView.class, getCurrentEditor()); + ActionsRegistry.getInstance() + .fillMenu( + data, + com.itsaky.androidide.actions.ActionItem.Location.EDITOR_TOOLBAR, + menu); } @Override @@ -728,7 +668,7 @@ public class EditorActivity extends StudioActivity editorView.onEditorSelected(); mViewModel.setCurrentFile(position, editorView.getFile()); refreshSymbolInput(editorView); - invalidateOptionsMenu(); + ensureToolbarMenu(); } @Override @@ -741,6 +681,10 @@ public class EditorActivity extends StudioActivity editor.ensureWindowsDismissed(); } } + + if (mViewModel.getOpenedFileCount() <= 0) { + ensureToolbarMenu(); + } } @Override @@ -928,7 +872,8 @@ public class EditorActivity extends StudioActivity modified = modified || editor.isModified(); } - mViewModel.setFilesModified(modified); + final boolean finalModified = modified; + ThreadUtils.runOnUiThread(() -> mViewModel.setFilesModified(finalModified)); } public void install(@NonNull File apk) { @@ -984,7 +929,7 @@ public class EditorActivity extends StudioActivity mBinding.editorContainer.setDisplayedChild(index); - invalidateOptionsMenu(); + ensureToolbarMenu(); try { return getEditorAtIndex(index); } catch (Throwable th) { @@ -1049,7 +994,7 @@ public class EditorActivity extends StudioActivity } mBinding.tabs.requestLayout(); - invalidateOptionsMenu(); + ensureToolbarMenu(); } public void closeAll() { @@ -1086,7 +1031,7 @@ public class EditorActivity extends StudioActivity mBinding.tabs.requestLayout(); mBinding.editorContainer.removeAllViews(); - invalidateOptionsMenu(); + ensureToolbarMenu(); if (onSaved != null) { onSaved.run(); @@ -1097,6 +1042,14 @@ public class EditorActivity extends StudioActivity } } + public boolean areFilesModified() { + return mViewModel.areFilesModified(); + } + + public EditorViewModel getViewModel() { + return mViewModel; + } + private void notifyFilesUnsaved(List unsavedEditors, Runnable invokeAfter) { //noinspection ConstantConditions final var mapped = @@ -1198,6 +1151,29 @@ public class EditorActivity extends StudioActivity return -1; } + public void previewLayout() { + try { + + if (getCurrentEditor() == null || getCurrentEditor().getFile() == null) { + LOG.error("No file is opened. Cannot preview layout."); + return; + } + + saveAll(false); + + final Intent intent = new Intent(this, DesignerActivity.class); + intent.putExtra( + DesignerActivity.KEY_LAYOUT_PATH, + getCurrentEditor().getFile().getAbsolutePath()); + intent.putStringArrayListExtra(DesignerActivity.KEY_RES_DIRS, getResourceDirectories()); + LOG.info("Launching UI Designer..."); + mUIDesignerLauncher.launch(intent); + } catch (Throwable th) { + LOG.error(getString(R.string.err_cannot_preview_layout), th); + getApp().toast(R.string.msg_cannot_preview_layout, Toaster.Type.ERROR); + } + } + ///////////////////////////////////////////////// ////////////// PRIVATE APIS ///////////////////// ///////////////////////////////////////////////// @@ -1330,29 +1306,6 @@ public class EditorActivity extends StudioActivity } } - private void previewLayout() { - try { - - if (getCurrentEditor() == null || getCurrentEditor().getFile() == null) { - LOG.error("No file is opened. Cannot preview layout."); - return; - } - - saveAll(false); - - final Intent intent = new Intent(this, DesignerActivity.class); - intent.putExtra( - DesignerActivity.KEY_LAYOUT_PATH, - getCurrentEditor().getFile().getAbsolutePath()); - intent.putStringArrayListExtra(DesignerActivity.KEY_RES_DIRS, getResourceDirectories()); - LOG.info("Launching UI Designer..."); - mUIDesignerLauncher.launch(intent); - } catch (Throwable th) { - LOG.error(getString(R.string.err_cannot_preview_layout), th); - getApp().toast(R.string.msg_cannot_preview_layout, Toaster.Type.ERROR); - } - } - @NonNull private ArrayList getResourceDirectories() { final var dirs = new ArrayList(); @@ -1504,7 +1457,7 @@ public class EditorActivity extends StudioActivity } private void onSoftInputChanged() { - invalidateOptionsMenu(); + ensureToolbarMenu(); if (KeyboardUtils.isSoftInputVisible(this)) { TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.TOP)); symbolInput.setVisibility(View.VISIBLE); @@ -1563,7 +1516,7 @@ public class EditorActivity extends StudioActivity builder.show(); } - private AlertDialog getFindInProjectDialog() { + public AlertDialog getFindInProjectDialog() { return mFindInProjectDialog == null ? createFindInProjectDialog() : mFindInProjectDialog; } diff --git a/app/src/main/java/com/itsaky/androidide/actions/BaseBuildAction.kt b/app/src/main/java/com/itsaky/androidide/actions/BaseBuildAction.kt new file mode 100644 index 000000000..e84524320 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/BaseBuildAction.kt @@ -0,0 +1,55 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions + +import com.itsaky.androidide.EditorActivity + +/** + * Marker class for actions that execute build related tasks. + * + * @author Akash Yadav + */ +abstract class BaseBuildAction : EditorActivityAction() { + + override fun prepare(data: ActionData) { + val context = getActivity(data) + if (context == null) { + visible = false + return + } else { + visible = true + } + + if (isBuildInProgress(context)) { + enabled = false + return + } else { + enabled = true + } + } + + override fun postExec(data: ActionData, execResult: Boolean) { + val context = getActivity(data) ?: return + context.ensureToolbarMenu() + } + + fun shouldPrepare() = visible && enabled + + private fun isBuildInProgress(activity: EditorActivity): Boolean = + activity.buildServiceHandler.isBuilding +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/BaseEditorAction.kt b/app/src/main/java/com/itsaky/androidide/actions/BaseEditorAction.kt new file mode 100644 index 000000000..0d133e1cb --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/BaseEditorAction.kt @@ -0,0 +1,62 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions + +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.core.content.ContextCompat +import com.itsaky.androidide.views.editor.IDEEditor + +/** @author Akash Yadav */ +abstract class BaseEditorAction : ActionItem { + + override var label: String = "" + override var visible: Boolean = true + override var enabled: Boolean = true + override var icon: Drawable? = null + override var requiresUIThread: Boolean = + true // all editor actions must be executed on UI thread + override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS + + override fun prepare(data: ActionData) { + getEditor(data) + ?: kotlin.run { + visible = false + enabled = false + return + } + + visible = true + enabled = true + } + + fun getEditor(data: ActionData): IDEEditor? { + return data.get(IDEEditor::class.java) + } + + fun getContext(data: ActionData): Context? { + val editor = getEditor(data) ?: return null + return editor.context + } + + fun tintDrawable(context: Context, drawable: Drawable): Drawable { + drawable.setTint( + ContextCompat.getColor(context, com.itsaky.androidide.R.color.primaryIconColor)) + return drawable + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/EditorActivityAction.kt b/app/src/main/java/com/itsaky/androidide/actions/EditorActivityAction.kt new file mode 100644 index 000000000..2823aed1b --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/EditorActivityAction.kt @@ -0,0 +1,39 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions + +import android.content.Context +import android.graphics.drawable.Drawable +import com.itsaky.androidide.EditorActivity + +/** @author Akash Yadav */ +abstract class EditorActivityAction : ActionItem { + + override var enabled: Boolean = true + override var visible: Boolean = true + override var icon: Drawable? = null + override var label: String = "" + override var location: ActionItem.Location = ActionItem.Location.EDITOR_TOOLBAR + + override var requiresUIThread: Boolean = false + + fun getActivity(data: ActionData): EditorActivity? { + val context = data.get(Context::class.java) ?: return null + return context as EditorActivity + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/EditorActivityActionMenu.kt b/app/src/main/java/com/itsaky/androidide/actions/EditorActivityActionMenu.kt new file mode 100644 index 000000000..44bc2ed00 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/EditorActivityActionMenu.kt @@ -0,0 +1,23 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions + +/** @author Akash Yadav */ +abstract class EditorActivityActionMenu : EditorActivityAction(), ActionMenu { + override val children: MutableSet = mutableSetOf() +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/EditorRelatedAction.kt b/app/src/main/java/com/itsaky/androidide/actions/EditorRelatedAction.kt new file mode 100644 index 000000000..928e99dd0 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/EditorRelatedAction.kt @@ -0,0 +1,34 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions + +import com.itsaky.androidide.views.editor.CodeEditorView +import java.io.File + +/** @author Akash Yadav */ +abstract class EditorRelatedAction : EditorActivityAction() { + override fun prepare(data: ActionData) { + val editor = getEditor(data) + val file = data.get(File::class.java) + + visible = editor != null && file != null + enabled = visible + } + + fun getEditor(data: ActionData): CodeEditorView? = data.get(CodeEditorView::class.java) +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/editor/CopyAction.kt b/app/src/main/java/com/itsaky/androidide/actions/editor/CopyAction.kt new file mode 100644 index 000000000..b48c24f51 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/editor/CopyAction.kt @@ -0,0 +1,42 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.editor + +import android.content.Context +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.BaseEditorAction + +/** @author Akash Yadav */ +class CopyAction() : BaseEditorAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.copy) + + val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCopyDrawable)) + icon = arr.getDrawable(0)?.let { tintDrawable(context, it) } + arr.recycle() + } + + override val id: String = "ideEditor_copy" + override fun execAction(data: ActionData): Boolean { + val editor = getEditor(data) ?: return false + editor.copyText() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/editor/CutAction.kt b/app/src/main/java/com/itsaky/androidide/actions/editor/CutAction.kt new file mode 100644 index 000000000..88e5d5ff4 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/editor/CutAction.kt @@ -0,0 +1,42 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.editor + +import android.content.Context +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.BaseEditorAction + +/** @author Akash Yadav */ +class CutAction() : BaseEditorAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.cut) + + val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCutDrawable)) + icon = arr.getDrawable(0)?.let { tintDrawable(context, it) } + arr.recycle() + } + + override val id: String = "ideEditor_cut" + override fun execAction(data: ActionData): Boolean { + val editor = getEditor(data) ?: return false + editor.cutText() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/editor/PasteAction.kt b/app/src/main/java/com/itsaky/androidide/actions/editor/PasteAction.kt new file mode 100644 index 000000000..3eeae7baa --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/editor/PasteAction.kt @@ -0,0 +1,44 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.editor + +import android.content.Context +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.BaseEditorAction + +/** @author Akash Yadav */ +class PasteAction() : BaseEditorAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.paste) + + val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModePasteDrawable)) + icon = arr.getDrawable(0)?.let { tintDrawable(context, it) } + arr.recycle() + } + + override val id: String + get() = "ideEditor_paste" + + override fun execAction(data: ActionData): Boolean { + val editor = getEditor(data) ?: return false + editor.pasteText() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/editor/SelectAllAction.kt b/app/src/main/java/com/itsaky/androidide/actions/editor/SelectAllAction.kt new file mode 100644 index 000000000..7aeed99f3 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/editor/SelectAllAction.kt @@ -0,0 +1,45 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.editor + +import android.content.Context +import android.content.res.TypedArray +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.BaseEditorAction + +/** @author Akash Yadav */ +class SelectAllAction() : BaseEditorAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.selectAll) + + val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeSelectAllDrawable)) + icon = arr.getDrawable(0)?.let { tintDrawable(context, it) } + arr.recycle() + } + + override val id: String = "ideEditor_selectAll" + + override fun execAction(data: ActionData): Boolean { + val editor = getEditor(data) ?: return false + editor.selectAll() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/etc/DaemonStatusAction.kt b/app/src/main/java/com/itsaky/androidide/actions/etc/DaemonStatusAction.kt new file mode 100644 index 000000000..e9dee8a9b --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/etc/DaemonStatusAction.kt @@ -0,0 +1,46 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.etc + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.EditorActivityAction + +/** @author Akash Yadav */ +class DaemonStatusAction() : EditorActivityAction() { + override val id: String = "editor_daemonStatus" + override var requiresUIThread: Boolean = true + + constructor(context: Context) : this() { + label = context.getString(R.string.gradle_daemon_status) + icon = ContextCompat.getDrawable(context, R.drawable.ic_info) + } + + override fun prepare(data: ActionData) { + visible = true + enabled = true + } + + override fun execAction(data: ActionData): Boolean { + val context = getActivity(data) ?: return false + context.showDaemonStatus() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/etc/FileTreeAction.kt b/app/src/main/java/com/itsaky/androidide/actions/etc/FileTreeAction.kt new file mode 100644 index 000000000..c87a5fc77 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/etc/FileTreeAction.kt @@ -0,0 +1,52 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.etc + +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.core.view.GravityCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.EditorActivityAction + +/** @author Akash Yadav */ +class FileTreeAction() : EditorActivityAction() { + + override val id: String = "editor_fileTree" + + constructor(context: Context) : this() { + label = context.getString(R.string.msg_file_tree) + icon = ContextCompat.getDrawable(context, R.drawable.ic_folder) + } + + override fun prepare(data: ActionData) { + visible = true + enabled = true + } + + override fun execAction(data: ActionData): Boolean { + val context = getActivity(data) ?: return false + + if (!context.binding.root.isDrawerOpen(GravityCompat.END)) { + context.binding.root.openDrawer(GravityCompat.END) + return true + } + + return false + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/etc/FindActionMenu.kt b/app/src/main/java/com/itsaky/androidide/actions/etc/FindActionMenu.kt new file mode 100644 index 000000000..394f4d0a8 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/etc/FindActionMenu.kt @@ -0,0 +1,46 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.etc + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.actions.ActionMenu +import com.itsaky.androidide.actions.EditorActivityAction + +/** @author Akash Yadav */ +class FindActionMenu() : EditorActivityAction(), ActionMenu { + + constructor(context: Context) : this() { + label = context.getString(R.string.menu_find) + icon = ContextCompat.getDrawable(context, R.drawable.ic_search) + + addAction(FindInFileAction(context)) + addAction(FindInProjectAction(context)) + } + + override val children: MutableSet = mutableSetOf() + override val id: String = "editor_findActions" + + override fun prepare(data: ActionData) { + visible = true + enabled = children.size > 0 + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/etc/FindInFileAction.kt b/app/src/main/java/com/itsaky/androidide/actions/etc/FindInFileAction.kt new file mode 100644 index 000000000..a5f7d8f3b --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/etc/FindInFileAction.kt @@ -0,0 +1,42 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.etc + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.EditorRelatedAction + +/** @author Akash Yadav */ +class FindInFileAction() : EditorRelatedAction() { + + override val id: String = "editor_findInFile" + override var requiresUIThread: Boolean = true + + constructor(context: Context) : this() { + label = context.getString(R.string.menu_find_file) + icon = ContextCompat.getDrawable(context, R.drawable.ic_search_file) + } + + override fun execAction(data: ActionData): Boolean { + val editor = getEditor(data) ?: return false + editor.beginSearch() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/etc/FindInProjectAction.kt b/app/src/main/java/com/itsaky/androidide/actions/etc/FindInProjectAction.kt new file mode 100644 index 000000000..5246f5c86 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/etc/FindInProjectAction.kt @@ -0,0 +1,59 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.etc + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.EditorActivityAction + +/** @author Akash Yadav */ +class FindInProjectAction() : EditorActivityAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.menu_find_project) + icon = ContextCompat.getDrawable(context, R.drawable.ic_search_project) + } + + override val id: String = "editor_findInProject" + + override fun prepare(data: ActionData) { + getActivity(data) + ?: run { + visible = false + enabled = false + return + } + + visible = true + enabled = true + } + + override fun execAction(data: ActionData): Boolean { + val context = getActivity(data) ?: return false + val dialog = context.findInProjectDialog + + return if (dialog != null) { + dialog.show() + true + } else { + false + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt b/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt new file mode 100644 index 000000000..c12826e3c --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt @@ -0,0 +1,77 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.file + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.EditorRelatedAction +import com.itsaky.androidide.app.StudioApp +import com.itsaky.androidide.utils.Logger +import com.itsaky.toaster.Toaster + +/** @author Akash Yadav */ +class SaveFileAction() : EditorRelatedAction() { + + private val log = Logger.newInstance("SaveFileAction") + + override val id: String = "editor_saveFile" + + constructor(context: Context) : this() { + label = context.getString(R.string.save) + icon = ContextCompat.getDrawable(context, R.drawable.ic_save) + } + + override fun prepare(data: ActionData) { + + val context = + getActivity(data) + ?: run { + visible = false + enabled = false + return + } + + visible = context.viewModel.openedFiles.isNotEmpty() + enabled = context.areFilesModified() + } + + override fun execAction(data: ActionData): Boolean { + val context = getActivity(data) ?: return false + + return try { + // Cannot use context.saveAll() because this.execAction is called on non-UI thread + // and saveAll call will result in UI actions + context.saveAllResult() + true + } catch (error: Throwable) { + log.error("Failed to save file", error) + false + } + } + + override fun postExec(data: ActionData, execResult: Boolean) { + if (execResult) { + StudioApp.getInstance().toast(R.string.all_saved, Toaster.Type.SUCCESS) + } else { + log.error("Failed to save file") + TODO("Create message in strings.xml") + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/lint/LintAction.kt b/app/src/main/java/com/itsaky/androidide/actions/lint/LintAction.kt new file mode 100644 index 000000000..8951cef0a --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/lint/LintAction.kt @@ -0,0 +1,41 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.lint + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.BaseBuildAction + +/** @author Akash Yadav */ +class LintAction() : BaseBuildAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.lint) + icon = ContextCompat.getDrawable(context, R.drawable.ic_lint) + } + + override val id: String = "editor_lint" + override fun execAction(data: ActionData): Boolean { + val context = getActivity(data) ?: return false + + context.buildServiceHandler.service.lint() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/lint/LintDebugAction.kt b/app/src/main/java/com/itsaky/androidide/actions/lint/LintDebugAction.kt new file mode 100644 index 000000000..28679e0cf --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/lint/LintDebugAction.kt @@ -0,0 +1,41 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.lint + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.BaseBuildAction + +/** @author Akash Yadav */ +class LintDebugAction() : BaseBuildAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.lint_debug) + icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug) + } + + override val id: String = "editor_lintDebug" + override fun execAction(data: ActionData): Boolean { + val context = getActivity(data) ?: return false + + context.buildServiceHandler.service.lintDebug() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/lint/LintReleaseAction.kt b/app/src/main/java/com/itsaky/androidide/actions/lint/LintReleaseAction.kt new file mode 100644 index 000000000..065e7e0dc --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/lint/LintReleaseAction.kt @@ -0,0 +1,41 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.lint + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.BaseBuildAction + +/** @author Akash Yadav */ +class LintReleaseAction() : BaseBuildAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.lint_release) + icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release) + } + + override val id: String = "editor_lintRelease" + override fun execAction(data: ActionData): Boolean { + val context = getActivity(data) ?: return false + + context.buildServiceHandler.service.lintRelease() + return true + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/lint/LintTaskMenu.kt b/app/src/main/java/com/itsaky/androidide/actions/lint/LintTaskMenu.kt new file mode 100644 index 000000000..490f3a20e --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/lint/LintTaskMenu.kt @@ -0,0 +1,41 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.lint + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.actions.ActionMenu +import com.itsaky.androidide.actions.BaseBuildAction + +/** @author Akash Yadav */ +class LintTaskMenu() : BaseBuildAction(), ActionMenu { + + constructor(context: Context) : this() { + label = context.getString(R.string.lint_tasks) + icon = ContextCompat.getDrawable(context, R.drawable.ic_lint) + + addAction(LintAction(context)) + addAction(LintDebugAction(context)) + addAction(LintReleaseAction(context)) + } + + override val children: MutableSet = mutableSetOf() + override val id: String = "editor_lintTasks" +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/text/RedoAction.kt b/app/src/main/java/com/itsaky/androidide/actions/text/RedoAction.kt new file mode 100644 index 000000000..e5840683e --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/text/RedoAction.kt @@ -0,0 +1,47 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.text + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.EditorRelatedAction +import com.itsaky.androidide.views.editor.CodeEditorView + +/** @author Akash Yadav */ +class RedoAction() : EditorRelatedAction() { + + constructor(context: Context) : this() { + label = context.getString(R.string.redo) + icon = ContextCompat.getDrawable(context, R.drawable.ic_redo) + } + + override val id: String = "editor_redo" + + override fun execAction(data: ActionData): Boolean { + val editor = data.get(CodeEditorView::class.java) + + return if (editor != null) { + editor.redo() + true + } else { + false + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/actions/text/UndoAction.kt b/app/src/main/java/com/itsaky/androidide/actions/text/UndoAction.kt new file mode 100644 index 000000000..a4fa3f9f9 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/text/UndoAction.kt @@ -0,0 +1,46 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.text + +import android.content.Context +import androidx.core.content.ContextCompat +import com.itsaky.androidide.R +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.EditorRelatedAction +import com.itsaky.androidide.views.editor.CodeEditorView + +/** @author Akash Yadav */ +class UndoAction() : EditorRelatedAction() { + + override val id: String = "editor_undo" + + constructor(context: Context) : this() { + label = context.getString(R.string.undo) + icon = ContextCompat.getDrawable(context, R.drawable.ic_undo) + } + + override fun execAction(data: ActionData): Boolean { + val editor = data.get(CodeEditorView::class.java) + return if (editor != null) { + editor.undo() + true + } else { + false + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java b/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java index ce8f2e193..07e27305d 100755 --- a/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java +++ b/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java @@ -54,6 +54,10 @@ public class BuildServiceHandler extends IDEHandler implements BuildListener { return service; } + public boolean isBuilding() { + return getService().isBuilding(); + } + @Override public void start() { Objects.requireNonNull(this.activity()); @@ -150,7 +154,7 @@ public class BuildServiceHandler extends IDEHandler implements BuildListener { .getApp() .getPrefManager() .putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false); - activity().invalidateOptionsMenu(); + activity().ensureToolbarMenu(); activity().getBinding().buildProgressIndicator.setVisibility(View.GONE); } @@ -169,7 +173,7 @@ public class BuildServiceHandler extends IDEHandler implements BuildListener { .getApp() .getPrefManager() .putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false); - activity().invalidateOptionsMenu(); + activity().ensureToolbarMenu(); activity().getBinding().buildProgressIndicator.setVisibility(View.GONE); } diff --git a/app/src/main/java/com/itsaky/androidide/handlers/EditorActivityActions.kt b/app/src/main/java/com/itsaky/androidide/handlers/EditorActivityActions.kt new file mode 100644 index 000000000..bcf5fc796 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/handlers/EditorActivityActions.kt @@ -0,0 +1,69 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.handlers + +import android.content.Context +import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.actions.ActionsRegistry +import com.itsaky.androidide.actions.build.QuickRunAction +import com.itsaky.androidide.actions.build.RunActionMenu +import com.itsaky.androidide.actions.build.StopDaemonsAction +import com.itsaky.androidide.actions.editor.CopyAction +import com.itsaky.androidide.actions.editor.CutAction +import com.itsaky.androidide.actions.editor.PasteAction +import com.itsaky.androidide.actions.editor.SelectAllAction +import com.itsaky.androidide.actions.etc.DaemonStatusAction +import com.itsaky.androidide.actions.etc.FileTreeAction +import com.itsaky.androidide.actions.etc.FindActionMenu +import com.itsaky.androidide.actions.file.SaveFileAction +import com.itsaky.androidide.actions.text.RedoAction +import com.itsaky.androidide.actions.text.UndoAction + +/** + * Takes care of registering actions to the actions registry for the editor activity. + * + * @author Akash Yadav + */ +class EditorActivityActions { + companion object { + @JvmStatic + fun register(context: Context) { + val registry = ActionsRegistry.getInstance() + + // Toolbar actions + registry.registerAction(UndoAction(context)) + registry.registerAction(RedoAction(context)) + registry.registerAction(QuickRunAction(context)) + registry.registerAction(RunActionMenu(context)) + registry.registerAction(SaveFileAction(context)) + registry.registerAction(FindActionMenu(context)) + registry.registerAction(FileTreeAction(context)) + registry.registerAction(DaemonStatusAction(context)) + registry.registerAction(StopDaemonsAction(context)) + + // editor text actions + registry.registerAction(SelectAllAction(context)) + registry.registerAction(CutAction(context)) + registry.registerAction(CopyAction(context)) + registry.registerAction(PasteAction(context)) + } + + @JvmStatic + fun clear() = ActionsRegistry.getInstance().clearActions(ActionItem.Location.EDITOR_TOOLBAR) + } +} diff --git a/build.gradle b/build.gradle index cea3dfe76..49e54f7d9 100755 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ import java.util.stream.Collectors buildscript { ext.kotlin_version = '1.6.10' + project.ext { compileSdk = 31 buildTools = "31.0.0" @@ -37,10 +38,6 @@ buildscript { } } -//dependencies { -// implementation 'com.google.googlejavaformat:google-java-format:1.15.0' -//} - subprojects { apply from: "${rootDir}/gradle/dependencies.gradle" diff --git a/settings.gradle b/settings.gradle index d300c6ef0..88d51f402 100755 --- a/settings.gradle +++ b/settings.gradle @@ -12,3 +12,4 @@ include ':lsp:api' include ':lsp:java' include ':lsp:xml' include ':lsp:testing' +include ':actions'