Resolved merge conflicts

This commit is contained in:
Akash Yadav
2022-06-05 08:23:44 +05:30
parent f95bf4839a
commit 4bab018d2c
471 changed files with 47292 additions and 47292 deletions
+23 -23
View File
@@ -1,24 +1,24 @@
name: Delete old workflow runs name: Delete old workflow runs
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
days: days:
description: 'Number of days.' description: 'Number of days.'
required: true required: true
default: 30 default: 30
minimum_runs: minimum_runs:
description: 'The minimum runs to keep for each workflow.' description: 'The minimum runs to keep for each workflow.'
required: true required: true
default: 6 default: 6
jobs: jobs:
del_runs: del_runs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Delete workflow runs - name: Delete workflow runs
uses: Mattraks/delete-workflow-runs@v2 uses: Mattraks/delete-workflow-runs@v2
with: with:
token: ${{ github.token }} token: ${{ github.token }}
repository: ${{ github.repository }} repository: ${{ github.repository }}
retain_days: ${{ github.event.inputs.days }} retain_days: ${{ github.event.inputs.days }}
keep_minimum_runs: ${{ github.event.inputs.minimum_runs }} keep_minimum_runs: ${{ github.event.inputs.minimum_runs }}
+1 -1
View File
@@ -5,7 +5,7 @@
<profile name="Gradle Imported" enabled="true"> <profile name="Gradle Imported" enabled="true">
<outputRelativeToContentRoot value="true" /> <outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false"> <processorPath useClasspath="false">
<entry name="$PROJECT_DIR$/../../../itsaky/.gradle/caches/modules-2/files-2.1/com.google.auto.value/auto-value/1.9/fd4ec236e20ae0895c30e83260f611bcbc185086/auto-value-1.9.jar" /> <entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.auto.value/auto-value/1.9/fd4ec236e20ae0895c30e83260f611bcbc185086/auto-value-1.9.jar" />
</processorPath> </processorPath>
<module name="AndroidIDE.build-tools.google-java-format.main" /> <module name="AndroidIDE.build-tools.google-java-format.main" />
</profile> </profile>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="b36e6c44" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-06-05T02:50:58.945303Z" />
</component>
</project>
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Encoding"> <component name="Encoding">
<file url="file://$PROJECT_DIR$/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/StringWrapper.java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/StringWrapper.java" charset="UTF-8" />
</component> </component>
</project> </project>
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GoogleJavaFormatSettings"> <component name="GoogleJavaFormatSettings">
<option name="enabled" value="true" /> <option name="enabled" value="true" />
</component> </component>
</project> </project>
+5
View File
@@ -61,5 +61,10 @@
<option name="name" value="maven2" /> <option name="name" value="maven2" />
<option name="url" value="https://repo.eclipse.org/content/repositories/lemminx-snapshots/" /> <option name="url" value="https://repo.eclipse.org/content/repositories/lemminx-snapshots/" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:$USER_HOME$/.m2/repository" />
</remote-repository>
</component> </component>
</project> </project>
+6 -6
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="KtfmtSettings"> <component name="KtfmtSettings">
<option name="enabled" value="true" /> <option name="enabled" value="true" />
<option name="uiFormatterStyle" value="Dropbox" /> <option name="uiFormatterStyle" value="Dropbox" />
</component> </component>
</project> </project>
+179 -179
View File
@@ -1,179 +1,179 @@
# AndroidIDE Changelog # AndroidIDE Changelog
This is the changelog of all the releases of AndroidIDE. This is the changelog of all the releases of AndroidIDE.
## **v2.0.2-beta** (in development) ## **v2.0.2-beta** (in development)
### Important notes ### Important notes
<!--Breaking changes and other important stuff here--> <!--Breaking changes and other important stuff here-->
### Additions ### Additions
- Warn about empty body for the following statements : - Warn about empty body for the following statements :
- `if` - `if`
- `else` - `else`
- `try` - `try`
- `catch` - `catch`
- `finally` - `finally`
- `for` - `for`
- `while` - `while`
- `do while` - `do while`
- New java code actions : - New java code actions :
- `Generate toString()`: Overrides the `toString()` method for the current class. Always accessible. - `Generate toString()`: Overrides the `toString()` method for the current class. Always accessible.
- `Generate constructor`: Allows the user to select fields from the current class, then generates a constructor with those fields as parameters. - `Generate constructor`: Allows the user to select fields from the current class, then generates a constructor with those fields as parameters.
- `Remove unused imports`: Removes imports that are not used in the source file. - `Remove unused imports`: Removes imports that are not used in the source file.
- `Import orderer`: Organizes the imports. Thanks to @MrIkso (#180). - `Import orderer`: Organizes the imports. Thanks to @MrIkso (#180).
- XML Code formatter. Thanks to @MrIkso (#180). - XML Code formatter. Thanks to @MrIkso (#180).
- New Gradle command line options: `--build-cache` and `--offline`. Thanks to @MrIkso (#180). - New Gradle command line options: `--build-cache` and `--offline`. Thanks to @MrIkso (#180).
- Highlight IDE logs based on the log line priority. - Highlight IDE logs based on the log line priority.
- Added Brazilian Portuguese translations. Thanks to @Hakaisu (#165). - Added Brazilian Portuguese translations. Thanks to @Hakaisu (#165).
### Removals ### Removals
<!--Things that were removed--> <!--Things that were removed-->
### Fixes ### Fixes
- Crash when performing 'Replace all' action in editor (#158). - Crash when performing 'Replace all' action in editor (#158).
- Crash when layout inflation fails (#168). - Crash when layout inflation fails (#168).
- Always show toasts on UI thread (#160). - Always show toasts on UI thread (#160).
- Fixed: Multiline logs in IDE logs are not highlighted properly. - Fixed: Multiline logs in IDE logs are not highlighted properly.
- Fixed: Invalid log line formatting when tag to too long. - Fixed: Invalid log line formatting when tag to too long.
- Fixed: Application crashes when layout inflation fails (#168). - Fixed: Application crashes when layout inflation fails (#168).
- Fixed: Invalid syntax highlighting for text tags in XML files. Thanks to @MrIkso (#180). - Fixed: Invalid syntax highlighting for text tags in XML files. Thanks to @MrIkso (#180).
- Fixed: Extra empty `new line` gets appendend to files while saving. - Fixed: Extra empty `new line` gets appendend to files while saving.
### Improvements ### Improvements
- Do not show 'Cut' & 'Paste' action if editor is not editable. - Do not show 'Cut' & 'Paste' action if editor is not editable.
- Use [ported version of NetBeans' `nb-javac`](https://github.com/itsaky/nb-javac-android) for Java language server. - Use [ported version of NetBeans' `nb-javac`](https://github.com/itsaky/nb-javac-android) for Java language server.
- Removed extra Maven repository URLs from project templates. Thanks to @MrIkso (#188). - Removed extra Maven repository URLs from project templates. Thanks to @MrIkso (#188).
- Update dependency versions in project templates and update the Kotlin project template image. Thanks to @BanDroid (#191). - Update dependency versions in project templates and update the Kotlin project template image. Thanks to @BanDroid (#191).
- Updated Hindi translations. Thanks to @Premjit-Chowdhury (#171). - Updated Hindi translations. Thanks to @Premjit-Chowdhury (#171).
- Updated Russian translation. Thansk to @AndreyKozhev (#181). - Updated Russian translation. Thansk to @AndreyKozhev (#181).
## **v2.0.1-beta** ## **v2.0.1-beta**
AndroidIDE v2.0.1-beta is here with new features, improvements and bug fixes. AndroidIDE v2.0.1-beta is here with new features, improvements and bug fixes.
### Important notes ### Important notes
- As 64-bit installation does not work on Android 12 (due - As 64-bit installation does not work on Android 12 (due
to [restriction by the OS](https://source.android.com/devices/tech/debug/tagged-pointers)), 32-bit to [restriction by the OS](https://source.android.com/devices/tech/debug/tagged-pointers)), 32-bit
build tools need to be installed when installing AndroidIDE on devices running Android 12. build tools need to be installed when installing AndroidIDE on devices running Android 12.
- Google services and Firebase integration has been removed. - Google services and Firebase integration has been removed.
### Additions ### Additions
- Code formatter for Java source code. - Code formatter for Java source code.
- Support for - Support for
[`GestureOverlayView`](https://developer.android.com/reference/android/gesture/GestureOverlayView), [`GestureOverlayView`](https://developer.android.com/reference/android/gesture/GestureOverlayView),
[`ToggleButton`](https://developer.android.com/reference/android/widget/ToggleButton), [`ToggleButton`](https://developer.android.com/reference/android/widget/ToggleButton),
[`Switch`](https://developer.android.com/reference/android/widget/Switch), [`Switch`](https://developer.android.com/reference/android/widget/Switch),
[`GridLayout`](https://developer.android.com/reference/android/widget/GridLayout), [`GridLayout`](https://developer.android.com/reference/android/widget/GridLayout),
[`ListView`](https://developer.android.com/reference/android/widget/ListView) (#145) in layout [`ListView`](https://developer.android.com/reference/android/widget/ListView) (#145) in layout
inflater. inflater.
- Preference to disable use of `TYPE_TEXT_VARIATION_VISIBLE_PASSWORD` flag in editor. - Preference to disable use of `TYPE_TEXT_VARIATION_VISIBLE_PASSWORD` flag in editor.
- Automatically trigger completion when attributes are inserted from completion window. - Automatically trigger completion when attributes are inserted from completion window.
- New java code actions : - New java code actions :
- Add import - Add import
- Add 'throws' - Add 'throws'
- Generate constructor - Generate constructor
- Generate setters/getters - Generate setters/getters
- Field to block - Field to block
- Remove unused class - Remove unused class
- Remove unused method - Remove unused method
- Remove unused 'throws' - Remove unused 'throws'
- You can now override multiple superclass methods at once. - You can now override multiple superclass methods at once.
- `DocumentsProvider` for providing access to files in `HOME` directory. - `DocumentsProvider` for providing access to files in `HOME` directory.
### Removals ### Removals
- Removed initial view attributes from view tags completion in XMLCompletionProvider. - Removed initial view attributes from view tags completion in XMLCompletionProvider.
- Vertical text action window has been removed. - Vertical text action window has been removed.
- Removed Google services and Firebase integration. - Removed Google services and Firebase integration.
- Removed the horizontal text actions menu from the editor in favour of the new actions menu. - Removed the horizontal text actions menu from the editor in favour of the new actions menu.
### Fixes ### Fixes
- Cash in attribute editors when adding an attribute. - Cash in attribute editors when adding an attribute.
- Crash when selecting diagnostic item from diagnostics list. - Crash when selecting diagnostic item from diagnostics list.
- Some diagnostic items are not shown in diagnostic window (#96). - Some diagnostic items are not shown in diagnostic window (#96).
- Diagnostics were updated late in the editor. - Diagnostics were updated late in the editor.
- Popup windows created by editor are not dismissed even when the file is closed. - Popup windows created by editor are not dismissed even when the file is closed.
- No build output when activity is recreated (#121). - No build output when activity is recreated (#121).
- Crash in UI Designer when activity is recreated (#124). - Crash in UI Designer when activity is recreated (#124).
- Invalid syntax highlighting for application logs (#152). - Invalid syntax highlighting for application logs (#152).
- Illegal argument exception while overriding methods in Java completion provider. - Illegal argument exception while overriding methods in Java completion provider.
### Improvements ### Improvements
- Do not import classes from `java.lang` package when completion items are selected. - Do not import classes from `java.lang` package when completion items are selected.
- XML completion provider now uses 'Match completions in lower case' preference. - XML completion provider now uses 'Match completions in lower case' preference.
- Dismiss all windows shown by editor when file tab is unselected or when the bottom sheet is - Dismiss all windows shown by editor when file tab is unselected or when the bottom sheet is
expanded. expanded.
- Improved the editor actions menu. - Improved the editor actions menu.
- Sort completion items according to their kind and sort text. - Sort completion items according to their kind and sort text.
- Restore file tree state when application is resumed (#150). - Restore file tree state when application is resumed (#150).
- Updated templates to work with Android 12 (#144). - Updated templates to work with Android 12 (#144).
- Updated Chinese translation. Thanks to @mikofe (#128). - Updated Chinese translation. Thanks to @mikofe (#128).
- Updated French translation. Thanks to @Se-Lyan (#136). - Updated French translation. Thanks to @Se-Lyan (#136).
- Updated German translation (#144). - Updated German translation (#144).
## **v2.0.0-beta** ## **v2.0.0-beta**
AndroidIDE v2.0.0-beta is here with new features and improvements. AndroidIDE v2.0.0-beta is here with new features and improvements.
### Important Notes ### Important Notes
- This is a pre-release and is not so stable. There are some features that are partially - This is a pre-release and is not so stable. There are some features that are partially
implemented. implemented.
- The minimum SDK has been raised to `26` (`Android O`). - The minimum SDK has been raised to `26` (`Android O`).
### Additions ### Additions
- XML Layout Designer with ability to preview XML drawables. - XML Layout Designer with ability to preview XML drawables.
- Added packages for terminal. - Added packages for terminal.
- libGDx project template. Thanks to @Smooth-E (#81). - libGDx project template. Thanks to @Smooth-E (#81).
- Kotlin project template. Thanks to @BanDroid (#60). - Kotlin project template. Thanks to @BanDroid (#60).
- Preference to switch between `bash` and `/system/bin/sh` in terminal. - Preference to switch between `bash` and `/system/bin/sh` in terminal.
- Preference for matching partial names in java completion. - Preference for matching partial names in java completion.
- Preference to choose between `floating window text actions` and `text actions in ActionMode`. - Preference to choose between `floating window text actions` and `text actions in ActionMode`.
- Preference to enable/disable font ligatures. - Preference to enable/disable font ligatures.
- Show process output when build tools installation fails. - Show process output when build tools installation fails.
- Ability to expand selection in supported languages. - Ability to expand selection in supported languages.
- Ask user to save unsaved files before closing project. - Ask user to save unsaved files before closing project.
- Hindi translation. Thanks to @Premjit-Chowdhury (#70, #127). - Hindi translation. Thanks to @Premjit-Chowdhury (#70, #127).
- German translation. Thanks to @MarvinStelter. - German translation. Thanks to @MarvinStelter.
- Indonesian translation. Thanks to @BanDroid (#54, #57, #60, #72, #125). - Indonesian translation. Thanks to @BanDroid (#54, #57, #60, #72, #125).
- Chinese translation. Thanks to @Rosemoe (#56, #73, #104) and @mikofe (#118, #119, #123). - Chinese translation. Thanks to @Rosemoe (#56, #73, #104) and @mikofe (#118, #119, #123).
- French translation. Thanks to @Se-Lyan (#79). - French translation. Thanks to @Se-Lyan (#79).
- Russian translation. Thanks to @Smooth-E (#97). - Russian translation. Thanks to @Smooth-E (#97).
### Removals ### Removals
- Autosave of files is removed. However, a preference has been added for the same which, when - Autosave of files is removed. However, a preference has been added for the same which, when
enabled, saves the files automatically before close. enabled, saves the files automatically before close.
### Fixes ### Fixes
- File does not close and crashes if reopened and closed again. - File does not close and crashes if reopened and closed again.
- Flickering of whole auto completion window. - Flickering of whole auto completion window.
- Flickering of completion item due to API info. - Flickering of completion item due to API info.
- Build tools installation fails if `JAVA_HOME` does not exist. - Build tools installation fails if `JAVA_HOME` does not exist.
- Invalid syntax highlight for method invocations in java. - Invalid syntax highlight for method invocations in java.
- Package private classes not included in java completion. - Package private classes not included in java completion.
- IDE crashes when `Paste` option is selected in terminal and no clip is available. - IDE crashes when `Paste` option is selected in terminal and no clip is available.
- Fixed a bug due to which the IDE was not able to open empty files. - Fixed a bug due to which the IDE was not able to open empty files.
- `LogSender` class not included in JLS workspace. - `LogSender` class not included in JLS workspace.
### Improvements ### Improvements
- Show crash report when the IDE crashes. - Show crash report when the IDE crashes.
- The Java language server is now embedded in the IDE itself. - The Java language server is now embedded in the IDE itself.
- The AndroidIDE Gradle plugin is now packaged with the IDE. - The AndroidIDE Gradle plugin is now packaged with the IDE.
- Show name of opened project. - Show name of opened project.
- Show signature help in window instead of using a `TextView`. - Show signature help in window instead of using a `TextView`.
- The java completion provider now uses a custom JDK runtime image with Android framework classes - The java completion provider now uses a custom JDK runtime image with Android framework classes
and is no more dependent on JDK installation. and is no more dependent on JDK installation.
- Disable `Save` option when all files are saved. - Disable `Save` option when all files are saved.
- Allow installation of files from file tree. - Allow installation of files from file tree.
### New Contributors ### New Contributors
* @BanDroid * @BanDroid
* @Rosemoe * @Rosemoe
* @Premjit-Chowdhury * @Premjit-Chowdhury
* @Se-Lyan * @Se-Lyan
* @Smooth-E * @Smooth-E
* @ansh2166 * @ansh2166
* @mikofe * @mikofe
+27 -27
View File
@@ -1,28 +1,28 @@
# Contributing # Contributing
This document outlines how to propose a change to AndrodIDE. By contributing to this project, you agree to abide the terms specified in [CODE OF CONDUCT](./CODE_OF_CONDUCT.md). This document outlines how to propose a change to AndrodIDE. By contributing to this project, you agree to abide the terms specified in [CODE OF CONDUCT](./CODE_OF_CONDUCT.md).
## Propose a change ## Propose a change
To contribute to this project, To contribute to this project,
- Fork the repo. - Fork the repo.
- Clone the forked repo to your local machine. - Clone the forked repo to your local machine.
- Open the project in Android Studio. - Open the project in Android Studio.
- Make your changes. - Make your changes.
- Create a pull request. - Create a pull request.
## Note ## Note
`Java 11` is required to build the project. `Java 11` is required to build the project.
Building AndroidIDE in AndroidIDE itself is also possible. But that might require some modifications. Building AndroidIDE in AndroidIDE itself is also possible. But that might require some modifications.
If you face `AAPT2` issue while building AndroidIDE in the IDE itself, specify the `android.aapt2FromMavenOverride` property in `gradle.properties` with location of `aapt2` binary from Android SDK. However, this is an **experimental** property. If you face `AAPT2` issue while building AndroidIDE in the IDE itself, specify the `android.aapt2FromMavenOverride` property in `gradle.properties` with location of `aapt2` binary from Android SDK. However, this is an **experimental** property.
## Source code format ## Source code format
The source code must have `2-space` indentation (soft tab). The source code must have `2-space` indentation (soft tab).
Java source code is formatted using `google-java-format` with `GOOGLE` style formatting. Java source code is formatted using `google-java-format` with `GOOGLE` style formatting.
You can execute `./gradlew formatJavaSources` in terminal to format all Java source files using Google Java Format. You can execute `./gradlew formatJavaSources` in terminal to format all Java source files using Google Java Format.
**This will commit the changes as well**. **This will commit the changes as well**.
## Issues & Pull requests ## Issues & Pull requests
Use the issue/PR templates when possible. Provide a proper title and a clear description for the issue/PR. For bug reports, provide a step-by-step procedure to reproduce the issue. Always put crash logs, build outputs, etc in `code blocks`. Use the issue/PR templates when possible. Provide a proper title and a clear description for the issue/PR. For bug reports, provide a step-by-step procedure to reproduce the issue. Always put crash logs, build outputs, etc in `code blocks`.
If you want to discuss anything about the project, you can discuss it in the [Telegram group](https://t.me/androidide_discussions). If you want to discuss anything about the project, you can discuss it in the [Telegram group](https://t.me/androidide_discussions).
+37 -37
View File
@@ -1,37 +1,37 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
android { android {
compileSdkVersion rootProject.ext.compileSdk compileSdkVersion rootProject.ext.compileSdk
buildToolsVersion rootProject.ext.buildTools buildToolsVersion rootProject.ext.buildTools
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.minSdk minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk targetSdkVersion rootProject.ext.targetSdk
} }
buildFeatures.viewBinding true buildFeatures.viewBinding true
compileOptions { compileOptions {
sourceCompatibility rootProject.ext.javaSourceVersion sourceCompatibility rootProject.ext.javaSourceVersion
targetCompatibility rootProject.ext.javaTargetVersion targetCompatibility rootProject.ext.javaTargetVersion
} }
buildTypes { buildTypes {
release { release {
minifyEnabled true minifyEnabled true
} }
} }
} }
dependencies { dependencies {
implementation project(':common') implementation project(':common')
implementation common.editor implementation common.editor
implementation common.utilcode implementation common.utilcode
implementation "androidx.core:core-ktx:1.7.0" implementation "androidx.core:core-ktx:1.7.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
} }
repositories { repositories {
mavenCentral() mavenCentral()
} }
+16 -16
View File
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of AndroidIDE. ~ This file is part of AndroidIDE.
~ ~
~ AndroidIDE is free software: you can redistribute it and/or modify ~ AndroidIDE is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by ~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or ~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version. ~ (at your option) any later version.
~ ~
~ AndroidIDE is distributed in the hope that it will be useful, ~ AndroidIDE is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details. ~ GNU General Public License for more details.
~ ~
~ You should have received a copy of the GNU General Public License ~ You should have received a copy of the GNU General Public License
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. ~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
--> -->
<manifest package="com.itsaky.androidide.actions" /> <manifest package="com.itsaky.androidide.actions" />
@@ -1,54 +1,54 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions; package com.itsaky.androidide.actions;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
/** /**
* @author Akash Yadav * @author Akash Yadav
*/ */
public class ActionData { public class ActionData {
private final Map<Class<?>, Object> instances = new HashMap<>(); private final Map<Class<?>, Object> instances = new HashMap<>();
public <T> void put(@NonNull Class<T> type, @Nullable T object) { public <T> void put(@NonNull Class<T> type, @Nullable T object) {
Objects.requireNonNull(type); Objects.requireNonNull(type);
this.instances.put(type, object); this.instances.put(type, object);
} }
@Nullable @Nullable
public <T> T get(@NonNull Class<T> type) { public <T> T get(@NonNull Class<T> type) {
Objects.requireNonNull(type); Objects.requireNonNull(type);
final var object = this.instances.get(type); final var object = this.instances.get(type);
if (object == null) { if (object == null) {
return null; return null;
} }
return type.cast(object); return type.cast(object);
} }
public void clear() { public void clear() {
this.instances.clear(); this.instances.clear();
} }
} }
@@ -1,113 +1,113 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions package com.itsaky.androidide.actions
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
/** /**
* An action that can be registered using the [ActionsRegistry] * An action that can be registered using the [ActionsRegistry]
* [com.itsaky.androidide.actions.ActionsRegistry] * [com.itsaky.androidide.actions.ActionsRegistry]
* @author Akash Yadav * @author Akash Yadav
*/ */
interface ActionItem { interface ActionItem {
val id: String val id: String
var label: String var label: String
var visible: Boolean var visible: Boolean
var enabled: Boolean var enabled: Boolean
var icon: Drawable? var icon: Drawable?
var requiresUIThread: Boolean var requiresUIThread: Boolean
var location: Location var location: Location
/** /**
* Prepare the action. Subclasses can modify the visual properties of this action here. * Prepare the action. Subclasses can modify the visual properties of this action here.
* @param data The data containing various information about the event. * @param data The data containing various information about the event.
*/ */
fun prepare(data: ActionData) fun prepare(data: ActionData)
/** /**
* Execute the action. The action executed in a background thread by default. * Execute the action. The action executed in a background thread by default.
* *
* @param data The data containing various information about the event. * @param data The data containing various information about the event.
* @return `true` if this action was executed successfully, `false` otherwise. * @return `true` if this action was executed successfully, `false` otherwise.
*/ */
fun execAction(data: ActionData): Any fun execAction(data: ActionData): Any
/** /**
* Called just after the [execAction] method executes **successfully** (i.e. returns `true`). * 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. * 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. * @param data The data containing various information about the event.
*/ */
fun postExec(data: ActionData, result: Any) = Unit fun postExec(data: ActionData, result: Any) = Unit
/** /**
* Return the show as action flags for the menu item. * Return the show as action flags for the menu item.
* *
* @return The show as action flags. * @return The show as action flags.
*/ */
fun getShowAsActionFlags(data: ActionData): Int = -1 fun getShowAsActionFlags(data: ActionData): Int = -1
/** /**
* Checks if the given [ActionData] has instances of the given [types]. * Checks if the given [ActionData] has instances of the given [types].
* *
* @param data The data to check. * @param data The data to check.
* @param types The type of objects to look for. * @param types The type of objects to look for.
* @return `true` if the [data] has the given [types], `false` otherwise. * @return `true` if the [data] has the given [types], `false` otherwise.
*/ */
fun hasRequiredData(data: ActionData, vararg types: Class<*>): Boolean { fun hasRequiredData(data: ActionData, vararg types: Class<*>): Boolean {
for (type in types) { for (type in types) {
data.get(type) ?: return false data.get(type) ?: return false
} }
return true return true
} }
/** Marks this action item as invisible. */ /** Marks this action item as invisible. */
fun markInvisible() { fun markInvisible() {
visible = false visible = false
enabled = false enabled = false
} }
/** Location where an action item will be shown. */ /** Location where an action item will be shown. */
enum class Location(val id: String) { enum class Location(val id: String) {
/** Location marker for action items shown in editor activity's toolbar. */ /** Location marker for action items shown in editor activity's toolbar. */
EDITOR_TOOLBAR("editor.activity.toolbar"), EDITOR_TOOLBAR("editor.activity.toolbar"),
/** Location marker for action items shown in editor's text action menu. */ /** Location marker for action items shown in editor's text action menu. */
EDITOR_TEXT_ACTIONS("editor.textActions"), EDITOR_TEXT_ACTIONS("editor.textActions"),
/** /**
* Location marker for action items shown in 'Code actions' submenu in editor's text action * Location marker for action items shown in 'Code actions' submenu in editor's text action
* menu. * menu.
*/ */
EDITOR_CODE_ACTIONS("editor.codeActions"), EDITOR_CODE_ACTIONS("editor.codeActions"),
/** Location marker for action items show in 'Find & Replace' action mode menu in editor. */ /** Location marker for action items show in 'Find & Replace' action mode menu in editor. */
EDITOR_SEARCH_ACTION_MODE("editor.searchActionMode"); EDITOR_SEARCH_ACTION_MODE("editor.searchActionMode");
override fun toString(): String { override fun toString(): String {
return id return id
} }
fun forId(id: String): Location { fun forId(id: String): Location {
return values().first { it.id == id } return values().first { it.id == id }
} }
} }
} }
@@ -1,36 +1,36 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions package com.itsaky.androidide.actions
/** /**
* An action menu is an action which can contain child actions. * An action menu is an action which can contain child actions.
* @author Akash Yadav * @author Akash Yadav
*/ */
interface ActionMenu : ActionItem { interface ActionMenu : ActionItem {
val children: MutableSet<ActionItem> val children: MutableSet<ActionItem>
fun addAction(action: ActionItem) = children.add(action) fun addAction(action: ActionItem) = children.add(action)
fun removeAction(action: ActionItem) = children.remove(action) fun removeAction(action: ActionItem) = children.remove(action)
/** Action menus are not supposed to perform any action */ /** Action menus are not supposed to perform any action */
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
return false return false
} }
} }
@@ -1,108 +1,108 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions package com.itsaky.androidide.actions
import android.view.Menu import android.view.Menu
import com.itsaky.androidide.actions.internal.DefaultActionsRegistry import com.itsaky.androidide.actions.internal.DefaultActionsRegistry
/** @author Akash Yadav */ /** @author Akash Yadav */
abstract class ActionsRegistry { abstract class ActionsRegistry {
companion object { companion object {
private var instance: ActionsRegistry? = null private var instance: ActionsRegistry? = null
@JvmStatic @JvmStatic
fun getInstance(): ActionsRegistry { fun getInstance(): ActionsRegistry {
if (instance == null) { if (instance == null) {
instance = DefaultActionsRegistry() instance = DefaultActionsRegistry()
} }
return instance as ActionsRegistry return instance as ActionsRegistry
} }
} }
/** /**
* Register the given action. * Register the given action.
* *
* @param action The action to register. Must not be `null`. * @param action The action to register. Must not be `null`.
* @return `true` if the action was registered successfully. `false` otherwise. * @return `true` if the action was registered successfully. `false` otherwise.
*/ */
abstract fun registerAction(action: ActionItem): Boolean abstract fun registerAction(action: ActionItem): Boolean
/** /**
* Unregister the given action. * Unregister the given action.
* *
* @param action The action to unregister. Must not be `null`. * @param action The action to unregister. Must not be `null`.
* @return `true` if the action was unregistered successfully. `false` otherwise. * @return `true` if the action was unregistered successfully. `false` otherwise.
*/ */
abstract fun unregisterAction(action: ActionItem): Boolean abstract fun unregisterAction(action: ActionItem): Boolean
/** /**
* Unregister the action with the given ID. * Unregister the action with the given ID.
* *
* @param id The ID of action to unregister. Must not be `null`. * @param id The ID of action to unregister. Must not be `null`.
* @return `true` if the action was unregistered successfully. `false` otherwise. * @return `true` if the action was unregistered successfully. `false` otherwise.
*/ */
abstract fun unregisterAction(id: String): Boolean abstract fun unregisterAction(id: String): Boolean
/** /**
* Find the action with the given ID. * Find the action with the given ID.
* *
* @param id The ID of the action to find. * @param id The ID of the action to find.
* @return The found action or `null`. * @return The found action or `null`.
*/ */
abstract fun findAction(location: ActionItem.Location, id: String): ActionItem? abstract fun findAction(location: ActionItem.Location, id: String): ActionItem?
/** /**
* Fill the given menu with the registered actions. * Fill the given menu with the registered actions.
* *
* Subclasses must first call [ActionItem.prepare] to update the action. * Subclasses must first call [ActionItem.prepare] to update the action.
*/ */
abstract fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) abstract fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu)
/** Get all the registered actions at the given location. */ /** Get all the registered actions at the given location. */
abstract fun getActions(location: ActionItem.Location): Map<String, ActionItem> abstract fun getActions(location: ActionItem.Location): Map<String, ActionItem>
/** Clear all the registered actions. */ /** Clear all the registered actions. */
abstract fun clearActions(location: ActionItem.Location) abstract fun clearActions(location: ActionItem.Location)
/** /**
* Register a listener that will be called when any action is executed. The callbacks are called * Register a listener that will be called when any action is executed. The callbacks are called
* irrespective of the action's execution result. * irrespective of the action's execution result.
* *
* @param listener The listener to register. * @param listener The listener to register.
*/ */
abstract fun registerActionExecListener(listener: ActionExecListener) abstract fun registerActionExecListener(listener: ActionExecListener)
/** /**
* Unregister the given listener. * Unregister the given listener.
* *
* @param listener The listener to unregister. * @param listener The listener to unregister.
*/ */
abstract fun unregisterActionExecListener(listener: ActionExecListener) abstract fun unregisterActionExecListener(listener: ActionExecListener)
/** Listener for listening to callbacks for action execution. */ /** Listener for listening to callbacks for action execution. */
interface ActionExecListener { interface ActionExecListener {
/** /**
* The given action was executed. * The given action was executed.
* @param action The action that was executed. * @param action The action that was executed.
* @param result The result of the action execution. * @param result The result of the action execution.
*/ */
fun onExec(action: ActionItem, result: Any) fun onExec(action: ActionItem, result: Any)
} }
} }
@@ -1,174 +1,174 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.internal package com.itsaky.androidide.actions.internal
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.blankj.utilcode.util.ThreadUtils import com.blankj.utilcode.util.ThreadUtils
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionMenu import com.itsaky.androidide.actions.ActionMenu
import com.itsaky.androidide.actions.ActionsRegistry import com.itsaky.androidide.actions.ActionsRegistry
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
import java.util.concurrent.* import java.util.concurrent.*
/** /**
* Default implementation for the [ActionsRegistry] * Default implementation for the [ActionsRegistry]
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
class DefaultActionsRegistry : ActionsRegistry() { class DefaultActionsRegistry : ActionsRegistry() {
private val log = ILogger.newInstance("DefaultActionsRegistry") private val log = ILogger.newInstance("DefaultActionsRegistry")
private val actions: MutableMap<String, MutableMap<String, ActionItem>> = mutableMapOf() private val actions: MutableMap<String, MutableMap<String, ActionItem>> = mutableMapOf()
private val listeners = mutableSetOf<ActionExecListener>() private val listeners = mutableSetOf<ActionExecListener>()
override fun getActions(location: ActionItem.Location): MutableMap<String, ActionItem> { override fun getActions(location: ActionItem.Location): MutableMap<String, ActionItem> {
if (actions[location.id] == null) { if (actions[location.id] == null) {
actions[location.id] = mutableMapOf() actions[location.id] = mutableMapOf()
} }
return actions[location.id]!! return actions[location.id]!!
} }
override fun registerAction(action: ActionItem): Boolean { override fun registerAction(action: ActionItem): Boolean {
val actionsAtLocation = getActions(action.location) val actionsAtLocation = getActions(action.location)
actionsAtLocation[action.id] = action actionsAtLocation[action.id] = action
return true return true
} }
override fun unregisterAction(action: ActionItem): Boolean { override fun unregisterAction(action: ActionItem): Boolean {
val actions = getActions(action.location) val actions = getActions(action.location)
val older = actions.remove(action.id) val older = actions.remove(action.id)
if (older != null) { if (older != null) {
return true return true
} }
return false return false
} }
override fun unregisterAction(id: String): Boolean { override fun unregisterAction(id: String): Boolean {
for (locations in this.actions.values) { for (locations in this.actions.values) {
val older = locations.remove(id) val older = locations.remove(id)
if (older != null) { if (older != null) {
return true return true
} }
} }
return false return false
} }
override fun findAction(location: ActionItem.Location, id: String): ActionItem? = override fun findAction(location: ActionItem.Location, id: String): ActionItem? =
getActions(location)[id] getActions(location)[id]
override fun clearActions(location: ActionItem.Location) = getActions(location).clear() override fun clearActions(location: ActionItem.Location) = getActions(location).clear()
override fun registerActionExecListener(listener: ActionExecListener) { override fun registerActionExecListener(listener: ActionExecListener) {
listeners.add(listener) listeners.add(listener)
} }
override fun unregisterActionExecListener(listener: ActionExecListener) { override fun unregisterActionExecListener(listener: ActionExecListener) {
listeners.remove(listener) listeners.remove(listener)
} }
override fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) { override fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) {
val actions = getActions(location) val actions = getActions(location)
for (action in actions.values) { for (action in actions.values) {
action.prepare(data) action.prepare(data)
if (!action.visible) { if (!action.visible) {
continue continue
} }
addActionToMenu(menu, action, data) addActionToMenu(menu, action, data)
} }
} }
private fun addActionToMenu(menu: Menu, action: ActionItem, data: ActionData) { private fun addActionToMenu(menu: Menu, action: ActionItem, data: ActionData) {
val item: MenuItem = val item: MenuItem =
if (action is ActionMenu) { if (action is ActionMenu) {
val sub = menu.addSubMenu(action.label) val sub = menu.addSubMenu(action.label)
for (subItem in action.children) { for (subItem in action.children) {
subItem.prepare(data) subItem.prepare(data)
if (subItem.visible) { if (subItem.visible) {
addActionToMenu(sub, subItem, data) addActionToMenu(sub, subItem, data)
} }
} }
sub.item sub.item
} else { } else {
menu.add(action.label) menu.add(action.label)
} }
item.isEnabled = action.enabled item.isEnabled = action.enabled
item.icon = action.icon item.icon = action.icon
if (item.icon != null) { if (item.icon != null) {
item.icon.alpha = if (action.enabled) 255 else 76 item.icon.alpha = if (action.enabled) 255 else 76
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
} else { } else {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
} }
if (action.getShowAsActionFlags(data) != -1) { if (action.getShowAsActionFlags(data) != -1) {
item.setShowAsAction(action.getShowAsActionFlags(data)) item.setShowAsAction(action.getShowAsActionFlags(data))
} }
if (action !is ActionMenu) { if (action !is ActionMenu) {
item.setOnMenuItemClickListener { item.setOnMenuItemClickListener {
if (action.requiresUIThread) { if (action.requiresUIThread) {
ThreadUtils.runOnUiThread { ThreadUtils.runOnUiThread {
val result = action.execAction(data) val result = action.execAction(data)
action.postExec(data, result) action.postExec(data, result)
notifyActionExec(action, result) notifyActionExec(action, result)
} }
} else { } else {
execInBackground(action, data, it) execInBackground(action, data, it)
} }
true true
} }
} }
} }
private fun execInBackground(action: ActionItem, data: ActionData, it: MenuItem) { private fun execInBackground(action: ActionItem, data: ActionData, it: MenuItem) {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
CompletableFuture.supplyAsync { action.execAction(data) } CompletableFuture.supplyAsync { action.execAction(data) }
.whenComplete { result, error -> .whenComplete { result, error ->
if (result == null || (result is Boolean && !result) || error != null) { if (result == null || (result is Boolean && !result) || error != null) {
log.error( log.error(
"An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms", "An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms",
error) error)
} else { } else {
log.info( log.info(
"Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms") "Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms")
} }
ThreadUtils.runOnUiThread { ThreadUtils.runOnUiThread {
action.postExec(data, result ?: false) action.postExec(data, result ?: false)
notifyActionExec(action, result ?: false) notifyActionExec(action, result ?: false)
} }
} }
} }
private fun notifyActionExec(action: ActionItem, result: Any) { private fun notifyActionExec(action: ActionItem, result: Any) {
for (listener in listeners) { for (listener in listeners) {
listener.onExec(action, result) listener.onExec(action, result)
} }
} }
} }
+143 -143
View File
@@ -1,144 +1,144 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
android { android {
compileSdkVersion rootProject.ext.compileSdk compileSdkVersion rootProject.ext.compileSdk
buildToolsVersion rootProject.ext.buildTools buildToolsVersion rootProject.ext.buildTools
defaultConfig { defaultConfig {
applicationId rootProject.ext.packageName applicationId rootProject.ext.packageName
minSdkVersion rootProject.ext.minSdk minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk targetSdkVersion rootProject.ext.targetSdk
versionCode rootProject.ext.versionCode versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName versionName rootProject.ext.versionName
multiDexEnabled true multiDexEnabled true
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true coreLibraryDesugaringEnabled true
sourceCompatibility rootProject.ext.javaSourceVersion sourceCompatibility rootProject.ext.javaSourceVersion
targetCompatibility rootProject.ext.javaTargetVersion targetCompatibility rootProject.ext.javaTargetVersion
} }
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
} }
buildFeatures { buildFeatures {
viewBinding true viewBinding true
} }
signingConfigs { signingConfigs {
debug { debug {
storeFile file ("dev.keystore") storeFile file ("dev.keystore")
keyAlias "androidide" keyAlias "androidide"
storePassword "ed68424fb109e5aa8146e4b86caa72e3" storePassword "ed68424fb109e5aa8146e4b86caa72e3"
keyPassword "ed68424fb109e5aa8146e4b86caa72e3" keyPassword "ed68424fb109e5aa8146e4b86caa72e3"
} }
release { release {
storeFile file ("dev.keystore") storeFile file ("dev.keystore")
keyAlias "androidide" keyAlias "androidide"
storePassword "ed68424fb109e5aa8146e4b86caa72e3" storePassword "ed68424fb109e5aa8146e4b86caa72e3"
keyPassword "ed68424fb109e5aa8146e4b86caa72e3" keyPassword "ed68424fb109e5aa8146e4b86caa72e3"
} }
} }
buildTypes { buildTypes {
debug { debug {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
release { release {
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
} }
packagingOptions { packagingOptions {
resources { resources {
excludes += ['META-INF/eclipse.inf', excludes += ['META-INF/eclipse.inf',
'META-INF/CHANGES', 'META-INF/CHANGES',
'META-INF/README.md', 'META-INF/README.md',
'about_files/LICENSE-2.0.txt', 'about_files/LICENSE-2.0.txt',
'plugin.xml', 'plugin.xml',
'plugin.properties', 'plugin.properties',
'about.mappings', 'about.mappings',
'about.properties', 'about.properties',
'about.ini', 'about.ini',
'modeling32.png'] 'modeling32.png']
} }
} }
lint { lint {
abortOnError false abortOnError false
disable 'VectorPath', 'NestedWeights', 'ContentDescription', 'SmallSp' disable 'VectorPath', 'NestedWeights', 'ContentDescription', 'SmallSp'
} }
} }
dependencies { dependencies {
// Annotation processors // Annotation processors
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.13.1'
implementation common.editor implementation common.editor
implementation common.utilcode implementation common.utilcode
implementation common.file_picker implementation common.file_picker
implementation common.glide implementation common.glide
implementation common.quickaction implementation common.quickaction
implementation common.bll implementation common.bll
implementation common.xml2json implementation common.xml2json
implementation google.gson implementation google.gson
implementation google.guava implementation google.guava
implementation common.jsoup implementation common.jsoup
// AndroidX // AndroidX
implementation androidx.annotation implementation androidx.annotation
implementation androidx.appcompat implementation androidx.appcompat
implementation androidx.cardview implementation androidx.cardview
implementation androidx.constraintlayout implementation androidx.constraintlayout
implementation androidx.coordinatorlayout implementation androidx.coordinatorlayout
implementation androidx.drawer implementation androidx.drawer
implementation androidx.grid implementation androidx.grid
implementation androidx.preference implementation androidx.preference
implementation androidx.recyclerview implementation androidx.recyclerview
implementation androidx.vectors implementation androidx.vectors
implementation androidx.animated_vectors implementation androidx.animated_vectors
implementation google.material implementation google.material
// Kotlin // Kotlin
implementation "androidx.core:core-ktx:1.7.0" implementation "androidx.core:core-ktx:1.7.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Local projects here // Local projects here
implementation project (':build-tools:tooling-api') implementation project (':build-tools:tooling-api')
implementation project (':treeview') implementation project (':treeview')
implementation project (':emulatorview') implementation project (':emulatorview')
implementation project (':xml-inflater') implementation project (':xml-inflater')
implementation project (':lexers') implementation project (':lexers')
implementation project (':common') implementation project (':common')
implementation project (':actions') implementation project (':actions')
implementation project (':lsp:api') implementation project (':lsp:api')
implementation project (':lsp:java') implementation project (':lsp:java')
implementation project (':lsp:xml') implementation project (':lsp:xml')
implementation fileTree (dir: 'libs', include: ['*.jar']) implementation fileTree (dir: 'libs', include: ['*.jar'])
// LeakCanary // LeakCanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
// This is to build the tooling-api-impl project before the app is built // This is to build the tooling-api-impl project before the app is built
// So we always copy the latest JAR file to assets // So we always copy the latest JAR file to assets
compileOnly project (path: ':build-tools:tooling-api-impl') compileOnly project (path: ':build-tools:tooling-api-impl')
} }
Binary file not shown.
@@ -1,187 +1,187 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide; package com.itsaky.androidide;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html; import android.text.Html;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.view.View; import android.view.View;
import androidx.appcompat.widget.TooltipCompat; import androidx.appcompat.widget.TooltipCompat;
import androidx.core.util.Pair; import androidx.core.util.Pair;
import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.app.StudioActivity;
import com.itsaky.androidide.databinding.ActivityAboutBinding; import com.itsaky.androidide.databinding.ActivityAboutBinding;
import com.itsaky.androidide.databinding.LayoutAboutItemsBinding; import com.itsaky.androidide.databinding.LayoutAboutItemsBinding;
import com.itsaky.androidide.models.License; import com.itsaky.androidide.models.License;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class AboutActivity extends StudioActivity { public class AboutActivity extends StudioActivity {
public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android"; public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android";
private ActivityAboutBinding binding; private ActivityAboutBinding binding;
@Override @Override
protected View bindLayout() { protected View bindLayout() {
binding = ActivityAboutBinding.inflate(getLayoutInflater()); binding = ActivityAboutBinding.inflate(getLayoutInflater());
return binding.getRoot(); return binding.getRoot();
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
binding.items.footerText.setText(getFooter()); binding.items.footerText.setText(getFooter());
setupTranslations(); setupTranslations();
setupLicenses(); setupLicenses();
LayoutAboutItemsBinding items = binding.items; LayoutAboutItemsBinding items = binding.items;
TooltipCompat.setTooltipText(items.discuss, getString(R.string.discussions_on_telegram)); TooltipCompat.setTooltipText(items.discuss, getString(R.string.discussions_on_telegram));
TooltipCompat.setTooltipText(items.github, getString(R.string.title_github)); TooltipCompat.setTooltipText(items.github, getString(R.string.title_github));
TooltipCompat.setTooltipText(items.email, getString(R.string.about_option_email)); TooltipCompat.setTooltipText(items.email, getString(R.string.about_option_email));
TooltipCompat.setTooltipText(items.website, getString(R.string.about_option_website)); TooltipCompat.setTooltipText(items.website, getString(R.string.about_option_website));
items.github.setOnClickListener(v -> getApp().openGitHub()); items.github.setOnClickListener(v -> getApp().openGitHub());
items.discuss.setOnClickListener(v -> getApp().openTelegramGroup()); items.discuss.setOnClickListener(v -> getApp().openTelegramGroup());
items.email.setOnClickListener(v -> getApp().emailUs()); items.email.setOnClickListener(v -> getApp().emailUs());
items.website.setOnClickListener(v -> getApp().openWebsite()); items.website.setOnClickListener(v -> getApp().openWebsite());
} }
private void setupTranslations() { private void setupTranslations() {
final var translations = createTranslationsList(); final var translations = createTranslationsList();
final var sb = new StringBuilder(); final var sb = new StringBuilder();
for (var translation : translations) { for (var translation : translations) {
final var author = translation.first; final var author = translation.first;
final var language = translation.second; final var language = translation.second;
sb.append(author); sb.append(author);
sb.append(" - "); sb.append(" - ");
sb.append(language); sb.append(language);
sb.append("\n"); sb.append("\n");
} }
binding.items.translations.setText(sb); binding.items.translations.setText(sb);
} }
/** /**
* Create the list of translations. The list items are pairs. The first item of the pair is the * Create the list of translations. The list items are pairs. The first item of the pair is the
* name of the language. The second item is the name of the person who made the translations. * name of the language. The second item is the name of the person who made the translations.
* *
* <p>If you're adding translations, make sure you add an item to this list. You can choose to * <p>If you're adding translations, make sure you add an item to this list. You can choose to
* display the language in a localized version. For example : Language: हिन्दी - < name of author * display the language in a localized version. For example : Language: हिन्दी - < name of author
* > * >
* *
* <p>Prefer using unicode escapes for language names. For example, हिन्दी can be written as : * <p>Prefer using unicode escapes for language names. For example, हिन्दी can be written as :
* Language: \u0939\u093f\u0928\u094d\u0926\u0940 - < name of author > * Language: \u0939\u093f\u0928\u094d\u0926\u0940 - < name of author >
* *
* @return The translations list. * @return The translations list.
*/ */
private List<Pair<String, String>> createTranslationsList() { private List<Pair<String, String>> createTranslationsList() {
final var list = new ArrayList<Pair<String, String>>(); final var list = new ArrayList<Pair<String, String>>();
list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani")); list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani"));
list.add(Pair.create("\u4e2d\u6587", "Rosemoe and @mikofe")); // 中文 list.add(Pair.create("\u4e2d\u6587", "Rosemoe and @mikofe")); // 中文
list.add(Pair.create("Deutsch", "Marvin Stelter")); list.add(Pair.create("Deutsch", "Marvin Stelter"));
list.add(Pair.create("\u0939\u093f\u0928\u094d\u0926\u0940", "Premjit Chowdhury")); // हिन्दी list.add(Pair.create("\u0939\u093f\u0928\u094d\u0926\u0940", "Premjit Chowdhury")); // हिन्दी
list.add(Pair.create("Russian", "Smooth-E")); list.add(Pair.create("Russian", "Smooth-E"));
list.add(Pair.create("French", "Se-Lyan")); list.add(Pair.create("French", "Se-Lyan"));
list.add(Pair.create("Portuguese-Brazilian", "Frederick'XS")); list.add(Pair.create("Portuguese-Brazilian", "Frederick'XS"));
return list; return list;
} }
private void setupLicenses() { private void setupLicenses() {
final var licenses = new ArrayList<License>(); final var licenses = new ArrayList<License>();
licenses.add( licenses.add(
new License( new License(
"AndroidX Libraries", "Apache License 2.0", "https://github.com/androidx/androidx")); "AndroidX Libraries", "Apache License 2.0", "https://github.com/androidx/androidx"));
licenses.add( licenses.add(
new License("Gradle Build Tool", "Apache License 2.0", "https://github.com/gradle/gradle")); new License("Gradle Build Tool", "Apache License 2.0", "https://github.com/gradle/gradle"));
licenses.add(new License("Gson", "Apache License 2.0", "https://github.com/google/gson")); licenses.add(new License("Gson", "Apache License 2.0", "https://github.com/google/gson"));
licenses.add( licenses.add(
new License( new License(
"Material Components for Android", "Material Components for Android",
"Apache License 2.0", "Apache License 2.0",
"https://github.com/material-components/material-components-android")); "https://github.com/material-components/material-components-android"));
licenses.add( licenses.add(
new License("ANTLR4 Runtime", "BSD 3-clause License", "https://github.com/antlr/antlr4")); new License("ANTLR4 Runtime", "BSD 3-clause License", "https://github.com/antlr/antlr4"));
licenses.add( licenses.add(
new License("Glide", "BSD, part MIT and Apache 2.0", "https://github.com/bumptech/glide")); new License("Glide", "BSD, part MIT and Apache 2.0", "https://github.com/bumptech/glide"));
licenses.add( licenses.add(
new License( new License(
"Android UtilCode", "Apache License 2.0", "https://github.com/Blankj/AndroidUtilCode")); "Android UtilCode", "Apache License 2.0", "https://github.com/Blankj/AndroidUtilCode"));
licenses.add( licenses.add(
new License( new License(
"UnicornFilePicker", "UnicornFilePicker",
"Apache License 2.0", "Apache License 2.0",
"https://github.com/abhishekti7/UnicornFilePicker")); "https://github.com/abhishekti7/UnicornFilePicker"));
licenses.add( licenses.add(
new License("QuickAction", "Apache License 2.0", "https://github.com/piruin/quickaction")); new License("QuickAction", "Apache License 2.0", "https://github.com/piruin/quickaction"));
licenses.add( licenses.add(
new License("XmlToJson", "Apache License 2.0", "https://github.com/smart-fun/XmlToJson")); new License("XmlToJson", "Apache License 2.0", "https://github.com/smart-fun/XmlToJson"));
licenses.add( licenses.add(
new License( new License(
"AndroidTreeView", "AndroidTreeView",
"Apache License 2.0", "Apache License 2.0",
"https://github.com/bmelnychuk/AndroidTreeView")); "https://github.com/bmelnychuk/AndroidTreeView"));
licenses.add( licenses.add(
new License( new License(
"CodeEditor v0.5.2", "Apache License 2.0", "https://github.com/Rosemoe/CodeEditor")); "CodeEditor v0.5.2", "Apache License 2.0", "https://github.com/Rosemoe/CodeEditor"));
licenses.add( licenses.add(
new License("Guava-Android", "Apache License 2.0", "https://github.com/google/guava")); new License("Guava-Android", "Apache License 2.0", "https://github.com/google/guava"));
licenses.add(new License("Jsoup", "MIT License", "https://github.com/jhy/jsoup")); licenses.add(new License("Jsoup", "MIT License", "https://github.com/jhy/jsoup"));
licenses.add( licenses.add(
new License("Termux [Terminal Emulator]", "GPL 3", "https://github.com/termux/termux-app")); new License("Termux [Terminal Emulator]", "GPL 3", "https://github.com/termux/termux-app"));
licenses.add( licenses.add(
new License("JavaPoet", "Apache License 2.0", "https://github.com/square/JavaPoet")); new License("JavaPoet", "Apache License 2.0", "https://github.com/square/JavaPoet"));
licenses.add( licenses.add(
new License("Zip4j", "Apache License 2.0", "https://github.com/srikanth-lingala/zip4j")); new License("Zip4j", "Apache License 2.0", "https://github.com/srikanth-lingala/zip4j"));
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (License license : licenses) { for (License license : licenses) {
sb.append("\u2022 "); sb.append("\u2022 ");
sb.append(license.name); sb.append(license.name);
sb.append("<br>"); sb.append("<br>");
sb.append(getString(R.string.msg_about_licensed_under)); sb.append(getString(R.string.msg_about_licensed_under));
sb.append(license.license); sb.append(license.license);
sb.append("<br><a href=\""); sb.append("<br><a href=\"");
sb.append(license.url); sb.append(license.url);
sb.append("\">"); sb.append("\">");
sb.append(license.url); sb.append(license.url);
sb.append("</a>"); sb.append("</a>");
sb.append("<br><br>"); sb.append("<br><br>");
} }
sb.append(getString(R.string.license_jdk)); sb.append(getString(R.string.license_jdk));
sb.append(String.format("<br><a href=\"%1$s\">%1$s</a>", JDK_SOURCE)); sb.append(String.format("<br><a href=\"%1$s\">%1$s</a>", JDK_SOURCE));
sb.append("<br><br>"); sb.append("<br><br>");
binding.items.licenses.setText(Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_LEGACY)); binding.items.licenses.setText(Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_LEGACY));
binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance()); binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance());
} }
private String getFooter() { private String getFooter() {
final String arch = android.os.Build.SUPPORTED_ABIS[0]; final String arch = android.os.Build.SUPPORTED_ABIS[0];
try { try {
final String version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; final String version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
return getString(R.string.about_footer, version, arch); return getString(R.string.about_footer, version, arch);
} catch (Throwable th) { } catch (Throwable th) {
return getString(R.string.about_footer_alternate, arch); return getString(R.string.about_footer_alternate, arch);
} }
} }
} }
@@ -1,321 +1,321 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide; package com.itsaky.androidide;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.os.Bundle; import android.os.Bundle;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.transition.TransitionManager; import androidx.transition.TransitionManager;
import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.SizeUtils; import com.blankj.utilcode.util.SizeUtils;
import com.blankj.utilcode.util.ThreadUtils; import com.blankj.utilcode.util.ThreadUtils;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.app.StudioActivity;
import com.itsaky.androidide.databinding.ActivityDownloadBinding; import com.itsaky.androidide.databinding.ActivityDownloadBinding;
import com.itsaky.androidide.fragments.sheets.ProgressSheet; import com.itsaky.androidide.fragments.sheets.ProgressSheet;
import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.androidide.managers.PreferenceManager;
import com.itsaky.androidide.shell.IProcessExecutor; import com.itsaky.androidide.shell.IProcessExecutor;
import com.itsaky.androidide.shell.ProcessExecutorFactory; import com.itsaky.androidide.shell.ProcessExecutorFactory;
import com.itsaky.androidide.shell.ProcessStreamsHolder; import com.itsaky.androidide.shell.ProcessStreamsHolder;
import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.tasks.callables.ListDirectoryCallable; import com.itsaky.androidide.tasks.callables.ListDirectoryCallable;
import com.itsaky.androidide.utils.DialogUtils; import com.itsaky.androidide.utils.DialogUtils;
import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.Environment;
import com.itsaky.androidide.utils.FileUtil; import com.itsaky.androidide.utils.FileUtil;
import com.itsaky.androidide.utils.InputStreamLineReader; import com.itsaky.androidide.utils.InputStreamLineReader;
import com.itsaky.toaster.Toaster; import com.itsaky.toaster.Toaster;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import abhishekti7.unicorn.filepicker.UnicornFilePicker; import abhishekti7.unicorn.filepicker.UnicornFilePicker;
public class DownloadActivity extends StudioActivity { public class DownloadActivity extends StudioActivity {
private ActivityDownloadBinding binding; private ActivityDownloadBinding binding;
private ProgressSheet progressSheet; private ProgressSheet progressSheet;
private File choosenDir = null; private File choosenDir = null;
@Override @Override
protected View bindLayout() { protected View bindLayout() {
binding = ActivityDownloadBinding.inflate(getLayoutInflater()); binding = ActivityDownloadBinding.inflate(getLayoutInflater());
return binding.getRoot(); return binding.getRoot();
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final OnClickListener clickListener = this::handleClick; final OnClickListener clickListener = this::handleClick;
binding.dismiss.setOnClickListener(clickListener); binding.dismiss.setOnClickListener(clickListener);
binding.choose.setOnClickListener(clickListener); binding.choose.setOnClickListener(clickListener);
binding.install.setOnClickListener(clickListener); binding.install.setOnClickListener(clickListener);
binding.install.setEnabled(false); binding.install.setEnabled(false);
} }
private void handleClick(View v) { private void handleClick(View v) {
final int id = v.getId(); final int id = v.getId();
if (id == binding.dismiss.getId()) { if (id == binding.dismiss.getId()) {
TransitionManager.beginDelayedTransition(binding.getRoot()); TransitionManager.beginDelayedTransition(binding.getRoot());
binding.card.setVisibility(View.GONE); binding.card.setVisibility(View.GONE);
} else if (id == binding.choose.getId()) { } else if (id == binding.choose.getId()) {
try { try {
binding.install.setEnabled(false); binding.install.setEnabled(false);
UnicornFilePicker.from(this) UnicornFilePicker.from(this)
.addConfigBuilder() .addConfigBuilder()
.addItemDivider(false) .addItemDivider(false)
.selectMultipleFiles(false) .selectMultipleFiles(false)
.setRootDirectory(FileUtil.getExternalStorageDir()) .setRootDirectory(FileUtil.getExternalStorageDir())
.showHiddenFiles(true) .showHiddenFiles(true)
.showOnlyDirectory(true) .showOnlyDirectory(true)
.theme(R.style.AppTheme_FilePicker) .theme(R.style.AppTheme_FilePicker)
.build() .build()
.forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE); .forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
} catch (Throwable th) { } catch (Throwable th) {
LOG.error(getString(R.string.err_cannot_start_file_picker), th); LOG.error(getString(R.string.err_cannot_start_file_picker), th);
} }
} else if (id == binding.install.getId()) { } else if (id == binding.install.getId()) {
if (choosenDir == null) { if (choosenDir == null) {
getApp().toast(R.string.msg_pick_files, Toaster.Type.ERROR); getApp().toast(R.string.msg_pick_files, Toaster.Type.ERROR);
return; return;
} }
if (!choosenDir.exists()) { if (!choosenDir.exists()) {
getApp().toast(R.string.msg_file_doesnt_exist, Toaster.Type.ERROR); getApp().toast(R.string.msg_file_doesnt_exist, Toaster.Type.ERROR);
return; return;
} }
installAll(); installAll();
} }
} }
private void installAll() { private void installAll() {
showProgress(); showProgress();
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true); getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true);
try { try {
final File script = createExtractScript(); final File script = createExtractScript();
final ProcessStreamsHolder holder = new ProcessStreamsHolder(); final ProcessStreamsHolder holder = new ProcessStreamsHolder();
final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor(); final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor();
executor.execAsync( executor.execAsync(
holder, holder,
this::onInstallProcessExit, this::onInstallProcessExit,
true, true,
Environment.BUSYBOX.getAbsolutePath(), Environment.BUSYBOX.getAbsolutePath(),
"sh", // We use busybox's sh, because Environment.SHELL is not installed yet... "sh", // We use busybox's sh, because Environment.SHELL is not installed yet...
script.getAbsolutePath()); script.getAbsolutePath());
this.output = new StringBuilder(); this.output = new StringBuilder();
final InputStreamLineReader reader = final InputStreamLineReader reader =
new InputStreamLineReader(holder.in, this::onInstallationOutput); new InputStreamLineReader(holder.in, this::onInstallationOutput);
new Thread(reader).start(); new Thread(reader).start();
} catch (DownloadActivity.InstallationException e) { } catch (DownloadActivity.InstallationException e) {
LOG.error(getString(R.string.err_installation), e); LOG.error(getString(R.string.err_installation), e);
onInstallationFailed(e.exitCode); onInstallationFailed(e.exitCode);
} catch (IOException e) { } catch (IOException e) {
LOG.error(getString(R.string.err_installation), e); LOG.error(getString(R.string.err_installation), e);
onInstallationFailed(5); // Exit code 5 : I/O Error onInstallationFailed(5); // Exit code 5 : I/O Error
} }
} }
private void onInstallationOutput(final String line) { private void onInstallationOutput(final String line) {
ThreadUtils.runOnUiThread(() -> this.appendOut(line)); ThreadUtils.runOnUiThread(() -> this.appendOut(line));
} }
private void onInstallProcessExit(final int code) { private void onInstallProcessExit(final int code) {
ThreadUtils.runOnUiThread( ThreadUtils.runOnUiThread(
() -> { () -> {
if (code == 0) { // 0 = normal execution if (code == 0) { // 0 = normal execution
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true); getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true);
showRestartNeeded(); showRestartNeeded();
if (getProgressSheet().isShowing()) { if (getProgressSheet().isShowing()) {
getProgressSheet().dismiss(); getProgressSheet().dismiss();
} }
} else { } else {
onInstallationFailed(code); onInstallationFailed(code);
} }
}); });
} }
private void onInstallationFailed(int code) { private void onInstallationFailed(int code) {
// TODO Can this be improved by adding some animations? // TODO Can this be improved by adding some animations?
if (getProgressSheet().isShowing()) { if (getProgressSheet().isShowing()) {
getProgressSheet().dismiss(); getProgressSheet().dismiss();
} }
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
builder.setTitle(R.string.title_installation_failed); builder.setTitle(R.string.title_installation_failed);
builder.setMessage(getString(R.string.msg_installation_failed, code, this.output.toString())); builder.setMessage(getString(R.string.msg_installation_failed, code, this.output.toString()));
builder.setPositiveButton( builder.setPositiveButton(
android.R.string.ok, android.R.string.ok,
(d, w) -> { (d, w) -> {
d.dismiss(); d.dismiss();
finishAffinity(); finishAffinity();
}); });
builder.setNegativeButton(android.R.string.cancel, null); builder.setNegativeButton(android.R.string.cancel, null);
builder.show(); builder.show();
} }
private File createExtractScript() throws DownloadActivity.InstallationException { private File createExtractScript() throws DownloadActivity.InstallationException {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append("cd"); sb.append("cd");
joiner(sb); joiner(sb);
sb.append("echo 'Installing...'"); sb.append("echo 'Installing...'");
joiner(sb); joiner(sb);
File[] files = choosenDir.listFiles(ARCHIVE_FILTER); File[] files = choosenDir.listFiles(ARCHIVE_FILTER);
if (files == null || files.length <= 0) { if (files == null || files.length <= 0) {
throw new InstallationException(2); throw new InstallationException(2);
} }
for (File f : files) { for (File f : files) {
if (f.getName().endsWith(".tar.xz")) { if (f.getName().endsWith(".tar.xz")) {
if (f.getName().startsWith("androidide-sysroot")) { if (f.getName().startsWith("androidide-sysroot")) {
sb.append("cd $SYSROOT/.."); sb.append("cd $SYSROOT/..");
joiner(sb); joiner(sb);
} }
sb.append("$BUSYBOX tar xvJf '").append(f.getAbsolutePath()).append("'"); sb.append("$BUSYBOX tar xvJf '").append(f.getAbsolutePath()).append("'");
joiner(sb); joiner(sb);
sb.append("cd $HOME"); sb.append("cd $HOME");
joiner(sb); joiner(sb);
} else if (f.getName().endsWith(".zip")) { } else if (f.getName().endsWith(".zip")) {
sb.append("$BUSYBOX unzip '").append(f.getAbsolutePath()).append("'"); sb.append("$BUSYBOX unzip '").append(f.getAbsolutePath()).append("'");
joiner(sb); joiner(sb);
} }
} }
sb.append("echo 'Cleaning unsupported flags in binaries...'"); sb.append("echo 'Cleaning unsupported flags in binaries...'");
joiner(sb); joiner(sb);
String DONE = "DONE"; String DONE = "DONE";
sb.append("echo ").append(DONE); sb.append("echo ").append(DONE);
final File script = new File(Environment.TMP_DIR, "extract_tools.sh"); final File script = new File(Environment.TMP_DIR, "extract_tools.sh");
if (!FileIOUtils.writeFileFromString(script, sb.toString())) { if (!FileIOUtils.writeFileFromString(script, sb.toString())) {
throw new InstallationException(2); throw new InstallationException(2);
} }
return script; return script;
} }
private void joiner(StringBuilder sb) { private void joiner(StringBuilder sb) {
sb.append(" && "); sb.append(" && ");
} }
private StringBuilder output = new StringBuilder(); private StringBuilder output = new StringBuilder();
private void appendOut(String line) { private void appendOut(String line) {
LOG.debug("Installation:", line); LOG.debug("Installation:", line);
output.append(line.trim()); output.append(line.trim());
output.append("\n"); output.append("\n");
getProgressSheet().setSubMessage(line); getProgressSheet().setSubMessage(line);
} }
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE
&& resultCode == RESULT_OK) { && resultCode == RESULT_OK) {
ArrayList<String> files = data.getStringArrayListExtra("filePaths"); ArrayList<String> files = data.getStringArrayListExtra("filePaths");
if (files != null) { if (files != null) {
if (files.size() == 1) { if (files.size() == 1) {
File choosenDir = new File(files.get(0)); File choosenDir = new File(files.get(0));
if (choosenDir.exists() && choosenDir.isDirectory()) { if (choosenDir.exists() && choosenDir.isDirectory()) {
this.choosenDir = choosenDir; this.choosenDir = choosenDir;
setDone(binding.choose); setDone(binding.choose);
binding.install.setEnabled(true); binding.install.setEnabled(true);
new TaskExecutor() new TaskExecutor()
.executeAsync( .executeAsync(
new ListDirectoryCallable(choosenDir), new ListDirectoryCallable(choosenDir),
__ -> binding.filesText.setText(getString(R.string.msg_installable_files, __))); __ -> binding.filesText.setText(getString(R.string.msg_installable_files, __)));
} else { } else {
getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR); getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
} }
} else { } else {
getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR); getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR);
} }
} }
} }
} }
private void setDone(MaterialButton button) { private void setDone(MaterialButton button) {
final int dp8 = SizeUtils.dp2px(8); final int dp8 = SizeUtils.dp2px(8);
final int dp4 = SizeUtils.dp2px(4); final int dp4 = SizeUtils.dp2px(4);
final int dp24 = SizeUtils.dp2px(24); final int dp24 = SizeUtils.dp2px(24);
button.setText(""); button.setText("");
button.setIconResource(R.drawable.ic_ok); button.setIconResource(R.drawable.ic_ok);
button.setIconTint(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.bg_green))); button.setIconTint(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.bg_green)));
button.setBackgroundColor(android.graphics.Color.TRANSPARENT); button.setBackgroundColor(android.graphics.Color.TRANSPARENT);
button.setPaddingRelative(dp8, dp4, dp8, dp4); button.setPaddingRelative(dp8, dp4, dp8, dp4);
button.setStrokeWidth(0); button.setStrokeWidth(0);
button.setGravity(Gravity.CENTER); button.setGravity(Gravity.CENTER);
button.setIconPadding(0); button.setIconPadding(0);
button.setInsetTop(0); button.setInsetTop(0);
button.setInsetBottom(0); button.setInsetBottom(0);
button.setPaddingRelative(dp24, dp4, dp24, dp4); button.setPaddingRelative(dp24, dp4, dp24, dp4);
} }
private void showProgress() { private void showProgress() {
getProgressSheet().setCancelable(false); getProgressSheet().setCancelable(false);
getProgressSheet().setShowShadow(false); getProgressSheet().setShowShadow(false);
getProgressSheet() getProgressSheet()
.setSubMessageEnabled(true) .setSubMessageEnabled(true)
.setWelcomeTextEnabled(true) .setWelcomeTextEnabled(true)
.show(getSupportFragmentManager(), "progress_sheet"); .show(getSupportFragmentManager(), "progress_sheet");
} }
private void showRestartNeeded() { private void showRestartNeeded() {
setDone(binding.install); setDone(binding.install);
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
builder.setTitle(R.string.title_restart); builder.setTitle(R.string.title_restart);
builder.setMessage(R.string.msg_restart); builder.setMessage(R.string.msg_restart);
builder.setCancelable(false); builder.setCancelable(false);
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity()); builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
builder.create().show(); builder.create().show();
} }
private ProgressSheet getProgressSheet() { private ProgressSheet getProgressSheet() {
return progressSheet == null return progressSheet == null
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait)) ? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
: progressSheet; : progressSheet;
} }
private final FileFilter ARCHIVE_FILTER = private final FileFilter ARCHIVE_FILTER =
p1 -> p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip")); p1 -> p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip"));
private static class InstallationException extends Exception { private static class InstallationException extends Exception {
private final int exitCode; private final int exitCode;
public InstallationException(int exitCode) { public InstallationException(int exitCode) {
this.exitCode = exitCode; this.exitCode = exitCode;
} }
} }
} }
@@ -425,4 +425,4 @@ public class MainActivity extends StudioActivity
mProgressSheet.dismiss(); mProgressSheet.dismiss();
} }
} }
} }
@@ -1,92 +1,92 @@
/************************************************************************************ /************************************************************************************
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* *
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
**************************************************************************************/ **************************************************************************************/
package com.itsaky.androidide; package com.itsaky.androidide;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.view.View; import android.view.View;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.app.StudioActivity;
import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.ActivitySplashBinding; import com.itsaky.androidide.databinding.ActivitySplashBinding;
import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.utils.DialogUtils; import com.itsaky.androidide.utils.DialogUtils;
public class SplashActivity extends StudioActivity { public class SplashActivity extends StudioActivity {
private static final Handler mHandler = new Handler(); private static final Handler mHandler = new Handler();
private final Runnable mRunnable = private final Runnable mRunnable =
() -> { () -> {
ConstantsBridge.SPLASH_TO_MAIN = true; ConstantsBridge.SPLASH_TO_MAIN = true;
startActivity(new Intent(SplashActivity.this, MainActivity.class)); startActivity(new Intent(SplashActivity.this, MainActivity.class));
finish(); finish();
}; };
private ActivitySplashBinding binding; private ActivitySplashBinding binding;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
binding.splashText.setText(getString(R.string.msg_checking_storage_permissions)); binding.splashText.setText(getString(R.string.msg_checking_storage_permissions));
} }
@Override @Override
protected void onStorageAlreadyGranted() { protected void onStorageAlreadyGranted() {
super.onStorageAlreadyGranted(); super.onStorageAlreadyGranted();
proceed(); proceed();
} }
@Override @Override
protected void onStorageGranted() { protected void onStorageGranted() {
super.onStorageGranted(); super.onStorageGranted();
proceed(); proceed();
} }
@Override @Override
protected View bindLayout() { protected View bindLayout() {
binding = ActivitySplashBinding.inflate(getLayoutInflater()); binding = ActivitySplashBinding.inflate(getLayoutInflater());
return binding.getRoot(); return binding.getRoot();
} }
private void proceed() { private void proceed() {
if (!StudioApp.isAbiSupported()) { if (!StudioApp.isAbiSupported()) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
builder.setTitle(R.string.title_device_not_supported); builder.setTitle(R.string.title_device_not_supported);
builder.setMessage(R.string.msg_device_not_supported); builder.setMessage(R.string.msg_device_not_supported);
builder.setCancelable(false); builder.setCancelable(false);
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity()); builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
builder.create().show(); builder.create().show();
} else { } else {
binding.splashText.setText(getString(R.string.msg_storage_granted)); binding.splashText.setText(getString(R.string.msg_storage_granted));
if (getApp().isFrameworkInstalled()) { if (getApp().isFrameworkInstalled()) {
goToMain(); goToMain();
} else { } else {
startActivity(new Intent(this, DownloadActivity.class)); startActivity(new Intent(this, DownloadActivity.class));
finish(); finish();
} }
} }
} }
private void goToMain() { private void goToMain() {
mHandler.removeCallbacks(mRunnable); mHandler.removeCallbacks(mRunnable);
mHandler.postDelayed(mRunnable, 1000); mHandler.postDelayed(mRunnable, 1000);
} }
} }
@@ -1,55 +1,55 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions package com.itsaky.androidide.actions
import com.itsaky.androidide.EditorActivity import com.itsaky.androidide.EditorActivity
/** /**
* Marker class for actions that execute build related tasks. * Marker class for actions that execute build related tasks.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
abstract class BaseBuildAction : EditorActivityAction() { abstract class BaseBuildAction : EditorActivityAction() {
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
val context = getActivity(data) val context = getActivity(data)
if (context == null) { if (context == null) {
visible = false visible = false
return return
} else { } else {
visible = true visible = true
} }
if (isBuildInProgress(context)) { if (isBuildInProgress(context)) {
enabled = false enabled = false
return return
} else { } else {
enabled = true enabled = true
} }
} }
override fun postExec(data: ActionData, result: Any) { override fun postExec(data: ActionData, result: Any) {
val context = getActivity(data) ?: return val context = getActivity(data) ?: return
context.invalidateOptionsMenu() context.invalidateOptionsMenu()
} }
fun shouldPrepare() = visible && enabled fun shouldPrepare() = visible && enabled
private fun isBuildInProgress(activity: EditorActivity): Boolean = private fun isBuildInProgress(activity: EditorActivity): Boolean =
activity.buildService == null || activity.buildService.isBuildInProgress activity.buildService == null || activity.buildService.isBuildInProgress
} }
@@ -1,62 +1,62 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions package com.itsaky.androidide.actions
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.views.editor.IDEEditor import com.itsaky.androidide.views.editor.IDEEditor
/** @author Akash Yadav */ /** @author Akash Yadav */
abstract class BaseEditorAction : ActionItem { abstract class BaseEditorAction : ActionItem {
override var label: String = "" override var label: String = ""
override var visible: Boolean = true override var visible: Boolean = true
override var enabled: Boolean = true override var enabled: Boolean = true
override var icon: Drawable? = null override var icon: Drawable? = null
override var requiresUIThread: Boolean = override var requiresUIThread: Boolean =
true // all editor actions must be executed on UI thread true // all editor actions must be executed on UI thread
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
getEditor(data) getEditor(data)
?: kotlin.run { ?: kotlin.run {
visible = false visible = false
enabled = false enabled = false
return return
} }
visible = true visible = true
enabled = true enabled = true
} }
fun getEditor(data: ActionData): IDEEditor? { fun getEditor(data: ActionData): IDEEditor? {
return data.get(IDEEditor::class.java) return data.get(IDEEditor::class.java)
} }
fun getContext(data: ActionData): Context? { fun getContext(data: ActionData): Context? {
val editor = getEditor(data) ?: return null val editor = getEditor(data) ?: return null
return editor.context return editor.context
} }
fun tintDrawable(context: Context, drawable: Drawable): Drawable { fun tintDrawable(context: Context, drawable: Drawable): Drawable {
drawable.setTint( drawable.setTint(
ContextCompat.getColor(context, com.itsaky.androidide.R.color.primaryIconColor)) ContextCompat.getColor(context, com.itsaky.androidide.R.color.primaryIconColor))
return drawable return drawable
} }
} }
@@ -1,45 +1,45 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions package com.itsaky.androidide.actions
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import com.itsaky.androidide.EditorActivity import com.itsaky.androidide.EditorActivity
/** @author Akash Yadav */ /** @author Akash Yadav */
abstract class EditorActivityAction : ActionItem { abstract class EditorActivityAction : ActionItem {
override var enabled: Boolean = true override var enabled: Boolean = true
override var visible: Boolean = true override var visible: Boolean = true
override var icon: Drawable? = null override var icon: Drawable? = null
override var label: String = "" override var label: String = ""
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TOOLBAR override var location: ActionItem.Location = ActionItem.Location.EDITOR_TOOLBAR
override var requiresUIThread: Boolean = false override var requiresUIThread: Boolean = false
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
if (!hasRequiredData(data, Context::class.java)) { if (!hasRequiredData(data, Context::class.java)) {
markInvisible() markInvisible()
} }
} }
fun getActivity(data: ActionData): EditorActivity? { fun getActivity(data: ActionData): EditorActivity? {
val context = data.get(Context::class.java) ?: return null val context = data.get(Context::class.java) ?: return null
return context as EditorActivity return context as EditorActivity
} }
} }
@@ -1,23 +1,23 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions package com.itsaky.androidide.actions
/** @author Akash Yadav */ /** @author Akash Yadav */
abstract class EditorActivityActionMenu : EditorActivityAction(), ActionMenu { abstract class EditorActivityActionMenu : EditorActivityAction(), ActionMenu {
override val children: MutableSet<ActionItem> = mutableSetOf() override val children: MutableSet<ActionItem> = mutableSetOf()
} }
@@ -1,46 +1,46 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions package com.itsaky.androidide.actions
import com.itsaky.androidide.views.editor.CodeEditorView import com.itsaky.androidide.views.editor.CodeEditorView
import com.itsaky.androidide.views.editor.IDEEditor import com.itsaky.androidide.views.editor.IDEEditor
/** @author Akash Yadav */ /** @author Akash Yadav */
abstract class EditorRelatedAction : EditorActivityAction() { abstract class EditorRelatedAction : EditorActivityAction() {
override var requiresUIThread: Boolean = true override var requiresUIThread: Boolean = true
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
val editor = val editor =
getEditor(data) getEditor(data)
?: run { ?: run {
visible = false visible = false
enabled = false enabled = false
return return
} }
val file = editor.file val file = editor.file
visible = file != null visible = file != null
enabled = visible enabled = visible
} }
fun getEditor(data: ActionData): IDEEditor? = data.get(IDEEditor::class.java) fun getEditor(data: ActionData): IDEEditor? = data.get(IDEEditor::class.java)
fun getEditorView(data: ActionData): CodeEditorView? = data[CodeEditorView::class.java] fun getEditorView(data: ActionData): CodeEditorView? = data[CodeEditorView::class.java]
} }
@@ -1,42 +1,42 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class AssembleDebugAction() : BaseBuildAction() { class AssembleDebugAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.build_debug) label = context.getString(R.string.build_debug)
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug) icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
} }
override val id: String = "editor_assembleDebug" override val id: String = "editor_assembleDebug"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val activity = getActivity(data) ?: return false val activity = getActivity(data) ?: return false
activity.assembleDebug(true) activity.assembleDebug(true)
return true return true
} }
} }
@@ -1,42 +1,42 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class AssembleReleaseAction() : BaseBuildAction() { class AssembleReleaseAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.build_release) label = context.getString(R.string.build_release)
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release) icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
} }
override val id: String = "editor_assembleRelease" override val id: String = "editor_assembleRelease"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val activity = getActivity(data) ?: return false val activity = getActivity(data) ?: return false
activity.assembleRelease() activity.assembleRelease()
return true return true
} }
} }
@@ -1,42 +1,42 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class BuildAction() : BaseBuildAction() { class BuildAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.build) label = context.getString(R.string.build)
icon = ContextCompat.getDrawable(context, R.drawable.ic_run) icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
} }
override val id: String = "editor_build" override val id: String = "editor_build"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val activity = getActivity(data) ?: return false val activity = getActivity(data) ?: return false
activity.build() activity.build()
return true return true
} }
} }
@@ -1,44 +1,44 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionMenu import com.itsaky.androidide.actions.ActionMenu
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class BuildActionMenu() : BaseBuildAction(), ActionMenu { class BuildActionMenu() : BaseBuildAction(), ActionMenu {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.build_tasks) label = context.getString(R.string.build_tasks)
icon = ContextCompat.getDrawable(context, R.drawable.ic_run) icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
addAction(BuildAction(context)) addAction(BuildAction(context))
addAction(AssembleDebugAction(context)) addAction(AssembleDebugAction(context))
addAction(AssembleReleaseAction(context)) addAction(AssembleReleaseAction(context))
addAction(CreateAABAction(context)) addAction(CreateAABAction(context))
addAction(CleanAction(context)) addAction(CleanAction(context))
addAction(RebuildAction(context)) addAction(RebuildAction(context))
} }
override val children: MutableSet<ActionItem> = mutableSetOf() override val children: MutableSet<ActionItem> = mutableSetOf()
override val id: String = "editor_buildTasks" override val id: String = "editor_buildTasks"
} }
@@ -1,66 +1,66 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorActivityAction import com.itsaky.androidide.actions.EditorActivityAction
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
/** @author Akash Yadav */ /** @author Akash Yadav */
class CancelBuildAction() : EditorActivityAction() { class CancelBuildAction() : EditorActivityAction() {
private val log = ILogger.newInstance(javaClass.simpleName) private val log = ILogger.newInstance(javaClass.simpleName)
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.title_cancel_build) label = context.getString(R.string.title_cancel_build)
icon = ContextCompat.getDrawable(context, R.drawable.ic_stop_daemons) icon = ContextCompat.getDrawable(context, R.drawable.ic_stop_daemons)
} }
override val id: String = "editor_stopGradleDaemons" override val id: String = "editor_stopGradleDaemons"
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
visible = true visible = true
enabled = true enabled = true
} }
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
log.info("Sending build cancellation request...") log.info("Sending build cancellation request...")
context.buildService.cancelCurrentBuild().whenComplete { result, error -> context.buildService.cancelCurrentBuild().whenComplete { result, error ->
if (error != null) { if (error != null) {
log.error("Failed to send build cancellation request", error) log.error("Failed to send build cancellation request", error)
return@whenComplete return@whenComplete
} }
if (!result.wasEnqueued) { if (!result.wasEnqueued) {
log.warn( log.warn(
"Unable to enqueue cancellation request", "Unable to enqueue cancellation request",
result.failureReason, result.failureReason,
result.failureReason!!.message) result.failureReason!!.message)
return@whenComplete return@whenComplete
} }
log.info("Build cancellation request was successfully enqueued...") log.info("Build cancellation request was successfully enqueued...")
} }
return true return true
} }
} }
@@ -1,41 +1,41 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class CleanAction() : BaseBuildAction() { class CleanAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.clean_project) label = context.getString(R.string.clean_project)
icon = ContextCompat.getDrawable(context, R.drawable.ic_clean) icon = ContextCompat.getDrawable(context, R.drawable.ic_clean)
} }
override val id: String = "editor_clean" override val id: String = "editor_clean"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
context.clean() context.clean()
return true return true
} }
} }
@@ -1,41 +1,41 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class CreateAABAction() : BaseBuildAction() { class CreateAABAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.create_aab) label = context.getString(R.string.create_aab)
icon = ContextCompat.getDrawable(context, R.drawable.ic_bundle) icon = ContextCompat.getDrawable(context, R.drawable.ic_bundle)
} }
override val id: String = "editor_createAAB" override val id: String = "editor_createAAB"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
context.bundle() context.bundle()
return true return true
} }
} }
@@ -1,46 +1,46 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.R.string import com.itsaky.androidide.R.string
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
import com.itsaky.androidide.actions.EditorActivityAction import com.itsaky.androidide.actions.EditorActivityAction
/** /**
* Triggers a project sync request. * Triggers a project sync request.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
class ProjectSyncAction() : BaseBuildAction() { class ProjectSyncAction() : BaseBuildAction() {
override val id: String = "action_editor_syncProject" override val id: String = "action_editor_syncProject"
override var requiresUIThread = true override var requiresUIThread = true
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(string.title_sync_project) label = context.getString(string.title_sync_project)
icon = ContextCompat.getDrawable(context, R.drawable.ic_sync) icon = ContextCompat.getDrawable(context, R.drawable.ic_sync)
} }
override fun execAction(data: ActionData): Any { override fun execAction(data: ActionData): Any {
return getActivity(data)!!.initializeProject() return getActivity(data)!!.initializeProject()
} }
} }
@@ -1,45 +1,45 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** /**
* The 'Quick Run' action in the editor activity. * The 'Quick Run' action in the editor activity.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
class QuickRunAction() : BaseBuildAction() { class QuickRunAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.quick_run_debug) label = context.getString(R.string.quick_run_debug)
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_outline) icon = ContextCompat.getDrawable(context, R.drawable.ic_run_outline)
} }
override val id: String = "editor_quick_run" override val id: String = "editor_quick_run"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
context.assembleDebug(true) context.assembleDebug(true)
return true return true
} }
} }
@@ -1,41 +1,41 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class RebuildAction() : BaseBuildAction() { class RebuildAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.clean_amp_build) label = context.getString(R.string.clean_amp_build)
icon = ContextCompat.getDrawable(context, R.drawable.ic_rebuild) icon = ContextCompat.getDrawable(context, R.drawable.ic_rebuild)
} }
override val id: String = "editor_cleanAndBuild" override val id: String = "editor_cleanAndBuild"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
context.cleanAndRebuild() context.cleanAndRebuild()
return true return true
} }
} }
@@ -1,42 +1,42 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.build package com.itsaky.androidide.actions.build
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionMenu import com.itsaky.androidide.actions.ActionMenu
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
import com.itsaky.androidide.actions.lint.LintTaskMenu import com.itsaky.androidide.actions.lint.LintTaskMenu
/** @author Akash Yadav */ /** @author Akash Yadav */
class RunActionMenu() : BaseBuildAction(), ActionMenu { class RunActionMenu() : BaseBuildAction(), ActionMenu {
override val children: MutableSet<ActionItem> = mutableSetOf() override val children: MutableSet<ActionItem> = mutableSetOf()
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.run) label = context.getString(R.string.run)
icon = ContextCompat.getDrawable(context, R.drawable.ic_run) icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
addAction(BuildActionMenu(context)) addAction(BuildActionMenu(context))
addAction(LintTaskMenu(context)) addAction(LintTaskMenu(context))
} }
override val id: String = "editor_runMenu" override val id: String = "editor_runMenu"
} }
@@ -1,42 +1,42 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor package com.itsaky.androidide.actions.editor
import android.content.Context import android.content.Context
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseEditorAction import com.itsaky.androidide.actions.BaseEditorAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class CopyAction() : BaseEditorAction() { class CopyAction() : BaseEditorAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.copy) label = context.getString(R.string.copy)
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCopyDrawable)) val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCopyDrawable))
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) } icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
arr.recycle() arr.recycle()
} }
override val id: String = "ideEditor_copy" override val id: String = "ideEditor_copy"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val editor = getEditor(data) ?: return false val editor = getEditor(data) ?: return false
editor.copyText() editor.copyText()
return true return true
} }
} }
@@ -1,53 +1,53 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor package com.itsaky.androidide.actions.editor
import android.content.Context import android.content.Context
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseEditorAction import com.itsaky.androidide.actions.BaseEditorAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class CutAction() : BaseEditorAction() { class CutAction() : BaseEditorAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.cut) label = context.getString(R.string.cut)
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCutDrawable)) val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCutDrawable))
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) } icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
arr.recycle() arr.recycle()
} }
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
super.prepare(data) super.prepare(data)
if (!visible) { if (!visible) {
return return
} }
visible = getEditor(data)?.isEditable ?: false visible = getEditor(data)?.isEditable ?: false
enabled = visible enabled = visible
} }
override val id: String = "ideEditor_cut" override val id: String = "ideEditor_cut"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val editor = getEditor(data) ?: return false val editor = getEditor(data) ?: return false
editor.cutText() editor.cutText()
return true return true
} }
} }
@@ -1,44 +1,44 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor package com.itsaky.androidide.actions.editor
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.EditorRelatedAction import com.itsaky.androidide.actions.EditorRelatedAction
/** /**
* Expands the current selection using the language server set in editor. * Expands the current selection using the language server set in editor.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
class ExpandSelectionAction() : EditorRelatedAction() { class ExpandSelectionAction() : EditorRelatedAction() {
override val id: String = "editor_expandSelection" override val id: String = "editor_expandSelection"
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.action_expand_selection) label = context.getString(R.string.action_expand_selection)
icon = ContextCompat.getDrawable(context, R.drawable.ic_expand_selection) icon = ContextCompat.getDrawable(context, R.drawable.ic_expand_selection)
} }
override fun execAction(data: ActionData): Any { override fun execAction(data: ActionData): Any {
return getEditor(data)!!.expandSelection() return getEditor(data)!!.expandSelection()
} }
} }
@@ -1,54 +1,54 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor package com.itsaky.androidide.actions.editor
import android.content.Context import android.content.Context
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseEditorAction import com.itsaky.androidide.actions.BaseEditorAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class PasteAction() : BaseEditorAction() { class PasteAction() : BaseEditorAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.paste) label = context.getString(R.string.paste)
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModePasteDrawable)) val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModePasteDrawable))
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) } icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
arr.recycle() arr.recycle()
} }
override val id: String = "ideEditor_paste" override val id: String = "ideEditor_paste"
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
super.prepare(data) super.prepare(data)
if (!visible) { if (!visible) {
return return
} }
visible = getEditor(data)?.isEditable ?: false visible = getEditor(data)?.isEditable ?: false
enabled = visible enabled = visible
} }
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val editor = getEditor(data) ?: return false val editor = getEditor(data) ?: return false
editor.pasteText() editor.pasteText()
return true return true
} }
} }
@@ -1,43 +1,43 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor package com.itsaky.androidide.actions.editor
import android.content.Context import android.content.Context
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseEditorAction import com.itsaky.androidide.actions.BaseEditorAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class SelectAllAction() : BaseEditorAction() { class SelectAllAction() : BaseEditorAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.selectAll) label = context.getString(R.string.selectAll)
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeSelectAllDrawable)) val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeSelectAllDrawable))
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) } icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
arr.recycle() arr.recycle()
} }
override val id: String = "ideEditor_selectAll" override val id: String = "ideEditor_selectAll"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val editor = getEditor(data) ?: return false val editor = getEditor(data) ?: return false
editor.selectAll() editor.selectAll()
return true return true
} }
} }
@@ -1,45 +1,45 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor.find package com.itsaky.androidide.actions.editor.find
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
/** /**
* 'Go to next' action in editor's search action mode. * 'Go to next' action in editor's search action mode.
* @author Akash Yadav * @author Akash Yadav
*/ */
class GoToNextAction() : SearchActionModeAction() { class GoToNextAction() : SearchActionModeAction() {
override val id: String = "editor.find.next" override val id: String = "editor.find.next"
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.next) label = context.getString(R.string.next)
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_next) icon = ContextCompat.getDrawable(context, R.drawable.ic_search_next)
} }
override fun execAction(data: ActionData): Any { override fun execAction(data: ActionData): Any {
val editor = getEditor(data)!! val editor = getEditor(data)!!
if (!editor.searcher.hasQuery()) { if (!editor.searcher.hasQuery()) {
return false return false
} }
return editor.searcher.gotoNext() return editor.searcher.gotoNext()
} }
} }
@@ -1,46 +1,46 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor.find package com.itsaky.androidide.actions.editor.find
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.R.string import com.itsaky.androidide.R.string
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
/** /**
* 'Go to previous' action in editor's search action mode. * 'Go to previous' action in editor's search action mode.
* @author Akash Yadav * @author Akash Yadav
*/ */
class GoToPreviousAction() : SearchActionModeAction() { class GoToPreviousAction() : SearchActionModeAction() {
override val id: String = "editor.find.previous" override val id: String = "editor.find.previous"
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(string.previous) label = context.getString(string.previous)
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_previous) icon = ContextCompat.getDrawable(context, R.drawable.ic_search_previous)
} }
override fun execAction(data: ActionData): Any { override fun execAction(data: ActionData): Any {
val editor = getEditor(data)!! val editor = getEditor(data)!!
if (!editor.searcher.hasQuery()) { if (!editor.searcher.hasQuery()) {
return false return false
} }
return editor.searcher.gotoPrevious() return editor.searcher.gotoPrevious()
} }
} }
@@ -1,87 +1,87 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor.find package com.itsaky.androidide.actions.editor.find
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.databinding.LayoutEditorFindReplaceBinding import com.itsaky.androidide.databinding.LayoutEditorFindReplaceBinding
import com.itsaky.androidide.utils.DialogUtils import com.itsaky.androidide.utils.DialogUtils
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
/** /**
* Shows dialog to replace one or all the matched text in editor searcher. * Shows dialog to replace one or all the matched text in editor searcher.
* @author Akash Yadav * @author Akash Yadav
*/ */
class ReplaceAction() : SearchActionModeAction() { class ReplaceAction() : SearchActionModeAction() {
override val id: String = "editor.search.replace" override val id: String = "editor.search.replace"
private val log = ILogger.newInstance(javaClass.simpleName) private val log = ILogger.newInstance(javaClass.simpleName)
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.replace) label = context.getString(R.string.replace)
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_replace) icon = ContextCompat.getDrawable(context, R.drawable.ic_search_replace)
} }
override fun execAction(data: ActionData): Any { override fun execAction(data: ActionData): Any {
val context = data[Context::class.java]!! val context = data[Context::class.java]!!
val editor = getEditor(data)!! val editor = getEditor(data)!!
if (!editor.searcher.hasQuery()) { if (!editor.searcher.hasQuery()) {
return false return false
} }
val binding = LayoutEditorFindReplaceBinding.inflate(LayoutInflater.from(context)) val binding = LayoutEditorFindReplaceBinding.inflate(LayoutInflater.from(context))
val builder = DialogUtils.newMaterialDialogBuilder(context) val builder = DialogUtils.newMaterialDialogBuilder(context)
builder.setTitle(R.string.replace) builder.setTitle(R.string.replace)
builder.setView(binding.root) builder.setView(binding.root)
builder.setNegativeButton(android.R.string.cancel, null) builder.setNegativeButton(android.R.string.cancel, null)
builder.setPositiveButton(R.string.replace) { dialog, _ -> builder.setPositiveButton(R.string.replace) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
val input = binding.replacementInput.editText val input = binding.replacementInput.editText
if (input == null) { if (input == null) {
log.error("Unable to perform replace action. Input field is null") log.error("Unable to perform replace action. Input field is null")
return@setPositiveButton return@setPositiveButton
} }
editor.searcher.replaceThis(input.text.toString()) editor.searcher.replaceThis(input.text.toString())
} }
builder.setNeutralButton(R.string.replaceAll) { dialog, _ -> builder.setNeutralButton(R.string.replaceAll) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
val input = binding.replacementInput.editText val input = binding.replacementInput.editText
if (input == null) { if (input == null) {
log.error("Unable to perform replace action. Input field is null") log.error("Unable to perform replace action. Input field is null")
return@setNeutralButton return@setNeutralButton
} }
editor.searcher.replaceAllAsync(input.text.toString()).whenComplete { _, error -> editor.searcher.replaceAllAsync(input.text.toString()).whenComplete { _, error ->
if (error != null) { if (error != null) {
log.error("Unable to replace all matched text", error) log.error("Unable to replace all matched text", error)
} else { } else {
editor.post { data[ActionMode::class.java]?.finish() } editor.post { data[ActionMode::class.java]?.finish() }
} }
} }
} }
builder.show() builder.show()
return true return true
} }
} }
@@ -1,27 +1,27 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.editor.find package com.itsaky.androidide.actions.editor.find
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionItem.Location import com.itsaky.androidide.actions.ActionItem.Location
import com.itsaky.androidide.actions.EditorRelatedAction import com.itsaky.androidide.actions.EditorRelatedAction
/** @author Akash Yadav */ /** @author Akash Yadav */
abstract class SearchActionModeAction : EditorRelatedAction() { abstract class SearchActionModeAction : EditorRelatedAction() {
override var location: Location = ActionItem.Location.EDITOR_SEARCH_ACTION_MODE override var location: Location = ActionItem.Location.EDITOR_SEARCH_ACTION_MODE
} }
@@ -1,46 +1,46 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.etc package com.itsaky.androidide.actions.etc
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorActivityAction import com.itsaky.androidide.actions.EditorActivityAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class DaemonStatusAction() : EditorActivityAction() { class DaemonStatusAction() : EditorActivityAction() {
override val id: String = "editor_daemonStatus" override val id: String = "editor_daemonStatus"
override var requiresUIThread: Boolean = true override var requiresUIThread: Boolean = true
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.gradle_daemon_status) label = context.getString(R.string.gradle_daemon_status)
icon = ContextCompat.getDrawable(context, R.drawable.ic_info) icon = ContextCompat.getDrawable(context, R.drawable.ic_info)
} }
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
visible = true visible = true
enabled = true enabled = true
} }
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
context.showDaemonStatus() context.showDaemonStatus()
return true return true
} }
} }
@@ -1,52 +1,52 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.etc package com.itsaky.androidide.actions.etc
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorActivityAction import com.itsaky.androidide.actions.EditorActivityAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class FileTreeAction() : EditorActivityAction() { class FileTreeAction() : EditorActivityAction() {
override val id: String = "editor_fileTree" override val id: String = "editor_fileTree"
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.msg_file_tree) label = context.getString(R.string.msg_file_tree)
icon = ContextCompat.getDrawable(context, R.drawable.ic_folder) icon = ContextCompat.getDrawable(context, R.drawable.ic_folder)
} }
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
visible = true visible = true
enabled = true enabled = true
} }
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
if (!context.binding.root.isDrawerOpen(GravityCompat.END)) { if (!context.binding.root.isDrawerOpen(GravityCompat.END)) {
context.binding.root.openDrawer(GravityCompat.END) context.binding.root.openDrawer(GravityCompat.END)
return true return true
} }
return false return false
} }
} }
@@ -1,46 +1,46 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.etc package com.itsaky.androidide.actions.etc
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionMenu import com.itsaky.androidide.actions.ActionMenu
import com.itsaky.androidide.actions.EditorActivityAction import com.itsaky.androidide.actions.EditorActivityAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class FindActionMenu() : EditorActivityAction(), ActionMenu { class FindActionMenu() : EditorActivityAction(), ActionMenu {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.menu_find) label = context.getString(R.string.menu_find)
icon = ContextCompat.getDrawable(context, R.drawable.ic_search) icon = ContextCompat.getDrawable(context, R.drawable.ic_search)
addAction(FindInFileAction(context)) addAction(FindInFileAction(context))
addAction(FindInProjectAction(context)) addAction(FindInProjectAction(context))
} }
override val children: MutableSet<ActionItem> = mutableSetOf() override val children: MutableSet<ActionItem> = mutableSetOf()
override val id: String = "editor_findActions" override val id: String = "editor_findActions"
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
visible = true visible = true
enabled = children.size > 0 enabled = children.size > 0
} }
} }
@@ -1,42 +1,42 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.etc package com.itsaky.androidide.actions.etc
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorRelatedAction import com.itsaky.androidide.actions.EditorRelatedAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class FindInFileAction() : EditorRelatedAction() { class FindInFileAction() : EditorRelatedAction() {
override val id: String = "editor_findInFile" override val id: String = "editor_findInFile"
override var requiresUIThread: Boolean = true override var requiresUIThread: Boolean = true
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.menu_find_file) label = context.getString(R.string.menu_find_file)
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_file) icon = ContextCompat.getDrawable(context, R.drawable.ic_search_file)
} }
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val editor = getEditorView(data) ?: return false val editor = getEditorView(data) ?: return false
editor.beginSearch() editor.beginSearch()
return true return true
} }
} }
@@ -1,61 +1,61 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.etc package com.itsaky.androidide.actions.etc
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorActivityAction import com.itsaky.androidide.actions.EditorActivityAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class FindInProjectAction() : EditorActivityAction() { class FindInProjectAction() : EditorActivityAction() {
override var requiresUIThread: Boolean = true override var requiresUIThread: Boolean = true
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.menu_find_project) label = context.getString(R.string.menu_find_project)
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_project) icon = ContextCompat.getDrawable(context, R.drawable.ic_search_project)
} }
override val id: String = "editor_findInProject" override val id: String = "editor_findInProject"
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
getActivity(data) getActivity(data)
?: run { ?: run {
visible = false visible = false
enabled = false enabled = false
return return
} }
visible = true visible = true
enabled = true enabled = true
} }
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
val dialog = context.findInProjectDialog val dialog = context.findInProjectDialog
return if (dialog != null) { return if (dialog != null) {
dialog.show() dialog.show()
true true
} else { } else {
false false
} }
} }
} }
@@ -1,62 +1,62 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.etc package com.itsaky.androidide.actions.etc
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorRelatedAction import com.itsaky.androidide.actions.EditorRelatedAction
import com.itsaky.androidide.handlers.FileOptionsHandler import com.itsaky.androidide.handlers.FileOptionsHandler
/** @author Akash Yadav */ /** @author Akash Yadav */
class PreviewLayoutAction() : EditorRelatedAction() { class PreviewLayoutAction() : EditorRelatedAction() {
override val id: String = "editor_previewLayout" override val id: String = "editor_previewLayout"
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.title_preview_layout) label = context.getString(R.string.title_preview_layout)
icon = ContextCompat.getDrawable(context, R.drawable.ic_preview_layout) icon = ContextCompat.getDrawable(context, R.drawable.ic_preview_layout)
} }
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
super.prepare(data) super.prepare(data)
if (!visible) { if (!visible) {
return return
} }
val editor = getEditor(data)!! val editor = getEditor(data)!!
val file = editor.file!! val file = editor.file!!
val isXml = file.name.endsWith(".xml") val isXml = file.name.endsWith(".xml")
visible = visible =
isXml && isXml &&
file.parentFile != null && file.parentFile != null &&
Regex(FileOptionsHandler.LAYOUT_RES_PATH_REGEX) Regex(FileOptionsHandler.LAYOUT_RES_PATH_REGEX)
.matches(file.parentFile!!.absolutePath) .matches(file.parentFile!!.absolutePath)
enabled = visible enabled = visible
} }
override fun execAction(data: ActionData): Any { override fun execAction(data: ActionData): Any {
val activity = getActivity(data)!! val activity = getActivity(data)!!
activity.previewLayout() activity.previewLayout()
return true return true
} }
} }
@@ -1,46 +1,46 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.file package com.itsaky.androidide.actions.file
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.EditorRelatedAction import com.itsaky.androidide.actions.EditorRelatedAction
/** /**
* Action that formats the code in the editor. * Action that formats the code in the editor.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
class FormatCodeAction() : EditorRelatedAction() { class FormatCodeAction() : EditorRelatedAction() {
override val id: String = "editor_file_formatCode" override val id: String = "editor_file_formatCode"
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.title_format_code) label = context.getString(R.string.title_format_code)
icon = ContextCompat.getDrawable(context, R.drawable.ic_format_code) icon = ContextCompat.getDrawable(context, R.drawable.ic_format_code)
} }
override fun execAction(data: ActionData): Any { override fun execAction(data: ActionData): Any {
val editor = getEditor(data)!! val editor = getEditor(data)!!
editor.formatCodeAsync() editor.formatCodeAsync()
return true return true
} }
} }
@@ -1,86 +1,86 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.file package com.itsaky.androidide.actions.file
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorRelatedAction import com.itsaky.androidide.actions.EditorRelatedAction
import com.itsaky.androidide.app.StudioApp import com.itsaky.androidide.app.StudioApp
import com.itsaky.androidide.projects.ProjectManager import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
import com.itsaky.toaster.Toaster import com.itsaky.toaster.Toaster
/** @author Akash Yadav */ /** @author Akash Yadav */
class SaveFileAction() : EditorRelatedAction() { class SaveFileAction() : EditorRelatedAction() {
private val log = ILogger.newInstance("SaveFileAction") private val log = ILogger.newInstance("SaveFileAction")
override val id: String = "editor_saveFile" override val id: String = "editor_saveFile"
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.save) label = context.getString(R.string.save)
icon = ContextCompat.getDrawable(context, R.drawable.ic_save) icon = ContextCompat.getDrawable(context, R.drawable.ic_save)
} }
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
val context = val context =
getActivity(data) getActivity(data)
?: run { ?: run {
visible = false visible = false
enabled = false enabled = false
return return
} }
visible = context.viewModel.openedFiles.isNotEmpty() visible = context.viewModel.openedFiles.isNotEmpty()
enabled = context.areFilesModified() enabled = context.areFilesModified()
} }
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
return try { return try {
// Cannot use context.saveAll() because this.execAction is called on non-UI thread // Cannot use context.saveAll() because this.execAction is called on non-UI thread
// and saveAll call will result in UI actions // and saveAll call will result in UI actions
val result = context.saveAllResult() val result = context.saveAllResult()
if (result.xmlSaved) { if (result.xmlSaved) {
ProjectManager.generateSources(context.buildService) ProjectManager.generateSources(context.buildService)
} }
if (result.gradleSaved) { if (result.gradleSaved) {
context.notifySyncNeeded() context.notifySyncNeeded()
} }
true true
} catch (error: Throwable) { } catch (error: Throwable) {
log.error("Failed to save file", error) log.error("Failed to save file", error)
false false
} }
} }
override fun postExec(data: ActionData, result: Any) { override fun postExec(data: ActionData, result: Any) {
if (result is Boolean && result) { if (result is Boolean && result) {
StudioApp.getInstance().toast(R.string.all_saved, Toaster.Type.SUCCESS) StudioApp.getInstance().toast(R.string.all_saved, Toaster.Type.SUCCESS)
} else { } else {
log.error("Failed to save file") log.error("Failed to save file")
TODO("Create message in strings.xml") TODO("Create message in strings.xml")
} }
} }
} }
@@ -1,41 +1,41 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.lint package com.itsaky.androidide.actions.lint
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class LintAction() : BaseBuildAction() { class LintAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.lint) label = context.getString(R.string.lint)
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint) icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
} }
override val id: String = "editor_lint" override val id: String = "editor_lint"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
context.lint() context.lint()
return true return true
} }
} }
@@ -1,41 +1,41 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.lint package com.itsaky.androidide.actions.lint
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class LintDebugAction() : BaseBuildAction() { class LintDebugAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.lint_debug) label = context.getString(R.string.lint_debug)
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug) icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
} }
override val id: String = "editor_lintDebug" override val id: String = "editor_lintDebug"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
context.lintDebug() context.lintDebug()
return true return true
} }
} }
@@ -1,41 +1,41 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.lint package com.itsaky.androidide.actions.lint
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class LintReleaseAction() : BaseBuildAction() { class LintReleaseAction() : BaseBuildAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.lint_release) label = context.getString(R.string.lint_release)
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release) icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
} }
override val id: String = "editor_lintRelease" override val id: String = "editor_lintRelease"
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val context = getActivity(data) ?: return false val context = getActivity(data) ?: return false
context.lintRelease() context.lintRelease()
return true return true
} }
} }
@@ -1,41 +1,41 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.lint package com.itsaky.androidide.actions.lint
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionMenu import com.itsaky.androidide.actions.ActionMenu
import com.itsaky.androidide.actions.BaseBuildAction import com.itsaky.androidide.actions.BaseBuildAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class LintTaskMenu() : BaseBuildAction(), ActionMenu { class LintTaskMenu() : BaseBuildAction(), ActionMenu {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.lint_tasks) label = context.getString(R.string.lint_tasks)
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint) icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
addAction(LintAction(context)) addAction(LintAction(context))
addAction(LintDebugAction(context)) addAction(LintDebugAction(context))
addAction(LintReleaseAction(context)) addAction(LintReleaseAction(context))
} }
override val children: MutableSet<ActionItem> = mutableSetOf() override val children: MutableSet<ActionItem> = mutableSetOf()
override val id: String = "editor_lintTasks" override val id: String = "editor_lintTasks"
} }
@@ -1,63 +1,63 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.text package com.itsaky.androidide.actions.text
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.view.MenuItem import android.view.MenuItem
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.blankj.utilcode.util.KeyboardUtils import com.blankj.utilcode.util.KeyboardUtils
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorRelatedAction import com.itsaky.androidide.actions.EditorRelatedAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class RedoAction() : EditorRelatedAction() { class RedoAction() : EditorRelatedAction() {
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.redo) label = context.getString(R.string.redo)
icon = ContextCompat.getDrawable(context, R.drawable.ic_redo) icon = ContextCompat.getDrawable(context, R.drawable.ic_redo)
} }
override val id: String = "editor_redo" override val id: String = "editor_redo"
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
super.prepare(data) super.prepare(data)
if (!visible) { if (!visible) {
return return
} }
val editor = getEditor(data)!! val editor = getEditor(data)!!
enabled = editor.canRedo() enabled = editor.canRedo()
} }
override fun execAction(data: ActionData): Boolean { override fun execAction(data: ActionData): Boolean {
val editor = getEditor(data)!! val editor = getEditor(data)!!
editor.redo() editor.redo()
return true return true
} }
override fun getShowAsActionFlags(data: ActionData): Int { override fun getShowAsActionFlags(data: ActionData): Int {
return if (KeyboardUtils.isSoftInputVisible(data.get(Context::class.java) as Activity)) { return if (KeyboardUtils.isSoftInputVisible(data.get(Context::class.java) as Activity)) {
MenuItem.SHOW_AS_ACTION_IF_ROOM MenuItem.SHOW_AS_ACTION_IF_ROOM
} else { } else {
MenuItem.SHOW_AS_ACTION_NEVER MenuItem.SHOW_AS_ACTION_NEVER
} }
} }
} }
@@ -1,56 +1,56 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.actions.text package com.itsaky.androidide.actions.text
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorRelatedAction import com.itsaky.androidide.actions.EditorRelatedAction
/** @author Akash Yadav */ /** @author Akash Yadav */
class UndoAction() : EditorRelatedAction() { class UndoAction() : EditorRelatedAction() {
override val id: String = "editor_undo" override val id: String = "editor_undo"
constructor(context: Context) : this() { constructor(context: Context) : this() {
label = context.getString(R.string.undo) label = context.getString(R.string.undo)
icon = ContextCompat.getDrawable(context, R.drawable.ic_undo) icon = ContextCompat.getDrawable(context, R.drawable.ic_undo)
} }
override fun prepare(data: ActionData) { override fun prepare(data: ActionData) {
super.prepare(data) super.prepare(data)
if (!visible) { if (!visible) {
return return
} }
val editor = getEditor(data)!! val editor = getEditor(data)!!
enabled = editor.canUndo() enabled = editor.canUndo()
} }
override fun execAction(data: ActionData): Any { override fun execAction(data: ActionData): Any {
val editor = getEditor(data) val editor = getEditor(data)
return if (editor != null) { return if (editor != null) {
editor.undo() editor.undo()
true true
} else { } else {
false false
} }
} }
} }
@@ -1,230 +1,230 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.adapters; package com.itsaky.androidide.adapters;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.blankj.utilcode.util.ThreadUtils; import com.blankj.utilcode.util.ThreadUtils;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.itsaky.androidide.R; import com.itsaky.androidide.R;
import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.LayoutCompletionItemBinding; import com.itsaky.androidide.databinding.LayoutCompletionItemBinding;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import com.itsaky.androidide.utils.TypefaceUtils; import com.itsaky.androidide.utils.TypefaceUtils;
import com.itsaky.apiinfo.ApiInfo; import com.itsaky.apiinfo.ApiInfo;
import com.itsaky.apiinfo.models.ClassInfo; import com.itsaky.apiinfo.models.ClassInfo;
import com.itsaky.apiinfo.models.FieldInfo; import com.itsaky.apiinfo.models.FieldInfo;
import com.itsaky.apiinfo.models.Info; import com.itsaky.apiinfo.models.Info;
import com.itsaky.apiinfo.models.MethodInfo; import com.itsaky.apiinfo.models.MethodInfo;
import com.itsaky.lsp.models.CompletionItem; import com.itsaky.lsp.models.CompletionItem;
import com.itsaky.lsp.models.CompletionItemKind; import com.itsaky.lsp.models.CompletionItemKind;
import java.util.List; import java.util.List;
import io.github.rosemoe.sora.widget.component.EditorAutoCompletion; import io.github.rosemoe.sora.widget.component.EditorAutoCompletion;
import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter; import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter;
public class CompletionListAdapter extends EditorCompletionAdapter { public class CompletionListAdapter extends EditorCompletionAdapter {
private static final ILogger LOG = ILogger.newInstance("CompletionListAdapter"); private static final ILogger LOG = ILogger.newInstance("CompletionListAdapter");
@Override @Override
public void attachValues( public void attachValues(
EditorAutoCompletion window, EditorAutoCompletion window,
List<io.github.rosemoe.sora.lang.completion.CompletionItem> items) { List<io.github.rosemoe.sora.lang.completion.CompletionItem> items) {
super.attachValues(window, items); super.attachValues(window, items);
} }
@Override @Override
public int getItemHeight() { public int getItemHeight() {
return (int) return (int)
TypedValue.applyDimension( TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics()); TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics());
} }
@Override @Override
protected View getView( protected View getView(
int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) { int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) {
final LayoutCompletionItemBinding binding = final LayoutCompletionItemBinding binding =
LayoutCompletionItemBinding.inflate(LayoutInflater.from(getContext()), parent, false); LayoutCompletionItemBinding.inflate(LayoutInflater.from(getContext()), parent, false);
var item = (CompletionItem) getItem(position); var item = (CompletionItem) getItem(position);
String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString(); String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString();
String header = type.length() <= 0 ? "O" : String.valueOf(type.charAt(0)); String header = type.length() <= 0 ? "O" : String.valueOf(type.charAt(0));
binding.completionIconText.setText(header); binding.completionIconText.setText(header);
binding.completionLabel.setText(label); binding.completionLabel.setText(label);
binding.completionType.setText(type); binding.completionType.setText(type);
binding.completionDetail.setText(desc); binding.completionDetail.setText(desc);
binding.completionIconText.setTypeface(TypefaceUtils.jetbrainsMono(), Typeface.BOLD); binding.completionIconText.setTypeface(TypefaceUtils.jetbrainsMono(), Typeface.BOLD);
if (desc.isEmpty()) { if (desc.isEmpty()) {
binding.completionDetail.setVisibility(View.GONE); binding.completionDetail.setVisibility(View.GONE);
} }
if (isCurrentCursorPosition) { if (isCurrentCursorPosition) {
binding binding
.getRoot() .getRoot()
.setBackgroundColor( .setBackgroundColor(
ContextCompat.getColor(getContext(), R.color.completionList_backgroundSelected)); ContextCompat.getColor(getContext(), R.color.completionList_backgroundSelected));
} else { } else {
binding binding
.getRoot() .getRoot()
.setBackgroundColor( .setBackgroundColor(
ContextCompat.getColor(getContext(), R.color.completionList_background)); ContextCompat.getColor(getContext(), R.color.completionList_background));
} }
binding.completionApiInfo.setVisibility(View.GONE); binding.completionApiInfo.setVisibility(View.GONE);
showApiInfoIfNeeded(item, binding.completionApiInfo); showApiInfoIfNeeded(item, binding.completionApiInfo);
return binding.getRoot(); return binding.getRoot();
} }
private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) { private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) {
new Thread( new Thread(
() -> { () -> {
final ApiInfo apiInfo = StudioApp.getInstance().apiInfo(); final ApiInfo apiInfo = StudioApp.getInstance().apiInfo();
boolean hasRead = apiInfo != null && apiInfo.hasRead(); boolean hasRead = apiInfo != null && apiInfo.hasRead();
boolean isValid = isValidForApiVersion(item); boolean isValid = isValidForApiVersion(item);
if (hasRead && isValid) { if (hasRead && isValid) {
JsonElement element = new Gson().toJsonTree(item.getData()); JsonElement element = new Gson().toJsonTree(item.getData());
if (element == null || !element.isJsonObject()) { if (element == null || !element.isJsonObject()) {
return; return;
} }
JsonObject data = element.getAsJsonObject(); JsonObject data = element.getAsJsonObject();
if (!data.has("className")) { if (!data.has("className")) {
return; return;
} }
final String className = data.get("className").getAsString(); final String className = data.get("className").getAsString();
CompletionItemKind kind = item.getKind(); CompletionItemKind kind = item.getKind();
ClassInfo clazz = apiInfo.getClassByName(className); ClassInfo clazz = apiInfo.getClassByName(className);
if (clazz == null) { if (clazz == null) {
return; return;
} }
Info info = clazz; Info info = clazz;
// If this Info is not a class info, find the right member // If this Info is not a class info, find the right member
if (kind == CompletionItemKind.METHOD if (kind == CompletionItemKind.METHOD
&& data.has("erasedParameterTypes") && data.has("erasedParameterTypes")
&& data.has("memberName")) { && data.has("memberName")) {
JsonElement erasedParameterTypesElement = data.get("erasedParameterTypes"); JsonElement erasedParameterTypesElement = data.get("erasedParameterTypes");
if (erasedParameterTypesElement.isJsonArray()) { if (erasedParameterTypesElement.isJsonArray()) {
String simpleName = data.get("memberName").getAsString(); String simpleName = data.get("memberName").getAsString();
JsonArray erasedParameterTypes = erasedParameterTypesElement.getAsJsonArray(); JsonArray erasedParameterTypes = erasedParameterTypesElement.getAsJsonArray();
String[] paramTypes = new String[erasedParameterTypes.size()]; String[] paramTypes = new String[erasedParameterTypes.size()];
for (int i = 0; i < erasedParameterTypes.size(); i++) { for (int i = 0; i < erasedParameterTypes.size(); i++) {
paramTypes[i] = erasedParameterTypes.get(i).getAsString(); paramTypes[i] = erasedParameterTypes.get(i).getAsString();
} }
MethodInfo method = clazz.getMethod(simpleName, paramTypes); MethodInfo method = clazz.getMethod(simpleName, paramTypes);
if (method != null) { if (method != null) {
info = method; info = method;
} }
} }
} else if (kind == CompletionItemKind.FIELD && data.has("memberName")) { } else if (kind == CompletionItemKind.FIELD && data.has("memberName")) {
String simpleName = data.get("memberName").getAsString(); String simpleName = data.get("memberName").getAsString();
FieldInfo field = clazz.getFieldByName(simpleName); FieldInfo field = clazz.getFieldByName(simpleName);
if (field != null) { if (field != null) {
info = field; info = field;
} }
} }
final StringBuilder infoBuilder = new StringBuilder(); final StringBuilder infoBuilder = new StringBuilder();
if (info.since > 1) { if (info.since > 1) {
infoBuilder.append( infoBuilder.append(
completionApiInfo completionApiInfo
.getContext() .getContext()
.getString(R.string.msg_api_info_since, info.since)); .getString(R.string.msg_api_info_since, info.since));
infoBuilder.append("\n"); infoBuilder.append("\n");
} }
if (info.removed > 0) { if (info.removed > 0) {
infoBuilder.append( infoBuilder.append(
completionApiInfo completionApiInfo
.getContext() .getContext()
.getString(R.string.msg_api_info_removed, info.removed)); .getString(R.string.msg_api_info_removed, info.removed));
infoBuilder.append("\n"); infoBuilder.append("\n");
} }
if (info.deprecated > 0) { if (info.deprecated > 0) {
infoBuilder.append( infoBuilder.append(
completionApiInfo completionApiInfo
.getContext() .getContext()
.getString(R.string.msg_api_info_deprecated, info.deprecated)); .getString(R.string.msg_api_info_deprecated, info.deprecated));
infoBuilder.append("\n"); infoBuilder.append("\n");
} }
ThreadUtils.runOnUiThread( ThreadUtils.runOnUiThread(
() -> { () -> {
if (infoBuilder.toString().trim().length() > 0) { if (infoBuilder.toString().trim().length() > 0) {
completionApiInfo.setText(infoBuilder.toString().trim()); completionApiInfo.setText(infoBuilder.toString().trim());
completionApiInfo.setVisibility(View.VISIBLE); completionApiInfo.setVisibility(View.VISIBLE);
} else { } else {
completionApiInfo.setVisibility(View.GONE); completionApiInfo.setVisibility(View.GONE);
} }
}); });
} }
}) })
.start(); .start();
} }
private boolean isValidForApiVersion(CompletionItem item) { private boolean isValidForApiVersion(CompletionItem item) {
if (item == null) { if (item == null) {
return false; return false;
} }
final CompletionItemKind type = item.getKind(); final CompletionItemKind type = item.getKind();
final var data = item.getData(); final var data = item.getData();
if ( if (
// These represent a class type // These represent a class type
(type == CompletionItemKind.CLASS (type == CompletionItemKind.CLASS
|| type == CompletionItemKind.INTERFACE || type == CompletionItemKind.INTERFACE
|| type == CompletionItemKind.ENUM || type == CompletionItemKind.ENUM
// These represent a method type // These represent a method type
|| type == CompletionItemKind.METHOD || type == CompletionItemKind.METHOD
|| type == CompletionItemKind.CONSTRUCTOR || type == CompletionItemKind.CONSTRUCTOR
// A field type // A field type
|| type == CompletionItemKind.FIELD) || type == CompletionItemKind.FIELD)
&& data != null) { && data != null) {
return !TextUtils.isEmpty(data.getClassName()); return !TextUtils.isEmpty(data.getClassName());
} }
return false; return false;
} }
} }
@@ -1,319 +1,319 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.filepicker; package com.itsaky.androidide.filepicker;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.graphics.Point; import android.graphics.Point;
import android.os.CancellationSignal; import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider; import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.itsaky.androidide.R; import com.itsaky.androidide.R;
import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.Environment;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
/** /**
* A document provider for the Storage Access Framework which exposes the files in the $HOME/ * A document provider for the Storage Access Framework which exposes the files in the $HOME/
* directory to other apps. * directory to other apps.
* *
* <p>Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent: * <p>Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
* *
* <p>"A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you * <p>"A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you
* support both of them simultaneously, your app will appear twice in the system picker UI, offering * support both of them simultaneously, your app will appear twice in the system picker UI, offering
* two different ways of accessing your stored data. This would be confusing for users." - * two different ways of accessing your stored data. This would be confusing for users." -
* http://developer.android.com/guide/topics/providers/document-provider.html#43 * http://developer.android.com/guide/topics/providers/document-provider.html#43
* *
* @see <a * @see <a
* href="https://github.com/termux/termux-app/blob/master/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java">TermuxDocumentsProvider</a> * href="https://github.com/termux/termux-app/blob/master/app/src/main/java/com/termux/filepicker/TermuxDocumentsProvider.java">TermuxDocumentsProvider</a>
*/ */
public class IDEDocumentsProvider extends DocumentsProvider { public class IDEDocumentsProvider extends DocumentsProvider {
private static final String ALL_MIME_TYPES = "*/*"; private static final String ALL_MIME_TYPES = "*/*";
private static final ILogger LOG = ILogger.newInstance("IDEDocumentsProvider"); private static final ILogger LOG = ILogger.newInstance("IDEDocumentsProvider");
private static final File BASE_DIR = getBaseDir(); private static final File BASE_DIR = getBaseDir();
// The default columns to return information about a root if no specific // The default columns to return information about a root if no specific
// columns are requested in a query. // columns are requested in a query.
private static final String[] DEFAULT_ROOT_PROJECTION = private static final String[] DEFAULT_ROOT_PROJECTION =
new String[] { new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_ID,
Root.COLUMN_MIME_TYPES, Root.COLUMN_MIME_TYPES,
Root.COLUMN_FLAGS, Root.COLUMN_FLAGS,
Root.COLUMN_ICON, Root.COLUMN_ICON,
Root.COLUMN_TITLE, Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY, Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID, Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES Root.COLUMN_AVAILABLE_BYTES
}; };
// The default columns to return information about a document if no specific // The default columns to return information about a document if no specific
// columns are requested in a query. // columns are requested in a query.
private static final String[] DEFAULT_DOCUMENT_PROJECTION = private static final String[] DEFAULT_DOCUMENT_PROJECTION =
new String[] { new String[] {
Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_MIME_TYPE, Document.COLUMN_MIME_TYPE,
Document.COLUMN_DISPLAY_NAME, Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_LAST_MODIFIED, Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS, Document.COLUMN_FLAGS,
Document.COLUMN_SIZE Document.COLUMN_SIZE
}; };
@NonNull @NonNull
private static File getBaseDir() { private static File getBaseDir() {
if (Environment.HOME != null) { if (Environment.HOME != null) {
return Environment.HOME; return Environment.HOME;
} }
return new File(Environment.DEFAULT_HOME); return new File(Environment.DEFAULT_HOME);
} }
/** /**
* Get the document id given a file. This document id must be consistent across time as other * Get the document id given a file. This document id must be consistent across time as other
* applications may save the ID and use it to reference documents later. * applications may save the ID and use it to reference documents later.
* *
* <p>The reverse of @{link #getFileForDocId}. * <p>The reverse of @{link #getFileForDocId}.
*/ */
private static String getDocIdForFile(File file) { private static String getDocIdForFile(File file) {
return file.getAbsolutePath(); return file.getAbsolutePath();
} }
/** Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). */ /** Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). */
private static File getFileForDocId(String docId) throws FileNotFoundException { private static File getFileForDocId(String docId) throws FileNotFoundException {
final File f = new File(docId); final File f = new File(docId);
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found"); if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
return f; return f;
} }
private static String getMimeType(File file) { private static String getMimeType(File file) {
if (file.isDirectory()) { if (file.isDirectory()) {
return Document.MIME_TYPE_DIR; return Document.MIME_TYPE_DIR;
} else { } else {
final String name = file.getName(); final String name = file.getName();
final int lastDot = name.lastIndexOf('.'); final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) { if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1).toLowerCase(); final String extension = name.substring(lastDot + 1).toLowerCase();
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) return mime; if (mime != null) return mime;
} }
return "application/octet-stream"; return "application/octet-stream";
} }
} }
@Override @Override
public Cursor queryRoots(String[] projection) { public Cursor queryRoots(String[] projection) {
final MatrixCursor result = final MatrixCursor result =
new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION); new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
final String applicationName = getContext().getString(R.string.app_name); final String applicationName = getContext().getString(R.string.app_name);
final MatrixCursor.RowBuilder row = result.newRow(); final MatrixCursor.RowBuilder row = result.newRow();
LOG.debug("queryRoots() before all add"); LOG.debug("queryRoots() before all add");
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR)); row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
LOG.debug("queryRoots() before all add, 1"); LOG.debug("queryRoots() before all add, 1");
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR)); row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
LOG.debug("queryRoots() before all add, 2"); LOG.debug("queryRoots() before all add, 2");
row.add(Root.COLUMN_SUMMARY, null); row.add(Root.COLUMN_SUMMARY, null);
LOG.debug("queryRoots() before all add, 3"); LOG.debug("queryRoots() before all add, 3");
row.add( row.add(
Root.COLUMN_FLAGS, Root.COLUMN_FLAGS,
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD); Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
LOG.debug("queryRoots() before all add, 4"); LOG.debug("queryRoots() before all add, 4");
row.add(Root.COLUMN_TITLE, applicationName); row.add(Root.COLUMN_TITLE, applicationName);
LOG.debug("queryRoots() before all add, 5"); LOG.debug("queryRoots() before all add, 5");
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
LOG.debug("queryRoots() before all add, 6"); LOG.debug("queryRoots() before all add, 6");
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace()); row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
LOG.debug("queryRoots() before all add, 7"); LOG.debug("queryRoots() before all add, 7");
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
LOG.debug("queryRoots() before all add"); LOG.debug("queryRoots() before all add");
return result; return result;
} }
@Override @Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
final MatrixCursor result = final MatrixCursor result =
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
includeFile(result, documentId, null); includeFile(result, documentId, null);
return result; return result;
} }
@Override @Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
throws FileNotFoundException { throws FileNotFoundException {
final MatrixCursor result = final MatrixCursor result =
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(parentDocumentId); final File parent = getFileForDocId(parentDocumentId);
for (File file : parent.listFiles()) { for (File file : parent.listFiles()) {
includeFile(result, null, file); includeFile(result, null, file);
} }
return result; return result;
} }
@Override @Override
public ParcelFileDescriptor openDocument( public ParcelFileDescriptor openDocument(
final String documentId, String mode, CancellationSignal signal) final String documentId, String mode, CancellationSignal signal)
throws FileNotFoundException { throws FileNotFoundException {
final File file = getFileForDocId(documentId); final File file = getFileForDocId(documentId);
final int accessMode = ParcelFileDescriptor.parseMode(mode); final int accessMode = ParcelFileDescriptor.parseMode(mode);
return ParcelFileDescriptor.open(file, accessMode); return ParcelFileDescriptor.open(file, accessMode);
} }
@Override @Override
public AssetFileDescriptor openDocumentThumbnail( public AssetFileDescriptor openDocumentThumbnail(
String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
final File file = getFileForDocId(documentId); final File file = getFileForDocId(documentId);
final ParcelFileDescriptor pfd = final ParcelFileDescriptor pfd =
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
return new AssetFileDescriptor(pfd, 0, file.length()); return new AssetFileDescriptor(pfd, 0, file.length());
} }
@Override @Override
public boolean onCreate() { public boolean onCreate() {
return true; return true;
} }
@Override @Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) public String createDocument(String parentDocumentId, String mimeType, String displayName)
throws FileNotFoundException { throws FileNotFoundException {
File newFile = new File(parentDocumentId, displayName); File newFile = new File(parentDocumentId, displayName);
int noConflictId = 2; int noConflictId = 2;
while (newFile.exists()) { while (newFile.exists()) {
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")"); newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
} }
try { try {
boolean succeeded; boolean succeeded;
if (Document.MIME_TYPE_DIR.equals(mimeType)) { if (Document.MIME_TYPE_DIR.equals(mimeType)) {
succeeded = newFile.mkdir(); succeeded = newFile.mkdir();
} else { } else {
succeeded = newFile.createNewFile(); succeeded = newFile.createNewFile();
} }
if (!succeeded) { if (!succeeded) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
} }
} catch (IOException e) { } catch (IOException e) {
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
} }
return newFile.getPath(); return newFile.getPath();
} }
@Override @Override
public void deleteDocument(String documentId) throws FileNotFoundException { public void deleteDocument(String documentId) throws FileNotFoundException {
File file = getFileForDocId(documentId); File file = getFileForDocId(documentId);
if (!file.delete()) { if (!file.delete()) {
throw new FileNotFoundException("Failed to delete document with id " + documentId); throw new FileNotFoundException("Failed to delete document with id " + documentId);
} }
} }
@Override @Override
public String getDocumentType(String documentId) throws FileNotFoundException { public String getDocumentType(String documentId) throws FileNotFoundException {
File file = getFileForDocId(documentId); File file = getFileForDocId(documentId);
return getMimeType(file); return getMimeType(file);
} }
@Override @Override
public Cursor querySearchDocuments(String rootId, String query, String[] projection) public Cursor querySearchDocuments(String rootId, String query, String[] projection)
throws FileNotFoundException { throws FileNotFoundException {
final MatrixCursor result = final MatrixCursor result =
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(rootId); final File parent = getFileForDocId(rootId);
// This example implementation searches file names for the query and doesn't rank search // This example implementation searches file names for the query and doesn't rank search
// results, so we can stop as soon as we find a sufficient number of matches. Other // results, so we can stop as soon as we find a sufficient number of matches. Other
// implementations might rank results and use other data about files, rather than the file // implementations might rank results and use other data about files, rather than the file
// name, to produce a match. // name, to produce a match.
final LinkedList<File> pending = new LinkedList<>(); final LinkedList<File> pending = new LinkedList<>();
pending.add(parent); pending.add(parent);
final int MAX_SEARCH_RESULTS = 50; final int MAX_SEARCH_RESULTS = 50;
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) { while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
final File file = pending.removeFirst(); final File file = pending.removeFirst();
// Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. // Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g.
// search // search
// through the whole SD card). // through the whole SD card).
boolean isInsideHome; boolean isInsideHome;
try { try {
isInsideHome = file.getCanonicalPath().startsWith(Environment.HOME.getAbsolutePath()); isInsideHome = file.getCanonicalPath().startsWith(Environment.HOME.getAbsolutePath());
} catch (IOException e) { } catch (IOException e) {
isInsideHome = true; isInsideHome = true;
} }
if (isInsideHome) { if (isInsideHome) {
if (file.isDirectory()) { if (file.isDirectory()) {
Collections.addAll(pending, file.listFiles()); Collections.addAll(pending, file.listFiles());
} else { } else {
if (file.getName().toLowerCase().contains(query)) { if (file.getName().toLowerCase().contains(query)) {
includeFile(result, null, file); includeFile(result, null, file);
} }
} }
} }
} }
return result; return result;
} }
@Override @Override
public boolean isChildDocument(String parentDocumentId, String documentId) { public boolean isChildDocument(String parentDocumentId, String documentId) {
return documentId.startsWith(parentDocumentId); return documentId.startsWith(parentDocumentId);
} }
/** /**
* Add a representation of a file to a cursor. * Add a representation of a file to a cursor.
* *
* @param result the cursor to modify * @param result the cursor to modify
* @param docId the document ID representing the desired file (may be null if given file) * @param docId the document ID representing the desired file (may be null if given file)
* @param file the File object representing the desired file (may be null if given docID) * @param file the File object representing the desired file (may be null if given docID)
*/ */
private void includeFile(MatrixCursor result, String docId, File file) private void includeFile(MatrixCursor result, String docId, File file)
throws FileNotFoundException { throws FileNotFoundException {
if (docId == null) { if (docId == null) {
docId = getDocIdForFile(file); docId = getDocIdForFile(file);
} else { } else {
file = getFileForDocId(docId); file = getFileForDocId(docId);
} }
int flags = 0; int flags = 0;
if (file.isDirectory()) { if (file.isDirectory()) {
if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE; if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
} else if (file.canWrite()) { } else if (file.canWrite()) {
flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_WRITE;
} }
if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE; if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE;
final String displayName = file.getName(); final String displayName = file.getName();
final String mimeType = getMimeType(file); final String mimeType = getMimeType(file);
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL; if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
final MatrixCursor.RowBuilder row = result.newRow(); final MatrixCursor.RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId); row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_DISPLAY_NAME, displayName); row.add(Document.COLUMN_DISPLAY_NAME, displayName);
row.add(Document.COLUMN_SIZE, file.length()); row.add(Document.COLUMN_SIZE, file.length());
row.add(Document.COLUMN_MIME_TYPE, mimeType); row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_FLAGS, flags); row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher); row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
} }
} }
@@ -1,340 +1,340 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.fragments; package com.itsaky.androidide.fragments;
import static com.unnamed.b.atv.view.AndroidTreeView.NODES_PATH_SEPARATOR; import static com.unnamed.b.atv.view.AndroidTreeView.NODES_PATH_SEPARATOR;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.HorizontalScrollView; import android.widget.HorizontalScrollView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.transition.ChangeBounds; import androidx.transition.ChangeBounds;
import androidx.transition.TransitionManager; import androidx.transition.TransitionManager;
import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.IntentUtils; import com.blankj.utilcode.util.IntentUtils;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.itsaky.androidide.R; import com.itsaky.androidide.R;
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder; import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.LayoutEditorFileTreeBinding; import com.itsaky.androidide.databinding.LayoutEditorFileTreeBinding;
import com.itsaky.androidide.projects.ProjectManager; import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.tasks.callables.FileTreeCallable; import com.itsaky.androidide.tasks.callables.FileTreeCallable;
import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.Environment;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import com.itsaky.androidide.views.editor.CodeEditorView; import com.itsaky.androidide.views.editor.CodeEditorView;
import com.itsaky.toaster.Toaster; import com.itsaky.toaster.Toaster;
import com.unnamed.b.atv.model.TreeNode; import com.unnamed.b.atv.model.TreeNode;
import com.unnamed.b.atv.view.AndroidTreeView; import com.unnamed.b.atv.view.AndroidTreeView;
import java.io.File; import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
public class FileTreeFragment extends BottomSheetDialogFragment public class FileTreeFragment extends BottomSheetDialogFragment
implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener { implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
private static final String KEY_STORED_TREE_STATE = "fileTree_state"; private static final String KEY_STORED_TREE_STATE = "fileTree_state";
private static final ILogger LOG = ILogger.newInstance("FileTreeFragment"); private static final ILogger LOG = ILogger.newInstance("FileTreeFragment");
private LayoutEditorFileTreeBinding binding; private LayoutEditorFileTreeBinding binding;
private AndroidTreeView mFileTreeView; private AndroidTreeView mFileTreeView;
private FileActionListener mFileActionListener; private FileActionListener mFileActionListener;
private TreeNode mRoot; private TreeNode mRoot;
private String mTreeState; private String mTreeState;
public FileTreeFragment() {} public FileTreeFragment() {}
@NonNull @NonNull
public static FileTreeFragment newInstance() { public static FileTreeFragment newInstance() {
return new FileTreeFragment(); return new FileTreeFragment();
} }
@Override @Override
public void onAttach(@NonNull Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
mFileActionListener = (FileActionListener) context; mFileActionListener = (FileActionListener) context;
} }
@Override @Override
public void onSaveInstanceState(@NonNull Bundle outState) { public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
saveTreeState(); saveTreeState();
outState.putString(KEY_STORED_TREE_STATE, mTreeState); outState.putString(KEY_STORED_TREE_STATE, mTreeState);
} }
public void saveTreeState() { public void saveTreeState() {
if (mFileTreeView != null) { if (mFileTreeView != null) {
mTreeState = mFileTreeView.getSaveState(); mTreeState = mFileTreeView.getSaveState();
} else { } else {
LOG.error("Unable to save tree state. TreeView is null."); LOG.error("Unable to save tree state. TreeView is null.");
mTreeState = null; mTreeState = null;
} }
} }
@Override @Override
public View onCreateView( public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false); binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false);
return binding.getRoot(); return binding.getRoot();
} }
@Override @Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) { if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) {
mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null); mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null);
} }
listProjectFiles(); listProjectFiles();
} }
@Override @Override
public void onClick(TreeNode node, Object p2) { public void onClick(TreeNode node, Object p2) {
final File f = (File) p2; final File f = (File) p2;
if (!f.exists()) { if (!f.exists()) {
return; return;
} }
if (f.isFile()) { if (f.isFile()) {
if (f.getName().endsWith(".apk")) { if (f.getName().endsWith(".apk")) {
final var intent = IntentUtils.getInstallAppIntent(f); final var intent = IntentUtils.getInstallAppIntent(f);
if (intent != null) { if (intent != null) {
requireContext().startActivity(intent); requireContext().startActivity(intent);
} else { } else {
StudioApp.getInstance() StudioApp.getInstance()
.toast(getString(R.string.msg_apk_install_intent_failed), Toaster.Type.ERROR); .toast(getString(R.string.msg_apk_install_intent_failed), Toaster.Type.ERROR);
} }
} else if (mFileActionListener != null && (FileUtils.isUtf8(f) || f.length() == 0)) { } else if (mFileActionListener != null && (FileUtils.isUtf8(f) || f.length() == 0)) {
mFileActionListener.openFile(f); mFileActionListener.openFile(f);
} }
} else if (f.isDirectory() && f.exists()) { } else if (f.isDirectory() && f.exists()) {
if (node.isExpanded()) { if (node.isExpanded()) {
collapseNode(node); collapseNode(node);
} else if (f.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()) } else if (f.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath())
&& !node.isExpanded()) { && !node.isExpanded()) {
expandNode(node); expandNode(node);
} else { } else {
setLoading(node); setLoading(node);
listNode( listNode(
node, node,
() -> { () -> {
updateChevron(node); updateChevron(node);
expandNode(node); expandNode(node);
}); });
} }
} }
} }
public void collapseNode(TreeNode node) { public void collapseNode(TreeNode node) {
if (mFileTreeView == null) { if (mFileTreeView == null) {
return; return;
} }
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
mFileTreeView.collapseNode(node); mFileTreeView.collapseNode(node);
} }
public void expandNode(TreeNode node) { public void expandNode(TreeNode node) {
if (mFileTreeView == null) { if (mFileTreeView == null) {
return; return;
} }
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
mFileTreeView.expandNode(node); mFileTreeView.expandNode(node);
} }
private void setLoading(@NonNull TreeNode node) { private void setLoading(@NonNull TreeNode node) {
if (node.getViewHolder() instanceof FileTreeViewHolder) { if (node.getViewHolder() instanceof FileTreeViewHolder) {
((FileTreeViewHolder) node.getViewHolder()).setLoading(); ((FileTreeViewHolder) node.getViewHolder()).setLoading();
} }
} }
private void listNode(@NonNull TreeNode node, Runnable whenDone) { private void listNode(@NonNull TreeNode node, Runnable whenDone) {
if (whenDone == null) { if (whenDone == null) {
whenDone = () -> {}; whenDone = () -> {};
} }
node.getChildren().clear(); node.getChildren().clear();
node.setExpanded(false); node.setExpanded(false);
final var finalWhenDone = whenDone; final var finalWhenDone = whenDone;
TaskExecutor.execAsync( TaskExecutor.execAsync(
() -> { () -> {
getNodeFromFiles(node.getValue().listFiles(), node); getNodeFromFiles(node.getValue().listFiles(), node);
TreeNode temp = node; TreeNode temp = node;
while (temp.size() == 1) { while (temp.size() == 1) {
temp = temp.childAt(0); temp = temp.childAt(0);
if (!temp.getValue().isDirectory()) { if (!temp.getValue().isDirectory()) {
break; break;
} }
getNodeFromFiles(temp.getValue().listFiles(), temp); getNodeFromFiles(temp.getValue().listFiles(), temp);
temp.setExpanded(true); temp.setExpanded(true);
} }
return null; return null;
}, },
__ -> finalWhenDone.run()); __ -> finalWhenDone.run());
} }
private void updateChevron(@NonNull TreeNode node) { private void updateChevron(@NonNull TreeNode node) {
if (node.getViewHolder() instanceof FileTreeViewHolder) { if (node.getViewHolder() instanceof FileTreeViewHolder) {
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded()); ((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
} }
} }
private void getNodeFromFiles(File[] files, TreeNode parent) { private void getNodeFromFiles(File[] files, TreeNode parent) {
Arrays.sort(files, new FileTreeCallable.SortFileName()); Arrays.sort(files, new FileTreeCallable.SortFileName());
Arrays.sort(files, new FileTreeCallable.SortFolder()); Arrays.sort(files, new FileTreeCallable.SortFolder());
for (File file : files) { for (File file : files) {
TreeNode node = new TreeNode(file); TreeNode node = new TreeNode(file);
node.setViewHolder(new FileTreeViewHolder(getContext())); node.setViewHolder(new FileTreeViewHolder(getContext()));
parent.addChild(node); parent.addChild(node);
} }
} }
@Override @Override
public boolean onLongClick(TreeNode node, Object value) { public boolean onLongClick(TreeNode node, Object value) {
if (mFileActionListener != null) { if (mFileActionListener != null) {
mFileActionListener.showFileOptions((File) value, node); mFileActionListener.showFileOptions((File) value, node);
} }
return true; return true;
} }
public void listProjectFiles() { public void listProjectFiles() {
final var projectDirPath = ProjectManager.INSTANCE.getProjectDirPath(); final var projectDirPath = ProjectManager.INSTANCE.getProjectDirPath();
if (projectDirPath == null) { if (projectDirPath == null) {
return; return;
} }
final File gradleProps = Environment.GRADLE_PROPS; final File gradleProps = Environment.GRADLE_PROPS;
final File gradleHome = Environment.GRADLE_USER_HOME; final File gradleHome = Environment.GRADLE_USER_HOME;
File projectDir = new File(projectDirPath); File projectDir = new File(projectDirPath);
mRoot = TreeNode.root(projectDir); mRoot = TreeNode.root(projectDir);
if (gradleHome.exists() && gradleHome.isDirectory()) { if (gradleHome.exists() && gradleHome.isDirectory()) {
if (!gradleProps.exists()) { if (!gradleProps.exists()) {
FileIOUtils.writeFileFromString( FileIOUtils.writeFileFromString(
gradleProps, gradleProps,
"# Specify global Gradle properties in this file\n" "# Specify global Gradle properties in this file\n"
+ "# These properties will be applicable for every project you build" + "# These properties will be applicable for every project you build"
+ " with Gradle."); + " with Gradle.");
} }
TreeNode home = new TreeNode(gradleHome); TreeNode home = new TreeNode(gradleHome);
home.setViewHolder(new FileTreeViewHolder(getContext())); home.setViewHolder(new FileTreeViewHolder(getContext()));
TreeNode prop = new TreeNode(gradleProps); TreeNode prop = new TreeNode(gradleProps);
prop.setViewHolder(new FileTreeViewHolder(getContext())); prop.setViewHolder(new FileTreeViewHolder(getContext()));
home.addChild(prop); home.addChild(prop);
mRoot.addChild(home); mRoot.addChild(home);
} }
mRoot.setViewHolder(new FileTreeViewHolder(getContext())); mRoot.setViewHolder(new FileTreeViewHolder(getContext()));
getScrollView().setVisibility(View.GONE); getScrollView().setVisibility(View.GONE);
getLoadingProgress().setVisibility(View.VISIBLE); getLoadingProgress().setVisibility(View.VISIBLE);
new TaskExecutor() new TaskExecutor()
.executeAsync( .executeAsync(
new FileTreeCallable(getContext(), mRoot, projectDir), new FileTreeCallable(getContext(), mRoot, projectDir),
(result) -> { (result) -> {
getScrollView().setVisibility(View.VISIBLE); getScrollView().setVisibility(View.VISIBLE);
getLoadingProgress().setVisibility(View.GONE); getLoadingProgress().setVisibility(View.GONE);
AndroidTreeView tree = createTreeView(mRoot); AndroidTreeView tree = createTreeView(mRoot);
if (tree != null) { if (tree != null) {
tree.setUseAutoToggle(false); tree.setUseAutoToggle(false);
tree.setDefaultNodeClickListener(FileTreeFragment.this); tree.setDefaultNodeClickListener(FileTreeFragment.this);
tree.setDefaultNodeLongClickListener(FileTreeFragment.this); tree.setDefaultNodeLongClickListener(FileTreeFragment.this);
getScrollView().removeAllViews(); getScrollView().removeAllViews();
final var view = tree.getView(); final var view = tree.getView();
getScrollView().addView(view); getScrollView().addView(view);
view.post(this::tryRestoreState); view.post(this::tryRestoreState);
} }
}); });
} }
public AndroidTreeView createTreeView(TreeNode node) { public AndroidTreeView createTreeView(TreeNode node) {
Context ctx = null; Context ctx = null;
if (getActivity() != null) { if (getActivity() != null) {
ctx = getActivity(); ctx = getActivity();
} else if (getContext() != null) { } else if (getContext() != null) {
ctx = getContext(); ctx = getContext();
} }
if (ctx == null) { if (ctx == null) {
return null; return null;
} }
return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple); return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple);
} }
public HorizontalScrollView getScrollView() { public HorizontalScrollView getScrollView() {
return binding.filetreeHorizontalScrollView; return binding.filetreeHorizontalScrollView;
} }
public ProgressBar getLoadingProgress() { public ProgressBar getLoadingProgress() {
return binding.fileTreeLoadingProgress; return binding.fileTreeLoadingProgress;
} }
private void tryRestoreState() { private void tryRestoreState() {
tryRestoreState(mTreeState); tryRestoreState(mTreeState);
} }
private void tryRestoreState(String state) { private void tryRestoreState(String state) {
if (!TextUtils.isEmpty(state) && mFileTreeView != null) { if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
LOG.debug("Restoring tree view state:", "'" + state + "'"); LOG.debug("Restoring tree view state:", "'" + state + "'");
mFileTreeView.collapseAll(); mFileTreeView.collapseAll();
final var openNodesArray = state.split(NODES_PATH_SEPARATOR); final var openNodesArray = state.split(NODES_PATH_SEPARATOR);
final var openNodes = new HashSet<>(Arrays.asList(openNodesArray)); final var openNodes = new HashSet<>(Arrays.asList(openNodesArray));
restoreNodeState(mRoot, openNodes); restoreNodeState(mRoot, openNodes);
} else { } else {
LOG.error("Unable to restore tree state", "treeState=" + state, "treeView=" + mFileTreeView); LOG.error("Unable to restore tree state", "treeState=" + state, "treeView=" + mFileTreeView);
} }
} }
private void restoreNodeState(@NonNull TreeNode root, Set<String> openNodes) { private void restoreNodeState(@NonNull TreeNode root, Set<String> openNodes) {
final var children = root.getChildren(); final var children = root.getChildren();
for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) { for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
final var node = children.get(i); final var node = children.get(i);
if (openNodes.contains(node.getPath())) { if (openNodes.contains(node.getPath())) {
listNode( listNode(
node, node,
() -> { () -> {
updateChevron(node); updateChevron(node);
expandNode(node); expandNode(node);
restoreNodeState(node, openNodes); restoreNodeState(node, openNodes);
}); });
} }
} }
} }
public interface FileActionListener { public interface FileActionListener {
CodeEditorView openFile(File file); CodeEditorView openFile(File file);
void showFileOptions(File file, TreeNode node); void showFileOptions(File file, TreeNode node);
} }
} }
@@ -1,103 +1,103 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.fragments.attr; package com.itsaky.androidide.fragments.attr;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.blankj.utilcode.util.ThreadUtils; import com.blankj.utilcode.util.ThreadUtils;
import com.google.android.material.textfield.MaterialAutoCompleteTextView; import com.google.android.material.textfield.MaterialAutoCompleteTextView;
import com.itsaky.androidide.adapters.AttrValueCompletionAdapter; import com.itsaky.androidide.adapters.AttrValueCompletionAdapter;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import com.itsaky.androidide.utils.TextWatcherAdapter; import com.itsaky.androidide.utils.TextWatcherAdapter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* @author Akash Yadav * @author Akash Yadav
*/ */
public abstract class AbstractReferenceEditor extends BaseValueEditorFragment { public abstract class AbstractReferenceEditor extends BaseValueEditorFragment {
private static final ILogger LOG = ILogger.newInstance("ReferenceEditor"); private static final ILogger LOG = ILogger.newInstance("ReferenceEditor");
public TextWatcherAdapter resInputWatcher; public TextWatcherAdapter resInputWatcher;
@Override @Override
protected void notifyValueChanged(@NonNull String newValue) { protected void notifyValueChanged(@NonNull String newValue) {
try { try {
super.notifyValueChanged(newValue); super.notifyValueChanged(newValue);
} catch (Throwable e) { } catch (Throwable e) {
LOG.error("Unable to update resource value to '" + newValue + "'", e); LOG.error("Unable to update resource value to '" + newValue + "'", e);
} }
} }
protected void setupReferenceInput(MaterialAutoCompleteTextView referenceInput) { protected void setupReferenceInput(MaterialAutoCompleteTextView referenceInput) {
Objects.requireNonNull(referenceInput); Objects.requireNonNull(referenceInput);
referenceInput.setText(attribute.getValue()); referenceInput.setText(attribute.getValue());
if (resInputWatcher != null) { if (resInputWatcher != null) {
referenceInput.removeTextChangedListener(resInputWatcher); referenceInput.removeTextChangedListener(resInputWatcher);
} }
resInputWatcher = resInputWatcher =
new TextWatcherAdapter() { new TextWatcherAdapter() {
@Override @Override
public void afterTextChanged(@NonNull Editable s) { public void afterTextChanged(@NonNull Editable s) {
final var text = s.toString().trim(); final var text = s.toString().trim();
if (TextUtils.isEmpty(text)) { if (TextUtils.isEmpty(text)) {
return; return;
} }
notifyValueChanged(text); notifyValueChanged(text);
} }
}; };
referenceInput.addTextChangedListener(resInputWatcher); referenceInput.addTextChangedListener(resInputWatcher);
CompletableFuture.supplyAsync(this::computeReferenceItems) CompletableFuture.supplyAsync(this::computeReferenceItems)
.whenComplete((list, error) -> handleAutoCompleteResult(list, error, referenceInput)); .whenComplete((list, error) -> handleAutoCompleteResult(list, error, referenceInput));
} }
protected void handleAutoCompleteResult( protected void handleAutoCompleteResult(
List<String> list, Throwable error, AutoCompleteTextView referenceInput) { List<String> list, Throwable error, AutoCompleteTextView referenceInput) {
if (list == null || list.isEmpty()) { if (list == null || list.isEmpty()) {
LOG.error("No completion items found"); LOG.error("No completion items found");
LOG.error("Error was:", error); LOG.error("Error was:", error);
return; return;
} }
LOG.debug("Found", list.size(), "resource items for completion"); LOG.debug("Found", list.size(), "resource items for completion");
ThreadUtils.runOnUiThread( ThreadUtils.runOnUiThread(
() -> { () -> {
final var adapter = new AttrValueCompletionAdapter(requireContext(), list); final var adapter = new AttrValueCompletionAdapter(requireContext(), list);
referenceInput.setThreshold(1); referenceInput.setThreshold(1);
referenceInput.setAdapter(adapter); referenceInput.setAdapter(adapter);
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
}); });
} }
protected List<String> computeReferenceItems() { protected List<String> computeReferenceItems() {
return new ArrayList<>(); return new ArrayList<>();
} }
} }
@@ -1,83 +1,83 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.fragments.attr; package com.itsaky.androidide.fragments.attr;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.material.textfield.MaterialAutoCompleteTextView; import com.google.android.material.textfield.MaterialAutoCompleteTextView;
import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.databinding.LayoutReferenceAttrEditorBinding; import com.itsaky.androidide.databinding.LayoutReferenceAttrEditorBinding;
import com.itsaky.inflater.values.FrameworkValues; import com.itsaky.inflater.values.FrameworkValues;
import com.itsaky.inflater.values.ValuesTableFactory; import com.itsaky.inflater.values.ValuesTableFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* @author Akash Yadav * @author Akash Yadav
*/ */
public class ReferenceEditor extends AbstractReferenceEditor { public class ReferenceEditor extends AbstractReferenceEditor {
private LayoutReferenceAttrEditorBinding binding; private LayoutReferenceAttrEditorBinding binding;
@Nullable @Nullable
@Override @Override
public View onCreateView( public View onCreateView(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
this.binding = LayoutReferenceAttrEditorBinding.inflate(inflater, container, false); this.binding = LayoutReferenceAttrEditorBinding.inflate(inflater, container, false);
return this.binding.getRoot(); return this.binding.getRoot();
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
setupReferenceInput((MaterialAutoCompleteTextView) this.binding.resInput.getEditText()); setupReferenceInput((MaterialAutoCompleteTextView) this.binding.resInput.getEditText());
} }
@Override @Override
protected List<String> computeReferenceItems() { protected List<String> computeReferenceItems() {
final var resTable = StudioApp.getInstance().getResourceTable(); final var resTable = StudioApp.getInstance().getResourceTable();
final var list = new ArrayList<>(resTable.listResourceNames(null)); final var list = new ArrayList<>(resTable.listResourceNames(null));
final var tables = ValuesTableFactory.getAllTables(); final var tables = ValuesTableFactory.getAllTables();
for (var entry : tables.entrySet()) { for (var entry : tables.entrySet()) {
final var resourceMap = entry.getValue().getResourceMap(); final var resourceMap = entry.getValue().getResourceMap();
for (var resourceEntries : resourceMap.entrySet()) { for (var resourceEntries : resourceMap.entrySet()) {
final var name = resourceEntries.getKey(); final var name = resourceEntries.getKey();
list.addAll( list.addAll(
resourceEntries.getValue().keySet().stream() resourceEntries.getValue().keySet().stream()
.map(("@android:" + name + "/")::concat) .map(("@android:" + name + "/")::concat)
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
} }
list.addAll(FrameworkValues.listAllResources()); list.addAll(FrameworkValues.listAllResources());
return list; return list;
} }
} }
@@ -1,164 +1,164 @@
/************************************************************************************ /************************************************************************************
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* *
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
**************************************************************************************/ **************************************************************************************/
package com.itsaky.androidide.fragments.preferences; package com.itsaky.androidide.fragments.preferences;
import android.os.Bundle; import android.os.Bundle;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.FileUtils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.itsaky.androidide.R; import com.itsaky.androidide.R;
import com.itsaky.androidide.fragments.sheets.ProgressSheet; import com.itsaky.androidide.fragments.sheets.ProgressSheet;
import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.utils.DialogUtils; import com.itsaky.androidide.utils.DialogUtils;
import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.Environment;
import java.io.File; import java.io.File;
public class BuildPreferences extends BasePreferenceFragment public class BuildPreferences extends BasePreferenceFragment
implements Preference.OnPreferenceClickListener { implements Preference.OnPreferenceClickListener {
public static final String KEY_GRADLE_COMMANDS = "idepref_build_gradleCommands"; public static final String KEY_GRADLE_COMMANDS = "idepref_build_gradleCommands";
public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache"; public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache";
private ProgressSheet progressSheet; private ProgressSheet progressSheet;
@Override @Override
public void onCreatePreferences(Bundle p1, String p2) { public void onCreatePreferences(Bundle p1, String p2) {
super.onCreatePreferences(p1, p2); super.onCreatePreferences(p1, p2);
if (getContext() == null) return; if (getContext() == null) return;
final PreferenceScreen screen = getPreferenceScreen(); final PreferenceScreen screen = getPreferenceScreen();
final PreferenceCategory categoryGradle = new PreferenceCategory(getContext()); final PreferenceCategory categoryGradle = new PreferenceCategory(getContext());
final Preference customCommands = new Preference(getContext()); final Preference customCommands = new Preference(getContext());
final Preference clearCache = new Preference(getContext()); final Preference clearCache = new Preference(getContext());
screen.addPreference(categoryGradle); screen.addPreference(categoryGradle);
customCommands.setKey(KEY_GRADLE_COMMANDS); customCommands.setKey(KEY_GRADLE_COMMANDS);
customCommands.setIcon(R.drawable.ic_bash_commands); customCommands.setIcon(R.drawable.ic_bash_commands);
customCommands.setTitle(R.string.idepref_build_customgradlecommands_title); customCommands.setTitle(R.string.idepref_build_customgradlecommands_title);
customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary); customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary);
clearCache.setKey(KEY_GRADLE_CLEAR_CACHE); clearCache.setKey(KEY_GRADLE_CLEAR_CACHE);
clearCache.setIcon(R.drawable.ic_delete); clearCache.setIcon(R.drawable.ic_delete);
clearCache.setTitle(R.string.idepref_build_clearCache_title); clearCache.setTitle(R.string.idepref_build_clearCache_title);
clearCache.setSummary(R.string.idepref_build_clearCache_summary); clearCache.setSummary(R.string.idepref_build_clearCache_summary);
categoryGradle.setTitle(R.string.gradle); categoryGradle.setTitle(R.string.gradle);
categoryGradle.addPreference(customCommands); categoryGradle.addPreference(customCommands);
categoryGradle.addPreference(clearCache); categoryGradle.addPreference(clearCache);
setPreferenceScreen(screen); setPreferenceScreen(screen);
customCommands.setOnPreferenceClickListener(this); customCommands.setOnPreferenceClickListener(this);
clearCache.setOnPreferenceClickListener(this); clearCache.setOnPreferenceClickListener(this);
} }
@Override @Override
public boolean onPreferenceClick(Preference p1) { public boolean onPreferenceClick(Preference p1) {
final String key = p1.getKey(); final String key = p1.getKey();
if (key.equals(KEY_GRADLE_COMMANDS)) { if (key.equals(KEY_GRADLE_COMMANDS)) {
showGradleCommandsDialog(); showGradleCommandsDialog();
} else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) { } else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) {
showClearCacheDialog(); showClearCacheDialog();
} }
return true; return true;
} }
private void showGradleCommandsDialog() { private void showGradleCommandsDialog() {
final String[] labels = { final String[] labels = {
"--stacktrace", "--stacktrace",
"--info", "--info",
"--debug", "--debug",
"--scan", "--scan",
"--warning-mode all", "--warning-mode all",
"--build-cache", "--build-cache",
"--offline" "--offline"
}; };
final boolean[] checked = { final boolean[] checked = {
getPrefManager().isStackTraceEnabled(), getPrefManager().isStackTraceEnabled(),
getPrefManager().isGradleInfoEnabled(), getPrefManager().isGradleInfoEnabled(),
getPrefManager().isGradleDebugEnabled(), getPrefManager().isGradleDebugEnabled(),
getPrefManager().isGradleScanEnabled(), getPrefManager().isGradleScanEnabled(),
getPrefManager().isGradleWarningEnabled(), getPrefManager().isGradleWarningEnabled(),
getPrefManager().isGradleBuildCacheEnabled(), getPrefManager().isGradleBuildCacheEnabled(),
getPrefManager().isGradleOfflineModeEnabled() getPrefManager().isGradleOfflineModeEnabled()
}; };
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
builder.setTitle(R.string.idepref_build_customgradlecommands_title); builder.setTitle(R.string.idepref_build_customgradlecommands_title);
builder.setMultiChoiceItems( builder.setMultiChoiceItems(
labels, labels,
checked, checked,
(p1, p2, p3) -> { (p1, p2, p3) -> {
if (p2 == 0) { if (p2 == 0) {
getPrefManager().setGradleStacktraceEnabled(p3); getPrefManager().setGradleStacktraceEnabled(p3);
} else if (p2 == 1) { } else if (p2 == 1) {
getPrefManager().setGradleInfoEnabled(p3); getPrefManager().setGradleInfoEnabled(p3);
} else if (p2 == 2) { } else if (p2 == 2) {
getPrefManager().setGradleDebugEnabled(p3); getPrefManager().setGradleDebugEnabled(p3);
} else if (p2 == 3) { } else if (p2 == 3) {
getPrefManager().setGradleScanEnabled(p3); getPrefManager().setGradleScanEnabled(p3);
} else if (p2 == 4) { } else if (p2 == 4) {
getPrefManager().setGradleWarningEnabled(p3); getPrefManager().setGradleWarningEnabled(p3);
} else if (p2 == 5) { } else if (p2 == 5) {
getPrefManager().setGradleBuildCacheEnabled(p3); getPrefManager().setGradleBuildCacheEnabled(p3);
} else if (p2 == 6) { } else if (p2 == 6) {
getPrefManager().setGradleOfflineModeEnabled(p3); getPrefManager().setGradleOfflineModeEnabled(p3);
} }
}); });
builder.setPositiveButton(android.R.string.ok, null); builder.setPositiveButton(android.R.string.ok, null);
builder.setCancelable(false); builder.setCancelable(false);
builder.create().show(); builder.create().show();
} }
private void showClearCacheDialog() { private void showClearCacheDialog() {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
builder.setTitle(R.string.idepref_build_clearCache_title); builder.setTitle(R.string.idepref_build_clearCache_title);
builder.setMessage(R.string.msg_clear_cache); builder.setMessage(R.string.msg_clear_cache);
builder.setCancelable(false); builder.setCancelable(false);
builder.setPositiveButton( builder.setPositiveButton(
R.string.yes, R.string.yes,
(p1, p2) -> { (p1, p2) -> {
p1.dismiss(); p1.dismiss();
getProgressSheet().show(getChildFragmentManager(), "progress_sheet"); getProgressSheet().show(getChildFragmentManager(), "progress_sheet");
new TaskExecutor().executeAsync(this::deleteCaches, __ -> getProgressSheet().dismiss()); new TaskExecutor().executeAsync(this::deleteCaches, __ -> getProgressSheet().dismiss());
}); });
builder.setNegativeButton(R.string.no, null); builder.setNegativeButton(R.string.no, null);
builder.create().show(); builder.create().show();
} }
private Object deleteCaches() { private Object deleteCaches() {
File file = new File(Environment.GRADLE_USER_HOME, "caches"); File file = new File(Environment.GRADLE_USER_HOME, "caches");
if (file.exists()) { if (file.exists()) {
FileUtils.delete(file); FileUtils.delete(file);
} }
return null; return null;
} }
private ProgressSheet getProgressSheet() { private ProgressSheet getProgressSheet() {
return progressSheet == null return progressSheet == null
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait)) ? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
: progressSheet; : progressSheet;
} }
} }
@@ -1,319 +1,319 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.fragments.preferences; package com.itsaky.androidide.fragments.preferences;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_AUTO_SAVE; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_AUTO_SAVE;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_DRAW_HEX; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_DRAW_HEX;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_LINE_BREAK; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_LINE_BREAK;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_PASSWORD; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_PASSWORD;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_EMPTY_LINE; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_EMPTY_LINE;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_INNER; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_INNER;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_LEADING; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_LEADING;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_TRAILING; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FLAG_WS_TRAILING;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FONT_LIGATURES; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FONT_LIGATURES;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FONT_SIZE; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_FONT_SIZE;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_PRINTABLE_CHARS; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_PRINTABLE_CHARS;
import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_TAB_SIZE; import static com.itsaky.androidide.managers.PreferenceManager.KEY_EDITOR_TAB_SIZE;
import static com.itsaky.lsp.java.models.JavaServerSettings.KEY_COMPLETIONS_MATCH_LOWER; import static com.itsaky.lsp.java.models.JavaServerSettings.KEY_COMPLETIONS_MATCH_LOWER;
import static com.itsaky.lsp.java.models.JavaServerSettings.KEY_JAVA_PREF_GOOGLE_CODE_STYLE; import static com.itsaky.lsp.java.models.JavaServerSettings.KEY_JAVA_PREF_GOOGLE_CODE_STYLE;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference; import androidx.preference.SwitchPreference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.itsaky.androidide.R; import com.itsaky.androidide.R;
import com.itsaky.androidide.databinding.LayoutTextSizeSliderBinding; import com.itsaky.androidide.databinding.LayoutTextSizeSliderBinding;
import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.androidide.managers.PreferenceManager;
import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.models.ConstantsBridge;
import com.itsaky.androidide.utils.DialogUtils; import com.itsaky.androidide.utils.DialogUtils;
public class EditorPreferences extends BasePreferenceFragment public class EditorPreferences extends BasePreferenceFragment
implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
@Override @Override
public void onCreatePreferences(Bundle p1, String p2) { public void onCreatePreferences(Bundle p1, String p2) {
super.onCreatePreferences(p1, p2); super.onCreatePreferences(p1, p2);
if (getContext() == null) { if (getContext() == null) {
return; return;
} }
final var screen = getPreferenceScreen(); final var screen = getPreferenceScreen();
final var commonCategory = new PreferenceCategory(getContext()); final var commonCategory = new PreferenceCategory(getContext());
final var javaCategory = new PreferenceCategory(getContext()); final var javaCategory = new PreferenceCategory(getContext());
final var fontSize = new Preference(getContext()); final var fontSize = new Preference(getContext());
final var nonPrintable = new Preference(getContext()); final var nonPrintable = new Preference(getContext());
final var tabSize = new Preference(getContext()); final var tabSize = new Preference(getContext());
final var drawHex = new SwitchPreference(getContext()); final var drawHex = new SwitchPreference(getContext());
final var wordWrap = new SwitchPreference(getContext()); final var wordWrap = new SwitchPreference(getContext());
final var magnifier = new SwitchPreference(getContext()); final var magnifier = new SwitchPreference(getContext());
final var fontLigatures = new SwitchPreference(getContext()); final var fontLigatures = new SwitchPreference(getContext());
final var autoSave = new SwitchPreference(getContext()); final var autoSave = new SwitchPreference(getContext());
final var useGoogleCodeStyle = new SwitchPreference(getContext()); final var useGoogleCodeStyle = new SwitchPreference(getContext());
final var visiblePasswordFlag = new SwitchPreference(getContext()); final var visiblePasswordFlag = new SwitchPreference(getContext());
final var completionsMatchLower = new SwitchPreference(getContext()); final var completionsMatchLower = new SwitchPreference(getContext());
screen.addPreference(commonCategory); screen.addPreference(commonCategory);
screen.addPreference(javaCategory); screen.addPreference(javaCategory);
fontSize.setIcon(R.drawable.ic_text_size); fontSize.setIcon(R.drawable.ic_text_size);
fontSize.setKey(KEY_EDITOR_FONT_SIZE); fontSize.setKey(KEY_EDITOR_FONT_SIZE);
fontSize.setTitle(R.string.idepref_editor_fontsize_title); fontSize.setTitle(R.string.idepref_editor_fontsize_title);
fontSize.setSummary(R.string.idepref_editor_fontsize_summary); fontSize.setSummary(R.string.idepref_editor_fontsize_summary);
wordWrap.setIcon(R.drawable.ic_wrap_text); wordWrap.setIcon(R.drawable.ic_wrap_text);
wordWrap.setKey(PreferenceManager.KEY_EDITOR_WORD_WRAP); wordWrap.setKey(PreferenceManager.KEY_EDITOR_WORD_WRAP);
wordWrap.setTitle(R.string.idepref_editor_word_wrap_title); wordWrap.setTitle(R.string.idepref_editor_word_wrap_title);
wordWrap.setSummary(R.string.idepref_editor_word_wrap_summary); wordWrap.setSummary(R.string.idepref_editor_word_wrap_summary);
magnifier.setIcon(R.drawable.ic_loupe); magnifier.setIcon(R.drawable.ic_loupe);
magnifier.setKey(PreferenceManager.KEY_EDITOR_USE_MAGNIFER); magnifier.setKey(PreferenceManager.KEY_EDITOR_USE_MAGNIFER);
magnifier.setTitle(R.string.idepref_editor_use_magnifier_title); magnifier.setTitle(R.string.idepref_editor_use_magnifier_title);
magnifier.setSummary(R.string.idepref_editor_use_magnifier_summary); magnifier.setSummary(R.string.idepref_editor_use_magnifier_summary);
nonPrintable.setIcon(R.drawable.ic_drawing); nonPrintable.setIcon(R.drawable.ic_drawing);
nonPrintable.setKey(KEY_EDITOR_PRINTABLE_CHARS); nonPrintable.setKey(KEY_EDITOR_PRINTABLE_CHARS);
nonPrintable.setTitle(R.string.idepref_editor_paintingflags_title); nonPrintable.setTitle(R.string.idepref_editor_paintingflags_title);
nonPrintable.setSummary(R.string.idepref_editor_paintingflags_summary); nonPrintable.setSummary(R.string.idepref_editor_paintingflags_summary);
tabSize.setIcon(R.drawable.ic_tab); tabSize.setIcon(R.drawable.ic_tab);
tabSize.setKey(KEY_EDITOR_TAB_SIZE); tabSize.setKey(KEY_EDITOR_TAB_SIZE);
tabSize.setTitle(R.string.title_tab_size); tabSize.setTitle(R.string.title_tab_size);
tabSize.setSummary(R.string.msg_tab_size); tabSize.setSummary(R.string.msg_tab_size);
drawHex.setIcon(R.drawable.ic_hexadecimal); drawHex.setIcon(R.drawable.ic_hexadecimal);
drawHex.setKey(KEY_EDITOR_DRAW_HEX); drawHex.setKey(KEY_EDITOR_DRAW_HEX);
drawHex.setTitle(R.string.idepref_editor_drawhexcolors_title); drawHex.setTitle(R.string.idepref_editor_drawhexcolors_title);
drawHex.setSummary(R.string.idepref_editor_drawhexcolors_summary); drawHex.setSummary(R.string.idepref_editor_drawhexcolors_summary);
fontLigatures.setIcon(R.drawable.ic_font_ligatures); fontLigatures.setIcon(R.drawable.ic_font_ligatures);
fontLigatures.setKey(KEY_EDITOR_FONT_LIGATURES); fontLigatures.setKey(KEY_EDITOR_FONT_LIGATURES);
fontLigatures.setTitle(getString(R.string.idepref_editor_ligatures_title)); fontLigatures.setTitle(getString(R.string.idepref_editor_ligatures_title));
fontLigatures.setSummary(getString(R.string.idepref_editor_ligatures_summary)); fontLigatures.setSummary(getString(R.string.idepref_editor_ligatures_summary));
autoSave.setIcon(R.drawable.ic_save); autoSave.setIcon(R.drawable.ic_save);
autoSave.setKey(KEY_EDITOR_AUTO_SAVE); autoSave.setKey(KEY_EDITOR_AUTO_SAVE);
autoSave.setTitle(getString(R.string.idepref_editor_autoSave_title)); autoSave.setTitle(getString(R.string.idepref_editor_autoSave_title));
autoSave.setSummary(getString(R.string.idepref_editor_autoSave_summary)); autoSave.setSummary(getString(R.string.idepref_editor_autoSave_summary));
completionsMatchLower.setKey(KEY_COMPLETIONS_MATCH_LOWER); completionsMatchLower.setKey(KEY_COMPLETIONS_MATCH_LOWER);
completionsMatchLower.setIcon(R.drawable.ic_text_lower); completionsMatchLower.setIcon(R.drawable.ic_text_lower);
completionsMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title)); completionsMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title));
completionsMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary)); completionsMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary));
useGoogleCodeStyle.setIcon(R.drawable.ic_format_code); useGoogleCodeStyle.setIcon(R.drawable.ic_format_code);
useGoogleCodeStyle.setKey(KEY_JAVA_PREF_GOOGLE_CODE_STYLE); useGoogleCodeStyle.setKey(KEY_JAVA_PREF_GOOGLE_CODE_STYLE);
useGoogleCodeStyle.setTitle(getString(R.string.idepref_java_useGoogleStyle_title)); useGoogleCodeStyle.setTitle(getString(R.string.idepref_java_useGoogleStyle_title));
useGoogleCodeStyle.setSummary(getString(R.string.idepref_java_useGoogleStyle_summary)); useGoogleCodeStyle.setSummary(getString(R.string.idepref_java_useGoogleStyle_summary));
visiblePasswordFlag.setIcon(R.drawable.ic_password_input); visiblePasswordFlag.setIcon(R.drawable.ic_password_input);
visiblePasswordFlag.setKey(KEY_EDITOR_FLAG_PASSWORD); visiblePasswordFlag.setKey(KEY_EDITOR_FLAG_PASSWORD);
visiblePasswordFlag.setTitle(getString(R.string.idepref_visiblePassword_title)); visiblePasswordFlag.setTitle(getString(R.string.idepref_visiblePassword_title));
visiblePasswordFlag.setSummary(getString(R.string.idepref_visiblePassword_summary)); visiblePasswordFlag.setSummary(getString(R.string.idepref_visiblePassword_summary));
commonCategory.setTitle(getString(R.string.idepref_editor_category_common)); commonCategory.setTitle(getString(R.string.idepref_editor_category_common));
commonCategory.addPreference(fontSize); commonCategory.addPreference(fontSize);
commonCategory.addPreference(fontLigatures); commonCategory.addPreference(fontLigatures);
commonCategory.addPreference(tabSize); commonCategory.addPreference(tabSize);
commonCategory.addPreference(wordWrap); commonCategory.addPreference(wordWrap);
commonCategory.addPreference(magnifier); commonCategory.addPreference(magnifier);
commonCategory.addPreference(completionsMatchLower); commonCategory.addPreference(completionsMatchLower);
commonCategory.addPreference(visiblePasswordFlag); commonCategory.addPreference(visiblePasswordFlag);
commonCategory.addPreference(nonPrintable); commonCategory.addPreference(nonPrintable);
commonCategory.addPreference(drawHex); commonCategory.addPreference(drawHex);
commonCategory.addPreference(autoSave); commonCategory.addPreference(autoSave);
javaCategory.setTitle(getString(R.string.idepref_editor_category_java)); javaCategory.setTitle(getString(R.string.idepref_editor_category_java));
javaCategory.addPreference(useGoogleCodeStyle); javaCategory.addPreference(useGoogleCodeStyle);
setPreferenceScreen(screen); setPreferenceScreen(screen);
fontSize.setOnPreferenceClickListener(this); fontSize.setOnPreferenceClickListener(this);
fontLigatures.setOnPreferenceChangeListener(this); fontLigatures.setOnPreferenceChangeListener(this);
nonPrintable.setOnPreferenceClickListener(this); nonPrintable.setOnPreferenceClickListener(this);
tabSize.setOnPreferenceClickListener(this); tabSize.setOnPreferenceClickListener(this);
drawHex.setOnPreferenceChangeListener(this); drawHex.setOnPreferenceChangeListener(this);
autoSave.setOnPreferenceChangeListener(this); autoSave.setOnPreferenceChangeListener(this);
completionsMatchLower.setOnPreferenceChangeListener(this); completionsMatchLower.setOnPreferenceChangeListener(this);
useGoogleCodeStyle.setOnPreferenceChangeListener(this); useGoogleCodeStyle.setOnPreferenceChangeListener(this);
visiblePasswordFlag.setOnPreferenceChangeListener(this); visiblePasswordFlag.setOnPreferenceChangeListener(this);
wordWrap.setOnPreferenceChangeListener(this); wordWrap.setOnPreferenceChangeListener(this);
magnifier.setOnPreferenceChangeListener(this); magnifier.setOnPreferenceChangeListener(this);
fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true)); fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true));
drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true)); drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true));
autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false)); autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false));
completionsMatchLower.setChecked( completionsMatchLower.setChecked(
getPrefManager().getBoolean(KEY_COMPLETIONS_MATCH_LOWER, false)); getPrefManager().getBoolean(KEY_COMPLETIONS_MATCH_LOWER, false));
useGoogleCodeStyle.setChecked( useGoogleCodeStyle.setChecked(
getPrefManager().getBoolean(KEY_JAVA_PREF_GOOGLE_CODE_STYLE, false)); getPrefManager().getBoolean(KEY_JAVA_PREF_GOOGLE_CODE_STYLE, false));
visiblePasswordFlag.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FLAG_PASSWORD, true)); visiblePasswordFlag.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FLAG_PASSWORD, true));
wordWrap.setChecked(getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_WORD_WRAP, false)); wordWrap.setChecked(getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_WORD_WRAP, false));
magnifier.setChecked( magnifier.setChecked(
getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_USE_MAGNIFER, true)); getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_USE_MAGNIFER, true));
} }
@Override @Override
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
final boolean value = (boolean) newValue; final boolean value = (boolean) newValue;
switch (preference.getKey()) { switch (preference.getKey()) {
case KEY_EDITOR_FLAG_PASSWORD: case KEY_EDITOR_FLAG_PASSWORD:
ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED = true; ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED = true;
break; break;
case KEY_EDITOR_DRAW_HEX: case KEY_EDITOR_DRAW_HEX:
ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true; ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true;
break; break;
case KEY_EDITOR_FONT_LIGATURES: case KEY_EDITOR_FONT_LIGATURES:
ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true; ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true;
break; break;
case PreferenceManager.KEY_EDITOR_WORD_WRAP: case PreferenceManager.KEY_EDITOR_WORD_WRAP:
ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED = true; ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED = true;
break; break;
case PreferenceManager.KEY_EDITOR_USE_MAGNIFER: case PreferenceManager.KEY_EDITOR_USE_MAGNIFER:
ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED = true; ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED = true;
break; break;
} }
getPrefManager().putBoolean(preference.getKey(), value); getPrefManager().putBoolean(preference.getKey(), value);
return true; return true;
} }
@Override @Override
public boolean onPreferenceClick(@NonNull Preference preference) { public boolean onPreferenceClick(@NonNull Preference preference) {
final String key = preference.getKey(); final String key = preference.getKey();
switch (key) { switch (key) {
case KEY_EDITOR_FONT_SIZE: case KEY_EDITOR_FONT_SIZE:
showTextSizeDialog(); showTextSizeDialog();
break; break;
case KEY_EDITOR_PRINTABLE_CHARS: case KEY_EDITOR_PRINTABLE_CHARS:
showPrintableCharsDialog(); showPrintableCharsDialog();
break; break;
case KEY_EDITOR_TAB_SIZE: case KEY_EDITOR_TAB_SIZE:
showTabSizeDialog(); showTabSizeDialog();
break; break;
} }
return true; return true;
} }
private void showTabSizeDialog() { private void showTabSizeDialog() {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
final String[] sizes = new String[] {"2", "4", "6", "8"}; final String[] sizes = new String[] {"2", "4", "6", "8"};
int current = (getPrefManager().getEditorTabSize() / 2) - 1; int current = (getPrefManager().getEditorTabSize() / 2) - 1;
if (current < 0 || current >= sizes.length) { if (current < 0 || current >= sizes.length) {
current = 1; current = 1;
} }
builder.setTitle(R.string.title_tab_size); builder.setTitle(R.string.title_tab_size);
builder.setSingleChoiceItems( builder.setSingleChoiceItems(
sizes, sizes,
current, current,
(d, i) -> { (d, i) -> {
d.dismiss(); d.dismiss();
int tabSize = (i + 1) * 2; int tabSize = (i + 1) * 2;
if (tabSize < 2 || tabSize > 8) { if (tabSize < 2 || tabSize > 8) {
tabSize = 4; tabSize = 4;
} }
getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize); getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize);
}); });
builder.setCancelable(true); builder.setCancelable(true);
builder.show(); builder.show();
} }
private void showTextSizeDialog() { private void showTextSizeDialog() {
final LayoutTextSizeSliderBinding binding = final LayoutTextSizeSliderBinding binding =
LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(getContext())); LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(getContext()));
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
float size = getPrefManager().getFloat(KEY_EDITOR_FONT_SIZE, 14); float size = getPrefManager().getFloat(KEY_EDITOR_FONT_SIZE, 14);
if (size < 6 || size > 32) { if (size < 6 || size > 32) {
size = 14; size = 14;
} }
changeTextSize(binding, size); changeTextSize(binding, size);
binding.slider.setLabelFormatter(p1 -> String.valueOf((int) p1)); binding.slider.setLabelFormatter(p1 -> String.valueOf((int) p1));
builder.setTitle(R.string.title_change_text_size); builder.setTitle(R.string.title_change_text_size);
builder.setMessage(R.string.msg_editor_font_size); builder.setMessage(R.string.msg_editor_font_size);
builder.setView(binding.getRoot()); builder.setView(binding.getRoot());
builder.setPositiveButton( builder.setPositiveButton(
android.R.string.ok, android.R.string.ok,
(p1, p2) -> { (p1, p2) -> {
p1.dismiss(); p1.dismiss();
float s = binding.slider.getValue(); float s = binding.slider.getValue();
changeTextSize(binding, s); changeTextSize(binding, s);
}); });
builder.setNeutralButton(R.string.reset, (p1, p2) -> changeTextSize(binding, 14)); builder.setNeutralButton(R.string.reset, (p1, p2) -> changeTextSize(binding, 14));
builder.setCancelable(false); builder.setCancelable(false);
builder.show(); builder.show();
} }
private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) { private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) {
getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size); getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size);
binding.slider.setValue(size); binding.slider.setValue(size);
ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true; ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true;
} }
private void showPrintableCharsDialog() { private void showPrintableCharsDialog() {
String[] labels = { String[] labels = {
"Leading whitespaces", "Leading whitespaces",
"Trailing whitespaces", "Trailing whitespaces",
"Inner whitespaces", "Inner whitespaces",
"Whitespaces in empty lines", "Whitespaces in empty lines",
"Line breaks" "Line breaks"
}; };
final boolean[] checked = { final boolean[] checked = {
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_LEADING, true), getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_LEADING, true),
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_TRAILING, false), getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_TRAILING, false),
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_INNER, true), getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_INNER, true),
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, true), getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, true),
getPrefManager().getBoolean(KEY_EDITOR_FLAG_LINE_BREAK, true) getPrefManager().getBoolean(KEY_EDITOR_FLAG_LINE_BREAK, true)
}; };
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
builder.setTitle(R.string.idepref_editor_paintingflags_title); builder.setTitle(R.string.idepref_editor_paintingflags_title);
builder.setMultiChoiceItems( builder.setMultiChoiceItems(
labels, labels,
checked, checked,
(dialog, which, isChecked) -> { (dialog, which, isChecked) -> {
if (which == 0) { if (which == 0) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_LEADING, isChecked); getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_LEADING, isChecked);
} else if (which == 1) { } else if (which == 1) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_TRAILING, isChecked); getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_TRAILING, isChecked);
} else if (which == 2) { } else if (which == 2) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_INNER, isChecked); getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_INNER, isChecked);
} else if (which == 3) { } else if (which == 3) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked); getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked);
} else if (which == 4) { } else if (which == 4) {
getPrefManager().putBoolean(KEY_EDITOR_FLAG_LINE_BREAK, isChecked); getPrefManager().putBoolean(KEY_EDITOR_FLAG_LINE_BREAK, isChecked);
} }
ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true; ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true;
}); });
builder.setPositiveButton(android.R.string.ok, null); builder.setPositiveButton(android.R.string.ok, null);
builder.setCancelable(false); builder.setCancelable(false);
builder.show(); builder.show();
} }
} }
@@ -1,192 +1,192 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.fragments.sheets; package com.itsaky.androidide.fragments.sheets;
import static com.itsaky.attrinfo.models.Attr.FORMAT_BOOLEAN; import static com.itsaky.attrinfo.models.Attr.FORMAT_BOOLEAN;
import static com.itsaky.attrinfo.models.Attr.FORMAT_COLOR; import static com.itsaky.attrinfo.models.Attr.FORMAT_COLOR;
import static com.itsaky.attrinfo.models.Attr.FORMAT_DIMENSION; import static com.itsaky.attrinfo.models.Attr.FORMAT_DIMENSION;
import static com.itsaky.attrinfo.models.Attr.FORMAT_ENUM; import static com.itsaky.attrinfo.models.Attr.FORMAT_ENUM;
import static com.itsaky.attrinfo.models.Attr.FORMAT_FLAG; import static com.itsaky.attrinfo.models.Attr.FORMAT_FLAG;
import static com.itsaky.attrinfo.models.Attr.FORMAT_FLOAT; import static com.itsaky.attrinfo.models.Attr.FORMAT_FLOAT;
import static com.itsaky.attrinfo.models.Attr.FORMAT_FRACTION; import static com.itsaky.attrinfo.models.Attr.FORMAT_FRACTION;
import static com.itsaky.attrinfo.models.Attr.FORMAT_INTEGER; import static com.itsaky.attrinfo.models.Attr.FORMAT_INTEGER;
import static com.itsaky.attrinfo.models.Attr.FORMAT_REFERENCE; import static com.itsaky.attrinfo.models.Attr.FORMAT_REFERENCE;
import static com.itsaky.attrinfo.models.Attr.FORMAT_STRING; import static com.itsaky.attrinfo.models.Attr.FORMAT_STRING;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.itsaky.androidide.adapters.AttrValueFormatTabAdapter; import com.itsaky.androidide.adapters.AttrValueFormatTabAdapter;
import com.itsaky.androidide.databinding.LayoutAttrValueEditorBinding; import com.itsaky.androidide.databinding.LayoutAttrValueEditorBinding;
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment; import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
import com.itsaky.androidide.fragments.attr.BooleanEditor; import com.itsaky.androidide.fragments.attr.BooleanEditor;
import com.itsaky.androidide.fragments.attr.ColorEditor; import com.itsaky.androidide.fragments.attr.ColorEditor;
import com.itsaky.androidide.fragments.attr.DimensionEditor; import com.itsaky.androidide.fragments.attr.DimensionEditor;
import com.itsaky.androidide.fragments.attr.EnumEditor; import com.itsaky.androidide.fragments.attr.EnumEditor;
import com.itsaky.androidide.fragments.attr.FlagEditor; import com.itsaky.androidide.fragments.attr.FlagEditor;
import com.itsaky.androidide.fragments.attr.FloatEditor; import com.itsaky.androidide.fragments.attr.FloatEditor;
import com.itsaky.androidide.fragments.attr.FractionEditor; import com.itsaky.androidide.fragments.attr.FractionEditor;
import com.itsaky.androidide.fragments.attr.IntegerEditor; import com.itsaky.androidide.fragments.attr.IntegerEditor;
import com.itsaky.androidide.fragments.attr.ReferenceEditor; import com.itsaky.androidide.fragments.attr.ReferenceEditor;
import com.itsaky.androidide.fragments.attr.StringEditor; import com.itsaky.androidide.fragments.attr.StringEditor;
import com.itsaky.androidide.models.XMLAttribute; import com.itsaky.androidide.models.XMLAttribute;
import com.itsaky.attrinfo.models.Attr; import com.itsaky.attrinfo.models.Attr;
import com.itsaky.inflater.IAttribute; import com.itsaky.inflater.IAttribute;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import java.util.Objects; import java.util.Objects;
/** /**
* Sheet for editing value of an attribute. * Sheet for editing value of an attribute.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
public class AttrValueEditorSheet extends BottomSheetDialogFragment public class AttrValueEditorSheet extends BottomSheetDialogFragment
implements BaseValueEditorFragment.OnValueChangeListener { implements BaseValueEditorFragment.OnValueChangeListener {
private static final String KEY_ATTRIBUTE = "attrEditorSheet_attribute"; private static final String KEY_ATTRIBUTE = "attrEditorSheet_attribute";
private BaseValueEditorFragment.OnValueChangeListener valueChangeListener; private BaseValueEditorFragment.OnValueChangeListener valueChangeListener;
private LayoutAttrValueEditorBinding binding; private LayoutAttrValueEditorBinding binding;
private AttrValueFormatTabAdapter adapter; private AttrValueFormatTabAdapter adapter;
private XMLAttribute attribute; private XMLAttribute attribute;
@NonNull @NonNull
public static AttrValueEditorSheet newInstance(final XMLAttribute attribute) { public static AttrValueEditorSheet newInstance(final XMLAttribute attribute) {
Objects.requireNonNull(attribute); Objects.requireNonNull(attribute);
final var args = new Bundle(); final var args = new Bundle();
args.putParcelable(KEY_ATTRIBUTE, attribute); args.putParcelable(KEY_ATTRIBUTE, attribute);
final var fragment = new AttrValueEditorSheet(); final var fragment = new AttrValueEditorSheet();
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
} }
@Override @Override
public void onAttach(@NonNull Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
this.valueChangeListener = (BaseValueEditorFragment.OnValueChangeListener) getParentFragment(); this.valueChangeListener = (BaseValueEditorFragment.OnValueChangeListener) getParentFragment();
} }
@Nullable @Nullable
@Override @Override
public View onCreateView( public View onCreateView(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
this.binding = LayoutAttrValueEditorBinding.inflate(inflater, container, false); this.binding = LayoutAttrValueEditorBinding.inflate(inflater, container, false);
return binding.getRoot(); return binding.getRoot();
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
if (attribute == null) { if (attribute == null) {
this.attribute = requireArguments().getParcelable(KEY_ATTRIBUTE); this.attribute = requireArguments().getParcelable(KEY_ATTRIBUTE);
} }
Objects.requireNonNull(this.attribute); Objects.requireNonNull(this.attribute);
final var namespace = this.attribute.getNamespace(); final var namespace = this.attribute.getNamespace();
final var formatText = Attr.createFormatText(attribute.getFormat()); final var formatText = Attr.createFormatText(attribute.getFormat());
this.binding.attributeName.setText( this.binding.attributeName.setText(
String.format("%s:%s", namespace.getPrefix(), attribute.getAttributeName())); String.format("%s:%s", namespace.getPrefix(), attribute.getAttributeName()));
this.binding.attributeFormat.setText(formatText); this.binding.attributeFormat.setText(formatText);
setupTabs(formatText); setupTabs(formatText);
} }
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private void setupTabs(@NonNull String formatText) { private void setupTabs(@NonNull String formatText) {
this.binding.tabs.removeAllTabs(); this.binding.tabs.removeAllTabs();
this.binding.pager.setSaveEnabled(false); this.binding.pager.setSaveEnabled(false);
if (this.adapter != null) { if (this.adapter != null) {
this.adapter.removeAll(); this.adapter.removeAll();
this.adapter.notifyDataSetChanged(); this.adapter.notifyDataSetChanged();
} }
this.adapter = new AttrValueFormatTabAdapter(getChildFragmentManager(), attribute); this.adapter = new AttrValueFormatTabAdapter(getChildFragmentManager(), attribute);
final var formats = formatText.split("\\|"); final var formats = formatText.split("\\|");
for (var formatName : formats) { for (var formatName : formats) {
if (TextUtils.isEmpty(formatName) || formatName.length() < 2) { if (TextUtils.isEmpty(formatName) || formatName.length() < 2) {
continue; continue;
} }
final var frag = getFragmentClass(formatName); final var frag = getFragmentClass(formatName);
if (frag != null) { if (frag != null) {
this.adapter.addFragment(frag, formatName); this.adapter.addFragment(frag, formatName);
} }
} }
binding.pager.setAdapter(this.adapter); binding.pager.setAdapter(this.adapter);
binding.tabs.setupWithViewPager(binding.pager); binding.tabs.setupWithViewPager(binding.pager);
} }
@Nullable @Nullable
@Contract(pure = true) @Contract(pure = true)
private Class<? extends BaseValueEditorFragment> getFragmentClass(@NonNull String name) { private Class<? extends BaseValueEditorFragment> getFragmentClass(@NonNull String name) {
switch (name) { switch (name) {
case FORMAT_REFERENCE: case FORMAT_REFERENCE:
return ReferenceEditor.class; return ReferenceEditor.class;
case FORMAT_COLOR: case FORMAT_COLOR:
return ColorEditor.class; return ColorEditor.class;
case FORMAT_BOOLEAN: case FORMAT_BOOLEAN:
return BooleanEditor.class; return BooleanEditor.class;
case FORMAT_DIMENSION: case FORMAT_DIMENSION:
return DimensionEditor.class; return DimensionEditor.class;
case FORMAT_FLOAT: case FORMAT_FLOAT:
return FloatEditor.class; return FloatEditor.class;
case FORMAT_INTEGER: case FORMAT_INTEGER:
return IntegerEditor.class; return IntegerEditor.class;
case FORMAT_FRACTION: case FORMAT_FRACTION:
return FractionEditor.class; return FractionEditor.class;
case FORMAT_STRING: case FORMAT_STRING:
return StringEditor.class; return StringEditor.class;
case FORMAT_ENUM: case FORMAT_ENUM:
return EnumEditor.class; return EnumEditor.class;
case FORMAT_FLAG: case FORMAT_FLAG:
return FlagEditor.class; return FlagEditor.class;
} }
return null; return null;
} }
public void setAttribute(XMLAttribute attribute) { public void setAttribute(XMLAttribute attribute) {
Objects.requireNonNull(attribute); Objects.requireNonNull(attribute);
this.attribute = attribute; this.attribute = attribute;
} }
@Override @Override
public void onValueChanged(IAttribute attribute, String newValue) { public void onValueChanged(IAttribute attribute, String newValue) {
if (valueChangeListener != null) { if (valueChangeListener != null) {
valueChangeListener.onValueChanged(attribute, newValue); valueChangeListener.onValueChanged(attribute, newValue);
} }
} }
} }
@@ -1,169 +1,169 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.handlers package com.itsaky.androidide.handlers
import android.content.DialogInterface import android.content.DialogInterface
import android.text.Layout.Alignment.ALIGN_CENTER import android.text.Layout.Alignment.ALIGN_CENTER
import android.text.Layout.Alignment.ALIGN_NORMAL import android.text.Layout.Alignment.ALIGN_NORMAL
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
import android.view.View import android.view.View
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import com.itsaky.androidide.EditorActivity import com.itsaky.androidide.EditorActivity
import com.itsaky.androidide.R.string import com.itsaky.androidide.R.string
import com.itsaky.androidide.managers.PreferenceManager import com.itsaky.androidide.managers.PreferenceManager
import com.itsaky.androidide.services.GradleBuildService import com.itsaky.androidide.services.GradleBuildService
import com.itsaky.androidide.tooling.events.ProgressEvent import com.itsaky.androidide.tooling.events.ProgressEvent
import com.itsaky.androidide.tooling.events.StatusEvent import com.itsaky.androidide.tooling.events.StatusEvent
import com.itsaky.androidide.tooling.events.download.FileDownloadFinishEvent import com.itsaky.androidide.tooling.events.download.FileDownloadFinishEvent
import com.itsaky.androidide.tooling.events.download.FileDownloadOperationDescriptor import com.itsaky.androidide.tooling.events.download.FileDownloadOperationDescriptor
import com.itsaky.androidide.tooling.events.task.TaskProgressEvent import com.itsaky.androidide.tooling.events.task.TaskProgressEvent
import com.itsaky.androidide.utils.DialogUtils import com.itsaky.androidide.utils.DialogUtils
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.StudioUtils import com.itsaky.androidide.utils.StudioUtils
import java.io.File import java.io.File
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
/** /**
* Handles events received from [GradleBuildService] updates [EditorActivity]. * Handles events received from [GradleBuildService] updates [EditorActivity].
* @author Akash Yadav * @author Akash Yadav
*/ */
class EditorEventListener : GradleBuildService.EventListener { class EditorEventListener : GradleBuildService.EventListener {
private var activityReference: WeakReference<EditorActivity?> = WeakReference(null) private var activityReference: WeakReference<EditorActivity?> = WeakReference(null)
private val log = ILogger.newInstance(javaClass.simpleName) private val log = ILogger.newInstance(javaClass.simpleName)
fun setActivity(activity: EditorActivity) { fun setActivity(activity: EditorActivity) {
this.activityReference = WeakReference(activity) this.activityReference = WeakReference(activity)
} }
override fun prepareBuild() { override fun prepareBuild() {
val isFirstBuild = val isFirstBuild =
activity() activity()
.app.prefManager.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true) .app.prefManager.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true)
activity() activity()
.setStatus( .setStatus(
activity() activity()
.getString(if (isFirstBuild) string.preparing_first else string.preparing)) .getString(if (isFirstBuild) string.preparing_first else string.preparing))
if (isFirstBuild) { if (isFirstBuild) {
activity().showFirstBuildNotice() activity().showFirstBuildNotice()
} }
activity().binding.buildProgressIndicator.visibility = View.VISIBLE activity().binding.buildProgressIndicator.visibility = View.VISIBLE
} }
override fun onBuildSuccessful(tasks: MutableList<String>) { override fun onBuildSuccessful(tasks: MutableList<String>) {
analyzeCurrentFile() analyzeCurrentFile()
appendOutputSeparator() appendOutputSeparator()
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false) activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
activity().binding.buildProgressIndicator.visibility = View.GONE activity().binding.buildProgressIndicator.visibility = View.GONE
} }
override fun onProgressEvent(event: ProgressEvent) { override fun onProgressEvent(event: ProgressEvent) {
if (event is FileDownloadFinishEvent) { if (event is FileDownloadFinishEvent) {
activity().setStatus("") activity().setStatus("")
} else if (event is TaskProgressEvent) { } else if (event is TaskProgressEvent) {
activity() activity()
.setStatus(activity().getString(string.msg_running_task, event.descriptor.taskPath)) .setStatus(activity().getString(string.msg_running_task, event.descriptor.taskPath))
} else if (event is StatusEvent && event.descriptor is FileDownloadOperationDescriptor) { } else if (event is StatusEvent && event.descriptor is FileDownloadOperationDescriptor) {
val total = StudioUtils.bytesToSizeString(event.total) val total = StudioUtils.bytesToSizeString(event.total)
val progress = StudioUtils.bytesToSizeString(event.progress) val progress = StudioUtils.bytesToSizeString(event.progress)
val fileName = val fileName =
URLUtil.guessFileName( URLUtil.guessFileName(
(event.descriptor as FileDownloadOperationDescriptor).uri.path, null, null) (event.descriptor as FileDownloadOperationDescriptor).uri.path, null, null)
val status = SpannableStringBuilder() val status = SpannableStringBuilder()
status.append( status.append(
"[$progress/$total]", "[$progress/$total]",
AlignmentSpan.Standard(ALIGN_NORMAL), AlignmentSpan.Standard(ALIGN_NORMAL),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
status.append( status.append(
" Download $fileName", " Download $fileName",
AlignmentSpan.Standard(ALIGN_CENTER), AlignmentSpan.Standard(ALIGN_CENTER),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
activity().setStatus(status, GravityCompat.START) activity().setStatus(status, GravityCompat.START)
} }
} }
private fun appendOutputSeparator() { private fun appendOutputSeparator() {
activity().appendBuildOut("\n\n") activity().appendBuildOut("\n\n")
} }
override fun onBuildFailed(tasks: MutableList<String>) { override fun onBuildFailed(tasks: MutableList<String>) {
analyzeCurrentFile() analyzeCurrentFile()
appendOutputSeparator() appendOutputSeparator()
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false) activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
activity().binding.buildProgressIndicator.visibility = View.GONE activity().binding.buildProgressIndicator.visibility = View.GONE
} }
override fun onOutput(line: String?) { override fun onOutput(line: String?) {
activity().appendBuildOut(line) activity().appendBuildOut(line)
// TODO This can be handled better when ProgressEvents are received from Tooling API server // TODO This can be handled better when ProgressEvents are received from Tooling API server
if (line!!.contains("BUILD SUCCESSFUL") || line.contains("BUILD FAILED")) { if (line!!.contains("BUILD SUCCESSFUL") || line.contains("BUILD FAILED")) {
activity().setStatus(line) activity().setStatus(line)
} }
} }
private fun analyzeCurrentFile() { private fun analyzeCurrentFile() {
val editorView = activity().currentEditor val editorView = activity().currentEditor
if (editorView != null) { if (editorView != null) {
val editor = editorView.editor val editor = editorView.editor
editor?.analyze() editor?.analyze()
} }
} }
fun activity(): EditorActivity { fun activity(): EditorActivity {
return activityReference.get() return activityReference.get()
?: throw IllegalStateException("Activity reference has been destroyed!") ?: throw IllegalStateException("Activity reference has been destroyed!")
} }
private fun installApks(apks: Set<File>?) { private fun installApks(apks: Set<File>?) {
if (apks == null || apks.isEmpty()) { if (apks == null || apks.isEmpty()) {
IDEHandler.LOG.error("Cannot install APKs: $apks") IDEHandler.LOG.error("Cannot install APKs: $apks")
return return
} }
if (apks.size == 1) { if (apks.size == 1) {
activity().install(apks.iterator().next()) activity().install(apks.iterator().next())
} else { } else {
IDEHandler.LOG.info("Multiple APKs found. Let the user select...") IDEHandler.LOG.info("Multiple APKs found. Let the user select...")
val files: List<File> = ArrayList(apks) val files: List<File> = ArrayList(apks)
val builder = DialogUtils.newMaterialDialogBuilder(activity()) val builder = DialogUtils.newMaterialDialogBuilder(activity())
builder.setTitle(activity().getString(string.title_install_apks)) builder.setTitle(activity().getString(string.title_install_apks))
builder.setItems(getNames(files)) { d: DialogInterface, w: Int -> builder.setItems(getNames(files)) { d: DialogInterface, w: Int ->
d.dismiss() d.dismiss()
activity().install(files[w]) activity().install(files[w])
} }
builder.show() builder.show()
} }
} }
private fun getNames(apks: Collection<File>): Array<String?> { private fun getNames(apks: Collection<File>): Array<String?> {
val names = arrayOfNulls<String>(apks.size) val names = arrayOfNulls<String>(apks.size)
for ((i, apk) in apks.withIndex()) { for ((i, apk) in apks.withIndex()) {
names[i] = apk.name names[i] = apk.name
} }
return names return names
} }
} }
@@ -1,55 +1,55 @@
/************************************************************************************ /************************************************************************************
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
**************************************************************************************/ **************************************************************************************/
package com.itsaky.androidide.handlers; package com.itsaky.androidide.handlers;
import com.itsaky.androidide.EditorActivity; import com.itsaky.androidide.EditorActivity;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
/** /**
* A handler is an implementation that handles a specific feature, data or anything else in * A handler is an implementation that handles a specific feature, data or anything else in
* AndroidIDE * AndroidIDE
*/ */
public abstract class IDEHandler { public abstract class IDEHandler {
protected static final ILogger LOG = ILogger.instance(); protected static final ILogger LOG = ILogger.instance();
protected Provider provider; protected Provider provider;
public IDEHandler(Provider provider) { public IDEHandler(Provider provider) {
this.provider = provider; this.provider = provider;
} }
public abstract void start(); public abstract void start();
public abstract void stop(); public abstract void stop();
protected EditorActivity activity() { protected EditorActivity activity() {
return provider.provideEditorActivity(); return provider.provideEditorActivity();
} }
/** An interface to communicate between a handler and its client */ /** An interface to communicate between a handler and its client */
public interface Provider { public interface Provider {
/** /**
* Called by handler to get a reference to {@link EditorActivity} * Called by handler to get a reference to {@link EditorActivity}
* *
* @throws NullPointerException is this is required * @throws NullPointerException is this is required
*/ */
EditorActivity provideEditorActivity(); EditorActivity provideEditorActivity();
} }
} }
@@ -1,128 +1,128 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.language; package com.itsaky.androidide.language;
import com.itsaky.androidide.projects.ProjectManager; import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.tooling.api.model.IdeGradleProject; import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import com.itsaky.lsp.api.ILanguageServer; import com.itsaky.lsp.api.ILanguageServer;
import com.itsaky.lsp.models.CompletionItem; import com.itsaky.lsp.models.CompletionItem;
import com.itsaky.lsp.models.CompletionParams; import com.itsaky.lsp.models.CompletionParams;
import com.itsaky.lsp.models.CompletionResult; import com.itsaky.lsp.models.CompletionResult;
import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Position;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate; import java.util.function.Predicate;
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
import io.github.rosemoe.sora.lang.completion.CompletionHelper; import io.github.rosemoe.sora.lang.completion.CompletionHelper;
import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.CharPosition;
import io.github.rosemoe.sora.text.ContentReference; import io.github.rosemoe.sora.text.ContentReference;
import io.github.rosemoe.sora.util.MyCharacter; import io.github.rosemoe.sora.util.MyCharacter;
/** /**
* Common implementation of completion provider which requests completions to provided language * Common implementation of completion provider which requests completions to provided language
* server. * server.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
public class CommonCompletionProvider { public class CommonCompletionProvider {
private static final ILogger LOG = ILogger.newInstance("CommonCompletionProvider"); private static final ILogger LOG = ILogger.newInstance("CommonCompletionProvider");
private final ILanguageServer server; private final ILanguageServer server;
private CompletableFuture<CompletionResult> future; private CompletableFuture<CompletionResult> future;
public CommonCompletionProvider(ILanguageServer server) { public CommonCompletionProvider(ILanguageServer server) {
this.server = server; this.server = server;
} }
public static boolean checkJavaCompletionChar(char c) { public static boolean checkJavaCompletionChar(char c) {
return MyCharacter.isJavaIdentifierPart(c) || c == '.'; return MyCharacter.isJavaIdentifierPart(c) || c == '.';
} }
public static boolean checkXMLCompletionChar(char c) { public static boolean checkXMLCompletionChar(char c) {
return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/'; return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/';
} }
/** /**
* Computes completion items using the provided language server instance. * Computes completion items using the provided language server instance.
* *
* @param content The reference to the content of the editor. * @param content The reference to the content of the editor.
* @param file The file to compute completions for. * @param file The file to compute completions for.
* @param position The position of the cursor in the content. * @param position The position of the cursor in the content.
* @return The computed completion items. May return an empty list if the there was an error * @return The computed completion items. May return an empty list if the there was an error
* computing the completion items. * computing the completion items.
*/ */
public List<CompletionItem> complete( public List<CompletionItem> complete(
ContentReference content, ContentReference content,
Path file, Path file,
CharPosition position, CharPosition position,
Predicate<Character> prefixMatcher) { Predicate<Character> prefixMatcher) {
if (this.future != null && !this.future.isDone()) { if (this.future != null && !this.future.isDone()) {
try { try {
this.future.cancel(true); this.future.cancel(true);
} catch (CancellationException e) { } catch (CancellationException e) {
return new ArrayList<>(); return new ArrayList<>();
} }
} }
this.future = this.future =
CompletableFuture.supplyAsync( CompletableFuture.supplyAsync(
() -> { () -> {
final var prefix = final var prefix =
CompletionHelper.computePrefix(content, position, prefixMatcher::test); CompletionHelper.computePrefix(content, position, prefixMatcher::test);
final var completer = server.getCompletionProvider(); final var completer = server.getCompletionProvider();
if (!completer.canComplete(file)) { if (!completer.canComplete(file)) {
return CompletionResult.EMPTY; return CompletionResult.EMPTY;
} }
IdeGradleProject fileModule = null; IdeGradleProject fileModule = null;
try { try {
fileModule = ProjectManager.INSTANCE.findModuleForFile(file.toFile()).get(); fileModule = ProjectManager.INSTANCE.findModuleForFile(file.toFile()).get();
} catch (Throwable e) { } catch (Throwable e) {
LOG.error("Unable to find module for current file", e); LOG.error("Unable to find module for current file", e);
} }
final var params = final var params =
new CompletionParams( new CompletionParams(
new Position(position.line, position.column, position.index), file); new Position(position.line, position.column, position.index), file);
params.setContent(content); params.setContent(content);
params.setPrefix(prefix); params.setPrefix(prefix);
params.setModule(fileModule); params.setModule(fileModule);
return completer.complete(params); return completer.complete(params);
}); });
try { try {
final var items = future.get ().getItems (); final var items = future.get ().getItems ();
return items; return items;
} catch (Throwable e) { } catch (Throwable e) {
// Do not log if completion was interrupted or cancelled // Do not log if completion was interrupted or cancelled
if (!(e instanceof InterruptedException || e instanceof CompletionCancelledException)) { if (!(e instanceof InterruptedException || e instanceof CompletionCancelledException)) {
LOG.error("Unable to compute completions", e); LOG.error("Unable to compute completions", e);
} }
return Collections.emptyList(); return Collections.emptyList();
} }
} }
} }
@@ -1,47 +1,47 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.language; package com.itsaky.androidide.language;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.itsaky.lsp.models.DiagnosticItem; import com.itsaky.lsp.models.DiagnosticItem;
import java.util.List; import java.util.List;
/** /**
* Base class for analyze managers. * Base class for analyze managers.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
public interface IAnalyzeManager { public interface IAnalyzeManager {
/** /**
* Update the diagnostic items. * Update the diagnostic items.
* *
* @param diagnostics The new list of diagnostics. * @param diagnostics The new list of diagnostics.
*/ */
default void updateDiagnostics(@NonNull List<DiagnosticItem> diagnostics) {} default void updateDiagnostics(@NonNull List<DiagnosticItem> diagnostics) {}
/** /**
* Get the diagnostic items. * Get the diagnostic items.
* *
* @return The diagnostic items. Must not be <code>null</code>. * @return The diagnostic items. Must not be <code>null</code>.
*/ */
@NonNull @NonNull
List<DiagnosticItem> getDiagnostics(); List<DiagnosticItem> getDiagnostics();
} }
@@ -1,154 +1,154 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.language.xml; package com.itsaky.androidide.language.xml;
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.OPERATOR; import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.OPERATOR;
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forComment; import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.forComment;
import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.get; import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.get;
import static io.github.rosemoe.sora.widget.schemes.EditorColorScheme.LITERAL; import static io.github.rosemoe.sora.widget.schemes.EditorColorScheme.LITERAL;
import com.itsaky.androidide.lexers.xml.XMLLexer; import com.itsaky.androidide.lexers.xml.XMLLexer;
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
import com.itsaky.androidide.utils.CharSequenceReader; import com.itsaky.androidide.utils.CharSequenceReader;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream; import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import java.io.IOException; import java.io.IOException;
import java.util.Stack; import java.util.Stack;
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
import io.github.rosemoe.sora.lang.styling.CodeBlock; import io.github.rosemoe.sora.lang.styling.CodeBlock;
import io.github.rosemoe.sora.lang.styling.MappedSpans; import io.github.rosemoe.sora.lang.styling.MappedSpans;
import io.github.rosemoe.sora.lang.styling.Styles; import io.github.rosemoe.sora.lang.styling.Styles;
/** /**
* Simple analyzer implementation for the XML files. * Simple analyzer implementation for the XML files.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
public class XMLAnalyzer extends SimpleAnalyzeManager<Void> { public class XMLAnalyzer extends SimpleAnalyzeManager<Void> {
private static final ILogger LOG = ILogger.newInstance("XMLAnalyzer"); private static final ILogger LOG = ILogger.newInstance("XMLAnalyzer");
@Override @Override
protected Styles analyze(StringBuilder text, Delegate<Void> delegate) { protected Styles analyze(StringBuilder text, Delegate<Void> delegate) {
final var styles = new Styles(); final var styles = new Styles();
final var colors = new MappedSpans.Builder(); final var colors = new MappedSpans.Builder();
CodePointCharStream stream; CodePointCharStream stream;
try { try {
stream = CharStreams.fromReader(new CharSequenceReader(text)); stream = CharStreams.fromReader(new CharSequenceReader(text));
} catch (IOException e) { } catch (IOException e) {
LOG.error("Unable to create stream for analyze", e); LOG.error("Unable to create stream for analyze", e);
return styles; return styles;
} }
XMLLexer lexer = new XMLLexer(stream); XMLLexer lexer = new XMLLexer(stream);
Token token; Token token;
int previous = 0; int previous = 0;
int line, column, lastLine = 1; int line, column, lastLine = 1;
var first = true; var first = true;
final var stack = new Stack<CodeBlock>(); final var stack = new Stack<CodeBlock>();
while (!delegate.isCancelled()) { while (!delegate.isCancelled()) {
token = lexer.nextToken(); token = lexer.nextToken();
if (token == null) { if (token == null) {
break; break;
} }
if (token.getType() == XMLLexer.EOF) { if (token.getType() == XMLLexer.EOF) {
lastLine = token.getLine() - 1; lastLine = token.getLine() - 1;
break; break;
} }
line = token.getLine() - 1; line = token.getLine() - 1;
column = token.getCharPositionInLine(); column = token.getCharPositionInLine();
lastLine = line; lastLine = line;
switch (token.getType()) { switch (token.getType()) {
case XMLLexer.S: case XMLLexer.S:
case XMLLexer.SEA_WS: case XMLLexer.SEA_WS:
if (first) { if (first) {
colors.addNormalIfNull(); colors.addNormalIfNull();
} }
break; break;
case XMLLexer.COMMENT: case XMLLexer.COMMENT:
colors.addIfNeeded(line, column, forComment()); colors.addIfNeeded(line, column, forComment());
break; break;
case XMLLexer.OPEN: case XMLLexer.OPEN:
case XMLLexer.OPEN_SLASH: case XMLLexer.OPEN_SLASH:
case XMLLexer.CLOSE: case XMLLexer.CLOSE:
case XMLLexer.SLASH: case XMLLexer.SLASH:
case XMLLexer.SPECIAL_CLOSE: case XMLLexer.SPECIAL_CLOSE:
case XMLLexer.EQUALS: case XMLLexer.EQUALS:
case XMLLexer.COLON: case XMLLexer.COLON:
case XMLLexer.XMLDeclOpen: case XMLLexer.XMLDeclOpen:
colors.addIfNeeded(line, column, get(OPERATOR)); colors.addIfNeeded(line, column, get(OPERATOR));
break; break;
case XMLLexer.SLASH_CLOSE: case XMLLexer.SLASH_CLOSE:
colors.addIfNeeded(line, column, get(OPERATOR)); colors.addIfNeeded(line, column, get(OPERATOR));
final var closeBlock = stack.pop(); final var closeBlock = stack.pop();
closeBlock.endLine = line; closeBlock.endLine = line;
closeBlock.endColumn = column; closeBlock.endColumn = column;
styles.addCodeBlock(closeBlock); styles.addCodeBlock(closeBlock);
break; break;
case XMLLexer.STRING: case XMLLexer.STRING:
colors.addIfNeeded(line, column, LITERAL); colors.addIfNeeded(line, column, LITERAL);
break; break;
case XMLLexer.Name: case XMLLexer.Name:
var type = SchemeAndroidIDE.TEXT_NORMAL; var type = SchemeAndroidIDE.TEXT_NORMAL;
if (previous == XMLLexer.OPEN) { if (previous == XMLLexer.OPEN) {
type = SchemeAndroidIDE.XML_TAG; type = SchemeAndroidIDE.XML_TAG;
final var block = styles.obtainNewBlock(); final var block = styles.obtainNewBlock();
block.startLine = line; block.startLine = line;
block.startColumn = column; block.startColumn = column;
stack.push(block); stack.push(block);
} }
if (previous == XMLLexer.OPEN_SLASH) { if (previous == XMLLexer.OPEN_SLASH) {
type = SchemeAndroidIDE.XML_TAG; type = SchemeAndroidIDE.XML_TAG;
final var block = stack.pop(); final var block = stack.pop();
block.endLine = line; block.endLine = line;
block.endColumn = column; block.endColumn = column;
styles.addCodeBlock(block); styles.addCodeBlock(block);
} }
colors.addIfNeeded(line, column, get(type)); colors.addIfNeeded(line, column, get(type));
break; break;
case XMLLexer.TEXT: case XMLLexer.TEXT:
default: default:
colors.addIfNeeded(line, column, get(SchemeAndroidIDE.TEXT_NORMAL)); colors.addIfNeeded(line, column, get(SchemeAndroidIDE.TEXT_NORMAL));
break; break;
} }
first = false; first = false;
if (token.getType() != XMLLexer.SEA_WS) { if (token.getType() != XMLLexer.SEA_WS) {
previous = token.getType(); previous = token.getType();
} }
} }
colors.determine(lastLine); colors.determine(lastLine);
styles.spans = colors.build(); styles.spans = colors.build();
return styles; return styles;
} }
} }
@@ -1,147 +1,147 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.language.xml; package com.itsaky.androidide.language.xml;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.language.CommonCompletionProvider; import com.itsaky.androidide.language.CommonCompletionProvider;
import com.itsaky.androidide.language.IDELanguage; import com.itsaky.androidide.language.IDELanguage;
import com.itsaky.androidide.lexers.xml.XMLLexer; import com.itsaky.androidide.lexers.xml.XMLLexer;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import com.itsaky.androidide.utils.JavaCharacter; import com.itsaky.androidide.utils.JavaCharacter;
import com.itsaky.androidide.views.editor.IDEEditor; import com.itsaky.androidide.views.editor.IDEEditor;
import com.itsaky.lsp.api.ILanguageServer; import com.itsaky.lsp.api.ILanguageServer;
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import java.io.StringReader; import java.io.StringReader;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
import io.github.rosemoe.sora.lang.completion.CompletionPublisher; import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler; import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler;
import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.CharPosition;
import io.github.rosemoe.sora.text.ContentReference; import io.github.rosemoe.sora.text.ContentReference;
import io.github.rosemoe.sora.widget.SymbolPairMatch; import io.github.rosemoe.sora.widget.SymbolPairMatch;
public class XMLLanguage extends IDELanguage { public class XMLLanguage extends IDELanguage {
private static final ILogger LOG = ILogger.newInstance("XMLLanguage"); private static final ILogger LOG = ILogger.newInstance("XMLLanguage");
private final CommonCompletionProvider completer; private final CommonCompletionProvider completer;
private final NewlineHandler[] newlineHandlers; private final NewlineHandler[] newlineHandlers;
private XMLAnalyzer analyzer; private XMLAnalyzer analyzer;
public XMLLanguage() { public XMLLanguage() {
this.completer = new CommonCompletionProvider(getLanguageServer()); this.completer = new CommonCompletionProvider(getLanguageServer());
this.analyzer = new XMLAnalyzer(); this.analyzer = new XMLAnalyzer();
this.newlineHandlers = new NewlineHandler[0]; this.newlineHandlers = new NewlineHandler[0];
} }
public boolean isAutoCompleteChar(char ch) { public boolean isAutoCompleteChar(char ch) {
return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/'; return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/';
} }
@Override @Override
public int getIndentAdvance(@NonNull String content) { public int getIndentAdvance(@NonNull String content) {
try { try {
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(content))); XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(content)));
Token token; Token token;
int advance = 0; int advance = 0;
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
switch (token.getType()) { switch (token.getType()) {
case XMLLexer.OPEN: case XMLLexer.OPEN:
advance++; advance++;
break; break;
case XMLLexer.CLOSE: case XMLLexer.CLOSE:
case XMLLexer.SLASH_CLOSE: case XMLLexer.SLASH_CLOSE:
advance--; advance--;
break; break;
default: default:
break; break;
} }
} }
advance = Math.max(0, advance); advance = Math.max(0, advance);
return advance * getTabSize(); return advance * getTabSize();
} catch (Throwable e) { } catch (Throwable e) {
LOG.error("Failed to compute indent advance", e); LOG.error("Failed to compute indent advance", e);
} }
return 0; return 0;
} }
@Override @Override
public SymbolPairMatch getSymbolPairs() { public SymbolPairMatch getSymbolPairs() {
return new SymbolPairMatch.DefaultSymbolPairs(); return new SymbolPairMatch.DefaultSymbolPairs();
} }
@NonNull @NonNull
@Override @Override
public AnalyzeManager getAnalyzeManager() { public AnalyzeManager getAnalyzeManager() {
return analyzer; return analyzer;
} }
@Override @Override
public int getInterruptionLevel() { public int getInterruptionLevel() {
return INTERRUPTION_LEVEL_STRONG; return INTERRUPTION_LEVEL_STRONG;
} }
@Override @Override
public void requireAutoComplete( public void requireAutoComplete(
@NonNull ContentReference content, @NonNull ContentReference content,
@NonNull CharPosition position, @NonNull CharPosition position,
@NonNull CompletionPublisher publisher, @NonNull CompletionPublisher publisher,
@NonNull Bundle extraArguments) @NonNull Bundle extraArguments)
throws CompletionCancelledException { throws CompletionCancelledException {
if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) { if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) {
return; return;
} }
final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE)); final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE));
publisher.setUpdateThreshold(0); publisher.setUpdateThreshold(0);
publisher.addItems( publisher.addItems(
new ArrayList<>( new ArrayList<>(
completer.complete( completer.complete(
content, file, position, CommonCompletionProvider::checkXMLCompletionChar))); content, file, position, CommonCompletionProvider::checkXMLCompletionChar)));
} }
@Override @Override
public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { public int getIndentAdvance(@NonNull ContentReference content, int line, int column) {
final var text = content.getLine(line).substring(0, column); final var text = content.getLine(line).substring(0, column);
return getIndentAdvance(text.trim()); return getIndentAdvance(text.trim());
} }
@Override @Override
protected ILanguageServer getLanguageServer() { protected ILanguageServer getLanguageServer() {
return StudioApp.getInstance().getXMLLanguageServer(); return StudioApp.getInstance().getXMLLanguageServer();
} }
@Override @Override
public NewlineHandler[] getNewlineHandlers() { public NewlineHandler[] getNewlineHandlers() {
return newlineHandlers; return newlineHandlers;
} }
@Override @Override
public void destroy() { public void destroy() {
analyzer = null; analyzer = null;
} }
} }
@@ -1,407 +1,407 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* *
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.lsp; package com.itsaky.androidide.lsp;
import android.view.View; import android.view.View;
import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.ThreadUtils; import com.blankj.utilcode.util.ThreadUtils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.itsaky.androidide.EditorActivity; import com.itsaky.androidide.EditorActivity;
import com.itsaky.androidide.R; import com.itsaky.androidide.R;
import com.itsaky.androidide.adapters.DiagnosticsAdapter; import com.itsaky.androidide.adapters.DiagnosticsAdapter;
import com.itsaky.androidide.adapters.SearchListAdapter; import com.itsaky.androidide.adapters.SearchListAdapter;
import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.app.StudioApp;
import com.itsaky.androidide.fragments.sheets.ProgressSheet; import com.itsaky.androidide.fragments.sheets.ProgressSheet;
import com.itsaky.androidide.interfaces.EditorActivityProvider; import com.itsaky.androidide.interfaces.EditorActivityProvider;
import com.itsaky.androidide.language.IDELanguage; import com.itsaky.androidide.language.IDELanguage;
import com.itsaky.androidide.models.DiagnosticGroup; import com.itsaky.androidide.models.DiagnosticGroup;
import com.itsaky.androidide.models.SearchResult; import com.itsaky.androidide.models.SearchResult;
import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.TaskExecutor;
import com.itsaky.androidide.utils.DialogUtils; import com.itsaky.androidide.utils.DialogUtils;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import com.itsaky.androidide.utils.LSPUtils; import com.itsaky.androidide.utils.LSPUtils;
import com.itsaky.androidide.views.editor.CodeEditorView; import com.itsaky.androidide.views.editor.CodeEditorView;
import com.itsaky.androidide.views.editor.IDEEditor; import com.itsaky.androidide.views.editor.IDEEditor;
import com.itsaky.lsp.api.ILanguageClient; import com.itsaky.lsp.api.ILanguageClient;
import com.itsaky.lsp.models.CodeActionItem; import com.itsaky.lsp.models.CodeActionItem;
import com.itsaky.lsp.models.Command; import com.itsaky.lsp.models.Command;
import com.itsaky.lsp.models.DiagnosticItem; import com.itsaky.lsp.models.DiagnosticItem;
import com.itsaky.lsp.models.DiagnosticResult; import com.itsaky.lsp.models.DiagnosticResult;
import com.itsaky.lsp.models.Location; import com.itsaky.lsp.models.Location;
import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.Range;
import com.itsaky.lsp.models.TextEdit; import com.itsaky.lsp.models.TextEdit;
import com.itsaky.toaster.Toaster; import com.itsaky.toaster.Toaster;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import io.github.rosemoe.sora.text.Content; import io.github.rosemoe.sora.text.Content;
/** AndroidIDE specific implementation of the LanguageClient */ /** AndroidIDE specific implementation of the LanguageClient */
public class IDELanguageClientImpl implements ILanguageClient { public class IDELanguageClientImpl implements ILanguageClient {
protected static final Gson gson = new Gson(); protected static final Gson gson = new Gson();
protected static final ILogger LOG = ILogger.newInstance("AbstractLanguageClient"); protected static final ILogger LOG = ILogger.newInstance("AbstractLanguageClient");
private static IDELanguageClientImpl mInstance; private static IDELanguageClientImpl mInstance;
private final Map<File, List<DiagnosticItem>> diagnostics = new HashMap<>(); private final Map<File, List<DiagnosticItem>> diagnostics = new HashMap<>();
protected EditorActivityProvider activityProvider; protected EditorActivityProvider activityProvider;
private IDELanguageClientImpl(EditorActivityProvider provider) { private IDELanguageClientImpl(EditorActivityProvider provider) {
setActivityProvider(provider); setActivityProvider(provider);
} }
public static IDELanguageClientImpl getInstance() { public static IDELanguageClientImpl getInstance() {
if (mInstance == null) { if (mInstance == null) {
throw new IllegalStateException("Client not initialized"); throw new IllegalStateException("Client not initialized");
} }
return mInstance; return mInstance;
} }
public static IDELanguageClientImpl initialize(EditorActivityProvider provider) { public static IDELanguageClientImpl initialize(EditorActivityProvider provider) {
if (mInstance != null) { if (mInstance != null) {
throw new IllegalStateException("Client is already initialized"); throw new IllegalStateException("Client is already initialized");
} }
mInstance = new IDELanguageClientImpl(provider); mInstance = new IDELanguageClientImpl(provider);
return getInstance(); return getInstance();
} }
public static void shutdown() { public static void shutdown() {
mInstance = null; mInstance = null;
} }
public static boolean isInitialized() { public static boolean isInitialized() {
return mInstance != null; return mInstance != null;
} }
public void setActivityProvider(EditorActivityProvider provider) { public void setActivityProvider(EditorActivityProvider provider) {
this.activityProvider = provider; this.activityProvider = provider;
} }
protected EditorActivity activity() { protected EditorActivity activity() {
if (activityProvider == null) return null; if (activityProvider == null) return null;
return activityProvider.provide(); return activityProvider.provide();
} }
private CodeEditorView findEditorByFile(File file) { private CodeEditorView findEditorByFile(File file) {
return activity().getEditorForFile(file); return activity().getEditorForFile(file);
} }
public void hideDiagnostics() { public void hideDiagnostics() {
if (activity() == null || activity().getDiagnosticBinding() == null) { if (activity() == null || activity().getDiagnosticBinding() == null) {
return; return;
} }
activity().getDiagnosticBinding().getRoot().setVisibility(View.GONE); activity().getDiagnosticBinding().getRoot().setVisibility(View.GONE);
} }
private void hideBottomDiagnosticView(final File file) { private void hideBottomDiagnosticView(final File file) {
if (activity() == null || file == null) { if (activity() == null || file == null) {
return; return;
} }
final var frag = findEditorByFile(file); final var frag = findEditorByFile(file);
if (frag == null || frag.getBinding() == null) { if (frag == null || frag.getBinding() == null) {
return; return;
} }
frag.getBinding().diagnosticTextContainer.setVisibility(View.GONE); frag.getBinding().diagnosticTextContainer.setVisibility(View.GONE);
frag.getBinding().diagnosticText.setVisibility(View.GONE); frag.getBinding().diagnosticText.setVisibility(View.GONE);
frag.getBinding().diagnosticText.setClickable(false); frag.getBinding().diagnosticText.setClickable(false);
} }
@Override @Override
public void publishDiagnostics(DiagnosticResult result) { public void publishDiagnostics(DiagnosticResult result) {
boolean error = result == null; boolean error = result == null;
activity().handleDiagnosticsResultVisibility(error || result.getDiagnostics().isEmpty()); activity().handleDiagnosticsResultVisibility(error || result.getDiagnostics().isEmpty());
if (error) { if (error) {
return; return;
} }
File file = result.getFile().toFile(); File file = result.getFile().toFile();
if (!file.exists() || !file.isFile()) return; if (!file.exists() || !file.isFile()) return;
final var editorView = activity().getEditorForFile(file); final var editorView = activity().getEditorForFile(file);
if (editorView != null) { if (editorView != null) {
final var editor = editorView.getEditor(); final var editor = editorView.getEditor();
if (editor != null) { if (editor != null) {
final var editorLanguage = editor.getEditorLanguage(); final var editorLanguage = editor.getEditorLanguage();
if (editorLanguage instanceof IDELanguage) { if (editorLanguage instanceof IDELanguage) {
((IDELanguage) editorLanguage).setDiagnostics(result.getDiagnostics()); ((IDELanguage) editorLanguage).setDiagnostics(result.getDiagnostics());
} }
} }
} }
diagnostics.put(file, result.getDiagnostics()); diagnostics.put(file, result.getDiagnostics());
activity().setDiagnosticsAdapter(newDiagnosticsAdapter()); activity().setDiagnosticsAdapter(newDiagnosticsAdapter());
} }
/** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */ /** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */
public void showLocations(List<Location> locations) { public void showLocations(List<Location> locations) {
// Cannot show anything if the activity() is null // Cannot show anything if the activity() is null
if (activity() == null) { if (activity() == null) {
return; return;
} }
boolean error = locations == null || locations.isEmpty(); boolean error = locations == null || locations.isEmpty();
activity().handleSearchResultVisibility(error); activity().handleSearchResultVisibility(error);
if (error) { if (error) {
activity().setSearchResultAdapter(new SearchListAdapter(null, null, null)); activity().setSearchResultAdapter(new SearchListAdapter(null, null, null));
return; return;
} }
final Map<File, List<SearchResult>> results = new HashMap<>(); final Map<File, List<SearchResult>> results = new HashMap<>();
for (int i = 0; i < locations.size(); i++) { for (int i = 0; i < locations.size(); i++) {
try { try {
final Location loc = locations.get(i); final Location loc = locations.get(i);
if (loc == null) { if (loc == null) {
continue; continue;
} }
final File file = loc.getFile().toFile(); final File file = loc.getFile().toFile();
if (!file.exists() || !file.isFile()) continue; if (!file.exists() || !file.isFile()) continue;
var frag = findEditorByFile(file); var frag = findEditorByFile(file);
Content content; Content content;
if (frag != null && frag.getEditor() != null) content = frag.getEditor().getText(); if (frag != null && frag.getEditor() != null) content = frag.getEditor().getText();
else content = new Content(FileIOUtils.readFile2String(file)); else content = new Content(FileIOUtils.readFile2String(file));
final List<SearchResult> matches = final List<SearchResult> matches =
results.containsKey(file) ? results.get(file) : new ArrayList<>(); results.containsKey(file) ? results.get(file) : new ArrayList<>();
Objects.requireNonNull(matches) Objects.requireNonNull(matches)
.add( .add(
new SearchResult( new SearchResult(
loc.getRange(), loc.getRange(),
file, file,
content.getLineString(loc.getRange().getStart().getLine()), content.getLineString(loc.getRange().getStart().getLine()),
content content
.subContent( .subContent(
loc.getRange().getStart().getLine(), loc.getRange().getStart().getLine(),
loc.getRange().getStart().getColumn(), loc.getRange().getStart().getColumn(),
loc.getRange().getEnd().getLine(), loc.getRange().getEnd().getLine(),
loc.getRange().getEnd().getColumn()) loc.getRange().getEnd().getColumn())
.toString())); .toString()));
results.put(file, matches); results.put(file, matches);
} catch (Throwable th) { } catch (Throwable th) {
LOG.error("Failed to show file location", th); LOG.error("Failed to show file location", th);
} }
} }
activity().handleSearchResults(results); activity().handleSearchResults(results);
} }
@Override @Override
public void performCodeAction(File file, CodeActionItem actionItem) { public void performCodeAction(File file, CodeActionItem actionItem) {
final var editor = activity().getEditorForFile(file); final var editor = activity().getEditorForFile(file);
if (editor != null) { if (editor != null) {
performCodeAction(editor.getEditor(), actionItem); performCodeAction(editor.getEditor(), actionItem);
} }
} }
/** /**
* Perform the given {@link CodeActionItem} * Perform the given {@link CodeActionItem}
* *
* @param editor The {@link IDEEditor} that invoked the code action request. This is required to * @param editor The {@link IDEEditor} that invoked the code action request. This is required to
* reduce the time finding the code action from the edits. * reduce the time finding the code action from the edits.
* @param action The action to perform * @param action The action to perform
*/ */
public void performCodeAction(IDEEditor editor, CodeActionItem action) { public void performCodeAction(IDEEditor editor, CodeActionItem action) {
if (activity() == null || editor == null || action == null) { if (activity() == null || editor == null || action == null) {
LOG.error( LOG.error(
"Unable to perform code action", "Unable to perform code action",
"activity=" + activity(), "activity=" + activity(),
"editor=" + editor, "editor=" + editor,
"action=" + action); "action=" + action);
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
return; return;
} }
final ProgressSheet progress = new ProgressSheet(); final ProgressSheet progress = new ProgressSheet();
progress.setSubMessageEnabled(false); progress.setSubMessageEnabled(false);
progress.setWelcomeTextEnabled(false); progress.setWelcomeTextEnabled(false);
progress.setCancelable(false); progress.setCancelable(false);
progress.setMessage(activity().getString(R.string.msg_performing_actions)); progress.setMessage(activity().getString(R.string.msg_performing_actions));
progress.show(activity().getSupportFragmentManager(), "quick_fix_progress"); progress.show(activity().getSupportFragmentManager(), "quick_fix_progress");
new TaskExecutor() new TaskExecutor()
.executeAsyncProvideError( .executeAsyncProvideError(
() -> performCodeActionAsync(editor, action), () -> performCodeActionAsync(editor, action),
(result, throwable) -> { (result, throwable) -> {
ThreadUtils.runOnUiThread(progress::dismiss); ThreadUtils.runOnUiThread(progress::dismiss);
if (result == null || throwable != null || !result) { if (result == null || throwable != null || !result) {
LOG.error( LOG.error(
"Unable to perform code action", "result=" + result, "throwable=" + throwable); "Unable to perform code action", "result=" + result, "throwable=" + throwable);
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
} else { } else {
editor.executeCommand(action.getCommand()); editor.executeCommand(action.getCommand());
} }
}); });
} }
private List<DiagnosticGroup> mapAsGroup(Map<File, List<DiagnosticItem>> diags) { private List<DiagnosticGroup> mapAsGroup(Map<File, List<DiagnosticItem>> diags) {
List<DiagnosticGroup> groups = new ArrayList<>(); List<DiagnosticGroup> groups = new ArrayList<>();
if (diags == null || diags.size() <= 0) return groups; if (diags == null || diags.size() <= 0) return groups;
for (File file : diags.keySet()) { for (File file : diags.keySet()) {
var fileDiags = diags.get(file); var fileDiags = diags.get(file);
if (fileDiags == null || fileDiags.size() <= 0) continue; if (fileDiags == null || fileDiags.size() <= 0) continue;
DiagnosticGroup group = new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags); DiagnosticGroup group = new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags);
groups.add(group); groups.add(group);
} }
return groups; return groups;
} }
private void showAvailableQuickfixes(IDEEditor editor, List<CodeActionItem> actions) { private void showAvailableQuickfixes(IDEEditor editor, List<CodeActionItem> actions) {
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
builder.setTitle(R.string.msg_code_actions); builder.setTitle(R.string.msg_code_actions);
builder.setItems( builder.setItems(
asArray(actions), asArray(actions),
(d, w) -> { (d, w) -> {
d.dismiss(); d.dismiss();
hideDiagnostics(); hideDiagnostics();
hideBottomDiagnosticView(editor.getFile()); hideBottomDiagnosticView(editor.getFile());
performCodeAction(editor, actions.get(w)); performCodeAction(editor, actions.get(w));
}); });
builder.show(); builder.show();
} }
private CharSequence[] asArray(List<CodeActionItem> actions) { private CharSequence[] asArray(List<CodeActionItem> actions) {
final String[] arr = new String[actions.size()]; final String[] arr = new String[actions.size()];
for (int i = 0; i < actions.size(); i++) { for (int i = 0; i < actions.size(); i++) {
arr[i] = actions.get(i).getTitle(); arr[i] = actions.get(i).getTitle();
} }
return arr; return arr;
} }
private Boolean performCodeActionAsync(final IDEEditor editor, final CodeActionItem action) { private Boolean performCodeActionAsync(final IDEEditor editor, final CodeActionItem action) {
LOG.debug("Performing code action:", action); LOG.debug("Performing code action:", action);
final var changes = action.getChanges(); final var changes = action.getChanges();
if (changes.isEmpty()) { if (changes.isEmpty()) {
return Boolean.FALSE; return Boolean.FALSE;
} }
for (var change : changes) { for (var change : changes) {
final var path = change.getFile(); final var path = change.getFile();
if (path == null) { if (path == null) {
continue; continue;
} }
final File file = path.toFile(); final File file = path.toFile();
if (!file.exists()) { if (!file.exists()) {
continue; continue;
} }
for (TextEdit edit : change.getEdits()) { for (TextEdit edit : change.getEdits()) {
final String editorFilepath = final String editorFilepath =
editor.getFile() == null ? "" : editor.getFile().getAbsolutePath(); editor.getFile() == null ? "" : editor.getFile().getAbsolutePath();
if (file.getAbsolutePath().equals(editorFilepath)) { if (file.getAbsolutePath().equals(editorFilepath)) {
// Edit is in the same editor which requested the code action // Edit is in the same editor which requested the code action
editInEditor(editor, edit); editInEditor(editor, edit);
} else { } else {
var openedFrag = findEditorByFile(file); var openedFrag = findEditorByFile(file);
if (openedFrag != null && openedFrag.getEditor() != null) { if (openedFrag != null && openedFrag.getEditor() != null) {
// Edit is in another 'opened' file // Edit is in another 'opened' file
editInEditor(openedFrag.getEditor(), edit); editInEditor(openedFrag.getEditor(), edit);
} else { } else {
// Edit is in some other file which is not opened // Edit is in some other file which is not opened
// We should open that file and perform the edit // We should open that file and perform the edit
openedFrag = activity().openFile(file); openedFrag = activity().openFile(file);
if (openedFrag != null && openedFrag.getEditor() != null) { if (openedFrag != null && openedFrag.getEditor() != null) {
editInEditor(openedFrag.getEditor(), edit); editInEditor(openedFrag.getEditor(), edit);
} }
} }
} }
} }
} }
return Boolean.TRUE; return Boolean.TRUE;
} }
private void execCommand(IDEEditor editor, Command command) { private void execCommand(IDEEditor editor, Command command) {
editor.executeCommand(command); editor.executeCommand(command);
} }
private void editInEditor(final IDEEditor editor, final TextEdit edit) { private void editInEditor(final IDEEditor editor, final TextEdit edit) {
final Range range = edit.getRange(); final Range range = edit.getRange();
final int startLine = range.getStart().getLine(); final int startLine = range.getStart().getLine();
final int startCol = range.getStart().getColumn(); final int startCol = range.getStart().getColumn();
final int endLine = range.getEnd().getLine(); final int endLine = range.getEnd().getLine();
final int endCol = range.getEnd().getColumn(); final int endCol = range.getEnd().getColumn();
activity() activity()
.runOnUiThread( .runOnUiThread(
() -> { () -> {
if (startLine == endLine && startCol == endCol) { if (startLine == endLine && startCol == endCol) {
editor.getText().insert(startLine, startCol, edit.getNewText()); editor.getText().insert(startLine, startCol, edit.getNewText());
} else { } else {
editor.getText().replace(startLine, startCol, endLine, endCol, edit.getNewText()); editor.getText().replace(startLine, startCol, endLine, endCol, edit.getNewText());
} }
}); });
} }
public DiagnosticsAdapter newDiagnosticsAdapter() { public DiagnosticsAdapter newDiagnosticsAdapter() {
return new DiagnosticsAdapter(mapAsGroup(this.diagnostics), activity()); return new DiagnosticsAdapter(mapAsGroup(this.diagnostics), activity());
} }
@Override @Override
public com.itsaky.lsp.models.ShowDocumentResult showDocument( public com.itsaky.lsp.models.ShowDocumentResult showDocument(
com.itsaky.lsp.models.ShowDocumentParams params) { com.itsaky.lsp.models.ShowDocumentParams params) {
boolean success = false; boolean success = false;
final var result = new com.itsaky.lsp.models.ShowDocumentResult(false); final var result = new com.itsaky.lsp.models.ShowDocumentResult(false);
if (activity() == null) { if (activity() == null) {
return result; return result;
} }
if (params != null) { if (params != null) {
File file = params.getFile().toFile(); File file = params.getFile().toFile();
if (file.exists() && file.isFile() && FileUtils.isUtf8(file)) { if (file.exists() && file.isFile() && FileUtils.isUtf8(file)) {
final var range = params.getSelection(); final var range = params.getSelection();
var frag = var frag =
activity().getEditorAtIndex(activity().getBinding().tabs.getSelectedTabPosition()); activity().getEditorAtIndex(activity().getBinding().tabs.getSelectedTabPosition());
if (frag != null if (frag != null
&& frag.getFile() != null && frag.getFile() != null
&& frag.getEditor() != null && frag.getEditor() != null
&& frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) { && frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) {
if (LSPUtils.isEqual(range.getStart(), range.getEnd())) { if (LSPUtils.isEqual(range.getStart(), range.getEnd())) {
frag.getEditor().setSelection(range.getStart().getLine(), range.getStart().getColumn()); frag.getEditor().setSelection(range.getStart().getLine(), range.getStart().getColumn());
} else { } else {
frag.getEditor().setSelection(range); frag.getEditor().setSelection(range);
} }
} else { } else {
activity().openFileAndSelect(file, range); activity().openFileAndSelect(file, range);
} }
success = true; success = true;
} }
} }
result.setSuccess(success); result.setSuccess(success);
return result; return result;
} }
} }
@@ -1,335 +1,335 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.models; package com.itsaky.androidide.models;
import static com.itsaky.androidide.utils.ILogger.newInstance; import static com.itsaky.androidide.utils.ILogger.newInstance;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import com.itsaky.androidide.utils.JSONUtility; import com.itsaky.androidide.utils.JSONUtility;
import com.itsaky.androidide.utils.ListingFileRedirect; import com.itsaky.androidide.utils.ListingFileRedirect;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
import java.util.List; import java.util.List;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class ApkMetadata { public class ApkMetadata {
private static final ILogger LOG = newInstance("ApkMetadata"); private static final ILogger LOG = newInstance("ApkMetadata");
@SerializedName("version") @SerializedName("version")
private int version; private int version;
@SerializedName("artifactType") @SerializedName("artifactType")
private ArtifactType artifactType; private ArtifactType artifactType;
@SerializedName("applicationId") @SerializedName("applicationId")
private String applicationId; private String applicationId;
@SerializedName("variantName") @SerializedName("variantName")
private String variantName; private String variantName;
@SerializedName("elements") @SerializedName("elements")
private List<Element> elements; private List<Element> elements;
@SerializedName("elementType") @SerializedName("elementType")
private String elementType; private String elementType;
@Nullable @Nullable
public static File findApkFile(@NonNull File listingFIle) { public static File findApkFile(@NonNull File listingFIle) {
try { try {
// This sometimes might be a redirect to the listing file // This sometimes might be a redirect to the listing file
// So we have to handle this case too. // So we have to handle this case too.
final var redirectedFile = ListingFileRedirect.maybeExtractRedirectedFile(listingFIle); final var redirectedFile = ListingFileRedirect.maybeExtractRedirectedFile(listingFIle);
if (redirectedFile != null) { if (redirectedFile != null) {
listingFIle = redirectedFile; listingFIle = redirectedFile;
} }
final var dir = listingFIle.getParentFile(); final var dir = listingFIle.getParentFile();
final var metadata = final var metadata =
JSONUtility.gson.fromJson(new FileReader(listingFIle), ApkMetadata.class); JSONUtility.gson.fromJson(new FileReader(listingFIle), ApkMetadata.class);
if (!isValid(metadata)) { if (!isValid(metadata)) {
LOG.warn("Invalid APK metadata:", metadata); LOG.warn("Invalid APK metadata:", metadata);
return null; return null;
} }
for (Element element : metadata.getElements()) { for (Element element : metadata.getElements()) {
if (element == null || element.getOutputFile() == null) { if (element == null || element.getOutputFile() == null) {
LOG.warn("No output file specified in APK metadata element:", element); LOG.warn("No output file specified in APK metadata element:", element);
continue; continue;
} }
if (element.getOutputFile().endsWith(".apk")) { if (element.getOutputFile().endsWith(".apk")) {
final File apk = new File(dir, element.getOutputFile()); final File apk = new File(dir, element.getOutputFile());
if (apk.exists() && apk.isFile()) { if (apk.exists() && apk.isFile()) {
LOG.info("Found apk in metadata:", apk); LOG.info("Found apk in metadata:", apk);
return apk; return apk;
} }
} }
} }
return null; return null;
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
LOG.error("Metadata file not found...", e); LOG.error("Metadata file not found...", e);
return null; return null;
} }
} }
private static boolean isValid(ApkMetadata metadata) { private static boolean isValid(ApkMetadata metadata) {
// Null checks // Null checks
if (metadata == null if (metadata == null
|| metadata.getArtifactType() == null || metadata.getArtifactType() == null
|| metadata.getArtifactType().getType() == null || metadata.getArtifactType().getType() == null
|| metadata.getElements() == null) { || metadata.getElements() == null) {
LOG.warn("APK metadata null check failed. Metadata:", metadata); LOG.warn("APK metadata null check failed. Metadata:", metadata);
return false; return false;
} }
final ArtifactType type = metadata.getArtifactType(); final ArtifactType type = metadata.getArtifactType();
final List<Element> elements = metadata.getElements(); final List<Element> elements = metadata.getElements();
if (!type.getType().equals(ArtifactType.TYPE_APK)) { if (!type.getType().equals(ArtifactType.TYPE_APK)) {
LOG.warn("Artifact is not of type APK. Metadata:", metadata); LOG.warn("Artifact is not of type APK. Metadata:", metadata);
return false; return false;
} }
if (elements.isEmpty()) { if (elements.isEmpty()) {
LOG.warn("No output elements found for metadata:", metadata); LOG.warn("No output elements found for metadata:", metadata);
return false; return false;
} }
boolean atLeastOneApk = false; boolean atLeastOneApk = false;
for (Element element : elements) { for (Element element : elements) {
if (element == null if (element == null
|| element.getOutputFile() == null || element.getOutputFile() == null
|| !element.getOutputFile().endsWith(".apk")) { || !element.getOutputFile().endsWith(".apk")) {
LOG.warn("Skipping output element because file is not APK:", element); LOG.warn("Skipping output element because file is not APK:", element);
continue; continue;
} }
if (element.getOutputFile().endsWith(".apk")) { if (element.getOutputFile().endsWith(".apk")) {
atLeastOneApk = true; atLeastOneApk = true;
break; break;
} }
} }
LOG.debug("Output metadata validation succeeded"); LOG.debug("Output metadata validation succeeded");
return atLeastOneApk; return atLeastOneApk;
} }
public List<Element> getElements() { public List<Element> getElements() {
return elements; return elements;
} }
public ArtifactType getArtifactType() { public ArtifactType getArtifactType() {
return artifactType; return artifactType;
} }
public void setArtifactType(ArtifactType artifactType) { public void setArtifactType(ArtifactType artifactType) {
this.artifactType = artifactType; this.artifactType = artifactType;
} }
public void setElements(List<Element> elements) { public void setElements(List<Element> elements) {
this.elements = elements; this.elements = elements;
} }
public int getVersion() { public int getVersion() {
return version; return version;
} }
public void setVersion(int version) { public void setVersion(int version) {
this.version = version; this.version = version;
} }
public String getApplicationId() { public String getApplicationId() {
return applicationId; return applicationId;
} }
public void setApplicationId(String applicationId) { public void setApplicationId(String applicationId) {
this.applicationId = applicationId; this.applicationId = applicationId;
} }
public String getVariantName() { public String getVariantName() {
return variantName; return variantName;
} }
public void setVariantName(String variantName) { public void setVariantName(String variantName) {
this.variantName = variantName; this.variantName = variantName;
} }
public String getElementType() { public String getElementType() {
return elementType; return elementType;
} }
public void setElementType(String elementType) { public void setElementType(String elementType) {
this.elementType = elementType; this.elementType = elementType;
} }
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "ApkMetadata{" return "ApkMetadata{"
+ "version=" + "version="
+ version + version
+ ", artifactType=" + ", artifactType="
+ artifactType + artifactType
+ ", applicationId='" + ", applicationId='"
+ applicationId + applicationId
+ '\'' + '\''
+ ", variantName='" + ", variantName='"
+ variantName + variantName
+ '\'' + '\''
+ ", elements=" + ", elements="
+ elements + elements
+ ", elementType='" + ", elementType='"
+ elementType + elementType
+ '\'' + '\''
+ '}'; + '}';
} }
public static class ArtifactType { public static class ArtifactType {
public static String TYPE_APK = "APK"; public static String TYPE_APK = "APK";
@SerializedName("type") @SerializedName("type")
private String type; private String type;
@SerializedName("kind") @SerializedName("kind")
private String kind; private String kind;
public String getType() { public String getType() {
return type; return type;
} }
public void setType(String type) { public void setType(String type) {
this.type = type; this.type = type;
} }
public String getKind() { public String getKind() {
return kind; return kind;
} }
public void setKind(String kind) { public void setKind(String kind) {
this.kind = kind; this.kind = kind;
} }
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "ArtifactType{" + "type='" + type + '\'' + ", kind='" + kind + '\'' + '}'; return "ArtifactType{" + "type='" + type + '\'' + ", kind='" + kind + '\'' + '}';
} }
} }
public static class Element { public static class Element {
@SerializedName("type") @SerializedName("type")
private String type; private String type;
@SerializedName("filters") @SerializedName("filters")
private List<Object> filters; private List<Object> filters;
@SerializedName("attributes") @SerializedName("attributes")
private List<Object> attributes; private List<Object> attributes;
@SerializedName("versionCode") @SerializedName("versionCode")
private int versionCode; private int versionCode;
@SerializedName("versionName") @SerializedName("versionName")
private String versionName; private String versionName;
@SerializedName("outputFile") @SerializedName("outputFile")
private String outputFile; private String outputFile;
public String getType() { public String getType() {
return type; return type;
} }
public void setType(String type) { public void setType(String type) {
this.type = type; this.type = type;
} }
public List<Object> getFilters() { public List<Object> getFilters() {
return filters; return filters;
} }
public void setFilters(List<Object> filters) { public void setFilters(List<Object> filters) {
this.filters = filters; this.filters = filters;
} }
public List<Object> getAttributes() { public List<Object> getAttributes() {
return attributes; return attributes;
} }
public void setAttributes(List<Object> attributes) { public void setAttributes(List<Object> attributes) {
this.attributes = attributes; this.attributes = attributes;
} }
public int getVersionCode() { public int getVersionCode() {
return versionCode; return versionCode;
} }
public void setVersionCode(int versionCode) { public void setVersionCode(int versionCode) {
this.versionCode = versionCode; this.versionCode = versionCode;
} }
public String getVersionName() { public String getVersionName() {
return versionName; return versionName;
} }
public void setVersionName(String versionName) { public void setVersionName(String versionName) {
this.versionName = versionName; this.versionName = versionName;
} }
public String getOutputFile() { public String getOutputFile() {
return outputFile; return outputFile;
} }
public void setOutputFile(String outputFile) { public void setOutputFile(String outputFile) {
this.outputFile = outputFile; this.outputFile = outputFile;
} }
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "Element{" return "Element{"
+ "type='" + "type='"
+ type + type
+ '\'' + '\''
+ ", filters=" + ", filters="
+ filters + filters
+ ", attributes=" + ", attributes="
+ attributes + attributes
+ ", versionCode=" + ", versionCode="
+ versionCode + versionCode
+ ", versionName='" + ", versionName='"
+ versionName + versionName
+ '\'' + '\''
+ ", outputFile='" + ", outputFile='"
+ outputFile + outputFile
+ '\'' + '\''
+ '}'; + '}';
} }
} }
} }
@@ -1,155 +1,155 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.projects package com.itsaky.androidide.projects
import com.itsaky.androidide.tooling.api.IProject import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.IProject.Type import com.itsaky.androidide.tooling.api.IProject.Type
import com.itsaky.androidide.tooling.api.messages.VariantDataRequest import com.itsaky.androidide.tooling.api.messages.VariantDataRequest
import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData
import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeGradleProject import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.androidide.tooling.api.model.IdeGradleTask import com.itsaky.androidide.tooling.api.model.IdeGradleTask
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
import java.io.File import java.io.File
import java.util.concurrent.* import java.util.concurrent.*
/** /**
* A project which lazily caches some required properties of the given project. * A project which lazily caches some required properties of the given project.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
class CachingProject(val project: IProject) : IProject { class CachingProject(val project: IProject) : IProject {
private val log = ILogger.newInstance(javaClass.simpleName) private val log = ILogger.newInstance(javaClass.simpleName)
private val mName: String by lazy { this.project.name.get() } private val mName: String by lazy { this.project.name.get() }
private val mDescription: String by lazy { this.project.description.get() } private val mDescription: String by lazy { this.project.description.get() }
private val mProjectPath: String by lazy { this.project.projectPath.get() } private val mProjectPath: String by lazy { this.project.projectPath.get() }
private val mProjectDir: File by lazy { this.project.projectDir.get() } private val mProjectDir: File by lazy { this.project.projectDir.get() }
private val mBuildDir: File by lazy { this.project.buildDir.get() } private val mBuildDir: File by lazy { this.project.buildDir.get() }
private val mBuildScript: File by lazy { this.project.buildScript.get() } private val mBuildScript: File by lazy { this.project.buildScript.get() }
private val mProjectType: Type by lazy { this.project.type.get() } private val mProjectType: Type by lazy { this.project.type.get() }
private var mFirstAppModule: IdeAndroidModule? = null private var mFirstAppModule: IdeAndroidModule? = null
private val mCachedVariants: MutableMap<VariantDataRequest, SimpleVariantData> = mutableMapOf() private val mCachedVariants: MutableMap<VariantDataRequest, SimpleVariantData> = mutableMapOf()
private val mModules: MutableList<SimpleModuleData> = mutableListOf() private val mModules: MutableList<SimpleModuleData> = mutableListOf()
override fun isProjectInitialized(): CompletableFuture<Boolean> { override fun isProjectInitialized(): CompletableFuture<Boolean> {
return CompletableFuture.completedFuture(true) return CompletableFuture.completedFuture(true)
} }
override fun getName(): CompletableFuture<String> { override fun getName(): CompletableFuture<String> {
return CompletableFuture.completedFuture(this.mName) return CompletableFuture.completedFuture(this.mName)
} }
override fun getDescription(): CompletableFuture<String> { override fun getDescription(): CompletableFuture<String> {
return CompletableFuture.completedFuture(mDescription) return CompletableFuture.completedFuture(mDescription)
} }
override fun getProjectPath(): CompletableFuture<String> { override fun getProjectPath(): CompletableFuture<String> {
return CompletableFuture.completedFuture(mProjectPath) return CompletableFuture.completedFuture(mProjectPath)
} }
override fun getType(): CompletableFuture<Type> { override fun getType(): CompletableFuture<Type> {
return CompletableFuture.completedFuture(mProjectType) return CompletableFuture.completedFuture(mProjectType)
} }
override fun getProjectDir(): CompletableFuture<File> { override fun getProjectDir(): CompletableFuture<File> {
return CompletableFuture.completedFuture(mProjectDir) return CompletableFuture.completedFuture(mProjectDir)
} }
override fun getBuildDir(): CompletableFuture<File> { override fun getBuildDir(): CompletableFuture<File> {
return CompletableFuture.completedFuture(mBuildDir) return CompletableFuture.completedFuture(mBuildDir)
} }
override fun getBuildScript(): CompletableFuture<File> { override fun getBuildScript(): CompletableFuture<File> {
return CompletableFuture.completedFuture(mBuildScript) return CompletableFuture.completedFuture(mBuildScript)
} }
override fun getTasks(): CompletableFuture<MutableList<IdeGradleTask>> { override fun getTasks(): CompletableFuture<MutableList<IdeGradleTask>> {
return this.project.tasks return this.project.tasks
} }
override fun getModules(): CompletableFuture<MutableList<IdeGradleProject>> { override fun getModules(): CompletableFuture<MutableList<IdeGradleProject>> {
return this.project.modules return this.project.modules
} }
override fun listModules(): CompletableFuture<MutableList<SimpleModuleData>> { override fun listModules(): CompletableFuture<MutableList<SimpleModuleData>> {
return if (this.mModules.isNotEmpty()) { return if (this.mModules.isNotEmpty()) {
log.debug("Using cached module data...") log.debug("Using cached module data...")
CompletableFuture.completedFuture(mModules) CompletableFuture.completedFuture(mModules)
} else { } else {
return this.project.listModules().whenComplete { modules, err -> return this.project.listModules().whenComplete { modules, err ->
if (err != null || modules == null) { if (err != null || modules == null) {
log.debug("Unable to fetch module data from tooling server", err) log.debug("Unable to fetch module data from tooling server", err)
return@whenComplete return@whenComplete
} }
if (modules.isEmpty()) { if (modules.isEmpty()) {
log.debug("Empty module data returned by tooling server. Ignoring...") log.debug("Empty module data returned by tooling server. Ignoring...")
return@whenComplete return@whenComplete
} }
mModules.clear() mModules.clear()
mModules.addAll(modules) mModules.addAll(modules)
} }
} }
} }
override fun getVariantData(request: VariantDataRequest): CompletableFuture<SimpleVariantData> { override fun getVariantData(request: VariantDataRequest): CompletableFuture<SimpleVariantData> {
val cached = mCachedVariants[request] val cached = mCachedVariants[request]
if (cached != null) { if (cached != null) {
return CompletableFuture.completedFuture(cached) return CompletableFuture.completedFuture(cached)
} }
return this.project.getVariantData(request).whenComplete { t, u -> return this.project.getVariantData(request).whenComplete { t, u ->
if (t == null) { if (t == null) {
log.warn( log.warn(
"Unable to get variant data for " + "Unable to get variant data for " +
"variant ${request.variantName} ('${request.projectPath}') from tooling server", "variant ${request.variantName} ('${request.projectPath}') from tooling server",
u) u)
return@whenComplete return@whenComplete
} }
mCachedVariants[request] = t mCachedVariants[request] = t
} }
} }
override fun findByPath(path: String): CompletableFuture<IdeGradleProject> { override fun findByPath(path: String): CompletableFuture<IdeGradleProject> {
return this.project.findByPath(path) return this.project.findByPath(path)
} }
override fun findAndroidModules(): CompletableFuture<MutableList<IdeAndroidModule>> { override fun findAndroidModules(): CompletableFuture<MutableList<IdeAndroidModule>> {
return this.project.findAndroidModules() return this.project.findAndroidModules()
} }
override fun findFirstAndroidModule(): CompletableFuture<IdeAndroidModule> { override fun findFirstAndroidModule(): CompletableFuture<IdeAndroidModule> {
return this.project.findFirstAndroidModule() return this.project.findFirstAndroidModule()
} }
override fun findFirstAndroidAppModule(): CompletableFuture<IdeAndroidModule> { override fun findFirstAndroidAppModule(): CompletableFuture<IdeAndroidModule> {
if (mFirstAppModule != null) { if (mFirstAppModule != null) {
return CompletableFuture.completedFuture(mFirstAppModule) return CompletableFuture.completedFuture(mFirstAppModule)
} }
return this.project.findFirstAndroidAppModule().whenComplete { module, _ -> return this.project.findFirstAndroidAppModule().whenComplete { module, _ ->
this.mFirstAppModule = module this.mFirstAppModule = module
} }
} }
} }
@@ -1,207 +1,207 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.projects package com.itsaky.androidide.projects
import com.itsaky.androidide.app.StudioApp import com.itsaky.androidide.app.StudioApp
import com.itsaky.androidide.services.BuildService import com.itsaky.androidide.services.BuildService
import com.itsaky.androidide.tooling.api.IProject import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeGradleProject import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.androidide.tooling.api.model.IdeJavaModule import com.itsaky.androidide.tooling.api.model.IdeJavaModule
import com.itsaky.androidide.tooling.api.model.IdeModule import com.itsaky.androidide.tooling.api.model.IdeModule
import com.itsaky.androidide.tooling.api.util.ProjectDataCollector import com.itsaky.androidide.tooling.api.util.ProjectDataCollector
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
import com.itsaky.lsp.java.models.JavaServerConfiguration import com.itsaky.lsp.java.models.JavaServerConfiguration
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.* import java.util.concurrent.*
/** /**
* Manages projects in AndroidIDE. * Manages projects in AndroidIDE.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
object ProjectManager { object ProjectManager {
private val log = ILogger.newInstance(javaClass.simpleName) private val log = ILogger.newInstance(javaClass.simpleName)
var rootProject: IProject? = null var rootProject: IProject? = null
set(value) { set(value) {
field = if (value == null) null else CachingProject(value) field = if (value == null) null else CachingProject(value)
// Cache the module data in advance for future use // Cache the module data in advance for future use
if (field != null) { if (field != null) {
field!!.listModules() field!!.listModules()
} }
} }
var projectPath: String? = null var projectPath: String? = null
fun isInitialized() = this.rootProject != null && this.rootProject!!.isProjectInitialized.get() fun isInitialized() = this.rootProject != null && this.rootProject!!.isProjectInitialized.get()
fun checkInit(): Boolean { fun checkInit(): Boolean {
if (isInitialized()) { if (isInitialized()) {
return true return true
} }
log.warn("Project is not initialized yet!") log.warn("Project is not initialized yet!")
return false return false
} }
fun getProjectDir(): File? = if (!checkInit()) null else File(getProjectDirPath()!!) fun getProjectDir(): File? = if (!checkInit()) null else File(getProjectDirPath()!!)
fun getProjectDirPath(): String? { fun getProjectDirPath(): String? {
return projectPath return projectPath
} }
fun generateSources(builder: BuildService?) { fun generateSources(builder: BuildService?) {
if (builder == null) { if (builder == null) {
log.warn("Cannot generate sources. BuildService is null.") log.warn("Cannot generate sources. BuildService is null.")
return return
} }
getApplicationModule().whenCompleteAsync { app, _ -> getApplicationModule().whenCompleteAsync { app, _ ->
if (app == null) { if (app == null) {
log.warn( log.warn(
"Cannot run resource and source generation task. No application module found.") "Cannot run resource and source generation task. No application module found.")
return@whenCompleteAsync return@whenCompleteAsync
} }
val debug = app.simpleVariants.firstOrNull { it.name == "debug" } val debug = app.simpleVariants.firstOrNull { it.name == "debug" }
if (debug == null) { if (debug == null) {
log.warn("No debug variant found in application project ${app.name}") log.warn("No debug variant found in application project ${app.name}")
return@whenCompleteAsync return@whenCompleteAsync
} }
val mainArtifact = app.simpleVariants.first { it.name == "debug" }.mainArtifact val mainArtifact = app.simpleVariants.first { it.name == "debug" }.mainArtifact
val genResourcesTask = mainArtifact.resGenTaskName val genResourcesTask = mainArtifact.resGenTaskName
val genSourcesTask = mainArtifact.sourceGenTaskName val genSourcesTask = mainArtifact.sourceGenTaskName
builder builder
.executeProjectTasks( .executeProjectTasks(
app.projectPath, app.projectPath,
genResourcesTask ?: "", genResourcesTask ?: "",
genSourcesTask, genSourcesTask,
// If view binding is enabled, generate the view binding classes too // If view binding is enabled, generate the view binding classes too
if (app.viewBindingOptions != null && app.viewBindingOptions!!.isEnabled) if (app.viewBindingOptions != null && app.viewBindingOptions!!.isEnabled)
"dataBindingGenBaseClassesDebug" "dataBindingGenBaseClassesDebug"
else "") else "")
.whenComplete { result, taskErr -> .whenComplete { result, taskErr ->
if (!result.isSuccessful || taskErr != null) { if (!result.isSuccessful || taskErr != null) {
log.warn( log.warn(
"Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.", "Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.",
taskErr ?: "") taskErr ?: "")
} else { } else {
notifyProjectUpdate() notifyProjectUpdate()
} }
} }
} }
} }
fun getApplicationModule(): CompletableFuture<IdeAndroidModule?> { fun getApplicationModule(): CompletableFuture<IdeAndroidModule?> {
if (rootProject == null) { if (rootProject == null) {
log.error( log.error(
"No root project instance is set. Is the project initialization process finished?") "No root project instance is set. Is the project initialization process finished?")
return CompletableFuture.completedFuture(null) return CompletableFuture.completedFuture(null)
} }
return rootProject!!.findFirstAndroidAppModule().whenComplete { app, err -> return rootProject!!.findFirstAndroidAppModule().whenComplete { app, err ->
if (app == null || err != null) { if (app == null || err != null) {
log.debug("An error occurred while fetching model for application module.") log.debug("An error occurred while fetching model for application module.")
return@whenComplete return@whenComplete
} }
} }
} }
fun getApplicationResDirectories(): CompletableFuture<List<File>?> { fun getApplicationResDirectories(): CompletableFuture<List<File>?> {
return getApplicationModule().thenApplyAsync { return getApplicationModule().thenApplyAsync {
if (it == null) null else collectResDirectories(it) if (it == null) null else collectResDirectories(it)
} }
} }
fun notifyProjectUpdate() { fun notifyProjectUpdate() {
log.debug("Updating class paths in language servers...") log.debug("Updating class paths in language servers...")
getApplicationModule().whenCompleteAsync { it, _ -> getApplicationModule().whenCompleteAsync { it, _ ->
if (it == null) { if (it == null) {
log.debug("Cannot find application module. Unable to update class paths to LSP.") log.debug("Cannot find application module. Unable to update class paths to LSP.")
return@whenCompleteAsync return@whenCompleteAsync
} }
val server = StudioApp.getInstance().javaLanguageServer val server = StudioApp.getInstance().javaLanguageServer
val classPaths = collectClassPaths(it) val classPaths = collectClassPaths(it)
val sourceDirs = collectSourceDirs(it) val sourceDirs = collectSourceDirs(it)
val configuration = JavaServerConfiguration(classPaths, sourceDirs) val configuration = JavaServerConfiguration(classPaths, sourceDirs)
server.configurationChanged(configuration) server.configurationChanged(configuration)
} }
} }
fun collectResDirectories(android: IdeAndroidModule) = fun collectResDirectories(android: IdeAndroidModule) =
ProjectDataCollector.collectResDirectories(this.rootProject!!, android) ProjectDataCollector.collectResDirectories(this.rootProject!!, android)
fun collectClassPaths(app: IdeAndroidModule) = fun collectClassPaths(app: IdeAndroidModule) =
ProjectDataCollector.collectClassPaths(this.rootProject!!, app) ProjectDataCollector.collectClassPaths(this.rootProject!!, app)
fun collectSourceDirs(app: IdeAndroidModule): Set<Path> = fun collectSourceDirs(app: IdeAndroidModule): Set<Path> =
ProjectDataCollector.collectSourceDirs(this.rootProject!!, app) ProjectDataCollector.collectSourceDirs(this.rootProject!!, app)
@Suppress("unused") @Suppress("unused")
fun collectProjectDependencies(project: IProject, app: IdeAndroidModule) = fun collectProjectDependencies(project: IProject, app: IdeAndroidModule) =
ProjectDataCollector.collectProjectDependencies(project, app) ProjectDataCollector.collectProjectDependencies(project, app)
@Suppress("unused") @Suppress("unused")
fun collectSourceDirs(projects: List<IdeModule>) = fun collectSourceDirs(projects: List<IdeModule>) =
ProjectDataCollector.collectSourceDirs(projects) ProjectDataCollector.collectSourceDirs(projects)
@Suppress("unused") @Suppress("unused")
fun collectSources(java: IdeJavaModule) = ProjectDataCollector.collectSources(java) fun collectSources(java: IdeJavaModule) = ProjectDataCollector.collectSources(java)
@Suppress("unused") @Suppress("unused")
fun collectSources(android: IdeAndroidModule) = ProjectDataCollector.collectSources(android) fun collectSources(android: IdeAndroidModule) = ProjectDataCollector.collectSources(android)
fun findModuleForFile(file: File): CompletableFuture<IdeGradleProject?> { fun findModuleForFile(file: File): CompletableFuture<IdeGradleProject?> {
if (!checkInit()) { if (!checkInit()) {
return CompletableFuture.completedFuture(null) return CompletableFuture.completedFuture(null)
} }
val notAvailable = ":::::" val notAvailable = ":::::"
return rootProject!! return rootProject!!
.listModules() .listModules()
.thenApplyAsync { modules -> .thenApplyAsync { modules ->
val path = file.canonicalPath val path = file.canonicalPath
var longestPath = "" var longestPath = ""
for (module in modules) { for (module in modules) {
val modulePath = module.projectDir.canonicalPath val modulePath = module.projectDir.canonicalPath
if (path.startsWith(modulePath)) { if (path.startsWith(modulePath)) {
if (longestPath.length < modulePath.length) { if (longestPath.length < modulePath.length) {
longestPath = modulePath longestPath = modulePath
} }
} }
} }
if (longestPath.isEmpty()) { if (longestPath.isEmpty()) {
return@thenApplyAsync notAvailable return@thenApplyAsync notAvailable
} }
return@thenApplyAsync longestPath return@thenApplyAsync longestPath
} }
.thenApplyAsync { .thenApplyAsync {
if (it == notAvailable) { if (it == notAvailable) {
return@thenApplyAsync null return@thenApplyAsync null
} else return@thenApplyAsync rootProject!!.findByPath(it).get() } else return@thenApplyAsync rootProject!!.findByPath(it).get()
} }
} }
} }
@@ -1,312 +1,312 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.projects; package com.itsaky.androidide.projects;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.ILogger;
import com.itsaky.inflater.IResourceTable; import com.itsaky.inflater.IResourceTable;
import com.itsaky.inflater.values.ValuesTableFactory; import com.itsaky.inflater.values.ValuesTableFactory;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale; import java.util.Locale;
public class ProjectResourceTable implements IResourceTable { public class ProjectResourceTable implements IResourceTable {
private static final ILogger LOG = ILogger.newInstance("ProjectResourceFinder"); private static final ILogger LOG = ILogger.newInstance("ProjectResourceFinder");
private File resDir; private File resDir;
private File color; private File color;
private File layout; private File layout;
private File[] drawables; private File[] drawables;
private File[] mipmaps; private File[] mipmaps;
@Override @Override
public File findDrawable(@NonNull String name) { public File findDrawable(@NonNull String name) {
for (File drawable : drawables) { for (File drawable : drawables) {
final File file = findFileWithName(drawable.listFiles(), name); final File file = findFileWithName(drawable.listFiles(), name);
if (file != null) { if (file != null) {
return file; return file;
} }
} }
return null; return null;
} }
@Override @Override
public File findLayout(@NonNull String name) { public File findLayout(@NonNull String name) {
return findFileWithName(this.layout.listFiles(), name); return findFileWithName(this.layout.listFiles(), name);
} }
@Override @Override
public String findString(@NonNull String name) { public String findString(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir); final var table = ValuesTableFactory.getTable(resDir);
if (table == null) { if (table == null) {
return name; return name;
} }
final var resource = table.findString(name); final var resource = table.findString(name);
if (resource == null) { if (resource == null) {
return name; return name;
} }
final var value = resource.getValue(); final var value = resource.getValue();
if (value.startsWith("@string/")) { if (value.startsWith("@string/")) {
var ref = value.substring("@string/".length()); var ref = value.substring("@string/".length());
if (name.equals(ref)) { if (name.equals(ref)) {
return name; return name;
} }
} }
return value; return value;
} }
@Override @Override
public String findBoolean(@NonNull String name) { public String findBoolean(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir); final var table = ValuesTableFactory.getTable(resDir);
if (table == null) { if (table == null) {
return name; return name;
} }
final var resource = table.findBoolean(name); final var resource = table.findBoolean(name);
if (resource == null) { if (resource == null) {
return name; return name;
} }
final var val = resource.getValue(); final var val = resource.getValue();
if (val.startsWith("@bool/")) { if (val.startsWith("@bool/")) {
final var ref = val.substring("@bool/".length()); final var ref = val.substring("@bool/".length());
if (ref.equals(name)) { if (ref.equals(name)) {
// recursive reference // recursive reference
return name; return name;
} }
} }
return val; return val;
} }
@Override @Override
public String findInteger(@NonNull String name) { public String findInteger(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir); final var table = ValuesTableFactory.getTable(resDir);
if (table == null) { if (table == null) {
return name; return name;
} }
final var resource = table.findInteger(name); final var resource = table.findInteger(name);
if (resource == null) { if (resource == null) {
return name; return name;
} }
final var val = resource.getValue(); final var val = resource.getValue();
if (val.startsWith("@integer/")) { if (val.startsWith("@integer/")) {
final var ref = val.substring("@integer/".length()); final var ref = val.substring("@integer/".length());
if (ref.equals(name)) { if (ref.equals(name)) {
// recursive reference // recursive reference
return name; return name;
} }
} }
return val; return val;
} }
@Override @Override
public String findColor(@NonNull String name) { public String findColor(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir); final var table = ValuesTableFactory.getTable(resDir);
if (table == null) { if (table == null) {
return name; return name;
} }
final var resource = table.findColor(name); final var resource = table.findColor(name);
if (resource == null) { if (resource == null) {
return name; return name;
} }
final var value = resource.getValue(); final var value = resource.getValue();
if (value.startsWith("@color/")) { if (value.startsWith("@color/")) {
var ref = value.substring("@color/".length()); var ref = value.substring("@color/".length());
if (name.equals(ref)) { if (name.equals(ref)) {
return name; return name;
} }
} }
return value; return value;
} }
@Override @Override
public String[] findArray(@NonNull String name) { public String[] findArray(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir); final var table = ValuesTableFactory.getTable(resDir);
if (table == null) { if (table == null) {
return new String[] {name}; return new String[] {name};
} }
final var resource = table.findArray(name); final var resource = table.findArray(name);
if (resource == null) { if (resource == null) {
return new String[] {name}; return new String[] {name};
} }
return resource.getValues(); return resource.getValues();
} }
@Override @Override
public String findDimension(@NonNull String name) { public String findDimension(@NonNull String name) {
final var table = ValuesTableFactory.getTable(resDir); final var table = ValuesTableFactory.getTable(resDir);
if (table == null) { if (table == null) {
return name; return name;
} }
final var resource = table.findDimension(name); final var resource = table.findDimension(name);
if (resource == null) { if (resource == null) {
return name; return name;
} }
final var value = resource.getValue(); final var value = resource.getValue();
if (value.startsWith("@dimen/")) { if (value.startsWith("@dimen/")) {
var ref = value.substring("@dimen/".length()); var ref = value.substring("@dimen/".length());
if (name.equals(ref)) { if (name.equals(ref)) {
return name; return name;
} }
} }
return value; return value;
} }
@NonNull @NonNull
@Override @Override
public Collection<String> listResourceNames(@Nullable String type) { public Collection<String> listResourceNames(@Nullable String type) {
if (type == null) { if (type == null) {
return listAllResourceNames(); return listAllResourceNames();
} else { } else {
final var set = new HashSet<String>(); final var set = new HashSet<String>();
switch (type) { switch (type) {
case "drawable": case "drawable":
for (var folder : this.drawables) { for (var folder : this.drawables) {
listAndAddTo(set, folder, type); listAndAddTo(set, folder, type);
} }
break; break;
case "layout": case "layout":
listAndAddTo(set, this.layout, type); listAndAddTo(set, this.layout, type);
break; break;
case "color": case "color":
listAndAddTo(set, this.color, type); listAndAddTo(set, this.color, type);
break; break;
case "mipmap": case "mipmap":
for (var folder : this.mipmaps) { for (var folder : this.mipmaps) {
listAndAddTo(set, folder, type); listAndAddTo(set, folder, type);
} }
break; break;
} }
return set; return set;
} }
} }
private void listAndAddTo(HashSet<String> set, @NonNull File dir, String type) { private void listAndAddTo(HashSet<String> set, @NonNull File dir, String type) {
final var files = dir.listFiles(); final var files = dir.listFiles();
if (files == null) { if (files == null) {
return; return;
} }
for (var file : files) { for (var file : files) {
var name = file.getName(); var name = file.getName();
if (!name.contains(".")) { if (!name.contains(".")) {
continue; continue;
} }
name = name.substring(0, name.lastIndexOf('.')); name = name.substring(0, name.lastIndexOf('.'));
name = String.format(Locale.ROOT, "@%s/%s", type, name); name = String.format(Locale.ROOT, "@%s/%s", type, name);
set.add(name); set.add(name);
} }
} }
@NonNull @NonNull
private Collection<String> listAllResourceNames() { private Collection<String> listAllResourceNames() {
final var set = new HashSet<>(listResourceNames("color")); final var set = new HashSet<>(listResourceNames("color"));
set.addAll(listResourceNames("drawable")); set.addAll(listResourceNames("drawable"));
set.addAll(listResourceNames("mipmap")); set.addAll(listResourceNames("mipmap"));
set.addAll(listResourceNames("layout")); set.addAll(listResourceNames("layout"));
return set; return set;
} }
@Override @Override
public void setInflatingFile(@NonNull File file) { public void setInflatingFile(@NonNull File file) {
if (file.getParentFile() == null) { if (file.getParentFile() == null) {
throw new IllegalArgumentException("Invalid inflating file"); throw new IllegalArgumentException("Invalid inflating file");
} }
setupDirectories( setupDirectories(
file // layout file file // layout file
.getParentFile() // layout dir .getParentFile() // layout dir
.getParentFile()); // res dir .getParentFile()); // res dir
} }
private void setupDirectories(File resDir) { private void setupDirectories(File resDir) {
if (resDir == null) { if (resDir == null) {
LOG.error("Null resource directory passed to resource finder. Ignoring."); LOG.error("Null resource directory passed to resource finder. Ignoring.");
return; return;
} }
this.resDir = resDir; this.resDir = resDir;
this.color = new File(resDir, "color"); this.color = new File(resDir, "color");
this.layout = new File(resDir, "layout"); this.layout = new File(resDir, "layout");
final File[] drawables = resDir.listFiles(new NameStartsWith("drawable")); final File[] drawables = resDir.listFiles(new NameStartsWith("drawable"));
this.drawables = drawables == null ? new File[0] : drawables; this.drawables = drawables == null ? new File[0] : drawables;
final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap")); final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap"));
this.mipmaps = mipmaps == null ? new File[0] : mipmaps; this.mipmaps = mipmaps == null ? new File[0] : mipmaps;
} }
private File findFileWithName(File[] files, String name) { private File findFileWithName(File[] files, String name) {
if (files == null || name == null) { if (files == null || name == null) {
return null; return null;
} }
for (File file : files) { for (File file : files) {
var simpleName = file.getName(); var simpleName = file.getName();
if (simpleName.startsWith(name)) { if (simpleName.startsWith(name)) {
return file; return file;
} }
} }
return null; return null;
} }
private static class NameStartsWith implements FileFilter { private static class NameStartsWith implements FileFilter {
private final String name; private final String name;
public NameStartsWith(String name) { public NameStartsWith(String name) {
this.name = name; this.name = name;
} }
@Override @Override
public boolean accept(@NonNull File file) { public boolean accept(@NonNull File file) {
return file.getName().startsWith(name); return file.getName().startsWith(name);
} }
} }
} }
@@ -1,71 +1,71 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.services package com.itsaky.androidide.services
import com.itsaky.androidide.tooling.api.messages.result.BuildCancellationRequestResult import com.itsaky.androidide.tooling.api.messages.result.BuildCancellationRequestResult
import com.itsaky.androidide.tooling.api.messages.result.InitializeResult import com.itsaky.androidide.tooling.api.messages.result.InitializeResult
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult
import java.util.concurrent.* import java.util.concurrent.*
/** /**
* A build service provides API to initialize project, execute builds, query a build, cancel running * A build service provides API to initialize project, execute builds, query a build, cancel running
* builds, etc. * builds, etc.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
interface BuildService { interface BuildService {
/** /**
* Initialize the project. * Initialize the project.
* *
* @param rootDir The root directory of the project to initialize. * @param rootDir The root directory of the project to initialize.
* @return A [CompletableFuture] which returns an [InitializeResult] when the project * @return A [CompletableFuture] which returns an [InitializeResult] when the project
* initialization process finishes. * initialization process finishes.
*/ */
fun initializeProject(rootDir: String): CompletableFuture<InitializeResult> fun initializeProject(rootDir: String): CompletableFuture<InitializeResult>
/** /**
* Execute the given tasks. * Execute the given tasks.
* *
* @param tasks The tasks to execute. If the fully qualified path of the task is not specified, * @param tasks The tasks to execute. If the fully qualified path of the task is not specified,
* then it will be executed in the root project directory. * then it will be executed in the root project directory.
* @return A [CompletableFuture] which returns a list of [TaskExecutionResult]. The result * @return A [CompletableFuture] which returns a list of [TaskExecutionResult]. The result
* contains a list of tasks that were executed and the result of the whole execution. * contains a list of tasks that were executed and the result of the whole execution.
* @see BuildService.executeProjectTasks * @see BuildService.executeProjectTasks
*/ */
fun executeTasks(vararg tasks: String): CompletableFuture<TaskExecutionResult> fun executeTasks(vararg tasks: String): CompletableFuture<TaskExecutionResult>
/** /**
* Execute the given tasks of the given project. * Execute the given tasks of the given project.
* *
* @param projectPath The path of the project. All the tasks will be executed in this project. * @param projectPath The path of the project. All the tasks will be executed in this project.
* @param tasks The tasks to execute. These may or may not be fully qualified names of the * @param tasks The tasks to execute. These may or may not be fully qualified names of the
* project. If the task name is not fully qualified, then it will be executed in the given * project. If the task name is not fully qualified, then it will be executed in the given
* project path. For example, if the project path is ':app' and the task is 'assembleDebug', * project path. For example, if the project path is ':app' and the task is 'assembleDebug',
* then, ':app:assembleDebug' task will be executed. * then, ':app:assembleDebug' task will be executed.
* @return A [CompletableFuture] which returns a list of [TaskExecutionResult]. The result * @return A [CompletableFuture] which returns a list of [TaskExecutionResult]. The result
* contains a list of tasks that were executed and the result of the whole execution. * contains a list of tasks that were executed and the result of the whole execution.
*/ */
fun executeProjectTasks( fun executeProjectTasks(
projectPath: String, projectPath: String,
vararg tasks: String vararg tasks: String
): CompletableFuture<TaskExecutionResult> ): CompletableFuture<TaskExecutionResult>
/** Cancel any running build. */ /** Cancel any running build. */
fun cancelCurrentBuild(): CompletableFuture<BuildCancellationRequestResult> fun cancelCurrentBuild(): CompletableFuture<BuildCancellationRequestResult>
} }
File diff suppressed because it is too large Load Diff
@@ -1,88 +1,88 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.ui.edit; package com.itsaky.androidide.ui.edit;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.itsaky.attrinfo.models.Attr; import com.itsaky.attrinfo.models.Attr;
import com.itsaky.inflater.IAttribute; import com.itsaky.inflater.IAttribute;
import com.itsaky.lsp.util.StringUtils; import com.itsaky.lsp.util.StringUtils;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Handles editing dimension attributes. * Handles editing dimension attributes.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
public class DimensionSuggestionProvider extends BaseSuggestionProvider { public class DimensionSuggestionProvider extends BaseSuggestionProvider {
/** The dimension units that will be suggested. */ /** The dimension units that will be suggested. */
private final List<String> dimensions = new ArrayList<>(); private final List<String> dimensions = new ArrayList<>();
public DimensionSuggestionProvider(File layout) { public DimensionSuggestionProvider(File layout) {
super(layout); super(layout);
this.dimensions.add("dp"); this.dimensions.add("dp");
this.dimensions.add("sp"); this.dimensions.add("sp");
this.dimensions.add("px"); this.dimensions.add("px");
this.dimensions.add("pt"); this.dimensions.add("pt");
this.dimensions.add("in"); this.dimensions.add("in");
this.dimensions.add("mm"); this.dimensions.add("mm");
} }
@Override @Override
public boolean checkFormat(int format) { public boolean checkFormat(int format) {
return (format & Attr.DIMENSION) != 0; return (format & Attr.DIMENSION) != 0;
} }
@Override @Override
public @NonNull List<String> suggest(IAttribute attribute, @NonNull String prefix) { public @NonNull List<String> suggest(IAttribute attribute, @NonNull String prefix) {
final var list = new ArrayList<String>(); final var list = new ArrayList<String>();
if (prefix.length() > 0 && Character.isDigit(prefix.charAt(0))) { if (prefix.length() > 0 && Character.isDigit(prefix.charAt(0))) {
// Fixed dimension values // Fixed dimension values
// e.g. 10dp // e.g. 10dp
var val = extractDimensionVal(prefix); var val = extractDimensionVal(prefix);
for (var unit : this.dimensions) { for (var unit : this.dimensions) {
final var dimension = val + unit; final var dimension = val + unit;
if (StringUtils.matchesFuzzy(dimension, prefix, true)) { if (StringUtils.matchesFuzzy(dimension, prefix, true)) {
list.add(dimension); list.add(dimension);
} }
} }
} }
return list; return list;
} }
private int extractDimensionVal(@NonNull String prefix) { private int extractDimensionVal(@NonNull String prefix) {
StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder();
var i = 0; var i = 0;
while (i < prefix.length()) { while (i < prefix.length()) {
if (Character.isDigit(prefix.charAt(i))) { if (Character.isDigit(prefix.charAt(i))) {
s.append(prefix.charAt(i)); s.append(prefix.charAt(i));
} else { } else {
break; break;
} }
i++; i++;
} }
return Integer.parseInt(s.toString()); return Integer.parseInt(s.toString());
} }
} }
@@ -1,84 +1,84 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.utils package com.itsaky.androidide.utils
import android.content.Context import android.content.Context
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionsRegistry import com.itsaky.androidide.actions.ActionsRegistry
import com.itsaky.androidide.actions.build.CancelBuildAction import com.itsaky.androidide.actions.build.CancelBuildAction
import com.itsaky.androidide.actions.build.ProjectSyncAction import com.itsaky.androidide.actions.build.ProjectSyncAction
import com.itsaky.androidide.actions.build.QuickRunAction import com.itsaky.androidide.actions.build.QuickRunAction
import com.itsaky.androidide.actions.build.RunActionMenu import com.itsaky.androidide.actions.build.RunActionMenu
import com.itsaky.androidide.actions.editor.CopyAction import com.itsaky.androidide.actions.editor.CopyAction
import com.itsaky.androidide.actions.editor.CutAction import com.itsaky.androidide.actions.editor.CutAction
import com.itsaky.androidide.actions.editor.ExpandSelectionAction import com.itsaky.androidide.actions.editor.ExpandSelectionAction
import com.itsaky.androidide.actions.editor.PasteAction import com.itsaky.androidide.actions.editor.PasteAction
import com.itsaky.androidide.actions.editor.SelectAllAction import com.itsaky.androidide.actions.editor.SelectAllAction
import com.itsaky.androidide.actions.editor.find.GoToNextAction import com.itsaky.androidide.actions.editor.find.GoToNextAction
import com.itsaky.androidide.actions.editor.find.GoToPreviousAction import com.itsaky.androidide.actions.editor.find.GoToPreviousAction
import com.itsaky.androidide.actions.editor.find.ReplaceAction import com.itsaky.androidide.actions.editor.find.ReplaceAction
import com.itsaky.androidide.actions.etc.DaemonStatusAction import com.itsaky.androidide.actions.etc.DaemonStatusAction
import com.itsaky.androidide.actions.etc.FileTreeAction import com.itsaky.androidide.actions.etc.FileTreeAction
import com.itsaky.androidide.actions.etc.FindActionMenu import com.itsaky.androidide.actions.etc.FindActionMenu
import com.itsaky.androidide.actions.etc.PreviewLayoutAction import com.itsaky.androidide.actions.etc.PreviewLayoutAction
import com.itsaky.androidide.actions.file.FormatCodeAction import com.itsaky.androidide.actions.file.FormatCodeAction
import com.itsaky.androidide.actions.file.SaveFileAction import com.itsaky.androidide.actions.file.SaveFileAction
import com.itsaky.androidide.actions.text.RedoAction import com.itsaky.androidide.actions.text.RedoAction
import com.itsaky.androidide.actions.text.UndoAction import com.itsaky.androidide.actions.text.UndoAction
/** /**
* Takes care of registering actions to the actions registry for the editor activity. * Takes care of registering actions to the actions registry for the editor activity.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
class EditorActivityActions { class EditorActivityActions {
companion object { companion object {
@JvmStatic @JvmStatic
fun register(context: Context) { fun register(context: Context) {
val registry = ActionsRegistry.getInstance() val registry = ActionsRegistry.getInstance()
// Toolbar actions // Toolbar actions
registry.registerAction(UndoAction(context)) registry.registerAction(UndoAction(context))
registry.registerAction(RedoAction(context)) registry.registerAction(RedoAction(context))
registry.registerAction(QuickRunAction(context)) registry.registerAction(QuickRunAction(context))
registry.registerAction(RunActionMenu(context)) registry.registerAction(RunActionMenu(context))
registry.registerAction(SaveFileAction(context)) registry.registerAction(SaveFileAction(context))
registry.registerAction(PreviewLayoutAction(context)) registry.registerAction(PreviewLayoutAction(context))
registry.registerAction(FindActionMenu(context)) registry.registerAction(FindActionMenu(context))
registry.registerAction(FileTreeAction(context)) registry.registerAction(FileTreeAction(context))
registry.registerAction(DaemonStatusAction(context)) registry.registerAction(DaemonStatusAction(context))
registry.registerAction(CancelBuildAction(context)) registry.registerAction(CancelBuildAction(context))
registry.registerAction(ProjectSyncAction(context)) registry.registerAction(ProjectSyncAction(context))
// editor text actions // editor text actions
registry.registerAction(ExpandSelectionAction(context)) registry.registerAction(ExpandSelectionAction(context))
registry.registerAction(SelectAllAction(context)) registry.registerAction(SelectAllAction(context))
registry.registerAction(CutAction(context)) registry.registerAction(CutAction(context))
registry.registerAction(CopyAction(context)) registry.registerAction(CopyAction(context))
registry.registerAction(PasteAction(context)) registry.registerAction(PasteAction(context))
registry.registerAction(FormatCodeAction(context)) registry.registerAction(FormatCodeAction(context))
// editor search actions // editor search actions
registry.registerAction(GoToNextAction(context)) registry.registerAction(GoToNextAction(context))
registry.registerAction(GoToPreviousAction(context)) registry.registerAction(GoToPreviousAction(context))
registry.registerAction(ReplaceAction(context)) registry.registerAction(ReplaceAction(context))
} }
@JvmStatic @JvmStatic
fun clear() = ActionsRegistry.getInstance().clearActions(ActionItem.Location.EDITOR_TOOLBAR) fun clear() = ActionsRegistry.getInstance().clearActions(ActionItem.Location.EDITOR_TOOLBAR)
} }
} }
@@ -1,34 +1,34 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
* *
*/ */
package com.itsaky.androidide.utils; package com.itsaky.androidide.utils;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher; import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher;
public class JSONUtility { public class JSONUtility {
public static final Gson gson = new Gson(); public static final Gson gson = new Gson();
public static final Gson toolingGson; public static final Gson toolingGson;
public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create(); public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create();
static { static {
final var builder = new GsonBuilder(); final var builder = new GsonBuilder();
ToolingApiLauncher.configureGson(builder); ToolingApiLauncher.configureGson(builder);
toolingGson = builder.create(); toolingGson = builder.create();
} }
} }
@@ -1,43 +1,43 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.utils package com.itsaky.androidide.utils
import java.io.File import java.io.File
import java.io.StringReader import java.io.StringReader
import java.util.* import java.util.*
class ListingFileRedirect { class ListingFileRedirect {
companion object { companion object {
/** Redirect file will have this marker as the first line as comment. */ /** Redirect file will have this marker as the first line as comment. */
private const val REDIRECT_MARKER = "#- File Locator -" private const val REDIRECT_MARKER = "#- File Locator -"
/** Property name in a [Properties] for the metadata file location. */ /** Property name in a [Properties] for the metadata file location. */
private const val REDIRECT_PROPERTY_NAME = "listingFile" private const val REDIRECT_PROPERTY_NAME = "listingFile"
@JvmStatic @JvmStatic
fun maybeExtractRedirectedFile(redirectFile: File): File? { fun maybeExtractRedirectedFile(redirectFile: File): File? {
val fileContent = redirectFile.readText() val fileContent = redirectFile.readText()
return if (fileContent.startsWith(REDIRECT_MARKER)) { return if (fileContent.startsWith(REDIRECT_MARKER)) {
val fileLocator = Properties().also { it.load(StringReader(fileContent)) } val fileLocator = Properties().also { it.load(StringReader(fileContent)) }
val file = File(fileLocator.getProperty(REDIRECT_PROPERTY_NAME)) val file = File(fileLocator.getProperty(REDIRECT_PROPERTY_NAME))
if (!file.isAbsolute) redirectFile.parentFile!!.resolve(file) else file if (!file.isAbsolute) redirectFile.parentFile!!.resolve(file) else file
} else null } else null
} }
} }
} }
@@ -1,20 +1,3 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
@@ -473,4 +456,4 @@ public class CodeEditorView extends FrameLayout {
isModified = false; isModified = false;
notifySaved(); notifySaved();
} }
} }
@@ -1,391 +1,391 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.views.editor package com.itsaky.androidide.views.editor
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.RectF import android.graphics.RectF
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.BaseAdapter import android.widget.BaseAdapter
import android.widget.ListView import android.widget.ListView
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.view.menu.MenuItemImpl import androidx.appcompat.view.menu.MenuItemImpl
import androidx.appcompat.view.menu.SubMenuBuilder import androidx.appcompat.view.menu.SubMenuBuilder
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.transition.ChangeBounds import androidx.transition.ChangeBounds
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import com.blankj.utilcode.util.SizeUtils import com.blankj.utilcode.util.SizeUtils
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import com.itsaky.androidide.R import com.itsaky.androidide.R
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionsRegistry import com.itsaky.androidide.actions.ActionsRegistry
import com.itsaky.androidide.actions.ActionsRegistry.Companion.getInstance import com.itsaky.androidide.actions.ActionsRegistry.Companion.getInstance
import com.itsaky.androidide.actions.editor.SelectAllAction import com.itsaky.androidide.actions.editor.SelectAllAction
import com.itsaky.androidide.app.StudioApp import com.itsaky.androidide.app.StudioApp
import com.itsaky.androidide.language.IDELanguage import com.itsaky.androidide.language.IDELanguage
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
import com.itsaky.lsp.java.JavaLanguageServer import com.itsaky.lsp.java.JavaLanguageServer
import com.itsaky.lsp.models.DiagnosticItem import com.itsaky.lsp.models.DiagnosticItem
import com.itsaky.lsp.models.Range import com.itsaky.lsp.models.Range
import com.itsaky.lsp.util.DiagnosticUtil import com.itsaky.lsp.util.DiagnosticUtil
import com.itsaky.lsp.xml.XMLLanguageServer import com.itsaky.lsp.xml.XMLLanguageServer
import io.github.rosemoe.sora.event.HandleStateChangeEvent import io.github.rosemoe.sora.event.HandleStateChangeEvent
import io.github.rosemoe.sora.event.ScrollEvent import io.github.rosemoe.sora.event.ScrollEvent
import io.github.rosemoe.sora.event.SelectionChangeEvent import io.github.rosemoe.sora.event.SelectionChangeEvent
import io.github.rosemoe.sora.event.SubscriptionReceipt import io.github.rosemoe.sora.event.SubscriptionReceipt
import io.github.rosemoe.sora.widget.CodeEditor import io.github.rosemoe.sora.widget.CodeEditor
import io.github.rosemoe.sora.widget.EditorTouchEventHandler import io.github.rosemoe.sora.widget.EditorTouchEventHandler
import io.github.rosemoe.sora.widget.base.EditorPopupWindow import io.github.rosemoe.sora.widget.base.EditorPopupWindow
import java.io.File import java.io.File
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** /**
* PopupMenu for showing editor's text and code actions. * PopupMenu for showing editor's text and code actions.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
open class EditorActionsMenu(val editor: IDEEditor) : open class EditorActionsMenu(val editor: IDEEditor) :
EditorPopupWindow(editor, FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED), EditorPopupWindow(editor, FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED),
ActionsRegistry.ActionExecListener, ActionsRegistry.ActionExecListener,
MenuBuilder.Callback { MenuBuilder.Callback {
companion object { companion object {
const val DELAY: Long = 200 const val DELAY: Long = 200
} }
private val touchHandler: EditorTouchEventHandler = editor.eventHandler private val touchHandler: EditorTouchEventHandler = editor.eventHandler
private val receipts: MutableList<SubscriptionReceipt<*>> = mutableListOf() private val receipts: MutableList<SubscriptionReceipt<*>> = mutableListOf()
private val log = ILogger.newInstance(javaClass.simpleName) private val log = ILogger.newInstance(javaClass.simpleName)
private val list: ListView = ListView(editor.context) private val list: ListView = ListView(editor.context)
private var mLastScroll: Long = 0 private var mLastScroll: Long = 0
private var mLastPosition: Int = 0 private var mLastPosition: Int = 0
private val menu: MenuBuilder = MenuBuilder(editor.context) private val menu: MenuBuilder = MenuBuilder(editor.context)
protected open var location = ActionItem.Location.EDITOR_TEXT_ACTIONS protected open var location = ActionItem.Location.EDITOR_TEXT_ACTIONS
open fun init() { open fun init() {
subscribe() subscribe()
applyBackground() applyBackground()
list.clipChildren = true list.clipChildren = true
list.clipToOutline = true list.clipToOutline = true
list.isVerticalFadingEdgeEnabled = true list.isVerticalFadingEdgeEnabled = true
list.isVerticalScrollBarEnabled = true list.isVerticalScrollBarEnabled = true
list.setFadingEdgeLength(SizeUtils.dp2px(42f)) list.setFadingEdgeLength(SizeUtils.dp2px(42f))
list.divider = null list.divider = null
list.layoutParams = list.layoutParams =
ViewGroup.LayoutParams( ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
popup.contentView = this.list popup.contentView = this.list
popup.animationStyle = R.style.PopupAnimation popup.animationStyle = R.style.PopupAnimation
val menu = getMenu() val menu = getMenu()
if (menu is MenuBuilder) { if (menu is MenuBuilder) {
menu.setCallback(this) menu.setCallback(this)
} }
} }
open fun subscribe() { open fun subscribe() {
receipts.add( receipts.add(
editor.subscribeEvent(SelectionChangeEvent::class.java) { event, _ -> editor.subscribeEvent(SelectionChangeEvent::class.java) { event, _ ->
this.onSelectionChanged(event) this.onSelectionChanged(event)
}) })
receipts.add( receipts.add(
editor.subscribeEvent(ScrollEvent::class.java) { _, _ -> this.onScrollEvent() }) editor.subscribeEvent(ScrollEvent::class.java) { _, _ -> this.onScrollEvent() })
receipts.add( receipts.add(
editor.subscribeEvent(HandleStateChangeEvent::class.java) { event, _ -> editor.subscribeEvent(HandleStateChangeEvent::class.java) { event, _ ->
this.onHandleStateChanged(event) this.onHandleStateChanged(event)
}) })
} }
open fun unsubscribeEvents() { open fun unsubscribeEvents() {
for (receipt in receipts) { for (receipt in receipts) {
receipt.unsubscribe() receipt.unsubscribe()
} }
} }
protected open fun onSelectionChanged(event: SelectionChangeEvent) { protected open fun onSelectionChanged(event: SelectionChangeEvent) {
if (touchHandler.hasAnyHeldHandle()) { if (touchHandler.hasAnyHeldHandle()) {
return return
} }
if (event.isSelected) { if (event.isSelected) {
if (!isShowing) { if (!isShowing) {
editor.post(::displayWindow) editor.post(::displayWindow)
} }
mLastPosition = -1 mLastPosition = -1
} else { } else {
var show = false var show = false
if (event.cause == SelectionChangeEvent.CAUSE_TAP && if (event.cause == SelectionChangeEvent.CAUSE_TAP &&
event.left.index == mLastPosition && event.left.index == mLastPosition &&
!isShowing && !isShowing &&
!editor.text.isInBatchEdit) { !editor.text.isInBatchEdit) {
editor.post(::displayWindow) editor.post(::displayWindow)
show = true show = true
} else { } else {
dismiss() dismiss()
} }
mLastPosition = mLastPosition =
if (event.cause == SelectionChangeEvent.CAUSE_TAP && !show) { if (event.cause == SelectionChangeEvent.CAUSE_TAP && !show) {
event.left.index event.left.index
} else { } else {
-1 -1
} }
} }
} }
protected open fun onScrollEvent() { protected open fun onScrollEvent() {
val last = mLastScroll val last = mLastScroll
mLastScroll = System.currentTimeMillis() mLastScroll = System.currentTimeMillis()
if (mLastScroll - last < DELAY) { if (mLastScroll - last < DELAY) {
postDisplay() postDisplay()
} }
} }
protected open fun onHandleStateChanged( protected open fun onHandleStateChanged(
event: HandleStateChangeEvent, event: HandleStateChangeEvent,
) { ) {
if (event.isHeld) { if (event.isHeld) {
postDisplay() postDisplay()
} }
} }
protected open fun applyBackground() { protected open fun applyBackground() {
val drawable = GradientDrawable() val drawable = GradientDrawable()
drawable.shape = GradientDrawable.RECTANGLE drawable.shape = GradientDrawable.RECTANGLE
drawable.cornerRadius = 16f drawable.cornerRadius = 16f
drawable.color = drawable.color =
ColorStateList.valueOf( ColorStateList.valueOf(
ContextCompat.getColor(editor.context, R.color.content_background)) ContextCompat.getColor(editor.context, R.color.content_background))
drawable.setStroke( drawable.setStroke(
SizeUtils.dp2px(1f), ContextCompat.getColor(editor.context, R.color.primaryLightColor)) SizeUtils.dp2px(1f), ContextCompat.getColor(editor.context, R.color.primaryLightColor))
list.background = drawable list.background = drawable
} }
private fun postDisplay() { private fun postDisplay() {
if (!isShowing) { if (!isShowing) {
return return
} }
dismiss() dismiss()
if (!editor.cursor.isSelected) { if (!editor.cursor.isSelected) {
return return
} }
editor.postDelayed( editor.postDelayed(
object : Runnable { object : Runnable {
override fun run() { override fun run() {
if (!touchHandler.hasAnyHeldHandle() && if (!touchHandler.hasAnyHeldHandle() &&
System.currentTimeMillis() - mLastScroll > DELAY && System.currentTimeMillis() - mLastScroll > DELAY &&
touchHandler.scroller.isFinished) { touchHandler.scroller.isFinished) {
displayWindow() displayWindow()
} else { } else {
editor.postDelayed(this, DELAY) editor.postDelayed(this, DELAY)
} }
} }
}, },
DELAY) DELAY)
} }
private fun selectTop(rect: RectF): Int { private fun selectTop(rect: RectF): Int {
val rowHeight = editor.rowHeight val rowHeight = editor.rowHeight
return if (rect.top - rowHeight * 3 / 2f > height) { return if (rect.top - rowHeight * 3 / 2f > height) {
(rect.top - rowHeight * 3 / 2 - height).toInt() (rect.top - rowHeight * 3 / 2 - height).toInt()
} else { } else {
(rect.bottom + rowHeight / 2).toInt() (rect.bottom + rowHeight / 2).toInt()
} }
} }
open fun displayWindow() { open fun displayWindow() {
var top: Int var top: Int
val cursor = editor.cursor val cursor = editor.cursor
top = top =
if (cursor.isSelected) { if (cursor.isSelected) {
val leftRect = editor.leftHandleDescriptor.position val leftRect = editor.leftHandleDescriptor.position
val rightRect = editor.rightHandleDescriptor.position val rightRect = editor.rightHandleDescriptor.position
val top1 = selectTop(leftRect) val top1 = selectTop(leftRect)
val top2 = selectTop(rightRect) val top2 = selectTop(rightRect)
min(top1, top2) min(top1, top2)
} else { } else {
selectTop(editor.insertHandleDescriptor.position) selectTop(editor.insertHandleDescriptor.position)
} }
top = max(0, min(top, editor.height - height - 5)) top = max(0, min(top, editor.height - height - 5))
val handleLeftX = editor.getOffset(editor.cursor.leftLine, editor.cursor.leftColumn) val handleLeftX = editor.getOffset(editor.cursor.leftLine, editor.cursor.leftColumn)
val handleRightX = editor.getOffset(editor.cursor.rightLine, editor.cursor.rightColumn) val handleRightX = editor.getOffset(editor.cursor.rightLine, editor.cursor.rightColumn)
val panelX = ((handleLeftX + handleRightX) / 2f).toInt() val panelX = ((handleLeftX + handleRightX) / 2f).toInt()
setLocationAbsolutely(panelX, top) setLocationAbsolutely(panelX, top)
show() show()
} }
protected open fun fillMenu() { protected open fun fillMenu() {
getMenu().clear() getMenu().clear()
val data = onCreateActionData() val data = onCreateActionData()
val registry = getInstance() val registry = getInstance()
registry.registerActionExecListener(this) registry.registerActionExecListener(this)
onFillMenu(registry, data) onFillMenu(registry, data)
this.list.adapter = ActionsListAdapter(getMenu(), editor.context) this.list.adapter = ActionsListAdapter(getMenu(), editor.context)
} }
protected open fun onFillMenu(registry: ActionsRegistry, data: ActionData) { protected open fun onFillMenu(registry: ActionsRegistry, data: ActionData) {
registry.fillMenu(data, onGetActionLocation(), getMenu()) registry.fillMenu(data, onGetActionLocation(), getMenu())
} }
protected open fun onGetActionLocation() = location protected open fun onGetActionLocation() = location
protected open fun onCreateActionData(): ActionData { protected open fun onCreateActionData(): ActionData {
val data = ActionData() val data = ActionData()
data.put(Context::class.java, editor.context) data.put(Context::class.java, editor.context)
data.put(IDEEditor::class.java, this.editor) data.put(IDEEditor::class.java, this.editor)
data.put( data.put(
CodeEditor::class.java, CodeEditor::class.java,
editor) // For LSP actions, as they cannot access IDEEditor class editor) // For LSP actions, as they cannot access IDEEditor class
data.put(File::class.java, editor.file) data.put(File::class.java, editor.file)
data.put(DiagnosticItem::class.java, getDiagnosticAtCursor()) data.put(DiagnosticItem::class.java, getDiagnosticAtCursor())
data.put(Range::class.java, editor.cursorRange) data.put(Range::class.java, editor.cursorRange)
data.put( data.put(
JavaLanguageServer::class.java, JavaLanguageServer::class.java,
StudioApp.getInstance().javaLanguageServer as JavaLanguageServer) StudioApp.getInstance().javaLanguageServer as JavaLanguageServer)
data.put( data.put(
XMLLanguageServer::class.java, XMLLanguageServer::class.java,
StudioApp.getInstance().xmlLanguageServer as XMLLanguageServer) StudioApp.getInstance().xmlLanguageServer as XMLLanguageServer)
return data return data
} }
protected open fun getMenu(): Menu = menu protected open fun getMenu(): Menu = menu
private fun getDiagnosticAtCursor(): DiagnosticItem? { private fun getDiagnosticAtCursor(): DiagnosticItem? {
val language = editor.editorLanguage as? IDELanguage ?: return null val language = editor.editorLanguage as? IDELanguage ?: return null
val diagnostics = language.diagnostics val diagnostics = language.diagnostics
return DiagnosticUtil.binarySearchDiagnostic(diagnostics, editor.cursorRange.start) return DiagnosticUtil.binarySearchDiagnostic(diagnostics, editor.cursorRange.start)
} }
override fun onExec(action: ActionItem, result: Any) { override fun onExec(action: ActionItem, result: Any) {
if (action !is SelectAllAction) { if (action !is SelectAllAction) {
dismiss() dismiss()
} }
} }
override fun show() { override fun show() {
if (list.parent != null) { if (list.parent != null) {
(list.parent as ViewGroup).removeView(list) (list.parent as ViewGroup).removeView(list)
} }
fillMenu() fillMenu()
val dp8 = SizeUtils.dp2px(8f) val dp8 = SizeUtils.dp2px(8f)
val dp16 = dp8 * 2 val dp16 = dp8 * 2
this.list.measure( this.list.measure(
View.MeasureSpec.makeMeasureSpec(editor.width - dp16 * 2, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(editor.width - dp16 * 2, View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec( View.MeasureSpec.makeMeasureSpec(
(260 * editor.dpUnit).toInt() - dp16 * 2, View.MeasureSpec.AT_MOST)) (260 * editor.dpUnit).toInt() - dp16 * 2, View.MeasureSpec.AT_MOST))
setSize(findWidestItem(), this.list.measuredHeight) setSize(findWidestItem(), this.list.measuredHeight)
super.show() super.show()
} }
private fun findWidestItem(): Int { private fun findWidestItem(): Int {
var widest = 0 var widest = 0
val text = val text =
LayoutInflater.from(editor.context).inflate(R.layout.layout_popup_menu_item, null) as LayoutInflater.from(editor.context).inflate(R.layout.layout_popup_menu_item, null) as
MaterialTextView MaterialTextView
val dp30 = SizeUtils.dp2px(30f) val dp30 = SizeUtils.dp2px(30f)
val paddingHorizontal = text.paddingStart + text.paddingEnd val paddingHorizontal = text.paddingStart + text.paddingEnd
val drawablePadding = text.compoundDrawablePadding val drawablePadding = text.compoundDrawablePadding
val extraWidth = dp30 * 2 // 30dp for start and end drawables both val extraWidth = dp30 * 2 // 30dp for start and end drawables both
for (i in 0 until getMenu().size()) { for (i in 0 until getMenu().size()) {
val item = getMenu().getItem(i) val item = getMenu().getItem(i)
val title = item.title.toString() val title = item.title.toString()
var width = paddingHorizontal + (drawablePadding * 2) var width = paddingHorizontal + (drawablePadding * 2)
width += text.paint.measureText(title).toInt() width += text.paint.measureText(title).toInt()
width += extraWidth width += extraWidth
if (width > widest) { if (width > widest) {
widest = width widest = width
} }
} }
return widest return widest
} }
private class ActionsListAdapter(val menu: Menu, val context: Context) : BaseAdapter() { private class ActionsListAdapter(val menu: Menu, val context: Context) : BaseAdapter() {
override fun getCount(): Int = menu.size() override fun getCount(): Int = menu.size()
override fun getItem(position: Int): MenuItem = menu.getItem(position) override fun getItem(position: Int): MenuItem = menu.getItem(position)
override fun getItemId(position: Int): Long = position.toLong() override fun getItemId(position: Int): Long = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view: MaterialTextView = val view: MaterialTextView =
if (convertView == null || convertView !is MaterialButton) { if (convertView == null || convertView !is MaterialButton) {
(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater) (context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
.inflate(R.layout.layout_popup_menu_item, parent, false) as .inflate(R.layout.layout_popup_menu_item, parent, false) as
MaterialTextView MaterialTextView
} else { } else {
convertView as MaterialTextView convertView as MaterialTextView
} }
val item = getItem(position) val item = getItem(position)
view.text = item.title view.text = item.title
val start = item.icon ?: null val start = item.icon ?: null
var end: Drawable? = null var end: Drawable? = null
if (item.hasSubMenu()) { if (item.hasSubMenu()) {
end = ContextCompat.getDrawable(context, R.drawable.ic_submenu_arrow) end = ContextCompat.getDrawable(context, R.drawable.ic_submenu_arrow)
} }
view.setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null) view.setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null)
view.setOnClickListener { (item as MenuItemImpl).invoke() } view.setOnClickListener { (item as MenuItemImpl).invoke() }
return view return view
} }
} }
override fun onMenuItemSelected(menu: MenuBuilder, item: MenuItem): Boolean { override fun onMenuItemSelected(menu: MenuBuilder, item: MenuItem): Boolean {
// Click event of MenuItems without SubMenu is consumed by the ActionsRegistry // Click event of MenuItems without SubMenu is consumed by the ActionsRegistry
// So we only need to handle click event of SubMenus // So we only need to handle click event of SubMenus
if (!item.hasSubMenu()) { if (!item.hasSubMenu()) {
return false return false
} }
if (item.hasSubMenu() && item.subMenu is SubMenuBuilder) { if (item.hasSubMenu() && item.subMenu is SubMenuBuilder) {
(item.subMenu as SubMenuBuilder).setCallback(this) (item.subMenu as SubMenuBuilder).setCallback(this)
} }
this.editor.post { this.editor.post {
TransitionManager.beginDelayedTransition(this.list, ChangeBounds()) TransitionManager.beginDelayedTransition(this.list, ChangeBounds())
this.list.adapter = ActionsListAdapter(item.subMenu, editor.context) this.list.adapter = ActionsListAdapter(item.subMenu, editor.context)
} }
return true return true
} }
override fun onMenuModeChange(menu: MenuBuilder) {} override fun onMenuModeChange(menu: MenuBuilder) {}
} }
File diff suppressed because it is too large Load Diff
@@ -1,107 +1,107 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.itsaky.androidide.views.editor package com.itsaky.androidide.views.editor
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import android.view.ActionMode import android.view.ActionMode
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.ActionItem import com.itsaky.androidide.actions.ActionItem
import com.itsaky.androidide.actions.ActionsRegistry import com.itsaky.androidide.actions.ActionsRegistry
import io.github.rosemoe.sora.R import io.github.rosemoe.sora.R
import io.github.rosemoe.sora.widget.CodeEditor import io.github.rosemoe.sora.widget.CodeEditor
import io.github.rosemoe.sora.widget.EditorSearcher.SearchOptions import io.github.rosemoe.sora.widget.EditorSearcher.SearchOptions
/** /**
* Action mode for find and replace functions in editor. * Action mode for find and replace functions in editor.
* @author Akash Yadav * @author Akash Yadav
*/ */
class SearchActionMode(val editor: IDEEditor) : ActionMode.Callback { class SearchActionMode(val editor: IDEEditor) : ActionMode.Callback {
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
if (mode == null) { if (mode == null) {
return false return false
} }
if (menu is MenuBuilder) { if (menu is MenuBuilder) {
menu.setOptionalIconsVisible(true) menu.setOptionalIconsVisible(true)
} }
val search = SearchView(editor.context) val search = SearchView(editor.context)
search.setOnQueryTextListener(OnQueryListener(editor)) search.setOnQueryTextListener(OnQueryListener(editor))
search.isIconified = false search.isIconified = false
search.queryHint = editor.context.getString(R.string.text_to_search) search.queryHint = editor.context.getString(R.string.text_to_search)
search.performClick() search.performClick()
search.setIconifiedByDefault(false) search.setIconifiedByDefault(false)
mode.customView = search mode.customView = search
return true return true
} }
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
if (mode == null || menu == null) { if (mode == null || menu == null) {
return false return false
} }
menu.clear() menu.clear()
val registry = ActionsRegistry.getInstance() val registry = ActionsRegistry.getInstance()
registry.fillMenu(createData(mode), ActionItem.Location.EDITOR_SEARCH_ACTION_MODE, menu) registry.fillMenu(createData(mode), ActionItem.Location.EDITOR_SEARCH_ACTION_MODE, menu)
return true return true
} }
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return true return true
} }
override fun onDestroyActionMode(mode: ActionMode?) { override fun onDestroyActionMode(mode: ActionMode?) {
editor.searcher.stopSearch() editor.searcher.stopSearch()
} }
private fun createData(mode: ActionMode): ActionData { private fun createData(mode: ActionMode): ActionData {
return ActionData().apply { return ActionData().apply {
put(Context::class.java, editor.context) put(Context::class.java, editor.context)
put(IDEEditor::class.java, editor) put(IDEEditor::class.java, editor)
put(CodeEditor::class.java, editor) put(CodeEditor::class.java, editor)
put(ActionMode::class.java, mode) put(ActionMode::class.java, mode)
} }
} }
private class OnQueryListener(val editor: IDEEditor) : SearchView.OnQueryTextListener { private class OnQueryListener(val editor: IDEEditor) : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean { override fun onQueryTextSubmit(query: String?): Boolean {
editor.searcher.gotoNext() editor.searcher.gotoNext()
return false return false
} }
override fun onQueryTextChange(newText: String?): Boolean { override fun onQueryTextChange(newText: String?): Boolean {
if (TextUtils.isEmpty(newText)) { if (TextUtils.isEmpty(newText)) {
editor.searcher.stopSearch() editor.searcher.stopSearch()
return false return false
} }
editor.searcher.search(newText!!, SearchOptions(false, false)) editor.searcher.search(newText!!, SearchOptions(false, false))
return false return false
} }
} }
} }
@@ -1,117 +1,117 @@
/* /*
* This file is part of AndroidIDE. * This file is part of AndroidIDE.
* *
* AndroidIDE is free software: you can redistribute it and/or modify * AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* AndroidIDE is distributed in the hope that it will be useful, * AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. * along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/ */
@file:Suppress("DEPRECATION") @file:Suppress("DEPRECATION")
package io.github.rosemoe.sora.widget package io.github.rosemoe.sora.widget
import android.app.ProgressDialog import android.app.ProgressDialog
import android.widget.Toast import android.widget.Toast
import com.itsaky.androidide.R.string import com.itsaky.androidide.R.string
import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.views.editor.IDEEditor import com.itsaky.androidide.views.editor.IDEEditor
import io.github.rosemoe.sora.text.TextUtils import io.github.rosemoe.sora.text.TextUtils
import io.github.rosemoe.sora.util.IntPair import io.github.rosemoe.sora.util.IntPair
import java.util.concurrent.* import java.util.concurrent.*
/** /**
* Search text in editor. As the constructor of [EditorSearcher] is package private, we cannot * Search text in editor. As the constructor of [EditorSearcher] is package private, we cannot
* extend it in another package. So we put this class in the same package. * extend it in another package. So we put this class in the same package.
* *
* @author Akash Yadav * @author Akash Yadav
*/ */
open class IDEEditorSearcher(editor: IDEEditor) : EditorSearcher(editor) { open class IDEEditorSearcher(editor: IDEEditor) : EditorSearcher(editor) {
private val log = ILogger.newInstance(javaClass.simpleName) private val log = ILogger.newInstance(javaClass.simpleName)
protected fun getEditor(): CodeEditor { protected fun getEditor(): CodeEditor {
try { try {
val field = EditorSearcher::class.java.getDeclaredField("mEditor") val field = EditorSearcher::class.java.getDeclaredField("mEditor")
field.isAccessible = true field.isAccessible = true
return field.get(this) as CodeEditor return field.get(this) as CodeEditor
} catch (error: Throwable) { } catch (error: Throwable) {
throw RuntimeException("Unable get instance of editor", error) throw RuntimeException("Unable get instance of editor", error)
} }
} }
fun replaceAllAsync(replacement: String): CompletableFuture<Void> { fun replaceAllAsync(replacement: String): CompletableFuture<Void> {
val dialog = val dialog =
ProgressDialog.show( ProgressDialog.show(
getEditor().context, getEditor().context,
getEditor().context.getString(string.replaceAll), getEditor().context.getString(string.replaceAll),
getEditor().context.getString(string.msg_replacing), getEditor().context.getString(string.msg_replacing),
true, true,
false) false)
return CompletableFuture.runAsync { replaceAll(replacement) }.thenAccept { return CompletableFuture.runAsync { replaceAll(replacement) }.thenAccept {
getEditor().post { dialog.dismiss() } getEditor().post { dialog.dismiss() }
} }
} }
override fun replaceAll(replacement: String) { override fun replaceAll(replacement: String) {
checkState() checkState()
if (!isResultValid) { if (!isResultValid) {
Toast.makeText(getEditor().context, "Editor is still preparing", Toast.LENGTH_SHORT) Toast.makeText(getEditor().context, "Editor is still preparing", Toast.LENGTH_SHORT)
.show() .show()
return return
} }
val res = mLastResults val res = mLastResults
try { try {
val sb = getEditor().text.toStringBuilder() val sb = getEditor().text.toStringBuilder()
val newLength = replacement.length val newLength = replacement.length
if (mOptions.useRegex) { if (mOptions.useRegex) {
var delta = 0 var delta = 0
for (i in 0 until res.size()) { for (i in 0 until res.size()) {
val region = res[i] val region = res[i]
val start = IntPair.getFirst(region) val start = IntPair.getFirst(region)
val end = IntPair.getSecond(region) val end = IntPair.getSecond(region)
val oldLength = end - start val oldLength = end - start
sb.replace(start + delta, end + delta, replacement) sb.replace(start + delta, end + delta, replacement)
delta += newLength - oldLength delta += newLength - oldLength
} }
} else { } else {
var fromIndex = 0 var fromIndex = 0
var foundIndex: Int var foundIndex: Int
while (TextUtils.indexOf(sb, mPattern, mOptions.ignoreCase, fromIndex).also { while (TextUtils.indexOf(sb, mPattern, mOptions.ignoreCase, fromIndex).also {
foundIndex = it foundIndex = it
} != -1) { } != -1) {
sb.replace(foundIndex, foundIndex + mPattern.length, replacement) sb.replace(foundIndex, foundIndex + mPattern.length, replacement)
fromIndex = foundIndex + newLength fromIndex = foundIndex + newLength
} }
} }
getEditor().post { getEditor().post {
val pos = getEditor().cursor.left() val pos = getEditor().cursor.left()
// stopSearch(); // stopSearch();
getEditor() getEditor()
.text .text
.replace( .replace(
0, 0,
0, 0,
getEditor().lineCount - 1, getEditor().lineCount - 1,
getEditor().text.getColumnCount(getEditor().lineCount - 1), getEditor().text.getColumnCount(getEditor().lineCount - 1),
sb) sb)
getEditor().setSelectionAround(pos.line, pos.column) getEditor().setSelectionAround(pos.line, pos.column)
} }
} catch (e: Exception) { } catch (e: Exception) {
log.error("Failed to replace", e) log.error("Failed to replace", e)
} }
} }
private fun checkState() { private fun checkState() {
check(hasQuery()) { "pattern not set" } check(hasQuery()) { "pattern not set" }
} }
} }
+9 -9
View File
@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M13,7h-2v4L7,11v2h4v4h2v-4h4v-2h-4L13,7zM12,2C6.49,2 2,6.49 2,12s4.49,10 10,10h8c1.1,0 2,-0.9 2,-2v-8c0,-5.51 -4.49,-10 -10,-10zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/> android:pathData="M13,7h-2v4L7,11v2h4v4h2v-4h4v-2h-4L13,7zM12,2C6.49,2 2,6.49 2,12s4.49,10 10,10h8c1.1,0 2,-0.9 2,-2v-8c0,-5.51 -4.49,-10 -10,-10zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector> </vector>
@@ -1,8 +1,8 @@
<!-- drawable/form_textbox_password.xml --> <!-- drawable/form_textbox_password.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" android:height="24dp"
android:width="24dp" android:width="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path android:fillColor="#fff" android:pathData="M17,7H22V17H17V19A1,1 0 0,0 18,20H20V22H17.5C16.95,22 16,21.55 16,21C16,21.55 15.05,22 14.5,22H12V20H14A1,1 0 0,0 15,19V5A1,1 0 0,0 14,4H12V2H14.5C15.05,2 16,2.45 16,3C16,2.45 16.95,2 17.5,2H20V4H18A1,1 0 0,0 17,5V7M2,7H13V9H4V15H13V17H2V7M20,15V9H17V15H20M8.5,12A1.5,1.5 0 0,0 7,10.5A1.5,1.5 0 0,0 5.5,12A1.5,1.5 0 0,0 7,13.5A1.5,1.5 0 0,0 8.5,12M13,10.89C12.39,10.33 11.44,10.38 10.88,11C10.32,11.6 10.37,12.55 11,13.11C11.55,13.63 12.43,13.63 13,13.11V10.89Z" /> <path android:fillColor="#fff" android:pathData="M17,7H22V17H17V19A1,1 0 0,0 18,20H20V22H17.5C16.95,22 16,21.55 16,21C16,21.55 15.05,22 14.5,22H12V20H14A1,1 0 0,0 15,19V5A1,1 0 0,0 14,4H12V2H14.5C15.05,2 16,2.45 16,3C16,2.45 16.95,2 17.5,2H20V4H18A1,1 0 0,0 17,5V7M2,7H13V9H4V15H13V17H2V7M20,15V9H17V15H20M8.5,12A1.5,1.5 0 0,0 7,10.5A1.5,1.5 0 0,0 5.5,12A1.5,1.5 0 0,0 7,13.5A1.5,1.5 0 0,0 8.5,12M13,10.89C12.39,10.33 11.44,10.38 10.88,11C10.32,11.6 10.37,12.55 11,13.11C11.55,13.63 12.43,13.63 13,13.11V10.89Z" />
</vector> </vector>
+7 -7
View File
@@ -1,8 +1,8 @@
<!-- drawable/eyedropper.xml --> <!-- drawable/eyedropper.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" android:height="24dp"
android:width="24dp" android:width="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path android:fillColor="#fff" android:pathData="M19.35,11.72L17.22,13.85L15.81,12.43L8.1,20.14L3.5,22L2,20.5L3.86,15.9L11.57,8.19L10.15,6.78L12.28,4.65L19.35,11.72M16.76,3C17.93,1.83 19.83,1.83 21,3C22.17,4.17 22.17,6.07 21,7.24L19.08,9.16L14.84,4.92L16.76,3M5.56,17.03L4.5,19.5L6.97,18.44L14.4,11L13,9.6L5.56,17.03Z" /> <path android:fillColor="#fff" android:pathData="M19.35,11.72L17.22,13.85L15.81,12.43L8.1,20.14L3.5,22L2,20.5L3.86,15.9L11.57,8.19L10.15,6.78L12.28,4.65L19.35,11.72M16.76,3C17.93,1.83 19.83,1.83 21,3C22.17,4.17 22.17,6.07 21,7.24L19.08,9.16L14.84,4.92L16.76,3M5.56,17.03L4.5,19.5L6.97,18.44L14.4,11L13,9.6L5.56,17.03Z" />
</vector> </vector>
+9 -9
View File
@@ -1,10 +1,10 @@
<!-- drawable/auto_fix.xml --> <!-- drawable/auto_fix.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" /> android:pathData="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />
</vector> </vector>
@@ -1,10 +1,10 @@
<!-- drawable/menu_right.xml --> <!-- drawable/menu_right.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#fff" android:fillColor="#fff"
android:pathData="M10,17L15,12L10,7V17Z" /> android:pathData="M10,17L15,12L10,7V17Z" />
</vector> </vector>
+10 -10
View File
@@ -1,10 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24"
android:autoMirrored="true"> android:autoMirrored="true">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M4,19h6v-2L4,17v2zM20,5L4,5v2h16L20,5zM17,11L4,11v2h13.25c1.1,0 2,0.9 2,2s-0.9,2 -2,2L15,17v-2l-3,3 3,3v-2h2c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4z"/> android:pathData="M4,19h6v-2L4,17v2zM20,5L4,5v2h16L20,5zM17,11L4,11v2h13.25c1.1,0 2,0.9 2,2s-0.9,2 -2,2L15,17v-2l-3,3 3,3v-2h2c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4z"/>
</vector> </vector>
@@ -1,84 +1,84 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of AndroidIDE. ~ This file is part of AndroidIDE.
~ ~
~ AndroidIDE is free software: you can redistribute it and/or modify ~ AndroidIDE is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by ~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or ~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version. ~ (at your option) any later version.
~ ~
~ AndroidIDE is distributed in the hope that it will be useful, ~ AndroidIDE is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details. ~ GNU General Public License for more details.
~ ~
~ You should have received a copy of the GNU General Public License ~ You should have received a copy of the GNU General Public License
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. ~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
--> -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:padding="16dp"> tools:padding="16dp">
<RadioGroup <RadioGroup
android:id="@+id/choice_group" android:id="@+id/choice_group"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<RadioButton <RadioButton
android:id="@+id/bool_true" android:id="@+id/bool_true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:padding="2dp" android:padding="2dp"
android:text="true" android:text="true"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<RadioButton <RadioButton
android:id="@+id/bool_false" android:id="@+id/bool_false"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:padding="2dp" android:padding="2dp"
android:text="false" android:text="false"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</RadioGroup> </RadioGroup>
<View <View
android:id="@+id/divider1" android:id="@+id/divider1"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_margin="16dp" android:layout_margin="16dp"
android:background="@color/primaryLightColor" android:background="@color/primaryLightColor"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/choice_group" /> app:layout_constraintTop_toBottomOf="@id/choice_group" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/boolean_res_input" android:id="@+id/boolean_res_input"
style="@style/AppTheme.TextInputLayout.Outlined" style="@style/AppTheme.TextInputLayout.Outlined"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:hint="@string/hint_boolean_res" android:hint="@string/hint_boolean_res"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider1"> app:layout_constraintTop_toBottomOf="@id/divider1">
<com.google.android.material.textfield.MaterialAutoCompleteTextView <com.google.android.material.textfield.MaterialAutoCompleteTextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="56dp"
android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@" android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@"
android:padding="16dp" /> android:padding="16dp" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,29 +1,29 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of AndroidIDE. ~ This file is part of AndroidIDE.
~ ~
~ AndroidIDE is free software: you can redistribute it and/or modify ~ AndroidIDE is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by ~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or ~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version. ~ (at your option) any later version.
~ ~
~ AndroidIDE is distributed in the hope that it will be useful, ~ AndroidIDE is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details. ~ GNU General Public License for more details.
~ ~
~ You should have received a copy of the GNU General Public License ~ You should have received a copy of the GNU General Public License
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. ~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
--> -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="36dp" android:layout_height="36dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:textSize="11sp" android:textSize="11sp"
tools:text="Implement abstract methods"> tools:text="Implement abstract methods">
</TextView> </TextView>
@@ -1,78 +1,78 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of AndroidIDE. ~ This file is part of AndroidIDE.
~ ~
~ AndroidIDE is free software: you can redistribute it and/or modify ~ AndroidIDE is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by ~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or ~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version. ~ (at your option) any later version.
~ ~
~ AndroidIDE is distributed in the hope that it will be useful, ~ AndroidIDE is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details. ~ GNU General Public License for more details.
~ ~
~ You should have received a copy of the GNU General Public License ~ You should have received a copy of the GNU General Public License
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. ~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
--> -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:padding="16dp"> tools:padding="16dp">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/pick_color" android:id="@+id/pick_color"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/title_color_picker" android:text="@string/title_color_picker"
android:textColor="@color/primaryTextColor" android:textColor="@color/primaryTextColor"
app:icon="@drawable/ic_pick_color" app:icon="@drawable/ic_pick_color"
app:iconTint="@color/primaryIconColor" app:iconTint="@color/primaryIconColor"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:rippleColor="@color/secondaryColor" /> app:rippleColor="@color/secondaryColor" />
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/color_preview" android:id="@+id/color_preview"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/pick_color" app:layout_constraintStart_toEndOf="@id/pick_color"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:strokeColor="@color/primaryDarkColor" app:strokeColor="@color/primaryDarkColor"
app:strokeWidth="1dp" /> app:strokeWidth="1dp" />
<View <View
android:id="@+id/divider1" android:id="@+id/divider1"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_margin="16dp" android:layout_margin="16dp"
android:background="@color/primaryLightColor" android:background="@color/primaryLightColor"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pick_color" /> app:layout_constraintTop_toBottomOf="@id/pick_color" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/color_res_input" android:id="@+id/color_res_input"
style="@style/AppTheme.TextInputLayout.Outlined" style="@style/AppTheme.TextInputLayout.Outlined"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:hint="@string/hint_color_resource" android:hint="@string/hint_color_resource"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider1"> app:layout_constraintTop_toBottomOf="@id/divider1">
<com.google.android.material.textfield.MaterialAutoCompleteTextView <com.google.android.material.textfield.MaterialAutoCompleteTextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="56dp"
android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@" android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@"
android:padding="16dp" /> android:padding="16dp" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

Some files were not shown because too many files have changed in this diff Show More