mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Resolved merge conflicts
This commit is contained in:
@@ -1,24 +1,24 @@
|
||||
name: Delete old workflow runs
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days:
|
||||
description: 'Number of days.'
|
||||
required: true
|
||||
default: 30
|
||||
minimum_runs:
|
||||
description: 'The minimum runs to keep for each workflow.'
|
||||
required: true
|
||||
default: 6
|
||||
|
||||
jobs:
|
||||
del_runs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete workflow runs
|
||||
uses: Mattraks/delete-workflow-runs@v2
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
repository: ${{ github.repository }}
|
||||
retain_days: ${{ github.event.inputs.days }}
|
||||
name: Delete old workflow runs
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days:
|
||||
description: 'Number of days.'
|
||||
required: true
|
||||
default: 30
|
||||
minimum_runs:
|
||||
description: 'The minimum runs to keep for each workflow.'
|
||||
required: true
|
||||
default: 6
|
||||
|
||||
jobs:
|
||||
del_runs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete workflow runs
|
||||
uses: Mattraks/delete-workflow-runs@v2
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
repository: ${{ github.repository }}
|
||||
retain_days: ${{ github.event.inputs.days }}
|
||||
keep_minimum_runs: ${{ github.event.inputs.minimum_runs }}
|
||||
Generated
+1
-1
@@ -5,7 +5,7 @@
|
||||
<profile name="Gradle Imported" enabled="true">
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<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>
|
||||
<module name="AndroidIDE.build-tools.google-java-format.main" />
|
||||
</profile>
|
||||
|
||||
Generated
+17
@@ -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>
|
||||
Generated
+5
-5
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<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" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<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" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
-5
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GoogleJavaFormatSettings">
|
||||
<option name="enabled" value="true" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GoogleJavaFormatSettings">
|
||||
<option name="enabled" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
@@ -61,5 +61,10 @@
|
||||
<option name="name" value="maven2" />
|
||||
<option name="url" value="https://repo.eclipse.org/content/repositories/lemminx-snapshots/" />
|
||||
</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>
|
||||
</project>
|
||||
Generated
+6
-6
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KtfmtSettings">
|
||||
<option name="enabled" value="true" />
|
||||
<option name="uiFormatterStyle" value="Dropbox" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KtfmtSettings">
|
||||
<option name="enabled" value="true" />
|
||||
<option name="uiFormatterStyle" value="Dropbox" />
|
||||
</component>
|
||||
</project>
|
||||
+179
-179
@@ -1,179 +1,179 @@
|
||||
# AndroidIDE Changelog
|
||||
This is the changelog of all the releases of AndroidIDE.
|
||||
|
||||
## **v2.0.2-beta** (in development)
|
||||
|
||||
### Important notes
|
||||
<!--Breaking changes and other important stuff here-->
|
||||
|
||||
### Additions
|
||||
- Warn about empty body for the following statements :
|
||||
- `if`
|
||||
- `else`
|
||||
- `try`
|
||||
- `catch`
|
||||
- `finally`
|
||||
- `for`
|
||||
- `while`
|
||||
- `do while`
|
||||
- New java code actions :
|
||||
- `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.
|
||||
- `Remove unused imports`: Removes imports that are not used in the source file.
|
||||
- `Import orderer`: Organizes the imports. Thanks to @MrIkso (#180).
|
||||
- XML Code formatter. 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.
|
||||
- Added Brazilian Portuguese translations. Thanks to @Hakaisu (#165).
|
||||
|
||||
### Removals
|
||||
<!--Things that were removed-->
|
||||
|
||||
### Fixes
|
||||
- Crash when performing 'Replace all' action in editor (#158).
|
||||
- Crash when layout inflation fails (#168).
|
||||
- Always show toasts on UI thread (#160).
|
||||
- Fixed: Multiline logs in IDE logs are not highlighted properly.
|
||||
- Fixed: Invalid log line formatting when tag to too long.
|
||||
- Fixed: Application crashes when layout inflation fails (#168).
|
||||
- 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.
|
||||
|
||||
### Improvements
|
||||
- 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.
|
||||
- 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).
|
||||
- Updated Hindi translations. Thanks to @Premjit-Chowdhury (#171).
|
||||
- Updated Russian translation. Thansk to @AndreyKozhev (#181).
|
||||
|
||||
## **v2.0.1-beta**
|
||||
AndroidIDE v2.0.1-beta is here with new features, improvements and bug fixes.
|
||||
|
||||
### Important notes
|
||||
- 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
|
||||
build tools need to be installed when installing AndroidIDE on devices running Android 12.
|
||||
- Google services and Firebase integration has been removed.
|
||||
|
||||
### Additions
|
||||
- Code formatter for Java source code.
|
||||
- Support for
|
||||
[`GestureOverlayView`](https://developer.android.com/reference/android/gesture/GestureOverlayView),
|
||||
[`ToggleButton`](https://developer.android.com/reference/android/widget/ToggleButton),
|
||||
[`Switch`](https://developer.android.com/reference/android/widget/Switch),
|
||||
[`GridLayout`](https://developer.android.com/reference/android/widget/GridLayout),
|
||||
[`ListView`](https://developer.android.com/reference/android/widget/ListView) (#145) in layout
|
||||
inflater.
|
||||
- Preference to disable use of `TYPE_TEXT_VARIATION_VISIBLE_PASSWORD` flag in editor.
|
||||
- Automatically trigger completion when attributes are inserted from completion window.
|
||||
- New java code actions :
|
||||
- Add import
|
||||
- Add 'throws'
|
||||
- Generate constructor
|
||||
- Generate setters/getters
|
||||
- Field to block
|
||||
- Remove unused class
|
||||
- Remove unused method
|
||||
- Remove unused 'throws'
|
||||
- You can now override multiple superclass methods at once.
|
||||
- `DocumentsProvider` for providing access to files in `HOME` directory.
|
||||
|
||||
### Removals
|
||||
- Removed initial view attributes from view tags completion in XMLCompletionProvider.
|
||||
- Vertical text action window has been removed.
|
||||
- Removed Google services and Firebase integration.
|
||||
- Removed the horizontal text actions menu from the editor in favour of the new actions menu.
|
||||
|
||||
### Fixes
|
||||
- Cash in attribute editors when adding an attribute.
|
||||
- Crash when selecting diagnostic item from diagnostics list.
|
||||
- Some diagnostic items are not shown in diagnostic window (#96).
|
||||
- Diagnostics were updated late in the editor.
|
||||
- Popup windows created by editor are not dismissed even when the file is closed.
|
||||
- No build output when activity is recreated (#121).
|
||||
- Crash in UI Designer when activity is recreated (#124).
|
||||
- Invalid syntax highlighting for application logs (#152).
|
||||
- Illegal argument exception while overriding methods in Java completion provider.
|
||||
|
||||
### Improvements
|
||||
- Do not import classes from `java.lang` package when completion items are selected.
|
||||
- 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
|
||||
expanded.
|
||||
- Improved the editor actions menu.
|
||||
- Sort completion items according to their kind and sort text.
|
||||
- Restore file tree state when application is resumed (#150).
|
||||
- Updated templates to work with Android 12 (#144).
|
||||
- Updated Chinese translation. Thanks to @mikofe (#128).
|
||||
- Updated French translation. Thanks to @Se-Lyan (#136).
|
||||
- Updated German translation (#144).
|
||||
|
||||
## **v2.0.0-beta**
|
||||
|
||||
AndroidIDE v2.0.0-beta is here with new features and improvements.
|
||||
|
||||
### Important Notes
|
||||
|
||||
- This is a pre-release and is not so stable. There are some features that are partially
|
||||
implemented.
|
||||
- The minimum SDK has been raised to `26` (`Android O`).
|
||||
|
||||
### Additions
|
||||
|
||||
- XML Layout Designer with ability to preview XML drawables.
|
||||
- Added packages for terminal.
|
||||
- libGDx project template. Thanks to @Smooth-E (#81).
|
||||
- Kotlin project template. Thanks to @BanDroid (#60).
|
||||
- Preference to switch between `bash` and `/system/bin/sh` in terminal.
|
||||
- Preference for matching partial names in java completion.
|
||||
- Preference to choose between `floating window text actions` and `text actions in ActionMode`.
|
||||
- Preference to enable/disable font ligatures.
|
||||
- Show process output when build tools installation fails.
|
||||
- Ability to expand selection in supported languages.
|
||||
- Ask user to save unsaved files before closing project.
|
||||
- Hindi translation. Thanks to @Premjit-Chowdhury (#70, #127).
|
||||
- German translation. Thanks to @MarvinStelter.
|
||||
- Indonesian translation. Thanks to @BanDroid (#54, #57, #60, #72, #125).
|
||||
- Chinese translation. Thanks to @Rosemoe (#56, #73, #104) and @mikofe (#118, #119, #123).
|
||||
- French translation. Thanks to @Se-Lyan (#79).
|
||||
- Russian translation. Thanks to @Smooth-E (#97).
|
||||
|
||||
### Removals
|
||||
|
||||
- Autosave of files is removed. However, a preference has been added for the same which, when
|
||||
enabled, saves the files automatically before close.
|
||||
|
||||
### Fixes
|
||||
|
||||
- File does not close and crashes if reopened and closed again.
|
||||
- Flickering of whole auto completion window.
|
||||
- Flickering of completion item due to API info.
|
||||
- Build tools installation fails if `JAVA_HOME` does not exist.
|
||||
- Invalid syntax highlight for method invocations in java.
|
||||
- Package private classes not included in java completion.
|
||||
- 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.
|
||||
- `LogSender` class not included in JLS workspace.
|
||||
|
||||
### Improvements
|
||||
|
||||
- Show crash report when the IDE crashes.
|
||||
- The Java language server is now embedded in the IDE itself.
|
||||
- The AndroidIDE Gradle plugin is now packaged with the IDE.
|
||||
- Show name of opened project.
|
||||
- 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
|
||||
and is no more dependent on JDK installation.
|
||||
- Disable `Save` option when all files are saved.
|
||||
- Allow installation of files from file tree.
|
||||
|
||||
### New Contributors
|
||||
|
||||
* @BanDroid
|
||||
* @Rosemoe
|
||||
* @Premjit-Chowdhury
|
||||
* @Se-Lyan
|
||||
* @Smooth-E
|
||||
* @ansh2166
|
||||
* @mikofe
|
||||
# AndroidIDE Changelog
|
||||
This is the changelog of all the releases of AndroidIDE.
|
||||
|
||||
## **v2.0.2-beta** (in development)
|
||||
|
||||
### Important notes
|
||||
<!--Breaking changes and other important stuff here-->
|
||||
|
||||
### Additions
|
||||
- Warn about empty body for the following statements :
|
||||
- `if`
|
||||
- `else`
|
||||
- `try`
|
||||
- `catch`
|
||||
- `finally`
|
||||
- `for`
|
||||
- `while`
|
||||
- `do while`
|
||||
- New java code actions :
|
||||
- `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.
|
||||
- `Remove unused imports`: Removes imports that are not used in the source file.
|
||||
- `Import orderer`: Organizes the imports. Thanks to @MrIkso (#180).
|
||||
- XML Code formatter. 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.
|
||||
- Added Brazilian Portuguese translations. Thanks to @Hakaisu (#165).
|
||||
|
||||
### Removals
|
||||
<!--Things that were removed-->
|
||||
|
||||
### Fixes
|
||||
- Crash when performing 'Replace all' action in editor (#158).
|
||||
- Crash when layout inflation fails (#168).
|
||||
- Always show toasts on UI thread (#160).
|
||||
- Fixed: Multiline logs in IDE logs are not highlighted properly.
|
||||
- Fixed: Invalid log line formatting when tag to too long.
|
||||
- Fixed: Application crashes when layout inflation fails (#168).
|
||||
- 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.
|
||||
|
||||
### Improvements
|
||||
- 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.
|
||||
- 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).
|
||||
- Updated Hindi translations. Thanks to @Premjit-Chowdhury (#171).
|
||||
- Updated Russian translation. Thansk to @AndreyKozhev (#181).
|
||||
|
||||
## **v2.0.1-beta**
|
||||
AndroidIDE v2.0.1-beta is here with new features, improvements and bug fixes.
|
||||
|
||||
### Important notes
|
||||
- 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
|
||||
build tools need to be installed when installing AndroidIDE on devices running Android 12.
|
||||
- Google services and Firebase integration has been removed.
|
||||
|
||||
### Additions
|
||||
- Code formatter for Java source code.
|
||||
- Support for
|
||||
[`GestureOverlayView`](https://developer.android.com/reference/android/gesture/GestureOverlayView),
|
||||
[`ToggleButton`](https://developer.android.com/reference/android/widget/ToggleButton),
|
||||
[`Switch`](https://developer.android.com/reference/android/widget/Switch),
|
||||
[`GridLayout`](https://developer.android.com/reference/android/widget/GridLayout),
|
||||
[`ListView`](https://developer.android.com/reference/android/widget/ListView) (#145) in layout
|
||||
inflater.
|
||||
- Preference to disable use of `TYPE_TEXT_VARIATION_VISIBLE_PASSWORD` flag in editor.
|
||||
- Automatically trigger completion when attributes are inserted from completion window.
|
||||
- New java code actions :
|
||||
- Add import
|
||||
- Add 'throws'
|
||||
- Generate constructor
|
||||
- Generate setters/getters
|
||||
- Field to block
|
||||
- Remove unused class
|
||||
- Remove unused method
|
||||
- Remove unused 'throws'
|
||||
- You can now override multiple superclass methods at once.
|
||||
- `DocumentsProvider` for providing access to files in `HOME` directory.
|
||||
|
||||
### Removals
|
||||
- Removed initial view attributes from view tags completion in XMLCompletionProvider.
|
||||
- Vertical text action window has been removed.
|
||||
- Removed Google services and Firebase integration.
|
||||
- Removed the horizontal text actions menu from the editor in favour of the new actions menu.
|
||||
|
||||
### Fixes
|
||||
- Cash in attribute editors when adding an attribute.
|
||||
- Crash when selecting diagnostic item from diagnostics list.
|
||||
- Some diagnostic items are not shown in diagnostic window (#96).
|
||||
- Diagnostics were updated late in the editor.
|
||||
- Popup windows created by editor are not dismissed even when the file is closed.
|
||||
- No build output when activity is recreated (#121).
|
||||
- Crash in UI Designer when activity is recreated (#124).
|
||||
- Invalid syntax highlighting for application logs (#152).
|
||||
- Illegal argument exception while overriding methods in Java completion provider.
|
||||
|
||||
### Improvements
|
||||
- Do not import classes from `java.lang` package when completion items are selected.
|
||||
- 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
|
||||
expanded.
|
||||
- Improved the editor actions menu.
|
||||
- Sort completion items according to their kind and sort text.
|
||||
- Restore file tree state when application is resumed (#150).
|
||||
- Updated templates to work with Android 12 (#144).
|
||||
- Updated Chinese translation. Thanks to @mikofe (#128).
|
||||
- Updated French translation. Thanks to @Se-Lyan (#136).
|
||||
- Updated German translation (#144).
|
||||
|
||||
## **v2.0.0-beta**
|
||||
|
||||
AndroidIDE v2.0.0-beta is here with new features and improvements.
|
||||
|
||||
### Important Notes
|
||||
|
||||
- This is a pre-release and is not so stable. There are some features that are partially
|
||||
implemented.
|
||||
- The minimum SDK has been raised to `26` (`Android O`).
|
||||
|
||||
### Additions
|
||||
|
||||
- XML Layout Designer with ability to preview XML drawables.
|
||||
- Added packages for terminal.
|
||||
- libGDx project template. Thanks to @Smooth-E (#81).
|
||||
- Kotlin project template. Thanks to @BanDroid (#60).
|
||||
- Preference to switch between `bash` and `/system/bin/sh` in terminal.
|
||||
- Preference for matching partial names in java completion.
|
||||
- Preference to choose between `floating window text actions` and `text actions in ActionMode`.
|
||||
- Preference to enable/disable font ligatures.
|
||||
- Show process output when build tools installation fails.
|
||||
- Ability to expand selection in supported languages.
|
||||
- Ask user to save unsaved files before closing project.
|
||||
- Hindi translation. Thanks to @Premjit-Chowdhury (#70, #127).
|
||||
- German translation. Thanks to @MarvinStelter.
|
||||
- Indonesian translation. Thanks to @BanDroid (#54, #57, #60, #72, #125).
|
||||
- Chinese translation. Thanks to @Rosemoe (#56, #73, #104) and @mikofe (#118, #119, #123).
|
||||
- French translation. Thanks to @Se-Lyan (#79).
|
||||
- Russian translation. Thanks to @Smooth-E (#97).
|
||||
|
||||
### Removals
|
||||
|
||||
- Autosave of files is removed. However, a preference has been added for the same which, when
|
||||
enabled, saves the files automatically before close.
|
||||
|
||||
### Fixes
|
||||
|
||||
- File does not close and crashes if reopened and closed again.
|
||||
- Flickering of whole auto completion window.
|
||||
- Flickering of completion item due to API info.
|
||||
- Build tools installation fails if `JAVA_HOME` does not exist.
|
||||
- Invalid syntax highlight for method invocations in java.
|
||||
- Package private classes not included in java completion.
|
||||
- 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.
|
||||
- `LogSender` class not included in JLS workspace.
|
||||
|
||||
### Improvements
|
||||
|
||||
- Show crash report when the IDE crashes.
|
||||
- The Java language server is now embedded in the IDE itself.
|
||||
- The AndroidIDE Gradle plugin is now packaged with the IDE.
|
||||
- Show name of opened project.
|
||||
- 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
|
||||
and is no more dependent on JDK installation.
|
||||
- Disable `Save` option when all files are saved.
|
||||
- Allow installation of files from file tree.
|
||||
|
||||
### New Contributors
|
||||
|
||||
* @BanDroid
|
||||
* @Rosemoe
|
||||
* @Premjit-Chowdhury
|
||||
* @Se-Lyan
|
||||
* @Smooth-E
|
||||
* @ansh2166
|
||||
* @mikofe
|
||||
|
||||
+27
-27
@@ -1,28 +1,28 @@
|
||||
# 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).
|
||||
|
||||
## Propose a change
|
||||
To contribute to this project,
|
||||
- Fork the repo.
|
||||
- Clone the forked repo to your local machine.
|
||||
- Open the project in Android Studio.
|
||||
- Make your changes.
|
||||
- Create a pull request.
|
||||
|
||||
## Note
|
||||
`Java 11` is required to build the project.
|
||||
|
||||
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.
|
||||
|
||||
## Source code format
|
||||
The source code must have `2-space` indentation (soft tab).
|
||||
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.
|
||||
**This will commit the changes as well**.
|
||||
|
||||
## 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`.
|
||||
|
||||
# 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).
|
||||
|
||||
## Propose a change
|
||||
To contribute to this project,
|
||||
- Fork the repo.
|
||||
- Clone the forked repo to your local machine.
|
||||
- Open the project in Android Studio.
|
||||
- Make your changes.
|
||||
- Create a pull request.
|
||||
|
||||
## Note
|
||||
`Java 11` is required to build the project.
|
||||
|
||||
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.
|
||||
|
||||
## Source code format
|
||||
The source code must have `2-space` indentation (soft tab).
|
||||
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.
|
||||
**This will commit the changes as well**.
|
||||
|
||||
## 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`.
|
||||
|
||||
If you want to discuss anything about the project, you can discuss it in the [Telegram group](https://t.me/androidide_discussions).
|
||||
+37
-37
@@ -1,37 +1,37 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
}
|
||||
|
||||
buildFeatures.viewBinding true
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
implementation common.editor
|
||||
implementation common.utilcode
|
||||
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
}
|
||||
|
||||
buildFeatures.viewBinding true
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
implementation common.editor
|
||||
implementation common.utilcode
|
||||
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<manifest package="com.itsaky.androidide.actions" />
|
||||
@@ -1,54 +1,54 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ActionData {
|
||||
|
||||
private final Map<Class<?>, Object> instances = new HashMap<>();
|
||||
|
||||
public <T> void put(@NonNull Class<T> type, @Nullable T object) {
|
||||
Objects.requireNonNull(type);
|
||||
this.instances.put(type, object);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T get(@NonNull Class<T> type) {
|
||||
Objects.requireNonNull(type);
|
||||
|
||||
final var object = this.instances.get(type);
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return type.cast(object);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.instances.clear();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ActionData {
|
||||
|
||||
private final Map<Class<?>, Object> instances = new HashMap<>();
|
||||
|
||||
public <T> void put(@NonNull Class<T> type, @Nullable T object) {
|
||||
Objects.requireNonNull(type);
|
||||
this.instances.put(type, object);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T get(@NonNull Class<T> type) {
|
||||
Objects.requireNonNull(type);
|
||||
|
||||
final var object = this.instances.get(type);
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return type.cast(object);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.instances.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
* An action that can be registered using the [ActionsRegistry]
|
||||
* [com.itsaky.androidide.actions.ActionsRegistry]
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ActionItem {
|
||||
|
||||
val id: String
|
||||
var label: String
|
||||
var visible: Boolean
|
||||
var enabled: Boolean
|
||||
var icon: Drawable?
|
||||
var requiresUIThread: Boolean
|
||||
var location: Location
|
||||
|
||||
/**
|
||||
* Prepare the action. Subclasses can modify the visual properties of this action here.
|
||||
* @param data The data containing various information about the event.
|
||||
*/
|
||||
fun prepare(data: ActionData)
|
||||
|
||||
/**
|
||||
* Execute the action. The action executed in a background thread by default.
|
||||
*
|
||||
* @param data The data containing various information about the event.
|
||||
* @return `true` if this action was executed successfully, `false` otherwise.
|
||||
*/
|
||||
fun execAction(data: ActionData): Any
|
||||
|
||||
/**
|
||||
* Called just after the [execAction] method executes **successfully** (i.e. returns `true`).
|
||||
* Subclasses are free to do UI related work here as this method is called on UI thread.
|
||||
*
|
||||
* @param data The data containing various information about the event.
|
||||
*/
|
||||
fun postExec(data: ActionData, result: Any) = Unit
|
||||
|
||||
/**
|
||||
* Return the show as action flags for the menu item.
|
||||
*
|
||||
* @return The show as action flags.
|
||||
*/
|
||||
fun getShowAsActionFlags(data: ActionData): Int = -1
|
||||
|
||||
/**
|
||||
* Checks if the given [ActionData] has instances of the given [types].
|
||||
*
|
||||
* @param data The data to check.
|
||||
* @param types The type of objects to look for.
|
||||
* @return `true` if the [data] has the given [types], `false` otherwise.
|
||||
*/
|
||||
fun hasRequiredData(data: ActionData, vararg types: Class<*>): Boolean {
|
||||
for (type in types) {
|
||||
data.get(type) ?: return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Marks this action item as invisible. */
|
||||
fun markInvisible() {
|
||||
visible = false
|
||||
enabled = false
|
||||
}
|
||||
|
||||
/** Location where an action item will be shown. */
|
||||
enum class Location(val id: String) {
|
||||
|
||||
/** Location marker for action items shown in editor activity's toolbar. */
|
||||
EDITOR_TOOLBAR("editor.activity.toolbar"),
|
||||
|
||||
/** Location marker for action items shown in editor's text action menu. */
|
||||
EDITOR_TEXT_ACTIONS("editor.textActions"),
|
||||
|
||||
/**
|
||||
* Location marker for action items shown in 'Code actions' submenu in editor's text action
|
||||
* menu.
|
||||
*/
|
||||
EDITOR_CODE_ACTIONS("editor.codeActions"),
|
||||
|
||||
/** Location marker for action items show in 'Find & Replace' action mode menu in editor. */
|
||||
EDITOR_SEARCH_ACTION_MODE("editor.searchActionMode");
|
||||
|
||||
override fun toString(): String {
|
||||
return id
|
||||
}
|
||||
|
||||
fun forId(id: String): Location {
|
||||
return values().first { it.id == id }
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
* An action that can be registered using the [ActionsRegistry]
|
||||
* [com.itsaky.androidide.actions.ActionsRegistry]
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ActionItem {
|
||||
|
||||
val id: String
|
||||
var label: String
|
||||
var visible: Boolean
|
||||
var enabled: Boolean
|
||||
var icon: Drawable?
|
||||
var requiresUIThread: Boolean
|
||||
var location: Location
|
||||
|
||||
/**
|
||||
* Prepare the action. Subclasses can modify the visual properties of this action here.
|
||||
* @param data The data containing various information about the event.
|
||||
*/
|
||||
fun prepare(data: ActionData)
|
||||
|
||||
/**
|
||||
* Execute the action. The action executed in a background thread by default.
|
||||
*
|
||||
* @param data The data containing various information about the event.
|
||||
* @return `true` if this action was executed successfully, `false` otherwise.
|
||||
*/
|
||||
fun execAction(data: ActionData): Any
|
||||
|
||||
/**
|
||||
* Called just after the [execAction] method executes **successfully** (i.e. returns `true`).
|
||||
* Subclasses are free to do UI related work here as this method is called on UI thread.
|
||||
*
|
||||
* @param data The data containing various information about the event.
|
||||
*/
|
||||
fun postExec(data: ActionData, result: Any) = Unit
|
||||
|
||||
/**
|
||||
* Return the show as action flags for the menu item.
|
||||
*
|
||||
* @return The show as action flags.
|
||||
*/
|
||||
fun getShowAsActionFlags(data: ActionData): Int = -1
|
||||
|
||||
/**
|
||||
* Checks if the given [ActionData] has instances of the given [types].
|
||||
*
|
||||
* @param data The data to check.
|
||||
* @param types The type of objects to look for.
|
||||
* @return `true` if the [data] has the given [types], `false` otherwise.
|
||||
*/
|
||||
fun hasRequiredData(data: ActionData, vararg types: Class<*>): Boolean {
|
||||
for (type in types) {
|
||||
data.get(type) ?: return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Marks this action item as invisible. */
|
||||
fun markInvisible() {
|
||||
visible = false
|
||||
enabled = false
|
||||
}
|
||||
|
||||
/** Location where an action item will be shown. */
|
||||
enum class Location(val id: String) {
|
||||
|
||||
/** Location marker for action items shown in editor activity's toolbar. */
|
||||
EDITOR_TOOLBAR("editor.activity.toolbar"),
|
||||
|
||||
/** Location marker for action items shown in editor's text action menu. */
|
||||
EDITOR_TEXT_ACTIONS("editor.textActions"),
|
||||
|
||||
/**
|
||||
* Location marker for action items shown in 'Code actions' submenu in editor's text action
|
||||
* menu.
|
||||
*/
|
||||
EDITOR_CODE_ACTIONS("editor.codeActions"),
|
||||
|
||||
/** Location marker for action items show in 'Find & Replace' action mode menu in editor. */
|
||||
EDITOR_SEARCH_ACTION_MODE("editor.searchActionMode");
|
||||
|
||||
override fun toString(): String {
|
||||
return id
|
||||
}
|
||||
|
||||
fun forId(id: String): Location {
|
||||
return values().first { it.id == id }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
/**
|
||||
* An action menu is an action which can contain child actions.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ActionMenu : ActionItem {
|
||||
|
||||
val children: MutableSet<ActionItem>
|
||||
|
||||
fun addAction(action: ActionItem) = children.add(action)
|
||||
|
||||
fun removeAction(action: ActionItem) = children.remove(action)
|
||||
|
||||
/** Action menus are not supposed to perform any action */
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
/**
|
||||
* An action menu is an action which can contain child actions.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface ActionMenu : ActionItem {
|
||||
|
||||
val children: MutableSet<ActionItem>
|
||||
|
||||
fun addAction(action: ActionItem) = children.add(action)
|
||||
|
||||
fun removeAction(action: ActionItem) = children.remove(action)
|
||||
|
||||
/** Action menus are not supposed to perform any action */
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +1,108 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.view.Menu
|
||||
import com.itsaky.androidide.actions.internal.DefaultActionsRegistry
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class ActionsRegistry {
|
||||
|
||||
companion object {
|
||||
|
||||
private var instance: ActionsRegistry? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(): ActionsRegistry {
|
||||
if (instance == null) {
|
||||
instance = DefaultActionsRegistry()
|
||||
}
|
||||
|
||||
return instance as ActionsRegistry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given action.
|
||||
*
|
||||
* @param action The action to register. Must not be `null`.
|
||||
* @return `true` if the action was registered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun registerAction(action: ActionItem): Boolean
|
||||
|
||||
/**
|
||||
* Unregister the given action.
|
||||
*
|
||||
* @param action The action to unregister. Must not be `null`.
|
||||
* @return `true` if the action was unregistered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun unregisterAction(action: ActionItem): Boolean
|
||||
|
||||
/**
|
||||
* Unregister the action with the given ID.
|
||||
*
|
||||
* @param id The ID of action to unregister. Must not be `null`.
|
||||
* @return `true` if the action was unregistered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun unregisterAction(id: String): Boolean
|
||||
|
||||
/**
|
||||
* Find the action with the given ID.
|
||||
*
|
||||
* @param id The ID of the action to find.
|
||||
* @return The found action or `null`.
|
||||
*/
|
||||
abstract fun findAction(location: ActionItem.Location, id: String): ActionItem?
|
||||
|
||||
/**
|
||||
* Fill the given menu with the registered actions.
|
||||
*
|
||||
* Subclasses must first call [ActionItem.prepare] to update the action.
|
||||
*/
|
||||
abstract fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu)
|
||||
|
||||
/** Get all the registered actions at the given location. */
|
||||
abstract fun getActions(location: ActionItem.Location): Map<String, ActionItem>
|
||||
/** Clear all the registered actions. */
|
||||
abstract fun clearActions(location: ActionItem.Location)
|
||||
|
||||
/**
|
||||
* Register a listener that will be called when any action is executed. The callbacks are called
|
||||
* irrespective of the action's execution result.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
abstract fun registerActionExecListener(listener: ActionExecListener)
|
||||
|
||||
/**
|
||||
* Unregister the given listener.
|
||||
*
|
||||
* @param listener The listener to unregister.
|
||||
*/
|
||||
abstract fun unregisterActionExecListener(listener: ActionExecListener)
|
||||
|
||||
/** Listener for listening to callbacks for action execution. */
|
||||
interface ActionExecListener {
|
||||
|
||||
/**
|
||||
* The given action was executed.
|
||||
* @param action The action that was executed.
|
||||
* @param result The result of the action execution.
|
||||
*/
|
||||
fun onExec(action: ActionItem, result: Any)
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.view.Menu
|
||||
import com.itsaky.androidide.actions.internal.DefaultActionsRegistry
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class ActionsRegistry {
|
||||
|
||||
companion object {
|
||||
|
||||
private var instance: ActionsRegistry? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(): ActionsRegistry {
|
||||
if (instance == null) {
|
||||
instance = DefaultActionsRegistry()
|
||||
}
|
||||
|
||||
return instance as ActionsRegistry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given action.
|
||||
*
|
||||
* @param action The action to register. Must not be `null`.
|
||||
* @return `true` if the action was registered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun registerAction(action: ActionItem): Boolean
|
||||
|
||||
/**
|
||||
* Unregister the given action.
|
||||
*
|
||||
* @param action The action to unregister. Must not be `null`.
|
||||
* @return `true` if the action was unregistered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun unregisterAction(action: ActionItem): Boolean
|
||||
|
||||
/**
|
||||
* Unregister the action with the given ID.
|
||||
*
|
||||
* @param id The ID of action to unregister. Must not be `null`.
|
||||
* @return `true` if the action was unregistered successfully. `false` otherwise.
|
||||
*/
|
||||
abstract fun unregisterAction(id: String): Boolean
|
||||
|
||||
/**
|
||||
* Find the action with the given ID.
|
||||
*
|
||||
* @param id The ID of the action to find.
|
||||
* @return The found action or `null`.
|
||||
*/
|
||||
abstract fun findAction(location: ActionItem.Location, id: String): ActionItem?
|
||||
|
||||
/**
|
||||
* Fill the given menu with the registered actions.
|
||||
*
|
||||
* Subclasses must first call [ActionItem.prepare] to update the action.
|
||||
*/
|
||||
abstract fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu)
|
||||
|
||||
/** Get all the registered actions at the given location. */
|
||||
abstract fun getActions(location: ActionItem.Location): Map<String, ActionItem>
|
||||
/** Clear all the registered actions. */
|
||||
abstract fun clearActions(location: ActionItem.Location)
|
||||
|
||||
/**
|
||||
* Register a listener that will be called when any action is executed. The callbacks are called
|
||||
* irrespective of the action's execution result.
|
||||
*
|
||||
* @param listener The listener to register.
|
||||
*/
|
||||
abstract fun registerActionExecListener(listener: ActionExecListener)
|
||||
|
||||
/**
|
||||
* Unregister the given listener.
|
||||
*
|
||||
* @param listener The listener to unregister.
|
||||
*/
|
||||
abstract fun unregisterActionExecListener(listener: ActionExecListener)
|
||||
|
||||
/** Listener for listening to callbacks for action execution. */
|
||||
interface ActionExecListener {
|
||||
|
||||
/**
|
||||
* The given action was executed.
|
||||
* @param action The action that was executed.
|
||||
* @param result The result of the action execution.
|
||||
*/
|
||||
fun onExec(action: ActionItem, result: Any)
|
||||
}
|
||||
}
|
||||
|
||||
+174
-174
@@ -1,174 +1,174 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.actions.internal
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* Default implementation for the [ActionsRegistry]
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class DefaultActionsRegistry : ActionsRegistry() {
|
||||
|
||||
private val log = ILogger.newInstance("DefaultActionsRegistry")
|
||||
private val actions: MutableMap<String, MutableMap<String, ActionItem>> = mutableMapOf()
|
||||
private val listeners = mutableSetOf<ActionExecListener>()
|
||||
|
||||
override fun getActions(location: ActionItem.Location): MutableMap<String, ActionItem> {
|
||||
if (actions[location.id] == null) {
|
||||
actions[location.id] = mutableMapOf()
|
||||
}
|
||||
|
||||
return actions[location.id]!!
|
||||
}
|
||||
|
||||
override fun registerAction(action: ActionItem): Boolean {
|
||||
val actionsAtLocation = getActions(action.location)
|
||||
actionsAtLocation[action.id] = action
|
||||
return true
|
||||
}
|
||||
|
||||
override fun unregisterAction(action: ActionItem): Boolean {
|
||||
val actions = getActions(action.location)
|
||||
val older = actions.remove(action.id)
|
||||
if (older != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun unregisterAction(id: String): Boolean {
|
||||
for (locations in this.actions.values) {
|
||||
val older = locations.remove(id)
|
||||
if (older != null) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun findAction(location: ActionItem.Location, id: String): ActionItem? =
|
||||
getActions(location)[id]
|
||||
|
||||
override fun clearActions(location: ActionItem.Location) = getActions(location).clear()
|
||||
|
||||
override fun registerActionExecListener(listener: ActionExecListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun unregisterActionExecListener(listener: ActionExecListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) {
|
||||
val actions = getActions(location)
|
||||
|
||||
for (action in actions.values) {
|
||||
action.prepare(data)
|
||||
|
||||
if (!action.visible) {
|
||||
continue
|
||||
}
|
||||
addActionToMenu(menu, action, data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addActionToMenu(menu: Menu, action: ActionItem, data: ActionData) {
|
||||
val item: MenuItem =
|
||||
if (action is ActionMenu) {
|
||||
val sub = menu.addSubMenu(action.label)
|
||||
|
||||
for (subItem in action.children) {
|
||||
subItem.prepare(data)
|
||||
if (subItem.visible) {
|
||||
addActionToMenu(sub, subItem, data)
|
||||
}
|
||||
}
|
||||
|
||||
sub.item
|
||||
} else {
|
||||
menu.add(action.label)
|
||||
}
|
||||
|
||||
item.isEnabled = action.enabled
|
||||
item.icon = action.icon
|
||||
|
||||
if (item.icon != null) {
|
||||
item.icon.alpha = if (action.enabled) 255 else 76
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
|
||||
} else {
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
||||
}
|
||||
|
||||
if (action.getShowAsActionFlags(data) != -1) {
|
||||
item.setShowAsAction(action.getShowAsActionFlags(data))
|
||||
}
|
||||
|
||||
if (action !is ActionMenu) {
|
||||
item.setOnMenuItemClickListener {
|
||||
if (action.requiresUIThread) {
|
||||
ThreadUtils.runOnUiThread {
|
||||
val result = action.execAction(data)
|
||||
action.postExec(data, result)
|
||||
notifyActionExec(action, result)
|
||||
}
|
||||
} else {
|
||||
execInBackground(action, data, it)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun execInBackground(action: ActionItem, data: ActionData, it: MenuItem) {
|
||||
val start = System.currentTimeMillis()
|
||||
CompletableFuture.supplyAsync { action.execAction(data) }
|
||||
.whenComplete { result, error ->
|
||||
if (result == null || (result is Boolean && !result) || error != null) {
|
||||
log.error(
|
||||
"An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms",
|
||||
error)
|
||||
} else {
|
||||
log.info(
|
||||
"Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms")
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
action.postExec(data, result ?: false)
|
||||
notifyActionExec(action, result ?: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyActionExec(action: ActionItem, result: Any) {
|
||||
for (listener in listeners) {
|
||||
listener.onExec(action, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.actions.internal
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* Default implementation for the [ActionsRegistry]
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class DefaultActionsRegistry : ActionsRegistry() {
|
||||
|
||||
private val log = ILogger.newInstance("DefaultActionsRegistry")
|
||||
private val actions: MutableMap<String, MutableMap<String, ActionItem>> = mutableMapOf()
|
||||
private val listeners = mutableSetOf<ActionExecListener>()
|
||||
|
||||
override fun getActions(location: ActionItem.Location): MutableMap<String, ActionItem> {
|
||||
if (actions[location.id] == null) {
|
||||
actions[location.id] = mutableMapOf()
|
||||
}
|
||||
|
||||
return actions[location.id]!!
|
||||
}
|
||||
|
||||
override fun registerAction(action: ActionItem): Boolean {
|
||||
val actionsAtLocation = getActions(action.location)
|
||||
actionsAtLocation[action.id] = action
|
||||
return true
|
||||
}
|
||||
|
||||
override fun unregisterAction(action: ActionItem): Boolean {
|
||||
val actions = getActions(action.location)
|
||||
val older = actions.remove(action.id)
|
||||
if (older != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun unregisterAction(id: String): Boolean {
|
||||
for (locations in this.actions.values) {
|
||||
val older = locations.remove(id)
|
||||
if (older != null) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun findAction(location: ActionItem.Location, id: String): ActionItem? =
|
||||
getActions(location)[id]
|
||||
|
||||
override fun clearActions(location: ActionItem.Location) = getActions(location).clear()
|
||||
|
||||
override fun registerActionExecListener(listener: ActionExecListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun unregisterActionExecListener(listener: ActionExecListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
override fun fillMenu(data: ActionData, location: ActionItem.Location, menu: Menu) {
|
||||
val actions = getActions(location)
|
||||
|
||||
for (action in actions.values) {
|
||||
action.prepare(data)
|
||||
|
||||
if (!action.visible) {
|
||||
continue
|
||||
}
|
||||
addActionToMenu(menu, action, data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addActionToMenu(menu: Menu, action: ActionItem, data: ActionData) {
|
||||
val item: MenuItem =
|
||||
if (action is ActionMenu) {
|
||||
val sub = menu.addSubMenu(action.label)
|
||||
|
||||
for (subItem in action.children) {
|
||||
subItem.prepare(data)
|
||||
if (subItem.visible) {
|
||||
addActionToMenu(sub, subItem, data)
|
||||
}
|
||||
}
|
||||
|
||||
sub.item
|
||||
} else {
|
||||
menu.add(action.label)
|
||||
}
|
||||
|
||||
item.isEnabled = action.enabled
|
||||
item.icon = action.icon
|
||||
|
||||
if (item.icon != null) {
|
||||
item.icon.alpha = if (action.enabled) 255 else 76
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
|
||||
} else {
|
||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
||||
}
|
||||
|
||||
if (action.getShowAsActionFlags(data) != -1) {
|
||||
item.setShowAsAction(action.getShowAsActionFlags(data))
|
||||
}
|
||||
|
||||
if (action !is ActionMenu) {
|
||||
item.setOnMenuItemClickListener {
|
||||
if (action.requiresUIThread) {
|
||||
ThreadUtils.runOnUiThread {
|
||||
val result = action.execAction(data)
|
||||
action.postExec(data, result)
|
||||
notifyActionExec(action, result)
|
||||
}
|
||||
} else {
|
||||
execInBackground(action, data, it)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun execInBackground(action: ActionItem, data: ActionData, it: MenuItem) {
|
||||
val start = System.currentTimeMillis()
|
||||
CompletableFuture.supplyAsync { action.execAction(data) }
|
||||
.whenComplete { result, error ->
|
||||
if (result == null || (result is Boolean && !result) || error != null) {
|
||||
log.error(
|
||||
"An error occurred when performing action '${it.title}'. Action failed in ${System.currentTimeMillis() - start}ms",
|
||||
error)
|
||||
} else {
|
||||
log.info(
|
||||
"Action '${it.title}' completed in ${System.currentTimeMillis() - start}ms")
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread {
|
||||
action.postExec(data, result ?: false)
|
||||
notifyActionExec(action, result ?: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyActionExec(action: ActionItem, result: Any) {
|
||||
for (listener in listeners) {
|
||||
listener.onExec(action, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+143
-143
@@ -1,144 +1,144 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
defaultConfig {
|
||||
applicationId rootProject.ext.packageName
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
|
||||
multiDexEnabled true
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
debug {
|
||||
storeFile file ("dev.keystore")
|
||||
keyAlias "androidide"
|
||||
storePassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
keyPassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
}
|
||||
|
||||
release {
|
||||
storeFile file ("dev.keystore")
|
||||
keyAlias "androidide"
|
||||
storePassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
keyPassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += ['META-INF/eclipse.inf',
|
||||
'META-INF/CHANGES',
|
||||
'META-INF/README.md',
|
||||
'about_files/LICENSE-2.0.txt',
|
||||
'plugin.xml',
|
||||
'plugin.properties',
|
||||
'about.mappings',
|
||||
'about.properties',
|
||||
'about.ini',
|
||||
'modeling32.png']
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError false
|
||||
disable 'VectorPath', 'NestedWeights', 'ContentDescription', 'SmallSp'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// Annotation processors
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.1'
|
||||
|
||||
implementation common.editor
|
||||
implementation common.utilcode
|
||||
implementation common.file_picker
|
||||
implementation common.glide
|
||||
implementation common.quickaction
|
||||
implementation common.bll
|
||||
implementation common.xml2json
|
||||
implementation google.gson
|
||||
implementation google.guava
|
||||
|
||||
implementation common.jsoup
|
||||
|
||||
// AndroidX
|
||||
implementation androidx.annotation
|
||||
implementation androidx.appcompat
|
||||
implementation androidx.cardview
|
||||
implementation androidx.constraintlayout
|
||||
implementation androidx.coordinatorlayout
|
||||
implementation androidx.drawer
|
||||
implementation androidx.grid
|
||||
implementation androidx.preference
|
||||
implementation androidx.recyclerview
|
||||
implementation androidx.vectors
|
||||
implementation androidx.animated_vectors
|
||||
implementation google.material
|
||||
|
||||
// Kotlin
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
// Local projects here
|
||||
implementation project (':build-tools:tooling-api')
|
||||
implementation project (':treeview')
|
||||
implementation project (':emulatorview')
|
||||
implementation project (':xml-inflater')
|
||||
implementation project (':lexers')
|
||||
implementation project (':common')
|
||||
implementation project (':actions')
|
||||
|
||||
implementation project (':lsp:api')
|
||||
implementation project (':lsp:java')
|
||||
implementation project (':lsp:xml')
|
||||
|
||||
implementation fileTree (dir: 'libs', include: ['*.jar'])
|
||||
|
||||
// LeakCanary
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
// This is to build the tooling-api-impl project before the app is built
|
||||
// So we always copy the latest JAR file to assets
|
||||
compileOnly project (path: ':build-tools:tooling-api-impl')
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
defaultConfig {
|
||||
applicationId rootProject.ext.packageName
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
|
||||
multiDexEnabled true
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility rootProject.ext.javaSourceVersion
|
||||
targetCompatibility rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
debug {
|
||||
storeFile file ("dev.keystore")
|
||||
keyAlias "androidide"
|
||||
storePassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
keyPassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
}
|
||||
|
||||
release {
|
||||
storeFile file ("dev.keystore")
|
||||
keyAlias "androidide"
|
||||
storePassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
keyPassword "ed68424fb109e5aa8146e4b86caa72e3"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += ['META-INF/eclipse.inf',
|
||||
'META-INF/CHANGES',
|
||||
'META-INF/README.md',
|
||||
'about_files/LICENSE-2.0.txt',
|
||||
'plugin.xml',
|
||||
'plugin.properties',
|
||||
'about.mappings',
|
||||
'about.properties',
|
||||
'about.ini',
|
||||
'modeling32.png']
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError false
|
||||
disable 'VectorPath', 'NestedWeights', 'ContentDescription', 'SmallSp'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// Annotation processors
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.1'
|
||||
|
||||
implementation common.editor
|
||||
implementation common.utilcode
|
||||
implementation common.file_picker
|
||||
implementation common.glide
|
||||
implementation common.quickaction
|
||||
implementation common.bll
|
||||
implementation common.xml2json
|
||||
implementation google.gson
|
||||
implementation google.guava
|
||||
|
||||
implementation common.jsoup
|
||||
|
||||
// AndroidX
|
||||
implementation androidx.annotation
|
||||
implementation androidx.appcompat
|
||||
implementation androidx.cardview
|
||||
implementation androidx.constraintlayout
|
||||
implementation androidx.coordinatorlayout
|
||||
implementation androidx.drawer
|
||||
implementation androidx.grid
|
||||
implementation androidx.preference
|
||||
implementation androidx.recyclerview
|
||||
implementation androidx.vectors
|
||||
implementation androidx.animated_vectors
|
||||
implementation google.material
|
||||
|
||||
// Kotlin
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
// Local projects here
|
||||
implementation project (':build-tools:tooling-api')
|
||||
implementation project (':treeview')
|
||||
implementation project (':emulatorview')
|
||||
implementation project (':xml-inflater')
|
||||
implementation project (':lexers')
|
||||
implementation project (':common')
|
||||
implementation project (':actions')
|
||||
|
||||
implementation project (':lsp:api')
|
||||
implementation project (':lsp:java')
|
||||
implementation project (':lsp:xml')
|
||||
|
||||
implementation fileTree (dir: 'libs', include: ['*.jar'])
|
||||
|
||||
// LeakCanary
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
// This is to build the tooling-api-impl project before the app is built
|
||||
// So we always copy the latest JAR file to assets
|
||||
compileOnly project (path: ':build-tools:tooling-api-impl')
|
||||
}
|
||||
Binary file not shown.
@@ -1,187 +1,187 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.widget.TooltipCompat;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.databinding.ActivityAboutBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutAboutItemsBinding;
|
||||
import com.itsaky.androidide.models.License;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AboutActivity extends StudioActivity {
|
||||
|
||||
public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android";
|
||||
private ActivityAboutBinding binding;
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityAboutBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding.items.footerText.setText(getFooter());
|
||||
|
||||
setupTranslations();
|
||||
setupLicenses();
|
||||
|
||||
LayoutAboutItemsBinding items = binding.items;
|
||||
TooltipCompat.setTooltipText(items.discuss, getString(R.string.discussions_on_telegram));
|
||||
TooltipCompat.setTooltipText(items.github, getString(R.string.title_github));
|
||||
TooltipCompat.setTooltipText(items.email, getString(R.string.about_option_email));
|
||||
TooltipCompat.setTooltipText(items.website, getString(R.string.about_option_website));
|
||||
items.github.setOnClickListener(v -> getApp().openGitHub());
|
||||
items.discuss.setOnClickListener(v -> getApp().openTelegramGroup());
|
||||
items.email.setOnClickListener(v -> getApp().emailUs());
|
||||
items.website.setOnClickListener(v -> getApp().openWebsite());
|
||||
}
|
||||
|
||||
private void setupTranslations() {
|
||||
final var translations = createTranslationsList();
|
||||
final var sb = new StringBuilder();
|
||||
|
||||
for (var translation : translations) {
|
||||
final var author = translation.first;
|
||||
final var language = translation.second;
|
||||
|
||||
sb.append(author);
|
||||
sb.append(" - ");
|
||||
sb.append(language);
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
binding.items.translations.setText(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <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
|
||||
* >
|
||||
*
|
||||
* <p>Prefer using unicode escapes for language names. For example, हिन्दी can be written as :
|
||||
* Language: \u0939\u093f\u0928\u094d\u0926\u0940 - < name of author >
|
||||
*
|
||||
* @return The translations list.
|
||||
*/
|
||||
private List<Pair<String, String>> createTranslationsList() {
|
||||
final var list = new ArrayList<Pair<String, String>>();
|
||||
|
||||
list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani"));
|
||||
list.add(Pair.create("\u4e2d\u6587", "Rosemoe and @mikofe")); // 中文
|
||||
list.add(Pair.create("Deutsch", "Marvin Stelter"));
|
||||
list.add(Pair.create("\u0939\u093f\u0928\u094d\u0926\u0940", "Premjit Chowdhury")); // हिन्दी
|
||||
list.add(Pair.create("Russian", "Smooth-E"));
|
||||
list.add(Pair.create("French", "Se-Lyan"));
|
||||
list.add(Pair.create("Portuguese-Brazilian", "Frederick'XS"));
|
||||
return list;
|
||||
}
|
||||
|
||||
private void setupLicenses() {
|
||||
final var licenses = new ArrayList<License>();
|
||||
licenses.add(
|
||||
new License(
|
||||
"AndroidX Libraries", "Apache License 2.0", "https://github.com/androidx/androidx"));
|
||||
licenses.add(
|
||||
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(
|
||||
"Material Components for Android",
|
||||
"Apache License 2.0",
|
||||
"https://github.com/material-components/material-components-android"));
|
||||
licenses.add(
|
||||
new License("ANTLR4 Runtime", "BSD 3-clause License", "https://github.com/antlr/antlr4"));
|
||||
licenses.add(
|
||||
new License("Glide", "BSD, part MIT and Apache 2.0", "https://github.com/bumptech/glide"));
|
||||
licenses.add(
|
||||
new License(
|
||||
"Android UtilCode", "Apache License 2.0", "https://github.com/Blankj/AndroidUtilCode"));
|
||||
licenses.add(
|
||||
new License(
|
||||
"UnicornFilePicker",
|
||||
"Apache License 2.0",
|
||||
"https://github.com/abhishekti7/UnicornFilePicker"));
|
||||
licenses.add(
|
||||
new License("QuickAction", "Apache License 2.0", "https://github.com/piruin/quickaction"));
|
||||
licenses.add(
|
||||
new License("XmlToJson", "Apache License 2.0", "https://github.com/smart-fun/XmlToJson"));
|
||||
licenses.add(
|
||||
new License(
|
||||
"AndroidTreeView",
|
||||
"Apache License 2.0",
|
||||
"https://github.com/bmelnychuk/AndroidTreeView"));
|
||||
licenses.add(
|
||||
new License(
|
||||
"CodeEditor v0.5.2", "Apache License 2.0", "https://github.com/Rosemoe/CodeEditor"));
|
||||
licenses.add(
|
||||
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("Termux [Terminal Emulator]", "GPL 3", "https://github.com/termux/termux-app"));
|
||||
licenses.add(
|
||||
new License("JavaPoet", "Apache License 2.0", "https://github.com/square/JavaPoet"));
|
||||
licenses.add(
|
||||
new License("Zip4j", "Apache License 2.0", "https://github.com/srikanth-lingala/zip4j"));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (License license : licenses) {
|
||||
sb.append("\u2022 ");
|
||||
sb.append(license.name);
|
||||
sb.append("<br>");
|
||||
sb.append(getString(R.string.msg_about_licensed_under));
|
||||
sb.append(license.license);
|
||||
sb.append("<br><a href=\"");
|
||||
sb.append(license.url);
|
||||
sb.append("\">");
|
||||
sb.append(license.url);
|
||||
sb.append("</a>");
|
||||
sb.append("<br><br>");
|
||||
}
|
||||
sb.append(getString(R.string.license_jdk));
|
||||
sb.append(String.format("<br><a href=\"%1$s\">%1$s</a>", JDK_SOURCE));
|
||||
sb.append("<br><br>");
|
||||
binding.items.licenses.setText(Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_LEGACY));
|
||||
binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
private String getFooter() {
|
||||
final String arch = android.os.Build.SUPPORTED_ABIS[0];
|
||||
try {
|
||||
final String version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
|
||||
return getString(R.string.about_footer, version, arch);
|
||||
} catch (Throwable th) {
|
||||
return getString(R.string.about_footer_alternate, arch);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.widget.TooltipCompat;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.databinding.ActivityAboutBinding;
|
||||
import com.itsaky.androidide.databinding.LayoutAboutItemsBinding;
|
||||
import com.itsaky.androidide.models.License;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AboutActivity extends StudioActivity {
|
||||
|
||||
public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android";
|
||||
private ActivityAboutBinding binding;
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityAboutBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding.items.footerText.setText(getFooter());
|
||||
|
||||
setupTranslations();
|
||||
setupLicenses();
|
||||
|
||||
LayoutAboutItemsBinding items = binding.items;
|
||||
TooltipCompat.setTooltipText(items.discuss, getString(R.string.discussions_on_telegram));
|
||||
TooltipCompat.setTooltipText(items.github, getString(R.string.title_github));
|
||||
TooltipCompat.setTooltipText(items.email, getString(R.string.about_option_email));
|
||||
TooltipCompat.setTooltipText(items.website, getString(R.string.about_option_website));
|
||||
items.github.setOnClickListener(v -> getApp().openGitHub());
|
||||
items.discuss.setOnClickListener(v -> getApp().openTelegramGroup());
|
||||
items.email.setOnClickListener(v -> getApp().emailUs());
|
||||
items.website.setOnClickListener(v -> getApp().openWebsite());
|
||||
}
|
||||
|
||||
private void setupTranslations() {
|
||||
final var translations = createTranslationsList();
|
||||
final var sb = new StringBuilder();
|
||||
|
||||
for (var translation : translations) {
|
||||
final var author = translation.first;
|
||||
final var language = translation.second;
|
||||
|
||||
sb.append(author);
|
||||
sb.append(" - ");
|
||||
sb.append(language);
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
binding.items.translations.setText(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <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
|
||||
* >
|
||||
*
|
||||
* <p>Prefer using unicode escapes for language names. For example, हिन्दी can be written as :
|
||||
* Language: \u0939\u093f\u0928\u094d\u0926\u0940 - < name of author >
|
||||
*
|
||||
* @return The translations list.
|
||||
*/
|
||||
private List<Pair<String, String>> createTranslationsList() {
|
||||
final var list = new ArrayList<Pair<String, String>>();
|
||||
|
||||
list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani"));
|
||||
list.add(Pair.create("\u4e2d\u6587", "Rosemoe and @mikofe")); // 中文
|
||||
list.add(Pair.create("Deutsch", "Marvin Stelter"));
|
||||
list.add(Pair.create("\u0939\u093f\u0928\u094d\u0926\u0940", "Premjit Chowdhury")); // हिन्दी
|
||||
list.add(Pair.create("Russian", "Smooth-E"));
|
||||
list.add(Pair.create("French", "Se-Lyan"));
|
||||
list.add(Pair.create("Portuguese-Brazilian", "Frederick'XS"));
|
||||
return list;
|
||||
}
|
||||
|
||||
private void setupLicenses() {
|
||||
final var licenses = new ArrayList<License>();
|
||||
licenses.add(
|
||||
new License(
|
||||
"AndroidX Libraries", "Apache License 2.0", "https://github.com/androidx/androidx"));
|
||||
licenses.add(
|
||||
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(
|
||||
"Material Components for Android",
|
||||
"Apache License 2.0",
|
||||
"https://github.com/material-components/material-components-android"));
|
||||
licenses.add(
|
||||
new License("ANTLR4 Runtime", "BSD 3-clause License", "https://github.com/antlr/antlr4"));
|
||||
licenses.add(
|
||||
new License("Glide", "BSD, part MIT and Apache 2.0", "https://github.com/bumptech/glide"));
|
||||
licenses.add(
|
||||
new License(
|
||||
"Android UtilCode", "Apache License 2.0", "https://github.com/Blankj/AndroidUtilCode"));
|
||||
licenses.add(
|
||||
new License(
|
||||
"UnicornFilePicker",
|
||||
"Apache License 2.0",
|
||||
"https://github.com/abhishekti7/UnicornFilePicker"));
|
||||
licenses.add(
|
||||
new License("QuickAction", "Apache License 2.0", "https://github.com/piruin/quickaction"));
|
||||
licenses.add(
|
||||
new License("XmlToJson", "Apache License 2.0", "https://github.com/smart-fun/XmlToJson"));
|
||||
licenses.add(
|
||||
new License(
|
||||
"AndroidTreeView",
|
||||
"Apache License 2.0",
|
||||
"https://github.com/bmelnychuk/AndroidTreeView"));
|
||||
licenses.add(
|
||||
new License(
|
||||
"CodeEditor v0.5.2", "Apache License 2.0", "https://github.com/Rosemoe/CodeEditor"));
|
||||
licenses.add(
|
||||
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("Termux [Terminal Emulator]", "GPL 3", "https://github.com/termux/termux-app"));
|
||||
licenses.add(
|
||||
new License("JavaPoet", "Apache License 2.0", "https://github.com/square/JavaPoet"));
|
||||
licenses.add(
|
||||
new License("Zip4j", "Apache License 2.0", "https://github.com/srikanth-lingala/zip4j"));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (License license : licenses) {
|
||||
sb.append("\u2022 ");
|
||||
sb.append(license.name);
|
||||
sb.append("<br>");
|
||||
sb.append(getString(R.string.msg_about_licensed_under));
|
||||
sb.append(license.license);
|
||||
sb.append("<br><a href=\"");
|
||||
sb.append(license.url);
|
||||
sb.append("\">");
|
||||
sb.append(license.url);
|
||||
sb.append("</a>");
|
||||
sb.append("<br><br>");
|
||||
}
|
||||
sb.append(getString(R.string.license_jdk));
|
||||
sb.append(String.format("<br><a href=\"%1$s\">%1$s</a>", JDK_SOURCE));
|
||||
sb.append("<br><br>");
|
||||
binding.items.licenses.setText(Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_LEGACY));
|
||||
binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
private String getFooter() {
|
||||
final String arch = android.os.Build.SUPPORTED_ABIS[0];
|
||||
try {
|
||||
final String version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
|
||||
return getString(R.string.about_footer, version, arch);
|
||||
} catch (Throwable th) {
|
||||
return getString(R.string.about_footer_alternate, arch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,321 +1,321 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.databinding.ActivityDownloadBinding;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.shell.IProcessExecutor;
|
||||
import com.itsaky.androidide.shell.ProcessExecutorFactory;
|
||||
import com.itsaky.androidide.shell.ProcessStreamsHolder;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.ListDirectoryCallable;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.InputStreamLineReader;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
|
||||
|
||||
public class DownloadActivity extends StudioActivity {
|
||||
|
||||
private ActivityDownloadBinding binding;
|
||||
private ProgressSheet progressSheet;
|
||||
private File choosenDir = null;
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityDownloadBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final OnClickListener clickListener = this::handleClick;
|
||||
binding.dismiss.setOnClickListener(clickListener);
|
||||
binding.choose.setOnClickListener(clickListener);
|
||||
binding.install.setOnClickListener(clickListener);
|
||||
|
||||
binding.install.setEnabled(false);
|
||||
}
|
||||
|
||||
private void handleClick(View v) {
|
||||
final int id = v.getId();
|
||||
if (id == binding.dismiss.getId()) {
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||
binding.card.setVisibility(View.GONE);
|
||||
} else if (id == binding.choose.getId()) {
|
||||
try {
|
||||
binding.install.setEnabled(false);
|
||||
UnicornFilePicker.from(this)
|
||||
.addConfigBuilder()
|
||||
.addItemDivider(false)
|
||||
.selectMultipleFiles(false)
|
||||
.setRootDirectory(FileUtil.getExternalStorageDir())
|
||||
.showHiddenFiles(true)
|
||||
.showOnlyDirectory(true)
|
||||
.theme(R.style.AppTheme_FilePicker)
|
||||
.build()
|
||||
.forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(getString(R.string.err_cannot_start_file_picker), th);
|
||||
}
|
||||
} else if (id == binding.install.getId()) {
|
||||
if (choosenDir == null) {
|
||||
getApp().toast(R.string.msg_pick_files, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!choosenDir.exists()) {
|
||||
getApp().toast(R.string.msg_file_doesnt_exist, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
installAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void installAll() {
|
||||
showProgress();
|
||||
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true);
|
||||
|
||||
try {
|
||||
final File script = createExtractScript();
|
||||
final ProcessStreamsHolder holder = new ProcessStreamsHolder();
|
||||
final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor();
|
||||
|
||||
executor.execAsync(
|
||||
holder,
|
||||
this::onInstallProcessExit,
|
||||
true,
|
||||
Environment.BUSYBOX.getAbsolutePath(),
|
||||
"sh", // We use busybox's sh, because Environment.SHELL is not installed yet...
|
||||
script.getAbsolutePath());
|
||||
|
||||
this.output = new StringBuilder();
|
||||
final InputStreamLineReader reader =
|
||||
new InputStreamLineReader(holder.in, this::onInstallationOutput);
|
||||
new Thread(reader).start();
|
||||
|
||||
} catch (DownloadActivity.InstallationException e) {
|
||||
LOG.error(getString(R.string.err_installation), e);
|
||||
onInstallationFailed(e.exitCode);
|
||||
} catch (IOException e) {
|
||||
LOG.error(getString(R.string.err_installation), e);
|
||||
onInstallationFailed(5); // Exit code 5 : I/O Error
|
||||
}
|
||||
}
|
||||
|
||||
private void onInstallationOutput(final String line) {
|
||||
ThreadUtils.runOnUiThread(() -> this.appendOut(line));
|
||||
}
|
||||
|
||||
private void onInstallProcessExit(final int code) {
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
if (code == 0) { // 0 = normal execution
|
||||
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true);
|
||||
showRestartNeeded();
|
||||
if (getProgressSheet().isShowing()) {
|
||||
getProgressSheet().dismiss();
|
||||
}
|
||||
} else {
|
||||
onInstallationFailed(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onInstallationFailed(int code) {
|
||||
// TODO Can this be improved by adding some animations?
|
||||
|
||||
if (getProgressSheet().isShowing()) {
|
||||
getProgressSheet().dismiss();
|
||||
}
|
||||
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_installation_failed);
|
||||
builder.setMessage(getString(R.string.msg_installation_failed, code, this.output.toString()));
|
||||
builder.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
(d, w) -> {
|
||||
d.dismiss();
|
||||
finishAffinity();
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private File createExtractScript() throws DownloadActivity.InstallationException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("cd");
|
||||
joiner(sb);
|
||||
sb.append("echo 'Installing...'");
|
||||
joiner(sb);
|
||||
|
||||
File[] files = choosenDir.listFiles(ARCHIVE_FILTER);
|
||||
|
||||
if (files == null || files.length <= 0) {
|
||||
throw new InstallationException(2);
|
||||
}
|
||||
|
||||
for (File f : files) {
|
||||
if (f.getName().endsWith(".tar.xz")) {
|
||||
if (f.getName().startsWith("androidide-sysroot")) {
|
||||
sb.append("cd $SYSROOT/..");
|
||||
joiner(sb);
|
||||
}
|
||||
sb.append("$BUSYBOX tar xvJf '").append(f.getAbsolutePath()).append("'");
|
||||
joiner(sb);
|
||||
sb.append("cd $HOME");
|
||||
joiner(sb);
|
||||
} else if (f.getName().endsWith(".zip")) {
|
||||
sb.append("$BUSYBOX unzip '").append(f.getAbsolutePath()).append("'");
|
||||
joiner(sb);
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("echo 'Cleaning unsupported flags in binaries...'");
|
||||
joiner(sb);
|
||||
String DONE = "DONE";
|
||||
sb.append("echo ").append(DONE);
|
||||
|
||||
final File script = new File(Environment.TMP_DIR, "extract_tools.sh");
|
||||
if (!FileIOUtils.writeFileFromString(script, sb.toString())) {
|
||||
throw new InstallationException(2);
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
private void joiner(StringBuilder sb) {
|
||||
sb.append(" && ");
|
||||
}
|
||||
|
||||
private StringBuilder output = new StringBuilder();
|
||||
|
||||
private void appendOut(String line) {
|
||||
LOG.debug("Installation:", line);
|
||||
output.append(line.trim());
|
||||
output.append("\n");
|
||||
|
||||
getProgressSheet().setSubMessage(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE
|
||||
&& resultCode == RESULT_OK) {
|
||||
ArrayList<String> files = data.getStringArrayListExtra("filePaths");
|
||||
if (files != null) {
|
||||
if (files.size() == 1) {
|
||||
File choosenDir = new File(files.get(0));
|
||||
if (choosenDir.exists() && choosenDir.isDirectory()) {
|
||||
this.choosenDir = choosenDir;
|
||||
setDone(binding.choose);
|
||||
binding.install.setEnabled(true);
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
new ListDirectoryCallable(choosenDir),
|
||||
__ -> binding.filesText.setText(getString(R.string.msg_installable_files, __)));
|
||||
} else {
|
||||
getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
|
||||
}
|
||||
} else {
|
||||
getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setDone(MaterialButton button) {
|
||||
final int dp8 = SizeUtils.dp2px(8);
|
||||
final int dp4 = SizeUtils.dp2px(4);
|
||||
final int dp24 = SizeUtils.dp2px(24);
|
||||
button.setText("");
|
||||
button.setIconResource(R.drawable.ic_ok);
|
||||
button.setIconTint(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.bg_green)));
|
||||
button.setBackgroundColor(android.graphics.Color.TRANSPARENT);
|
||||
button.setPaddingRelative(dp8, dp4, dp8, dp4);
|
||||
button.setStrokeWidth(0);
|
||||
button.setGravity(Gravity.CENTER);
|
||||
button.setIconPadding(0);
|
||||
button.setInsetTop(0);
|
||||
button.setInsetBottom(0);
|
||||
button.setPaddingRelative(dp24, dp4, dp24, dp4);
|
||||
}
|
||||
|
||||
private void showProgress() {
|
||||
getProgressSheet().setCancelable(false);
|
||||
getProgressSheet().setShowShadow(false);
|
||||
getProgressSheet()
|
||||
.setSubMessageEnabled(true)
|
||||
.setWelcomeTextEnabled(true)
|
||||
.show(getSupportFragmentManager(), "progress_sheet");
|
||||
}
|
||||
|
||||
private void showRestartNeeded() {
|
||||
setDone(binding.install);
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_restart);
|
||||
builder.setMessage(R.string.msg_restart);
|
||||
builder.setCancelable(false);
|
||||
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private ProgressSheet getProgressSheet() {
|
||||
return progressSheet == null
|
||||
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
|
||||
: progressSheet;
|
||||
}
|
||||
|
||||
private final FileFilter ARCHIVE_FILTER =
|
||||
p1 -> p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip"));
|
||||
|
||||
private static class InstallationException extends Exception {
|
||||
private final int exitCode;
|
||||
|
||||
public InstallationException(int exitCode) {
|
||||
this.exitCode = exitCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.databinding.ActivityDownloadBinding;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.shell.IProcessExecutor;
|
||||
import com.itsaky.androidide.shell.ProcessExecutorFactory;
|
||||
import com.itsaky.androidide.shell.ProcessStreamsHolder;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.ListDirectoryCallable;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.FileUtil;
|
||||
import com.itsaky.androidide.utils.InputStreamLineReader;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import abhishekti7.unicorn.filepicker.UnicornFilePicker;
|
||||
|
||||
public class DownloadActivity extends StudioActivity {
|
||||
|
||||
private ActivityDownloadBinding binding;
|
||||
private ProgressSheet progressSheet;
|
||||
private File choosenDir = null;
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivityDownloadBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final OnClickListener clickListener = this::handleClick;
|
||||
binding.dismiss.setOnClickListener(clickListener);
|
||||
binding.choose.setOnClickListener(clickListener);
|
||||
binding.install.setOnClickListener(clickListener);
|
||||
|
||||
binding.install.setEnabled(false);
|
||||
}
|
||||
|
||||
private void handleClick(View v) {
|
||||
final int id = v.getId();
|
||||
if (id == binding.dismiss.getId()) {
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||
binding.card.setVisibility(View.GONE);
|
||||
} else if (id == binding.choose.getId()) {
|
||||
try {
|
||||
binding.install.setEnabled(false);
|
||||
UnicornFilePicker.from(this)
|
||||
.addConfigBuilder()
|
||||
.addItemDivider(false)
|
||||
.selectMultipleFiles(false)
|
||||
.setRootDirectory(FileUtil.getExternalStorageDir())
|
||||
.showHiddenFiles(true)
|
||||
.showOnlyDirectory(true)
|
||||
.theme(R.style.AppTheme_FilePicker)
|
||||
.build()
|
||||
.forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE);
|
||||
} catch (Throwable th) {
|
||||
LOG.error(getString(R.string.err_cannot_start_file_picker), th);
|
||||
}
|
||||
} else if (id == binding.install.getId()) {
|
||||
if (choosenDir == null) {
|
||||
getApp().toast(R.string.msg_pick_files, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!choosenDir.exists()) {
|
||||
getApp().toast(R.string.msg_file_doesnt_exist, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
installAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void installAll() {
|
||||
showProgress();
|
||||
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true);
|
||||
|
||||
try {
|
||||
final File script = createExtractScript();
|
||||
final ProcessStreamsHolder holder = new ProcessStreamsHolder();
|
||||
final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor();
|
||||
|
||||
executor.execAsync(
|
||||
holder,
|
||||
this::onInstallProcessExit,
|
||||
true,
|
||||
Environment.BUSYBOX.getAbsolutePath(),
|
||||
"sh", // We use busybox's sh, because Environment.SHELL is not installed yet...
|
||||
script.getAbsolutePath());
|
||||
|
||||
this.output = new StringBuilder();
|
||||
final InputStreamLineReader reader =
|
||||
new InputStreamLineReader(holder.in, this::onInstallationOutput);
|
||||
new Thread(reader).start();
|
||||
|
||||
} catch (DownloadActivity.InstallationException e) {
|
||||
LOG.error(getString(R.string.err_installation), e);
|
||||
onInstallationFailed(e.exitCode);
|
||||
} catch (IOException e) {
|
||||
LOG.error(getString(R.string.err_installation), e);
|
||||
onInstallationFailed(5); // Exit code 5 : I/O Error
|
||||
}
|
||||
}
|
||||
|
||||
private void onInstallationOutput(final String line) {
|
||||
ThreadUtils.runOnUiThread(() -> this.appendOut(line));
|
||||
}
|
||||
|
||||
private void onInstallProcessExit(final int code) {
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
if (code == 0) { // 0 = normal execution
|
||||
getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true);
|
||||
showRestartNeeded();
|
||||
if (getProgressSheet().isShowing()) {
|
||||
getProgressSheet().dismiss();
|
||||
}
|
||||
} else {
|
||||
onInstallationFailed(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onInstallationFailed(int code) {
|
||||
// TODO Can this be improved by adding some animations?
|
||||
|
||||
if (getProgressSheet().isShowing()) {
|
||||
getProgressSheet().dismiss();
|
||||
}
|
||||
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_installation_failed);
|
||||
builder.setMessage(getString(R.string.msg_installation_failed, code, this.output.toString()));
|
||||
builder.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
(d, w) -> {
|
||||
d.dismiss();
|
||||
finishAffinity();
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private File createExtractScript() throws DownloadActivity.InstallationException {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("cd");
|
||||
joiner(sb);
|
||||
sb.append("echo 'Installing...'");
|
||||
joiner(sb);
|
||||
|
||||
File[] files = choosenDir.listFiles(ARCHIVE_FILTER);
|
||||
|
||||
if (files == null || files.length <= 0) {
|
||||
throw new InstallationException(2);
|
||||
}
|
||||
|
||||
for (File f : files) {
|
||||
if (f.getName().endsWith(".tar.xz")) {
|
||||
if (f.getName().startsWith("androidide-sysroot")) {
|
||||
sb.append("cd $SYSROOT/..");
|
||||
joiner(sb);
|
||||
}
|
||||
sb.append("$BUSYBOX tar xvJf '").append(f.getAbsolutePath()).append("'");
|
||||
joiner(sb);
|
||||
sb.append("cd $HOME");
|
||||
joiner(sb);
|
||||
} else if (f.getName().endsWith(".zip")) {
|
||||
sb.append("$BUSYBOX unzip '").append(f.getAbsolutePath()).append("'");
|
||||
joiner(sb);
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("echo 'Cleaning unsupported flags in binaries...'");
|
||||
joiner(sb);
|
||||
String DONE = "DONE";
|
||||
sb.append("echo ").append(DONE);
|
||||
|
||||
final File script = new File(Environment.TMP_DIR, "extract_tools.sh");
|
||||
if (!FileIOUtils.writeFileFromString(script, sb.toString())) {
|
||||
throw new InstallationException(2);
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
private void joiner(StringBuilder sb) {
|
||||
sb.append(" && ");
|
||||
}
|
||||
|
||||
private StringBuilder output = new StringBuilder();
|
||||
|
||||
private void appendOut(String line) {
|
||||
LOG.debug("Installation:", line);
|
||||
output.append(line.trim());
|
||||
output.append("\n");
|
||||
|
||||
getProgressSheet().setSubMessage(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE
|
||||
&& resultCode == RESULT_OK) {
|
||||
ArrayList<String> files = data.getStringArrayListExtra("filePaths");
|
||||
if (files != null) {
|
||||
if (files.size() == 1) {
|
||||
File choosenDir = new File(files.get(0));
|
||||
if (choosenDir.exists() && choosenDir.isDirectory()) {
|
||||
this.choosenDir = choosenDir;
|
||||
setDone(binding.choose);
|
||||
binding.install.setEnabled(true);
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
new ListDirectoryCallable(choosenDir),
|
||||
__ -> binding.filesText.setText(getString(R.string.msg_installable_files, __)));
|
||||
} else {
|
||||
getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR);
|
||||
}
|
||||
} else {
|
||||
getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setDone(MaterialButton button) {
|
||||
final int dp8 = SizeUtils.dp2px(8);
|
||||
final int dp4 = SizeUtils.dp2px(4);
|
||||
final int dp24 = SizeUtils.dp2px(24);
|
||||
button.setText("");
|
||||
button.setIconResource(R.drawable.ic_ok);
|
||||
button.setIconTint(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.bg_green)));
|
||||
button.setBackgroundColor(android.graphics.Color.TRANSPARENT);
|
||||
button.setPaddingRelative(dp8, dp4, dp8, dp4);
|
||||
button.setStrokeWidth(0);
|
||||
button.setGravity(Gravity.CENTER);
|
||||
button.setIconPadding(0);
|
||||
button.setInsetTop(0);
|
||||
button.setInsetBottom(0);
|
||||
button.setPaddingRelative(dp24, dp4, dp24, dp4);
|
||||
}
|
||||
|
||||
private void showProgress() {
|
||||
getProgressSheet().setCancelable(false);
|
||||
getProgressSheet().setShowShadow(false);
|
||||
getProgressSheet()
|
||||
.setSubMessageEnabled(true)
|
||||
.setWelcomeTextEnabled(true)
|
||||
.show(getSupportFragmentManager(), "progress_sheet");
|
||||
}
|
||||
|
||||
private void showRestartNeeded() {
|
||||
setDone(binding.install);
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_restart);
|
||||
builder.setMessage(R.string.msg_restart);
|
||||
builder.setCancelable(false);
|
||||
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private ProgressSheet getProgressSheet() {
|
||||
return progressSheet == null
|
||||
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
|
||||
: progressSheet;
|
||||
}
|
||||
|
||||
private final FileFilter ARCHIVE_FILTER =
|
||||
p1 -> p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip"));
|
||||
|
||||
private static class InstallationException extends Exception {
|
||||
private final int exitCode;
|
||||
|
||||
public InstallationException(int exitCode) {
|
||||
this.exitCode = exitCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,4 +425,4 @@ public class MainActivity extends StudioActivity
|
||||
mProgressSheet.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +1,92 @@
|
||||
/************************************************************************************
|
||||
* 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/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
package com.itsaky.androidide;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.ActivitySplashBinding;
|
||||
import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
|
||||
public class SplashActivity extends StudioActivity {
|
||||
private static final Handler mHandler = new Handler();
|
||||
private final Runnable mRunnable =
|
||||
() -> {
|
||||
ConstantsBridge.SPLASH_TO_MAIN = true;
|
||||
startActivity(new Intent(SplashActivity.this, MainActivity.class));
|
||||
finish();
|
||||
};
|
||||
private ActivitySplashBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding.splashText.setText(getString(R.string.msg_checking_storage_permissions));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageAlreadyGranted() {
|
||||
super.onStorageAlreadyGranted();
|
||||
proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageGranted() {
|
||||
super.onStorageGranted();
|
||||
proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivitySplashBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void proceed() {
|
||||
if (!StudioApp.isAbiSupported()) {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_device_not_supported);
|
||||
builder.setMessage(R.string.msg_device_not_supported);
|
||||
builder.setCancelable(false);
|
||||
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
|
||||
builder.create().show();
|
||||
} else {
|
||||
binding.splashText.setText(getString(R.string.msg_storage_granted));
|
||||
if (getApp().isFrameworkInstalled()) {
|
||||
goToMain();
|
||||
} else {
|
||||
startActivity(new Intent(this, DownloadActivity.class));
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void goToMain() {
|
||||
mHandler.removeCallbacks(mRunnable);
|
||||
mHandler.postDelayed(mRunnable, 1000);
|
||||
}
|
||||
}
|
||||
/************************************************************************************
|
||||
* 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/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
package com.itsaky.androidide;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.app.StudioActivity;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.ActivitySplashBinding;
|
||||
import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
|
||||
public class SplashActivity extends StudioActivity {
|
||||
private static final Handler mHandler = new Handler();
|
||||
private final Runnable mRunnable =
|
||||
() -> {
|
||||
ConstantsBridge.SPLASH_TO_MAIN = true;
|
||||
startActivity(new Intent(SplashActivity.this, MainActivity.class));
|
||||
finish();
|
||||
};
|
||||
private ActivitySplashBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding.splashText.setText(getString(R.string.msg_checking_storage_permissions));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageAlreadyGranted() {
|
||||
super.onStorageAlreadyGranted();
|
||||
proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStorageGranted() {
|
||||
super.onStorageGranted();
|
||||
proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
binding = ActivitySplashBinding.inflate(getLayoutInflater());
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void proceed() {
|
||||
if (!StudioApp.isAbiSupported()) {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
|
||||
builder.setTitle(R.string.title_device_not_supported);
|
||||
builder.setMessage(R.string.msg_device_not_supported);
|
||||
builder.setCancelable(false);
|
||||
builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
|
||||
builder.create().show();
|
||||
} else {
|
||||
binding.splashText.setText(getString(R.string.msg_storage_granted));
|
||||
if (getApp().isFrameworkInstalled()) {
|
||||
goToMain();
|
||||
} else {
|
||||
startActivity(new Intent(this, DownloadActivity.class));
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void goToMain() {
|
||||
mHandler.removeCallbacks(mRunnable);
|
||||
mHandler.postDelayed(mRunnable, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import com.itsaky.androidide.EditorActivity
|
||||
|
||||
/**
|
||||
* Marker class for actions that execute build related tasks.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class BaseBuildAction : EditorActivityAction() {
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
val context = getActivity(data)
|
||||
if (context == null) {
|
||||
visible = false
|
||||
return
|
||||
} else {
|
||||
visible = true
|
||||
}
|
||||
|
||||
if (isBuildInProgress(context)) {
|
||||
enabled = false
|
||||
return
|
||||
} else {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
val context = getActivity(data) ?: return
|
||||
context.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
fun shouldPrepare() = visible && enabled
|
||||
|
||||
private fun isBuildInProgress(activity: EditorActivity): Boolean =
|
||||
activity.buildService == null || activity.buildService.isBuildInProgress
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import com.itsaky.androidide.EditorActivity
|
||||
|
||||
/**
|
||||
* Marker class for actions that execute build related tasks.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class BaseBuildAction : EditorActivityAction() {
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
val context = getActivity(data)
|
||||
if (context == null) {
|
||||
visible = false
|
||||
return
|
||||
} else {
|
||||
visible = true
|
||||
}
|
||||
|
||||
if (isBuildInProgress(context)) {
|
||||
enabled = false
|
||||
return
|
||||
} else {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
val context = getActivity(data) ?: return
|
||||
context.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
fun shouldPrepare() = visible && enabled
|
||||
|
||||
private fun isBuildInProgress(activity: EditorActivity): Boolean =
|
||||
activity.buildService == null || activity.buildService.isBuildInProgress
|
||||
}
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.views.editor.IDEEditor
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class BaseEditorAction : ActionItem {
|
||||
|
||||
override var label: String = ""
|
||||
override var visible: Boolean = true
|
||||
override var enabled: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var requiresUIThread: Boolean =
|
||||
true // all editor actions must be executed on UI thread
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
getEditor(data)
|
||||
?: kotlin.run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
fun getEditor(data: ActionData): IDEEditor? {
|
||||
return data.get(IDEEditor::class.java)
|
||||
}
|
||||
|
||||
fun getContext(data: ActionData): Context? {
|
||||
val editor = getEditor(data) ?: return null
|
||||
return editor.context
|
||||
}
|
||||
|
||||
fun tintDrawable(context: Context, drawable: Drawable): Drawable {
|
||||
drawable.setTint(
|
||||
ContextCompat.getColor(context, com.itsaky.androidide.R.color.primaryIconColor))
|
||||
return drawable
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.views.editor.IDEEditor
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class BaseEditorAction : ActionItem {
|
||||
|
||||
override var label: String = ""
|
||||
override var visible: Boolean = true
|
||||
override var enabled: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var requiresUIThread: Boolean =
|
||||
true // all editor actions must be executed on UI thread
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
getEditor(data)
|
||||
?: kotlin.run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
fun getEditor(data: ActionData): IDEEditor? {
|
||||
return data.get(IDEEditor::class.java)
|
||||
}
|
||||
|
||||
fun getContext(data: ActionData): Context? {
|
||||
val editor = getEditor(data) ?: return null
|
||||
return editor.context
|
||||
}
|
||||
|
||||
fun tintDrawable(context: Context, drawable: Drawable): Drawable {
|
||||
drawable.setTint(
|
||||
ContextCompat.getColor(context, com.itsaky.androidide.R.color.primaryIconColor))
|
||||
return drawable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.itsaky.androidide.EditorActivity
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorActivityAction : ActionItem {
|
||||
|
||||
override var enabled: Boolean = true
|
||||
override var visible: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var label: String = ""
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TOOLBAR
|
||||
|
||||
override var requiresUIThread: Boolean = false
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
if (!hasRequiredData(data, Context::class.java)) {
|
||||
markInvisible()
|
||||
}
|
||||
}
|
||||
|
||||
fun getActivity(data: ActionData): EditorActivity? {
|
||||
val context = data.get(Context::class.java) ?: return null
|
||||
return context as EditorActivity
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.itsaky.androidide.EditorActivity
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorActivityAction : ActionItem {
|
||||
|
||||
override var enabled: Boolean = true
|
||||
override var visible: Boolean = true
|
||||
override var icon: Drawable? = null
|
||||
override var label: String = ""
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TOOLBAR
|
||||
|
||||
override var requiresUIThread: Boolean = false
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
if (!hasRequiredData(data, Context::class.java)) {
|
||||
markInvisible()
|
||||
}
|
||||
}
|
||||
|
||||
fun getActivity(data: ActionData): EditorActivity? {
|
||||
val context = data.get(Context::class.java) ?: return null
|
||||
return context as EditorActivity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorActivityActionMenu : EditorActivityAction(), ActionMenu {
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorActivityActionMenu : EditorActivityAction(), ActionMenu {
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import com.itsaky.androidide.views.editor.CodeEditorView
|
||||
import com.itsaky.androidide.views.editor.IDEEditor
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorRelatedAction : EditorActivityAction() {
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
val editor =
|
||||
getEditor(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
val file = editor.file
|
||||
|
||||
visible = file != null
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
fun getEditor(data: ActionData): IDEEditor? = data.get(IDEEditor::class.java)
|
||||
|
||||
fun getEditorView(data: ActionData): CodeEditorView? = data[CodeEditorView::class.java]
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import com.itsaky.androidide.views.editor.CodeEditorView
|
||||
import com.itsaky.androidide.views.editor.IDEEditor
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class EditorRelatedAction : EditorActivityAction() {
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
val editor =
|
||||
getEditor(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
val file = editor.file
|
||||
|
||||
visible = file != null
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
fun getEditor(data: ActionData): IDEEditor? = data.get(IDEEditor::class.java)
|
||||
|
||||
fun getEditorView(data: ActionData): CodeEditorView? = data[CodeEditorView::class.java]
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class AssembleDebugAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
|
||||
}
|
||||
|
||||
override val id: String = "editor_assembleDebug"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class AssembleDebugAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
|
||||
}
|
||||
|
||||
override val id: String = "editor_assembleDebug"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class AssembleReleaseAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_release)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
|
||||
}
|
||||
|
||||
override val id: String = "editor_assembleRelease"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.assembleRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class AssembleReleaseAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_release)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
|
||||
}
|
||||
|
||||
override val id: String = "editor_assembleRelease"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.assembleRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class BuildAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
}
|
||||
|
||||
override val id: String = "editor_build"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.build()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class BuildAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
}
|
||||
|
||||
override val id: String = "editor_build"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.build()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class BuildActionMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_tasks)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
|
||||
addAction(BuildAction(context))
|
||||
addAction(AssembleDebugAction(context))
|
||||
addAction(AssembleReleaseAction(context))
|
||||
addAction(CreateAABAction(context))
|
||||
addAction(CleanAction(context))
|
||||
addAction(RebuildAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_buildTasks"
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class BuildActionMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.build_tasks)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
|
||||
addAction(BuildAction(context))
|
||||
addAction(AssembleDebugAction(context))
|
||||
addAction(AssembleReleaseAction(context))
|
||||
addAction(CreateAABAction(context))
|
||||
addAction(CleanAction(context))
|
||||
addAction(RebuildAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_buildTasks"
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CancelBuildAction() : EditorActivityAction() {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_cancel_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_stop_daemons)
|
||||
}
|
||||
|
||||
override val id: String = "editor_stopGradleDaemons"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
log.info("Sending build cancellation request...")
|
||||
context.buildService.cancelCurrentBuild().whenComplete { result, error ->
|
||||
if (error != null) {
|
||||
log.error("Failed to send build cancellation request", error)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
if (!result.wasEnqueued) {
|
||||
log.warn(
|
||||
"Unable to enqueue cancellation request",
|
||||
result.failureReason,
|
||||
result.failureReason!!.message)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
log.info("Build cancellation request was successfully enqueued...")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CancelBuildAction() : EditorActivityAction() {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_cancel_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_stop_daemons)
|
||||
}
|
||||
|
||||
override val id: String = "editor_stopGradleDaemons"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
log.info("Sending build cancellation request...")
|
||||
context.buildService.cancelCurrentBuild().whenComplete { result, error ->
|
||||
if (error != null) {
|
||||
log.error("Failed to send build cancellation request", error)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
if (!result.wasEnqueued) {
|
||||
log.warn(
|
||||
"Unable to enqueue cancellation request",
|
||||
result.failureReason,
|
||||
result.failureReason!!.message)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
log.info("Build cancellation request was successfully enqueued...")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CleanAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.clean_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_clean)
|
||||
}
|
||||
|
||||
override val id: String = "editor_clean"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.clean()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CleanAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.clean_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_clean)
|
||||
}
|
||||
|
||||
override val id: String = "editor_clean"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.clean()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CreateAABAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.create_aab)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_bundle)
|
||||
}
|
||||
|
||||
override val id: String = "editor_createAAB"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.bundle()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CreateAABAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.create_aab)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_bundle)
|
||||
}
|
||||
|
||||
override val id: String = "editor_createAAB"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.bundle()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/**
|
||||
* Triggers a project sync request.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ProjectSyncAction() : BaseBuildAction() {
|
||||
|
||||
override val id: String = "action_editor_syncProject"
|
||||
override var requiresUIThread = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.title_sync_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_sync)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return getActivity(data)!!.initializeProject()
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/**
|
||||
* Triggers a project sync request.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ProjectSyncAction() : BaseBuildAction() {
|
||||
|
||||
override val id: String = "action_editor_syncProject"
|
||||
override var requiresUIThread = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.title_sync_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_sync)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return getActivity(data)!!.initializeProject()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/**
|
||||
* The 'Quick Run' action in the editor activity.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class QuickRunAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.quick_run_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_outline)
|
||||
}
|
||||
|
||||
override val id: String = "editor_quick_run"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/**
|
||||
* The 'Quick Run' action in the editor activity.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class QuickRunAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.quick_run_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_outline)
|
||||
}
|
||||
|
||||
override val id: String = "editor_quick_run"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RebuildAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.clean_amp_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_rebuild)
|
||||
}
|
||||
|
||||
override val id: String = "editor_cleanAndBuild"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.cleanAndRebuild()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RebuildAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.clean_amp_build)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_rebuild)
|
||||
}
|
||||
|
||||
override val id: String = "editor_cleanAndBuild"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.cleanAndRebuild()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
import com.itsaky.androidide.actions.lint.LintTaskMenu
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RunActionMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.run)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
|
||||
addAction(BuildActionMenu(context))
|
||||
addAction(LintTaskMenu(context))
|
||||
}
|
||||
|
||||
override val id: String = "editor_runMenu"
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.build
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
import com.itsaky.androidide.actions.lint.LintTaskMenu
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RunActionMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.run)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run)
|
||||
|
||||
addAction(BuildActionMenu(context))
|
||||
addAction(LintTaskMenu(context))
|
||||
}
|
||||
|
||||
override val id: String = "editor_runMenu"
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CopyAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.copy)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCopyDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_copy"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.copyText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CopyAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.copy)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCopyDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_copy"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.copyText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CutAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.cut)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCutDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
visible = getEditor(data)?.isEditable ?: false
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_cut"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.cutText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class CutAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.cut)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeCutDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
visible = getEditor(data)?.isEditable ?: false
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_cut"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.cutText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/**
|
||||
* Expands the current selection using the language server set in editor.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ExpandSelectionAction() : EditorRelatedAction() {
|
||||
override val id: String = "editor_expandSelection"
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.action_expand_selection)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_expand_selection)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return getEditor(data)!!.expandSelection()
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/**
|
||||
* Expands the current selection using the language server set in editor.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ExpandSelectionAction() : EditorRelatedAction() {
|
||||
override val id: String = "editor_expandSelection"
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.action_expand_selection)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_expand_selection)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
return getEditor(data)!!.expandSelection()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class PasteAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.paste)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModePasteDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_paste"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
visible = getEditor(data)?.isEditable ?: false
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.pasteText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class PasteAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.paste)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModePasteDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_paste"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
visible = getEditor(data)?.isEditable ?: false
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.pasteText()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class SelectAllAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.selectAll)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeSelectAllDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_selectAll"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.selectAll()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseEditorAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class SelectAllAction() : BaseEditorAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.selectAll)
|
||||
|
||||
val arr = context.obtainStyledAttributes(intArrayOf(android.R.attr.actionModeSelectAllDrawable))
|
||||
icon = arr.getDrawable(0)?.let { tintDrawable(context, it) }
|
||||
arr.recycle()
|
||||
}
|
||||
|
||||
override val id: String = "ideEditor_selectAll"
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data) ?: return false
|
||||
editor.selectAll()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
|
||||
/**
|
||||
* 'Go to next' action in editor's search action mode.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToNextAction() : SearchActionModeAction() {
|
||||
override val id: String = "editor.find.next"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.next)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_next)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.searcher.gotoNext()
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
|
||||
/**
|
||||
* 'Go to next' action in editor's search action mode.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToNextAction() : SearchActionModeAction() {
|
||||
override val id: String = "editor.find.next"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.next)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_next)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.searcher.gotoNext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
|
||||
/**
|
||||
* 'Go to previous' action in editor's search action mode.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToPreviousAction() : SearchActionModeAction() {
|
||||
override val id: String = "editor.find.previous"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.previous)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_previous)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.searcher.gotoPrevious()
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
|
||||
/**
|
||||
* 'Go to previous' action in editor's search action mode.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class GoToPreviousAction() : SearchActionModeAction() {
|
||||
override val id: String = "editor.find.previous"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(string.previous)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_previous)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return editor.searcher.gotoPrevious()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.databinding.LayoutEditorFindReplaceBinding
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/**
|
||||
* Shows dialog to replace one or all the matched text in editor searcher.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ReplaceAction() : SearchActionModeAction() {
|
||||
|
||||
override val id: String = "editor.search.replace"
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.replace)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_replace)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val context = data[Context::class.java]!!
|
||||
val editor = getEditor(data)!!
|
||||
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val binding = LayoutEditorFindReplaceBinding.inflate(LayoutInflater.from(context))
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(context)
|
||||
builder.setTitle(R.string.replace)
|
||||
builder.setView(binding.root)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.setPositiveButton(R.string.replace) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceThis(input.text.toString())
|
||||
}
|
||||
builder.setNeutralButton(R.string.replaceAll) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setNeutralButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceAllAsync(input.text.toString()).whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to replace all matched text", error)
|
||||
} else {
|
||||
editor.post { data[ActionMode::class.java]?.finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.databinding.LayoutEditorFindReplaceBinding
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
|
||||
/**
|
||||
* Shows dialog to replace one or all the matched text in editor searcher.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ReplaceAction() : SearchActionModeAction() {
|
||||
|
||||
override val id: String = "editor.search.replace"
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.replace)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_replace)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val context = data[Context::class.java]!!
|
||||
val editor = getEditor(data)!!
|
||||
|
||||
if (!editor.searcher.hasQuery()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val binding = LayoutEditorFindReplaceBinding.inflate(LayoutInflater.from(context))
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(context)
|
||||
builder.setTitle(R.string.replace)
|
||||
builder.setView(binding.root)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.setPositiveButton(R.string.replace) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceThis(input.text.toString())
|
||||
}
|
||||
builder.setNeutralButton(R.string.replaceAll) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
val input = binding.replacementInput.editText
|
||||
if (input == null) {
|
||||
log.error("Unable to perform replace action. Input field is null")
|
||||
return@setNeutralButton
|
||||
}
|
||||
|
||||
editor.searcher.replaceAllAsync(input.text.toString()).whenComplete { _, error ->
|
||||
if (error != null) {
|
||||
log.error("Unable to replace all matched text", error)
|
||||
} else {
|
||||
editor.post { data[ActionMode::class.java]?.finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.show()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+27
-27
@@ -1,27 +1,27 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionItem.Location
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class SearchActionModeAction : EditorRelatedAction() {
|
||||
override var location: Location = ActionItem.Location.EDITOR_SEARCH_ACTION_MODE
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.editor.find
|
||||
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionItem.Location
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
abstract class SearchActionModeAction : EditorRelatedAction() {
|
||||
override var location: Location = ActionItem.Location.EDITOR_SEARCH_ACTION_MODE
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class DaemonStatusAction() : EditorActivityAction() {
|
||||
override val id: String = "editor_daemonStatus"
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.gradle_daemon_status)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_info)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.showDaemonStatus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class DaemonStatusAction() : EditorActivityAction() {
|
||||
override val id: String = "editor_daemonStatus"
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.gradle_daemon_status)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_info)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.showDaemonStatus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FileTreeAction() : EditorActivityAction() {
|
||||
|
||||
override val id: String = "editor_fileTree"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.msg_file_tree)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_folder)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
if (!context.binding.root.isDrawerOpen(GravityCompat.END)) {
|
||||
context.binding.root.openDrawer(GravityCompat.END)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FileTreeAction() : EditorActivityAction() {
|
||||
|
||||
override val id: String = "editor_fileTree"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.msg_file_tree)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_folder)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
if (!context.binding.root.isDrawerOpen(GravityCompat.END)) {
|
||||
context.binding.root.openDrawer(GravityCompat.END)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FindActionMenu() : EditorActivityAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search)
|
||||
|
||||
addAction(FindInFileAction(context))
|
||||
addAction(FindInProjectAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_findActions"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = children.size > 0
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FindActionMenu() : EditorActivityAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search)
|
||||
|
||||
addAction(FindInFileAction(context))
|
||||
addAction(FindInProjectAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_findActions"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
visible = true
|
||||
enabled = children.size > 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FindInFileAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_findInFile"
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find_file)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_file)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditorView(data) ?: return false
|
||||
editor.beginSearch()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FindInFileAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_findInFile"
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find_file)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_file)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditorView(data) ?: return false
|
||||
editor.beginSearch()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FindInProjectAction() : EditorActivityAction() {
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_project)
|
||||
}
|
||||
|
||||
override val id: String = "editor_findInProject"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
getActivity(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
val dialog = context.findInProjectDialog
|
||||
|
||||
return if (dialog != null) {
|
||||
dialog.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class FindInProjectAction() : EditorActivityAction() {
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.menu_find_project)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_search_project)
|
||||
}
|
||||
|
||||
override val id: String = "editor_findInProject"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
getActivity(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
enabled = true
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
val dialog = context.findInProjectDialog
|
||||
|
||||
return if (dialog != null) {
|
||||
dialog.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
import com.itsaky.androidide.handlers.FileOptionsHandler
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class PreviewLayoutAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_previewLayout"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_preview_layout)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_preview_layout)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
val file = editor.file!!
|
||||
|
||||
val isXml = file.name.endsWith(".xml")
|
||||
|
||||
visible =
|
||||
isXml &&
|
||||
file.parentFile != null &&
|
||||
Regex(FileOptionsHandler.LAYOUT_RES_PATH_REGEX)
|
||||
.matches(file.parentFile!!.absolutePath)
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val activity = getActivity(data)!!
|
||||
activity.previewLayout()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
import com.itsaky.androidide.handlers.FileOptionsHandler
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class PreviewLayoutAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_previewLayout"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_preview_layout)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_preview_layout)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
val file = editor.file!!
|
||||
|
||||
val isXml = file.name.endsWith(".xml")
|
||||
|
||||
visible =
|
||||
isXml &&
|
||||
file.parentFile != null &&
|
||||
Regex(FileOptionsHandler.LAYOUT_RES_PATH_REGEX)
|
||||
.matches(file.parentFile!!.absolutePath)
|
||||
enabled = visible
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val activity = getActivity(data)!!
|
||||
activity.previewLayout()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.file
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/**
|
||||
* Action that formats the code in the editor.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class FormatCodeAction() : EditorRelatedAction() {
|
||||
override val id: String = "editor_file_formatCode"
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_format_code)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_format_code)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
editor.formatCodeAsync()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.file
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/**
|
||||
* Action that formats the code in the editor.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class FormatCodeAction() : EditorRelatedAction() {
|
||||
override val id: String = "editor_file_formatCode"
|
||||
override var location: ActionItem.Location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.title_format_code)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_format_code)
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)!!
|
||||
editor.formatCodeAsync()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +1,86 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.file
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class SaveFileAction() : EditorRelatedAction() {
|
||||
|
||||
private val log = ILogger.newInstance("SaveFileAction")
|
||||
|
||||
override val id: String = "editor_saveFile"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.save)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_save)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
|
||||
val context =
|
||||
getActivity(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = context.viewModel.openedFiles.isNotEmpty()
|
||||
enabled = context.areFilesModified()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
return try {
|
||||
// Cannot use context.saveAll() because this.execAction is called on non-UI thread
|
||||
// and saveAll call will result in UI actions
|
||||
val result = context.saveAllResult()
|
||||
|
||||
if (result.xmlSaved) {
|
||||
ProjectManager.generateSources(context.buildService)
|
||||
}
|
||||
|
||||
if (result.gradleSaved) {
|
||||
context.notifySyncNeeded()
|
||||
}
|
||||
true
|
||||
} catch (error: Throwable) {
|
||||
log.error("Failed to save file", error)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result is Boolean && result) {
|
||||
StudioApp.getInstance().toast(R.string.all_saved, Toaster.Type.SUCCESS)
|
||||
} else {
|
||||
log.error("Failed to save file")
|
||||
TODO("Create message in strings.xml")
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.file
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.toaster.Toaster
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class SaveFileAction() : EditorRelatedAction() {
|
||||
|
||||
private val log = ILogger.newInstance("SaveFileAction")
|
||||
|
||||
override val id: String = "editor_saveFile"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.save)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_save)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
|
||||
val context =
|
||||
getActivity(data)
|
||||
?: run {
|
||||
visible = false
|
||||
enabled = false
|
||||
return
|
||||
}
|
||||
|
||||
visible = context.viewModel.openedFiles.isNotEmpty()
|
||||
enabled = context.areFilesModified()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
return try {
|
||||
// Cannot use context.saveAll() because this.execAction is called on non-UI thread
|
||||
// and saveAll call will result in UI actions
|
||||
val result = context.saveAllResult()
|
||||
|
||||
if (result.xmlSaved) {
|
||||
ProjectManager.generateSources(context.buildService)
|
||||
}
|
||||
|
||||
if (result.gradleSaved) {
|
||||
context.notifySyncNeeded()
|
||||
}
|
||||
true
|
||||
} catch (error: Throwable) {
|
||||
log.error("Failed to save file", error)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
if (result is Boolean && result) {
|
||||
StudioApp.getInstance().toast(R.string.all_saved, Toaster.Type.SUCCESS)
|
||||
} else {
|
||||
log.error("Failed to save file")
|
||||
TODO("Create message in strings.xml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.lint
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class LintAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lint"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lint()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.lint
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class LintAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lint"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lint()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.lint
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class LintDebugAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lintDebug"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lintDebug()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.lint
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class LintDebugAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_debug)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lintDebug"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lintDebug()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.lint
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class LintReleaseAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_release)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lintRelease"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lintRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.lint
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class LintReleaseAction() : BaseBuildAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_release)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_run_release)
|
||||
}
|
||||
|
||||
override val id: String = "editor_lintRelease"
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.lintRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.lint
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class LintTaskMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_tasks)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
|
||||
|
||||
addAction(LintAction(context))
|
||||
addAction(LintDebugAction(context))
|
||||
addAction(LintReleaseAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_lintTasks"
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.lint
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionMenu
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class LintTaskMenu() : BaseBuildAction(), ActionMenu {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.lint_tasks)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_lint)
|
||||
|
||||
addAction(LintAction(context))
|
||||
addAction(LintDebugAction(context))
|
||||
addAction(LintReleaseAction(context))
|
||||
}
|
||||
|
||||
override val children: MutableSet<ActionItem> = mutableSetOf()
|
||||
override val id: String = "editor_lintTasks"
|
||||
}
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.text
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.MenuItem
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.blankj.utilcode.util.KeyboardUtils
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RedoAction() : EditorRelatedAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.redo)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_redo)
|
||||
}
|
||||
|
||||
override val id: String = "editor_redo"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
enabled = editor.canRedo()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data)!!
|
||||
editor.redo()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getShowAsActionFlags(data: ActionData): Int {
|
||||
return if (KeyboardUtils.isSoftInputVisible(data.get(Context::class.java) as Activity)) {
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM
|
||||
} else {
|
||||
MenuItem.SHOW_AS_ACTION_NEVER
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.text
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.MenuItem
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.blankj.utilcode.util.KeyboardUtils
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class RedoAction() : EditorRelatedAction() {
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.redo)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_redo)
|
||||
}
|
||||
|
||||
override val id: String = "editor_redo"
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
enabled = editor.canRedo()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val editor = getEditor(data)!!
|
||||
editor.redo()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getShowAsActionFlags(data: ActionData): Int {
|
||||
return if (KeyboardUtils.isSoftInputVisible(data.get(Context::class.java) as Activity)) {
|
||||
MenuItem.SHOW_AS_ACTION_IF_ROOM
|
||||
} else {
|
||||
MenuItem.SHOW_AS_ACTION_NEVER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.text
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class UndoAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_undo"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.undo)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_undo)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
enabled = editor.canUndo()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)
|
||||
return if (editor != null) {
|
||||
editor.undo()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.text
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||
|
||||
/** @author Akash Yadav */
|
||||
class UndoAction() : EditorRelatedAction() {
|
||||
|
||||
override val id: String = "editor_undo"
|
||||
|
||||
constructor(context: Context) : this() {
|
||||
label = context.getString(R.string.undo)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_undo)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
super.prepare(data)
|
||||
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
val editor = getEditor(data)!!
|
||||
enabled = editor.canUndo()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData): Any {
|
||||
val editor = getEditor(data)
|
||||
return if (editor != null) {
|
||||
editor.undo()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,230 +1,230 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutCompletionItemBinding;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.apiinfo.ApiInfo;
|
||||
import com.itsaky.apiinfo.models.ClassInfo;
|
||||
import com.itsaky.apiinfo.models.FieldInfo;
|
||||
import com.itsaky.apiinfo.models.Info;
|
||||
import com.itsaky.apiinfo.models.MethodInfo;
|
||||
import com.itsaky.lsp.models.CompletionItem;
|
||||
import com.itsaky.lsp.models.CompletionItemKind;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.github.rosemoe.sora.widget.component.EditorAutoCompletion;
|
||||
import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter;
|
||||
|
||||
public class CompletionListAdapter extends EditorCompletionAdapter {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("CompletionListAdapter");
|
||||
|
||||
@Override
|
||||
public void attachValues(
|
||||
EditorAutoCompletion window,
|
||||
List<io.github.rosemoe.sora.lang.completion.CompletionItem> items) {
|
||||
super.attachValues(window, items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemHeight() {
|
||||
return (int)
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getView(
|
||||
int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) {
|
||||
final LayoutCompletionItemBinding binding =
|
||||
LayoutCompletionItemBinding.inflate(LayoutInflater.from(getContext()), parent, false);
|
||||
|
||||
var item = (CompletionItem) getItem(position);
|
||||
String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString();
|
||||
String header = type.length() <= 0 ? "O" : String.valueOf(type.charAt(0));
|
||||
|
||||
binding.completionIconText.setText(header);
|
||||
binding.completionLabel.setText(label);
|
||||
binding.completionType.setText(type);
|
||||
binding.completionDetail.setText(desc);
|
||||
binding.completionIconText.setTypeface(TypefaceUtils.jetbrainsMono(), Typeface.BOLD);
|
||||
if (desc.isEmpty()) {
|
||||
binding.completionDetail.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (isCurrentCursorPosition) {
|
||||
binding
|
||||
.getRoot()
|
||||
.setBackgroundColor(
|
||||
ContextCompat.getColor(getContext(), R.color.completionList_backgroundSelected));
|
||||
} else {
|
||||
binding
|
||||
.getRoot()
|
||||
.setBackgroundColor(
|
||||
ContextCompat.getColor(getContext(), R.color.completionList_background));
|
||||
}
|
||||
|
||||
binding.completionApiInfo.setVisibility(View.GONE);
|
||||
|
||||
showApiInfoIfNeeded(item, binding.completionApiInfo);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) {
|
||||
|
||||
new Thread(
|
||||
() -> {
|
||||
final ApiInfo apiInfo = StudioApp.getInstance().apiInfo();
|
||||
boolean hasRead = apiInfo != null && apiInfo.hasRead();
|
||||
boolean isValid = isValidForApiVersion(item);
|
||||
|
||||
if (hasRead && isValid) {
|
||||
|
||||
JsonElement element = new Gson().toJsonTree(item.getData());
|
||||
if (element == null || !element.isJsonObject()) {
|
||||
return;
|
||||
}
|
||||
JsonObject data = element.getAsJsonObject();
|
||||
if (!data.has("className")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String className = data.get("className").getAsString();
|
||||
CompletionItemKind kind = item.getKind();
|
||||
|
||||
ClassInfo clazz = apiInfo.getClassByName(className);
|
||||
if (clazz == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Info info = clazz;
|
||||
|
||||
// If this Info is not a class info, find the right member
|
||||
if (kind == CompletionItemKind.METHOD
|
||||
&& data.has("erasedParameterTypes")
|
||||
&& data.has("memberName")) {
|
||||
JsonElement erasedParameterTypesElement = data.get("erasedParameterTypes");
|
||||
if (erasedParameterTypesElement.isJsonArray()) {
|
||||
String simpleName = data.get("memberName").getAsString();
|
||||
JsonArray erasedParameterTypes = erasedParameterTypesElement.getAsJsonArray();
|
||||
String[] paramTypes = new String[erasedParameterTypes.size()];
|
||||
for (int i = 0; i < erasedParameterTypes.size(); i++) {
|
||||
paramTypes[i] = erasedParameterTypes.get(i).getAsString();
|
||||
}
|
||||
|
||||
MethodInfo method = clazz.getMethod(simpleName, paramTypes);
|
||||
|
||||
if (method != null) {
|
||||
info = method;
|
||||
}
|
||||
}
|
||||
} else if (kind == CompletionItemKind.FIELD && data.has("memberName")) {
|
||||
String simpleName = data.get("memberName").getAsString();
|
||||
FieldInfo field = clazz.getFieldByName(simpleName);
|
||||
|
||||
if (field != null) {
|
||||
info = field;
|
||||
}
|
||||
}
|
||||
|
||||
final StringBuilder infoBuilder = new StringBuilder();
|
||||
if (info.since > 1) {
|
||||
infoBuilder.append(
|
||||
completionApiInfo
|
||||
.getContext()
|
||||
.getString(R.string.msg_api_info_since, info.since));
|
||||
infoBuilder.append("\n");
|
||||
}
|
||||
|
||||
if (info.removed > 0) {
|
||||
infoBuilder.append(
|
||||
completionApiInfo
|
||||
.getContext()
|
||||
.getString(R.string.msg_api_info_removed, info.removed));
|
||||
infoBuilder.append("\n");
|
||||
}
|
||||
|
||||
if (info.deprecated > 0) {
|
||||
infoBuilder.append(
|
||||
completionApiInfo
|
||||
.getContext()
|
||||
.getString(R.string.msg_api_info_deprecated, info.deprecated));
|
||||
infoBuilder.append("\n");
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
if (infoBuilder.toString().trim().length() > 0) {
|
||||
completionApiInfo.setText(infoBuilder.toString().trim());
|
||||
completionApiInfo.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
completionApiInfo.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
private boolean isValidForApiVersion(CompletionItem item) {
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
final CompletionItemKind type = item.getKind();
|
||||
final var data = item.getData();
|
||||
if (
|
||||
|
||||
// These represent a class type
|
||||
(type == CompletionItemKind.CLASS
|
||||
|| type == CompletionItemKind.INTERFACE
|
||||
|| type == CompletionItemKind.ENUM
|
||||
|
||||
// These represent a method type
|
||||
|| type == CompletionItemKind.METHOD
|
||||
|| type == CompletionItemKind.CONSTRUCTOR
|
||||
|
||||
// A field type
|
||||
|| type == CompletionItemKind.FIELD)
|
||||
&& data != null) {
|
||||
return !TextUtils.isEmpty(data.getClassName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.adapters;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutCompletionItemBinding;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.TypefaceUtils;
|
||||
import com.itsaky.apiinfo.ApiInfo;
|
||||
import com.itsaky.apiinfo.models.ClassInfo;
|
||||
import com.itsaky.apiinfo.models.FieldInfo;
|
||||
import com.itsaky.apiinfo.models.Info;
|
||||
import com.itsaky.apiinfo.models.MethodInfo;
|
||||
import com.itsaky.lsp.models.CompletionItem;
|
||||
import com.itsaky.lsp.models.CompletionItemKind;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.github.rosemoe.sora.widget.component.EditorAutoCompletion;
|
||||
import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter;
|
||||
|
||||
public class CompletionListAdapter extends EditorCompletionAdapter {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("CompletionListAdapter");
|
||||
|
||||
@Override
|
||||
public void attachValues(
|
||||
EditorAutoCompletion window,
|
||||
List<io.github.rosemoe.sora.lang.completion.CompletionItem> items) {
|
||||
super.attachValues(window, items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemHeight() {
|
||||
return (int)
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getView(
|
||||
int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) {
|
||||
final LayoutCompletionItemBinding binding =
|
||||
LayoutCompletionItemBinding.inflate(LayoutInflater.from(getContext()), parent, false);
|
||||
|
||||
var item = (CompletionItem) getItem(position);
|
||||
String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString();
|
||||
String header = type.length() <= 0 ? "O" : String.valueOf(type.charAt(0));
|
||||
|
||||
binding.completionIconText.setText(header);
|
||||
binding.completionLabel.setText(label);
|
||||
binding.completionType.setText(type);
|
||||
binding.completionDetail.setText(desc);
|
||||
binding.completionIconText.setTypeface(TypefaceUtils.jetbrainsMono(), Typeface.BOLD);
|
||||
if (desc.isEmpty()) {
|
||||
binding.completionDetail.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (isCurrentCursorPosition) {
|
||||
binding
|
||||
.getRoot()
|
||||
.setBackgroundColor(
|
||||
ContextCompat.getColor(getContext(), R.color.completionList_backgroundSelected));
|
||||
} else {
|
||||
binding
|
||||
.getRoot()
|
||||
.setBackgroundColor(
|
||||
ContextCompat.getColor(getContext(), R.color.completionList_background));
|
||||
}
|
||||
|
||||
binding.completionApiInfo.setVisibility(View.GONE);
|
||||
|
||||
showApiInfoIfNeeded(item, binding.completionApiInfo);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) {
|
||||
|
||||
new Thread(
|
||||
() -> {
|
||||
final ApiInfo apiInfo = StudioApp.getInstance().apiInfo();
|
||||
boolean hasRead = apiInfo != null && apiInfo.hasRead();
|
||||
boolean isValid = isValidForApiVersion(item);
|
||||
|
||||
if (hasRead && isValid) {
|
||||
|
||||
JsonElement element = new Gson().toJsonTree(item.getData());
|
||||
if (element == null || !element.isJsonObject()) {
|
||||
return;
|
||||
}
|
||||
JsonObject data = element.getAsJsonObject();
|
||||
if (!data.has("className")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String className = data.get("className").getAsString();
|
||||
CompletionItemKind kind = item.getKind();
|
||||
|
||||
ClassInfo clazz = apiInfo.getClassByName(className);
|
||||
if (clazz == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Info info = clazz;
|
||||
|
||||
// If this Info is not a class info, find the right member
|
||||
if (kind == CompletionItemKind.METHOD
|
||||
&& data.has("erasedParameterTypes")
|
||||
&& data.has("memberName")) {
|
||||
JsonElement erasedParameterTypesElement = data.get("erasedParameterTypes");
|
||||
if (erasedParameterTypesElement.isJsonArray()) {
|
||||
String simpleName = data.get("memberName").getAsString();
|
||||
JsonArray erasedParameterTypes = erasedParameterTypesElement.getAsJsonArray();
|
||||
String[] paramTypes = new String[erasedParameterTypes.size()];
|
||||
for (int i = 0; i < erasedParameterTypes.size(); i++) {
|
||||
paramTypes[i] = erasedParameterTypes.get(i).getAsString();
|
||||
}
|
||||
|
||||
MethodInfo method = clazz.getMethod(simpleName, paramTypes);
|
||||
|
||||
if (method != null) {
|
||||
info = method;
|
||||
}
|
||||
}
|
||||
} else if (kind == CompletionItemKind.FIELD && data.has("memberName")) {
|
||||
String simpleName = data.get("memberName").getAsString();
|
||||
FieldInfo field = clazz.getFieldByName(simpleName);
|
||||
|
||||
if (field != null) {
|
||||
info = field;
|
||||
}
|
||||
}
|
||||
|
||||
final StringBuilder infoBuilder = new StringBuilder();
|
||||
if (info.since > 1) {
|
||||
infoBuilder.append(
|
||||
completionApiInfo
|
||||
.getContext()
|
||||
.getString(R.string.msg_api_info_since, info.since));
|
||||
infoBuilder.append("\n");
|
||||
}
|
||||
|
||||
if (info.removed > 0) {
|
||||
infoBuilder.append(
|
||||
completionApiInfo
|
||||
.getContext()
|
||||
.getString(R.string.msg_api_info_removed, info.removed));
|
||||
infoBuilder.append("\n");
|
||||
}
|
||||
|
||||
if (info.deprecated > 0) {
|
||||
infoBuilder.append(
|
||||
completionApiInfo
|
||||
.getContext()
|
||||
.getString(R.string.msg_api_info_deprecated, info.deprecated));
|
||||
infoBuilder.append("\n");
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
if (infoBuilder.toString().trim().length() > 0) {
|
||||
completionApiInfo.setText(infoBuilder.toString().trim());
|
||||
completionApiInfo.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
completionApiInfo.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
private boolean isValidForApiVersion(CompletionItem item) {
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
final CompletionItemKind type = item.getKind();
|
||||
final var data = item.getData();
|
||||
if (
|
||||
|
||||
// These represent a class type
|
||||
(type == CompletionItemKind.CLASS
|
||||
|| type == CompletionItemKind.INTERFACE
|
||||
|| type == CompletionItemKind.ENUM
|
||||
|
||||
// These represent a method type
|
||||
|| type == CompletionItemKind.METHOD
|
||||
|| type == CompletionItemKind.CONSTRUCTOR
|
||||
|
||||
// A field type
|
||||
|| type == CompletionItemKind.FIELD)
|
||||
&& data != null) {
|
||||
return !TextUtils.isEmpty(data.getClassName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,319 +1,319 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.filepicker;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* A document provider for the Storage Access Framework which exposes the files in the $HOME/
|
||||
* directory to other apps.
|
||||
*
|
||||
* <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
|
||||
* 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." -
|
||||
* http://developer.android.com/guide/topics/providers/document-provider.html#43
|
||||
*
|
||||
* @see <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 {
|
||||
|
||||
private static final String ALL_MIME_TYPES = "*/*";
|
||||
private static final ILogger LOG = ILogger.newInstance("IDEDocumentsProvider");
|
||||
private static final File BASE_DIR = getBaseDir();
|
||||
// The default columns to return information about a root if no specific
|
||||
// columns are requested in a query.
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION =
|
||||
new String[] {
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
};
|
||||
// The default columns to return information about a document if no specific
|
||||
// columns are requested in a query.
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION =
|
||||
new String[] {
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_SIZE
|
||||
};
|
||||
|
||||
@NonNull
|
||||
private static File getBaseDir() {
|
||||
if (Environment.HOME != null) {
|
||||
return Environment.HOME;
|
||||
}
|
||||
|
||||
return new File(Environment.DEFAULT_HOME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>The reverse of @{link #getFileForDocId}.
|
||||
*/
|
||||
private static String getDocIdForFile(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/** Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). */
|
||||
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
||||
final File f = new File(docId);
|
||||
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||
return f;
|
||||
}
|
||||
|
||||
private static String getMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
final String name = file.getName();
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1).toLowerCase();
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
final String applicationName = getContext().getString(R.string.app_name);
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
LOG.debug("queryRoots() before all add");
|
||||
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 1");
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 2");
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
LOG.debug("queryRoots() before all add, 3");
|
||||
row.add(
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
LOG.debug("queryRoots() before all add, 4");
|
||||
row.add(Root.COLUMN_TITLE, applicationName);
|
||||
LOG.debug("queryRoots() before all add, 5");
|
||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||
LOG.debug("queryRoots() before all add, 6");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||
LOG.debug("queryRoots() before all add, 7");
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
LOG.debug("queryRoots() before all add");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result, documentId, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = getFileForDocId(parentDocumentId);
|
||||
for (File file : parent.listFiles()) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(
|
||||
final String documentId, String mode, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||
return ParcelFileDescriptor.open(file, accessMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(
|
||||
String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final ParcelFileDescriptor pfd =
|
||||
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||
throws FileNotFoundException {
|
||||
File newFile = new File(parentDocumentId, displayName);
|
||||
int noConflictId = 2;
|
||||
while (newFile.exists()) {
|
||||
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
|
||||
}
|
||||
try {
|
||||
boolean succeeded;
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
succeeded = newFile.mkdir();
|
||||
} else {
|
||||
succeeded = newFile.createNewFile();
|
||||
}
|
||||
if (!succeeded) {
|
||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
return newFile.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
if (!file.delete()) {
|
||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
return getMimeType(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySearchDocuments(String rootId, String query, String[] projection)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = getFileForDocId(rootId);
|
||||
|
||||
// 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
|
||||
// implementations might rank results and use other data about files, rather than the file
|
||||
// name, to produce a match.
|
||||
final LinkedList<File> pending = new LinkedList<>();
|
||||
pending.add(parent);
|
||||
|
||||
final int MAX_SEARCH_RESULTS = 50;
|
||||
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
|
||||
final File file = pending.removeFirst();
|
||||
// Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g.
|
||||
// search
|
||||
// through the whole SD card).
|
||||
boolean isInsideHome;
|
||||
try {
|
||||
isInsideHome = file.getCanonicalPath().startsWith(Environment.HOME.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
isInsideHome = true;
|
||||
}
|
||||
if (isInsideHome) {
|
||||
if (file.isDirectory()) {
|
||||
Collections.addAll(pending, file.listFiles());
|
||||
} else {
|
||||
if (file.getName().toLowerCase().contains(query)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a representation of a file to a cursor.
|
||||
*
|
||||
* @param result the cursor to modify
|
||||
* @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)
|
||||
*/
|
||||
private void includeFile(MatrixCursor result, String docId, File file)
|
||||
throws FileNotFoundException {
|
||||
if (docId == null) {
|
||||
docId = getDocIdForFile(file);
|
||||
} else {
|
||||
file = getFileForDocId(docId);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (file.isDirectory()) {
|
||||
if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
} else if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||
}
|
||||
if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE;
|
||||
|
||||
final String displayName = file.getName();
|
||||
final String mimeType = getMimeType(file);
|
||||
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.filepicker;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* A document provider for the Storage Access Framework which exposes the files in the $HOME/
|
||||
* directory to other apps.
|
||||
*
|
||||
* <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
|
||||
* 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." -
|
||||
* http://developer.android.com/guide/topics/providers/document-provider.html#43
|
||||
*
|
||||
* @see <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 {
|
||||
|
||||
private static final String ALL_MIME_TYPES = "*/*";
|
||||
private static final ILogger LOG = ILogger.newInstance("IDEDocumentsProvider");
|
||||
private static final File BASE_DIR = getBaseDir();
|
||||
// The default columns to return information about a root if no specific
|
||||
// columns are requested in a query.
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION =
|
||||
new String[] {
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
};
|
||||
// The default columns to return information about a document if no specific
|
||||
// columns are requested in a query.
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION =
|
||||
new String[] {
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_SIZE
|
||||
};
|
||||
|
||||
@NonNull
|
||||
private static File getBaseDir() {
|
||||
if (Environment.HOME != null) {
|
||||
return Environment.HOME;
|
||||
}
|
||||
|
||||
return new File(Environment.DEFAULT_HOME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>The reverse of @{link #getFileForDocId}.
|
||||
*/
|
||||
private static String getDocIdForFile(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/** Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). */
|
||||
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
||||
final File f = new File(docId);
|
||||
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||
return f;
|
||||
}
|
||||
|
||||
private static String getMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
final String name = file.getName();
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1).toLowerCase();
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
final String applicationName = getContext().getString(R.string.app_name);
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
LOG.debug("queryRoots() before all add");
|
||||
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 1");
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||
LOG.debug("queryRoots() before all add, 2");
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
LOG.debug("queryRoots() before all add, 3");
|
||||
row.add(
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
LOG.debug("queryRoots() before all add, 4");
|
||||
row.add(Root.COLUMN_TITLE, applicationName);
|
||||
LOG.debug("queryRoots() before all add, 5");
|
||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||
LOG.debug("queryRoots() before all add, 6");
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||
LOG.debug("queryRoots() before all add, 7");
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
LOG.debug("queryRoots() before all add");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result, documentId, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = getFileForDocId(parentDocumentId);
|
||||
for (File file : parent.listFiles()) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(
|
||||
final String documentId, String mode, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||
return ParcelFileDescriptor.open(file, accessMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(
|
||||
String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final ParcelFileDescriptor pfd =
|
||||
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||
throws FileNotFoundException {
|
||||
File newFile = new File(parentDocumentId, displayName);
|
||||
int noConflictId = 2;
|
||||
while (newFile.exists()) {
|
||||
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
|
||||
}
|
||||
try {
|
||||
boolean succeeded;
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
succeeded = newFile.mkdir();
|
||||
} else {
|
||||
succeeded = newFile.createNewFile();
|
||||
}
|
||||
if (!succeeded) {
|
||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||
}
|
||||
return newFile.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
if (!file.delete()) {
|
||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
return getMimeType(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySearchDocuments(String rootId, String query, String[] projection)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = getFileForDocId(rootId);
|
||||
|
||||
// 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
|
||||
// implementations might rank results and use other data about files, rather than the file
|
||||
// name, to produce a match.
|
||||
final LinkedList<File> pending = new LinkedList<>();
|
||||
pending.add(parent);
|
||||
|
||||
final int MAX_SEARCH_RESULTS = 50;
|
||||
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
|
||||
final File file = pending.removeFirst();
|
||||
// Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g.
|
||||
// search
|
||||
// through the whole SD card).
|
||||
boolean isInsideHome;
|
||||
try {
|
||||
isInsideHome = file.getCanonicalPath().startsWith(Environment.HOME.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
isInsideHome = true;
|
||||
}
|
||||
if (isInsideHome) {
|
||||
if (file.isDirectory()) {
|
||||
Collections.addAll(pending, file.listFiles());
|
||||
} else {
|
||||
if (file.getName().toLowerCase().contains(query)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a representation of a file to a cursor.
|
||||
*
|
||||
* @param result the cursor to modify
|
||||
* @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)
|
||||
*/
|
||||
private void includeFile(MatrixCursor result, String docId, File file)
|
||||
throws FileNotFoundException {
|
||||
if (docId == null) {
|
||||
docId = getDocIdForFile(file);
|
||||
} else {
|
||||
file = getFileForDocId(docId);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (file.isDirectory()) {
|
||||
if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
} else if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||
}
|
||||
if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE;
|
||||
|
||||
final String displayName = file.getName();
|
||||
final String mimeType = getMimeType(file);
|
||||
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,340 +1,340 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.fragments;
|
||||
|
||||
import static com.unnamed.b.atv.view.AndroidTreeView.NODES_PATH_SEPARATOR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.IntentUtils;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutEditorFileTreeBinding;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.FileTreeCallable;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.views.editor.CodeEditorView;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.unnamed.b.atv.model.TreeNode;
|
||||
import com.unnamed.b.atv.view.AndroidTreeView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
|
||||
|
||||
private static final String KEY_STORED_TREE_STATE = "fileTree_state";
|
||||
private static final ILogger LOG = ILogger.newInstance("FileTreeFragment");
|
||||
private LayoutEditorFileTreeBinding binding;
|
||||
private AndroidTreeView mFileTreeView;
|
||||
private FileActionListener mFileActionListener;
|
||||
private TreeNode mRoot;
|
||||
private String mTreeState;
|
||||
|
||||
public FileTreeFragment() {}
|
||||
|
||||
@NonNull
|
||||
public static FileTreeFragment newInstance() {
|
||||
return new FileTreeFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mFileActionListener = (FileActionListener) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
saveTreeState();
|
||||
outState.putString(KEY_STORED_TREE_STATE, mTreeState);
|
||||
}
|
||||
|
||||
public void saveTreeState() {
|
||||
if (mFileTreeView != null) {
|
||||
mTreeState = mFileTreeView.getSaveState();
|
||||
} else {
|
||||
LOG.error("Unable to save tree state. TreeView is null.");
|
||||
mTreeState = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) {
|
||||
mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null);
|
||||
}
|
||||
|
||||
listProjectFiles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(TreeNode node, Object p2) {
|
||||
final File f = (File) p2;
|
||||
if (!f.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (f.isFile()) {
|
||||
if (f.getName().endsWith(".apk")) {
|
||||
final var intent = IntentUtils.getInstallAppIntent(f);
|
||||
if (intent != null) {
|
||||
requireContext().startActivity(intent);
|
||||
} else {
|
||||
StudioApp.getInstance()
|
||||
.toast(getString(R.string.msg_apk_install_intent_failed), Toaster.Type.ERROR);
|
||||
}
|
||||
} else if (mFileActionListener != null && (FileUtils.isUtf8(f) || f.length() == 0)) {
|
||||
mFileActionListener.openFile(f);
|
||||
}
|
||||
} else if (f.isDirectory() && f.exists()) {
|
||||
if (node.isExpanded()) {
|
||||
collapseNode(node);
|
||||
} else if (f.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath())
|
||||
&& !node.isExpanded()) {
|
||||
expandNode(node);
|
||||
} else {
|
||||
setLoading(node);
|
||||
listNode(
|
||||
node,
|
||||
() -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void collapseNode(TreeNode node) {
|
||||
if (mFileTreeView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
|
||||
mFileTreeView.collapseNode(node);
|
||||
}
|
||||
|
||||
public void expandNode(TreeNode node) {
|
||||
if (mFileTreeView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
|
||||
mFileTreeView.expandNode(node);
|
||||
}
|
||||
|
||||
private void setLoading(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).setLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private void listNode(@NonNull TreeNode node, Runnable whenDone) {
|
||||
|
||||
if (whenDone == null) {
|
||||
whenDone = () -> {};
|
||||
}
|
||||
|
||||
node.getChildren().clear();
|
||||
node.setExpanded(false);
|
||||
|
||||
final var finalWhenDone = whenDone;
|
||||
TaskExecutor.execAsync(
|
||||
() -> {
|
||||
getNodeFromFiles(node.getValue().listFiles(), node);
|
||||
TreeNode temp = node;
|
||||
while (temp.size() == 1) {
|
||||
temp = temp.childAt(0);
|
||||
if (!temp.getValue().isDirectory()) {
|
||||
break;
|
||||
}
|
||||
getNodeFromFiles(temp.getValue().listFiles(), temp);
|
||||
temp.setExpanded(true);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
__ -> finalWhenDone.run());
|
||||
}
|
||||
|
||||
private void updateChevron(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
private void getNodeFromFiles(File[] files, TreeNode parent) {
|
||||
Arrays.sort(files, new FileTreeCallable.SortFileName());
|
||||
Arrays.sort(files, new FileTreeCallable.SortFolder());
|
||||
for (File file : files) {
|
||||
TreeNode node = new TreeNode(file);
|
||||
node.setViewHolder(new FileTreeViewHolder(getContext()));
|
||||
parent.addChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(TreeNode node, Object value) {
|
||||
if (mFileActionListener != null) {
|
||||
mFileActionListener.showFileOptions((File) value, node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void listProjectFiles() {
|
||||
final var projectDirPath = ProjectManager.INSTANCE.getProjectDirPath();
|
||||
if (projectDirPath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final File gradleProps = Environment.GRADLE_PROPS;
|
||||
final File gradleHome = Environment.GRADLE_USER_HOME;
|
||||
File projectDir = new File(projectDirPath);
|
||||
mRoot = TreeNode.root(projectDir);
|
||||
if (gradleHome.exists() && gradleHome.isDirectory()) {
|
||||
if (!gradleProps.exists()) {
|
||||
FileIOUtils.writeFileFromString(
|
||||
gradleProps,
|
||||
"# Specify global Gradle properties in this file\n"
|
||||
+ "# These properties will be applicable for every project you build"
|
||||
+ " with Gradle.");
|
||||
}
|
||||
TreeNode home = new TreeNode(gradleHome);
|
||||
home.setViewHolder(new FileTreeViewHolder(getContext()));
|
||||
TreeNode prop = new TreeNode(gradleProps);
|
||||
prop.setViewHolder(new FileTreeViewHolder(getContext()));
|
||||
home.addChild(prop);
|
||||
mRoot.addChild(home);
|
||||
}
|
||||
mRoot.setViewHolder(new FileTreeViewHolder(getContext()));
|
||||
|
||||
getScrollView().setVisibility(View.GONE);
|
||||
getLoadingProgress().setVisibility(View.VISIBLE);
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
new FileTreeCallable(getContext(), mRoot, projectDir),
|
||||
(result) -> {
|
||||
getScrollView().setVisibility(View.VISIBLE);
|
||||
getLoadingProgress().setVisibility(View.GONE);
|
||||
AndroidTreeView tree = createTreeView(mRoot);
|
||||
if (tree != null) {
|
||||
tree.setUseAutoToggle(false);
|
||||
tree.setDefaultNodeClickListener(FileTreeFragment.this);
|
||||
tree.setDefaultNodeLongClickListener(FileTreeFragment.this);
|
||||
getScrollView().removeAllViews();
|
||||
|
||||
final var view = tree.getView();
|
||||
getScrollView().addView(view);
|
||||
|
||||
view.post(this::tryRestoreState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AndroidTreeView createTreeView(TreeNode node) {
|
||||
Context ctx = null;
|
||||
if (getActivity() != null) {
|
||||
ctx = getActivity();
|
||||
} else if (getContext() != null) {
|
||||
ctx = getContext();
|
||||
}
|
||||
|
||||
if (ctx == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple);
|
||||
}
|
||||
|
||||
public HorizontalScrollView getScrollView() {
|
||||
return binding.filetreeHorizontalScrollView;
|
||||
}
|
||||
|
||||
public ProgressBar getLoadingProgress() {
|
||||
return binding.fileTreeLoadingProgress;
|
||||
}
|
||||
|
||||
private void tryRestoreState() {
|
||||
tryRestoreState(mTreeState);
|
||||
}
|
||||
|
||||
private void tryRestoreState(String state) {
|
||||
if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
|
||||
|
||||
LOG.debug("Restoring tree view state:", "'" + state + "'");
|
||||
|
||||
mFileTreeView.collapseAll();
|
||||
final var openNodesArray = state.split(NODES_PATH_SEPARATOR);
|
||||
final var openNodes = new HashSet<>(Arrays.asList(openNodesArray));
|
||||
restoreNodeState(mRoot, openNodes);
|
||||
} else {
|
||||
LOG.error("Unable to restore tree state", "treeState=" + state, "treeView=" + mFileTreeView);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreNodeState(@NonNull TreeNode root, Set<String> openNodes) {
|
||||
final var children = root.getChildren();
|
||||
for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
|
||||
final var node = children.get(i);
|
||||
if (openNodes.contains(node.getPath())) {
|
||||
listNode(
|
||||
node,
|
||||
() -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
restoreNodeState(node, openNodes);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface FileActionListener {
|
||||
CodeEditorView openFile(File file);
|
||||
|
||||
void showFileOptions(File file, TreeNode node);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.fragments;
|
||||
|
||||
import static com.unnamed.b.atv.view.AndroidTreeView.NODES_PATH_SEPARATOR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.IntentUtils;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutEditorFileTreeBinding;
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tasks.callables.FileTreeCallable;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.views.editor.CodeEditorView;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
import com.unnamed.b.atv.model.TreeNode;
|
||||
import com.unnamed.b.atv.view.AndroidTreeView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class FileTreeFragment extends BottomSheetDialogFragment
|
||||
implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
|
||||
|
||||
private static final String KEY_STORED_TREE_STATE = "fileTree_state";
|
||||
private static final ILogger LOG = ILogger.newInstance("FileTreeFragment");
|
||||
private LayoutEditorFileTreeBinding binding;
|
||||
private AndroidTreeView mFileTreeView;
|
||||
private FileActionListener mFileActionListener;
|
||||
private TreeNode mRoot;
|
||||
private String mTreeState;
|
||||
|
||||
public FileTreeFragment() {}
|
||||
|
||||
@NonNull
|
||||
public static FileTreeFragment newInstance() {
|
||||
return new FileTreeFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mFileActionListener = (FileActionListener) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
saveTreeState();
|
||||
outState.putString(KEY_STORED_TREE_STATE, mTreeState);
|
||||
}
|
||||
|
||||
public void saveTreeState() {
|
||||
if (mFileTreeView != null) {
|
||||
mTreeState = mFileTreeView.getSaveState();
|
||||
} else {
|
||||
LOG.error("Unable to save tree state. TreeView is null.");
|
||||
mTreeState = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) {
|
||||
mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null);
|
||||
}
|
||||
|
||||
listProjectFiles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(TreeNode node, Object p2) {
|
||||
final File f = (File) p2;
|
||||
if (!f.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (f.isFile()) {
|
||||
if (f.getName().endsWith(".apk")) {
|
||||
final var intent = IntentUtils.getInstallAppIntent(f);
|
||||
if (intent != null) {
|
||||
requireContext().startActivity(intent);
|
||||
} else {
|
||||
StudioApp.getInstance()
|
||||
.toast(getString(R.string.msg_apk_install_intent_failed), Toaster.Type.ERROR);
|
||||
}
|
||||
} else if (mFileActionListener != null && (FileUtils.isUtf8(f) || f.length() == 0)) {
|
||||
mFileActionListener.openFile(f);
|
||||
}
|
||||
} else if (f.isDirectory() && f.exists()) {
|
||||
if (node.isExpanded()) {
|
||||
collapseNode(node);
|
||||
} else if (f.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath())
|
||||
&& !node.isExpanded()) {
|
||||
expandNode(node);
|
||||
} else {
|
||||
setLoading(node);
|
||||
listNode(
|
||||
node,
|
||||
() -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void collapseNode(TreeNode node) {
|
||||
if (mFileTreeView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
|
||||
mFileTreeView.collapseNode(node);
|
||||
}
|
||||
|
||||
public void expandNode(TreeNode node) {
|
||||
if (mFileTreeView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
|
||||
mFileTreeView.expandNode(node);
|
||||
}
|
||||
|
||||
private void setLoading(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).setLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private void listNode(@NonNull TreeNode node, Runnable whenDone) {
|
||||
|
||||
if (whenDone == null) {
|
||||
whenDone = () -> {};
|
||||
}
|
||||
|
||||
node.getChildren().clear();
|
||||
node.setExpanded(false);
|
||||
|
||||
final var finalWhenDone = whenDone;
|
||||
TaskExecutor.execAsync(
|
||||
() -> {
|
||||
getNodeFromFiles(node.getValue().listFiles(), node);
|
||||
TreeNode temp = node;
|
||||
while (temp.size() == 1) {
|
||||
temp = temp.childAt(0);
|
||||
if (!temp.getValue().isDirectory()) {
|
||||
break;
|
||||
}
|
||||
getNodeFromFiles(temp.getValue().listFiles(), temp);
|
||||
temp.setExpanded(true);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
__ -> finalWhenDone.run());
|
||||
}
|
||||
|
||||
private void updateChevron(@NonNull TreeNode node) {
|
||||
if (node.getViewHolder() instanceof FileTreeViewHolder) {
|
||||
((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
private void getNodeFromFiles(File[] files, TreeNode parent) {
|
||||
Arrays.sort(files, new FileTreeCallable.SortFileName());
|
||||
Arrays.sort(files, new FileTreeCallable.SortFolder());
|
||||
for (File file : files) {
|
||||
TreeNode node = new TreeNode(file);
|
||||
node.setViewHolder(new FileTreeViewHolder(getContext()));
|
||||
parent.addChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(TreeNode node, Object value) {
|
||||
if (mFileActionListener != null) {
|
||||
mFileActionListener.showFileOptions((File) value, node);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void listProjectFiles() {
|
||||
final var projectDirPath = ProjectManager.INSTANCE.getProjectDirPath();
|
||||
if (projectDirPath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final File gradleProps = Environment.GRADLE_PROPS;
|
||||
final File gradleHome = Environment.GRADLE_USER_HOME;
|
||||
File projectDir = new File(projectDirPath);
|
||||
mRoot = TreeNode.root(projectDir);
|
||||
if (gradleHome.exists() && gradleHome.isDirectory()) {
|
||||
if (!gradleProps.exists()) {
|
||||
FileIOUtils.writeFileFromString(
|
||||
gradleProps,
|
||||
"# Specify global Gradle properties in this file\n"
|
||||
+ "# These properties will be applicable for every project you build"
|
||||
+ " with Gradle.");
|
||||
}
|
||||
TreeNode home = new TreeNode(gradleHome);
|
||||
home.setViewHolder(new FileTreeViewHolder(getContext()));
|
||||
TreeNode prop = new TreeNode(gradleProps);
|
||||
prop.setViewHolder(new FileTreeViewHolder(getContext()));
|
||||
home.addChild(prop);
|
||||
mRoot.addChild(home);
|
||||
}
|
||||
mRoot.setViewHolder(new FileTreeViewHolder(getContext()));
|
||||
|
||||
getScrollView().setVisibility(View.GONE);
|
||||
getLoadingProgress().setVisibility(View.VISIBLE);
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
new FileTreeCallable(getContext(), mRoot, projectDir),
|
||||
(result) -> {
|
||||
getScrollView().setVisibility(View.VISIBLE);
|
||||
getLoadingProgress().setVisibility(View.GONE);
|
||||
AndroidTreeView tree = createTreeView(mRoot);
|
||||
if (tree != null) {
|
||||
tree.setUseAutoToggle(false);
|
||||
tree.setDefaultNodeClickListener(FileTreeFragment.this);
|
||||
tree.setDefaultNodeLongClickListener(FileTreeFragment.this);
|
||||
getScrollView().removeAllViews();
|
||||
|
||||
final var view = tree.getView();
|
||||
getScrollView().addView(view);
|
||||
|
||||
view.post(this::tryRestoreState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AndroidTreeView createTreeView(TreeNode node) {
|
||||
Context ctx = null;
|
||||
if (getActivity() != null) {
|
||||
ctx = getActivity();
|
||||
} else if (getContext() != null) {
|
||||
ctx = getContext();
|
||||
}
|
||||
|
||||
if (ctx == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple);
|
||||
}
|
||||
|
||||
public HorizontalScrollView getScrollView() {
|
||||
return binding.filetreeHorizontalScrollView;
|
||||
}
|
||||
|
||||
public ProgressBar getLoadingProgress() {
|
||||
return binding.fileTreeLoadingProgress;
|
||||
}
|
||||
|
||||
private void tryRestoreState() {
|
||||
tryRestoreState(mTreeState);
|
||||
}
|
||||
|
||||
private void tryRestoreState(String state) {
|
||||
if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
|
||||
|
||||
LOG.debug("Restoring tree view state:", "'" + state + "'");
|
||||
|
||||
mFileTreeView.collapseAll();
|
||||
final var openNodesArray = state.split(NODES_PATH_SEPARATOR);
|
||||
final var openNodes = new HashSet<>(Arrays.asList(openNodesArray));
|
||||
restoreNodeState(mRoot, openNodes);
|
||||
} else {
|
||||
LOG.error("Unable to restore tree state", "treeState=" + state, "treeView=" + mFileTreeView);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreNodeState(@NonNull TreeNode root, Set<String> openNodes) {
|
||||
final var children = root.getChildren();
|
||||
for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
|
||||
final var node = children.get(i);
|
||||
if (openNodes.contains(node.getPath())) {
|
||||
listNode(
|
||||
node,
|
||||
() -> {
|
||||
updateChevron(node);
|
||||
expandNode(node);
|
||||
restoreNodeState(node, openNodes);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface FileActionListener {
|
||||
CodeEditorView openFile(File file);
|
||||
|
||||
void showFileOptions(File file, TreeNode node);
|
||||
}
|
||||
}
|
||||
|
||||
+103
-103
@@ -1,103 +1,103 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.adapters.AttrValueCompletionAdapter;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.TextWatcherAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class AbstractReferenceEditor extends BaseValueEditorFragment {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ReferenceEditor");
|
||||
public TextWatcherAdapter resInputWatcher;
|
||||
|
||||
@Override
|
||||
protected void notifyValueChanged(@NonNull String newValue) {
|
||||
try {
|
||||
super.notifyValueChanged(newValue);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to update resource value to '" + newValue + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupReferenceInput(MaterialAutoCompleteTextView referenceInput) {
|
||||
Objects.requireNonNull(referenceInput);
|
||||
|
||||
referenceInput.setText(attribute.getValue());
|
||||
|
||||
if (resInputWatcher != null) {
|
||||
referenceInput.removeTextChangedListener(resInputWatcher);
|
||||
}
|
||||
|
||||
resInputWatcher =
|
||||
new TextWatcherAdapter() {
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull Editable s) {
|
||||
final var text = s.toString().trim();
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyValueChanged(text);
|
||||
}
|
||||
};
|
||||
|
||||
referenceInput.addTextChangedListener(resInputWatcher);
|
||||
|
||||
CompletableFuture.supplyAsync(this::computeReferenceItems)
|
||||
.whenComplete((list, error) -> handleAutoCompleteResult(list, error, referenceInput));
|
||||
}
|
||||
|
||||
protected void handleAutoCompleteResult(
|
||||
List<String> list, Throwable error, AutoCompleteTextView referenceInput) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
LOG.error("No completion items found");
|
||||
LOG.error("Error was:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Found", list.size(), "resource items for completion");
|
||||
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
final var adapter = new AttrValueCompletionAdapter(requireContext(), list);
|
||||
referenceInput.setThreshold(1);
|
||||
referenceInput.setAdapter(adapter);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
protected List<String> computeReferenceItems() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.adapters.AttrValueCompletionAdapter;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.TextWatcherAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public abstract class AbstractReferenceEditor extends BaseValueEditorFragment {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ReferenceEditor");
|
||||
public TextWatcherAdapter resInputWatcher;
|
||||
|
||||
@Override
|
||||
protected void notifyValueChanged(@NonNull String newValue) {
|
||||
try {
|
||||
super.notifyValueChanged(newValue);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to update resource value to '" + newValue + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupReferenceInput(MaterialAutoCompleteTextView referenceInput) {
|
||||
Objects.requireNonNull(referenceInput);
|
||||
|
||||
referenceInput.setText(attribute.getValue());
|
||||
|
||||
if (resInputWatcher != null) {
|
||||
referenceInput.removeTextChangedListener(resInputWatcher);
|
||||
}
|
||||
|
||||
resInputWatcher =
|
||||
new TextWatcherAdapter() {
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull Editable s) {
|
||||
final var text = s.toString().trim();
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyValueChanged(text);
|
||||
}
|
||||
};
|
||||
|
||||
referenceInput.addTextChangedListener(resInputWatcher);
|
||||
|
||||
CompletableFuture.supplyAsync(this::computeReferenceItems)
|
||||
.whenComplete((list, error) -> handleAutoCompleteResult(list, error, referenceInput));
|
||||
}
|
||||
|
||||
protected void handleAutoCompleteResult(
|
||||
List<String> list, Throwable error, AutoCompleteTextView referenceInput) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
LOG.error("No completion items found");
|
||||
LOG.error("Error was:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.debug("Found", list.size(), "resource items for completion");
|
||||
|
||||
ThreadUtils.runOnUiThread(
|
||||
() -> {
|
||||
final var adapter = new AttrValueCompletionAdapter(requireContext(), list);
|
||||
referenceInput.setThreshold(1);
|
||||
referenceInput.setAdapter(adapter);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
protected List<String> computeReferenceItems() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +1,83 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutReferenceAttrEditorBinding;
|
||||
import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ReferenceEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutReferenceAttrEditorBinding binding;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutReferenceAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setupReferenceInput((MaterialAutoCompleteTextView) this.binding.resInput.getEditText());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var resTable = StudioApp.getInstance().getResourceTable();
|
||||
final var list = new ArrayList<>(resTable.listResourceNames(null));
|
||||
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var resourceMap = entry.getValue().getResourceMap();
|
||||
for (var resourceEntries : resourceMap.entrySet()) {
|
||||
final var name = resourceEntries.getKey();
|
||||
list.addAll(
|
||||
resourceEntries.getValue().keySet().stream()
|
||||
.map(("@android:" + name + "/")::concat)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(FrameworkValues.listAllResources());
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments.attr;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.databinding.LayoutReferenceAttrEditorBinding;
|
||||
import com.itsaky.inflater.values.FrameworkValues;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class ReferenceEditor extends AbstractReferenceEditor {
|
||||
|
||||
private LayoutReferenceAttrEditorBinding binding;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutReferenceAttrEditorBinding.inflate(inflater, container, false);
|
||||
return this.binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setupReferenceInput((MaterialAutoCompleteTextView) this.binding.resInput.getEditText());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> computeReferenceItems() {
|
||||
final var resTable = StudioApp.getInstance().getResourceTable();
|
||||
final var list = new ArrayList<>(resTable.listResourceNames(null));
|
||||
|
||||
final var tables = ValuesTableFactory.getAllTables();
|
||||
for (var entry : tables.entrySet()) {
|
||||
final var resourceMap = entry.getValue().getResourceMap();
|
||||
for (var resourceEntries : resourceMap.entrySet()) {
|
||||
final var name = resourceEntries.getKey();
|
||||
list.addAll(
|
||||
resourceEntries.getValue().keySet().stream()
|
||||
.map(("@android:" + name + "/")::concat)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(FrameworkValues.listAllResources());
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
+164
-164
@@ -1,164 +1,164 @@
|
||||
/************************************************************************************
|
||||
* 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/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
package com.itsaky.androidide.fragments.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class BuildPreferences extends BasePreferenceFragment
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
|
||||
public static final String KEY_GRADLE_COMMANDS = "idepref_build_gradleCommands";
|
||||
public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache";
|
||||
|
||||
private ProgressSheet progressSheet;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle p1, String p2) {
|
||||
super.onCreatePreferences(p1, p2);
|
||||
if (getContext() == null) return;
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
final PreferenceCategory categoryGradle = new PreferenceCategory(getContext());
|
||||
final Preference customCommands = new Preference(getContext());
|
||||
final Preference clearCache = new Preference(getContext());
|
||||
|
||||
screen.addPreference(categoryGradle);
|
||||
|
||||
customCommands.setKey(KEY_GRADLE_COMMANDS);
|
||||
customCommands.setIcon(R.drawable.ic_bash_commands);
|
||||
customCommands.setTitle(R.string.idepref_build_customgradlecommands_title);
|
||||
customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary);
|
||||
|
||||
clearCache.setKey(KEY_GRADLE_CLEAR_CACHE);
|
||||
clearCache.setIcon(R.drawable.ic_delete);
|
||||
clearCache.setTitle(R.string.idepref_build_clearCache_title);
|
||||
clearCache.setSummary(R.string.idepref_build_clearCache_summary);
|
||||
|
||||
categoryGradle.setTitle(R.string.gradle);
|
||||
categoryGradle.addPreference(customCommands);
|
||||
categoryGradle.addPreference(clearCache);
|
||||
|
||||
setPreferenceScreen(screen);
|
||||
|
||||
customCommands.setOnPreferenceClickListener(this);
|
||||
clearCache.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference p1) {
|
||||
final String key = p1.getKey();
|
||||
if (key.equals(KEY_GRADLE_COMMANDS)) {
|
||||
showGradleCommandsDialog();
|
||||
} else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) {
|
||||
showClearCacheDialog();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showGradleCommandsDialog() {
|
||||
final String[] labels = {
|
||||
"--stacktrace",
|
||||
"--info",
|
||||
"--debug",
|
||||
"--scan",
|
||||
"--warning-mode all",
|
||||
"--build-cache",
|
||||
"--offline"
|
||||
};
|
||||
final boolean[] checked = {
|
||||
getPrefManager().isStackTraceEnabled(),
|
||||
getPrefManager().isGradleInfoEnabled(),
|
||||
getPrefManager().isGradleDebugEnabled(),
|
||||
getPrefManager().isGradleScanEnabled(),
|
||||
getPrefManager().isGradleWarningEnabled(),
|
||||
getPrefManager().isGradleBuildCacheEnabled(),
|
||||
getPrefManager().isGradleOfflineModeEnabled()
|
||||
};
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
builder.setTitle(R.string.idepref_build_customgradlecommands_title);
|
||||
builder.setMultiChoiceItems(
|
||||
labels,
|
||||
checked,
|
||||
(p1, p2, p3) -> {
|
||||
if (p2 == 0) {
|
||||
getPrefManager().setGradleStacktraceEnabled(p3);
|
||||
} else if (p2 == 1) {
|
||||
getPrefManager().setGradleInfoEnabled(p3);
|
||||
} else if (p2 == 2) {
|
||||
getPrefManager().setGradleDebugEnabled(p3);
|
||||
} else if (p2 == 3) {
|
||||
getPrefManager().setGradleScanEnabled(p3);
|
||||
} else if (p2 == 4) {
|
||||
getPrefManager().setGradleWarningEnabled(p3);
|
||||
} else if (p2 == 5) {
|
||||
getPrefManager().setGradleBuildCacheEnabled(p3);
|
||||
} else if (p2 == 6) {
|
||||
getPrefManager().setGradleOfflineModeEnabled(p3);
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setCancelable(false);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void showClearCacheDialog() {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
builder.setTitle(R.string.idepref_build_clearCache_title);
|
||||
builder.setMessage(R.string.msg_clear_cache);
|
||||
builder.setCancelable(false);
|
||||
builder.setPositiveButton(
|
||||
R.string.yes,
|
||||
(p1, p2) -> {
|
||||
p1.dismiss();
|
||||
getProgressSheet().show(getChildFragmentManager(), "progress_sheet");
|
||||
new TaskExecutor().executeAsync(this::deleteCaches, __ -> getProgressSheet().dismiss());
|
||||
});
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private Object deleteCaches() {
|
||||
File file = new File(Environment.GRADLE_USER_HOME, "caches");
|
||||
if (file.exists()) {
|
||||
FileUtils.delete(file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ProgressSheet getProgressSheet() {
|
||||
return progressSheet == null
|
||||
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
|
||||
: progressSheet;
|
||||
}
|
||||
}
|
||||
/************************************************************************************
|
||||
* 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/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
|
||||
package com.itsaky.androidide.fragments.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class BuildPreferences extends BasePreferenceFragment
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
|
||||
public static final String KEY_GRADLE_COMMANDS = "idepref_build_gradleCommands";
|
||||
public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache";
|
||||
|
||||
private ProgressSheet progressSheet;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle p1, String p2) {
|
||||
super.onCreatePreferences(p1, p2);
|
||||
if (getContext() == null) return;
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
final PreferenceCategory categoryGradle = new PreferenceCategory(getContext());
|
||||
final Preference customCommands = new Preference(getContext());
|
||||
final Preference clearCache = new Preference(getContext());
|
||||
|
||||
screen.addPreference(categoryGradle);
|
||||
|
||||
customCommands.setKey(KEY_GRADLE_COMMANDS);
|
||||
customCommands.setIcon(R.drawable.ic_bash_commands);
|
||||
customCommands.setTitle(R.string.idepref_build_customgradlecommands_title);
|
||||
customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary);
|
||||
|
||||
clearCache.setKey(KEY_GRADLE_CLEAR_CACHE);
|
||||
clearCache.setIcon(R.drawable.ic_delete);
|
||||
clearCache.setTitle(R.string.idepref_build_clearCache_title);
|
||||
clearCache.setSummary(R.string.idepref_build_clearCache_summary);
|
||||
|
||||
categoryGradle.setTitle(R.string.gradle);
|
||||
categoryGradle.addPreference(customCommands);
|
||||
categoryGradle.addPreference(clearCache);
|
||||
|
||||
setPreferenceScreen(screen);
|
||||
|
||||
customCommands.setOnPreferenceClickListener(this);
|
||||
clearCache.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference p1) {
|
||||
final String key = p1.getKey();
|
||||
if (key.equals(KEY_GRADLE_COMMANDS)) {
|
||||
showGradleCommandsDialog();
|
||||
} else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) {
|
||||
showClearCacheDialog();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showGradleCommandsDialog() {
|
||||
final String[] labels = {
|
||||
"--stacktrace",
|
||||
"--info",
|
||||
"--debug",
|
||||
"--scan",
|
||||
"--warning-mode all",
|
||||
"--build-cache",
|
||||
"--offline"
|
||||
};
|
||||
final boolean[] checked = {
|
||||
getPrefManager().isStackTraceEnabled(),
|
||||
getPrefManager().isGradleInfoEnabled(),
|
||||
getPrefManager().isGradleDebugEnabled(),
|
||||
getPrefManager().isGradleScanEnabled(),
|
||||
getPrefManager().isGradleWarningEnabled(),
|
||||
getPrefManager().isGradleBuildCacheEnabled(),
|
||||
getPrefManager().isGradleOfflineModeEnabled()
|
||||
};
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
builder.setTitle(R.string.idepref_build_customgradlecommands_title);
|
||||
builder.setMultiChoiceItems(
|
||||
labels,
|
||||
checked,
|
||||
(p1, p2, p3) -> {
|
||||
if (p2 == 0) {
|
||||
getPrefManager().setGradleStacktraceEnabled(p3);
|
||||
} else if (p2 == 1) {
|
||||
getPrefManager().setGradleInfoEnabled(p3);
|
||||
} else if (p2 == 2) {
|
||||
getPrefManager().setGradleDebugEnabled(p3);
|
||||
} else if (p2 == 3) {
|
||||
getPrefManager().setGradleScanEnabled(p3);
|
||||
} else if (p2 == 4) {
|
||||
getPrefManager().setGradleWarningEnabled(p3);
|
||||
} else if (p2 == 5) {
|
||||
getPrefManager().setGradleBuildCacheEnabled(p3);
|
||||
} else if (p2 == 6) {
|
||||
getPrefManager().setGradleOfflineModeEnabled(p3);
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setCancelable(false);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void showClearCacheDialog() {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
builder.setTitle(R.string.idepref_build_clearCache_title);
|
||||
builder.setMessage(R.string.msg_clear_cache);
|
||||
builder.setCancelable(false);
|
||||
builder.setPositiveButton(
|
||||
R.string.yes,
|
||||
(p1, p2) -> {
|
||||
p1.dismiss();
|
||||
getProgressSheet().show(getChildFragmentManager(), "progress_sheet");
|
||||
new TaskExecutor().executeAsync(this::deleteCaches, __ -> getProgressSheet().dismiss());
|
||||
});
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private Object deleteCaches() {
|
||||
File file = new File(Environment.GRADLE_USER_HOME, "caches");
|
||||
if (file.exists()) {
|
||||
FileUtils.delete(file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ProgressSheet getProgressSheet() {
|
||||
return progressSheet == null
|
||||
? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait))
|
||||
: progressSheet;
|
||||
}
|
||||
}
|
||||
|
||||
+319
-319
@@ -1,319 +1,319 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
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_DRAW_HEX;
|
||||
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_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_LEADING;
|
||||
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_SIZE;
|
||||
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.lsp.java.models.JavaServerSettings.KEY_COMPLETIONS_MATCH_LOWER;
|
||||
import static com.itsaky.lsp.java.models.JavaServerSettings.KEY_JAVA_PREF_GOOGLE_CODE_STYLE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.databinding.LayoutTextSizeSliderBinding;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
|
||||
public class EditorPreferences extends BasePreferenceFragment
|
||||
implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle p1, String p2) {
|
||||
super.onCreatePreferences(p1, p2);
|
||||
|
||||
if (getContext() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var screen = getPreferenceScreen();
|
||||
final var commonCategory = new PreferenceCategory(getContext());
|
||||
final var javaCategory = new PreferenceCategory(getContext());
|
||||
|
||||
final var fontSize = new Preference(getContext());
|
||||
final var nonPrintable = new Preference(getContext());
|
||||
final var tabSize = new Preference(getContext());
|
||||
final var drawHex = new SwitchPreference(getContext());
|
||||
final var wordWrap = new SwitchPreference(getContext());
|
||||
final var magnifier = new SwitchPreference(getContext());
|
||||
final var fontLigatures = new SwitchPreference(getContext());
|
||||
final var autoSave = new SwitchPreference(getContext());
|
||||
final var useGoogleCodeStyle = new SwitchPreference(getContext());
|
||||
final var visiblePasswordFlag = new SwitchPreference(getContext());
|
||||
|
||||
final var completionsMatchLower = new SwitchPreference(getContext());
|
||||
|
||||
screen.addPreference(commonCategory);
|
||||
screen.addPreference(javaCategory);
|
||||
|
||||
fontSize.setIcon(R.drawable.ic_text_size);
|
||||
fontSize.setKey(KEY_EDITOR_FONT_SIZE);
|
||||
fontSize.setTitle(R.string.idepref_editor_fontsize_title);
|
||||
fontSize.setSummary(R.string.idepref_editor_fontsize_summary);
|
||||
|
||||
wordWrap.setIcon(R.drawable.ic_wrap_text);
|
||||
wordWrap.setKey(PreferenceManager.KEY_EDITOR_WORD_WRAP);
|
||||
wordWrap.setTitle(R.string.idepref_editor_word_wrap_title);
|
||||
wordWrap.setSummary(R.string.idepref_editor_word_wrap_summary);
|
||||
|
||||
magnifier.setIcon(R.drawable.ic_loupe);
|
||||
magnifier.setKey(PreferenceManager.KEY_EDITOR_USE_MAGNIFER);
|
||||
magnifier.setTitle(R.string.idepref_editor_use_magnifier_title);
|
||||
magnifier.setSummary(R.string.idepref_editor_use_magnifier_summary);
|
||||
|
||||
nonPrintable.setIcon(R.drawable.ic_drawing);
|
||||
nonPrintable.setKey(KEY_EDITOR_PRINTABLE_CHARS);
|
||||
nonPrintable.setTitle(R.string.idepref_editor_paintingflags_title);
|
||||
nonPrintable.setSummary(R.string.idepref_editor_paintingflags_summary);
|
||||
|
||||
tabSize.setIcon(R.drawable.ic_tab);
|
||||
tabSize.setKey(KEY_EDITOR_TAB_SIZE);
|
||||
tabSize.setTitle(R.string.title_tab_size);
|
||||
tabSize.setSummary(R.string.msg_tab_size);
|
||||
|
||||
drawHex.setIcon(R.drawable.ic_hexadecimal);
|
||||
drawHex.setKey(KEY_EDITOR_DRAW_HEX);
|
||||
drawHex.setTitle(R.string.idepref_editor_drawhexcolors_title);
|
||||
drawHex.setSummary(R.string.idepref_editor_drawhexcolors_summary);
|
||||
|
||||
fontLigatures.setIcon(R.drawable.ic_font_ligatures);
|
||||
fontLigatures.setKey(KEY_EDITOR_FONT_LIGATURES);
|
||||
fontLigatures.setTitle(getString(R.string.idepref_editor_ligatures_title));
|
||||
fontLigatures.setSummary(getString(R.string.idepref_editor_ligatures_summary));
|
||||
|
||||
autoSave.setIcon(R.drawable.ic_save);
|
||||
autoSave.setKey(KEY_EDITOR_AUTO_SAVE);
|
||||
autoSave.setTitle(getString(R.string.idepref_editor_autoSave_title));
|
||||
autoSave.setSummary(getString(R.string.idepref_editor_autoSave_summary));
|
||||
|
||||
completionsMatchLower.setKey(KEY_COMPLETIONS_MATCH_LOWER);
|
||||
completionsMatchLower.setIcon(R.drawable.ic_text_lower);
|
||||
completionsMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title));
|
||||
completionsMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary));
|
||||
|
||||
useGoogleCodeStyle.setIcon(R.drawable.ic_format_code);
|
||||
useGoogleCodeStyle.setKey(KEY_JAVA_PREF_GOOGLE_CODE_STYLE);
|
||||
useGoogleCodeStyle.setTitle(getString(R.string.idepref_java_useGoogleStyle_title));
|
||||
useGoogleCodeStyle.setSummary(getString(R.string.idepref_java_useGoogleStyle_summary));
|
||||
|
||||
visiblePasswordFlag.setIcon(R.drawable.ic_password_input);
|
||||
visiblePasswordFlag.setKey(KEY_EDITOR_FLAG_PASSWORD);
|
||||
visiblePasswordFlag.setTitle(getString(R.string.idepref_visiblePassword_title));
|
||||
visiblePasswordFlag.setSummary(getString(R.string.idepref_visiblePassword_summary));
|
||||
|
||||
commonCategory.setTitle(getString(R.string.idepref_editor_category_common));
|
||||
commonCategory.addPreference(fontSize);
|
||||
commonCategory.addPreference(fontLigatures);
|
||||
commonCategory.addPreference(tabSize);
|
||||
commonCategory.addPreference(wordWrap);
|
||||
commonCategory.addPreference(magnifier);
|
||||
commonCategory.addPreference(completionsMatchLower);
|
||||
commonCategory.addPreference(visiblePasswordFlag);
|
||||
commonCategory.addPreference(nonPrintable);
|
||||
commonCategory.addPreference(drawHex);
|
||||
commonCategory.addPreference(autoSave);
|
||||
|
||||
javaCategory.setTitle(getString(R.string.idepref_editor_category_java));
|
||||
javaCategory.addPreference(useGoogleCodeStyle);
|
||||
|
||||
setPreferenceScreen(screen);
|
||||
|
||||
fontSize.setOnPreferenceClickListener(this);
|
||||
fontLigatures.setOnPreferenceChangeListener(this);
|
||||
nonPrintable.setOnPreferenceClickListener(this);
|
||||
tabSize.setOnPreferenceClickListener(this);
|
||||
drawHex.setOnPreferenceChangeListener(this);
|
||||
autoSave.setOnPreferenceChangeListener(this);
|
||||
completionsMatchLower.setOnPreferenceChangeListener(this);
|
||||
useGoogleCodeStyle.setOnPreferenceChangeListener(this);
|
||||
visiblePasswordFlag.setOnPreferenceChangeListener(this);
|
||||
wordWrap.setOnPreferenceChangeListener(this);
|
||||
magnifier.setOnPreferenceChangeListener(this);
|
||||
|
||||
fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true));
|
||||
drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true));
|
||||
autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false));
|
||||
completionsMatchLower.setChecked(
|
||||
getPrefManager().getBoolean(KEY_COMPLETIONS_MATCH_LOWER, false));
|
||||
useGoogleCodeStyle.setChecked(
|
||||
getPrefManager().getBoolean(KEY_JAVA_PREF_GOOGLE_CODE_STYLE, false));
|
||||
visiblePasswordFlag.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FLAG_PASSWORD, true));
|
||||
wordWrap.setChecked(getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_WORD_WRAP, false));
|
||||
magnifier.setChecked(
|
||||
getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_USE_MAGNIFER, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
|
||||
final boolean value = (boolean) newValue;
|
||||
|
||||
switch (preference.getKey()) {
|
||||
case KEY_EDITOR_FLAG_PASSWORD:
|
||||
ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED = true;
|
||||
break;
|
||||
case KEY_EDITOR_DRAW_HEX:
|
||||
ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true;
|
||||
break;
|
||||
case KEY_EDITOR_FONT_LIGATURES:
|
||||
ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true;
|
||||
break;
|
||||
case PreferenceManager.KEY_EDITOR_WORD_WRAP:
|
||||
ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED = true;
|
||||
break;
|
||||
case PreferenceManager.KEY_EDITOR_USE_MAGNIFER:
|
||||
ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED = true;
|
||||
break;
|
||||
}
|
||||
|
||||
getPrefManager().putBoolean(preference.getKey(), value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
final String key = preference.getKey();
|
||||
switch (key) {
|
||||
case KEY_EDITOR_FONT_SIZE:
|
||||
showTextSizeDialog();
|
||||
break;
|
||||
case KEY_EDITOR_PRINTABLE_CHARS:
|
||||
showPrintableCharsDialog();
|
||||
break;
|
||||
case KEY_EDITOR_TAB_SIZE:
|
||||
showTabSizeDialog();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showTabSizeDialog() {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
final String[] sizes = new String[] {"2", "4", "6", "8"};
|
||||
|
||||
int current = (getPrefManager().getEditorTabSize() / 2) - 1;
|
||||
if (current < 0 || current >= sizes.length) {
|
||||
current = 1;
|
||||
}
|
||||
|
||||
builder.setTitle(R.string.title_tab_size);
|
||||
builder.setSingleChoiceItems(
|
||||
sizes,
|
||||
current,
|
||||
(d, i) -> {
|
||||
d.dismiss();
|
||||
|
||||
int tabSize = (i + 1) * 2;
|
||||
if (tabSize < 2 || tabSize > 8) {
|
||||
tabSize = 4;
|
||||
}
|
||||
getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize);
|
||||
});
|
||||
builder.setCancelable(true);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showTextSizeDialog() {
|
||||
final LayoutTextSizeSliderBinding binding =
|
||||
LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(getContext()));
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
float size = getPrefManager().getFloat(KEY_EDITOR_FONT_SIZE, 14);
|
||||
if (size < 6 || size > 32) {
|
||||
size = 14;
|
||||
}
|
||||
changeTextSize(binding, size);
|
||||
|
||||
binding.slider.setLabelFormatter(p1 -> String.valueOf((int) p1));
|
||||
builder.setTitle(R.string.title_change_text_size);
|
||||
builder.setMessage(R.string.msg_editor_font_size);
|
||||
builder.setView(binding.getRoot());
|
||||
builder.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
(p1, p2) -> {
|
||||
p1.dismiss();
|
||||
float s = binding.slider.getValue();
|
||||
changeTextSize(binding, s);
|
||||
});
|
||||
builder.setNeutralButton(R.string.reset, (p1, p2) -> changeTextSize(binding, 14));
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) {
|
||||
getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size);
|
||||
binding.slider.setValue(size);
|
||||
ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true;
|
||||
}
|
||||
|
||||
private void showPrintableCharsDialog() {
|
||||
String[] labels = {
|
||||
"Leading whitespaces",
|
||||
"Trailing whitespaces",
|
||||
"Inner whitespaces",
|
||||
"Whitespaces in empty lines",
|
||||
"Line breaks"
|
||||
};
|
||||
final boolean[] checked = {
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_LEADING, true),
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_TRAILING, false),
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_INNER, true),
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, true),
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_LINE_BREAK, true)
|
||||
};
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
builder.setTitle(R.string.idepref_editor_paintingflags_title);
|
||||
builder.setMultiChoiceItems(
|
||||
labels,
|
||||
checked,
|
||||
(dialog, which, isChecked) -> {
|
||||
if (which == 0) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_LEADING, isChecked);
|
||||
} else if (which == 1) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_TRAILING, isChecked);
|
||||
} else if (which == 2) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_INNER, isChecked);
|
||||
} else if (which == 3) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked);
|
||||
} else if (which == 4) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_LINE_BREAK, isChecked);
|
||||
}
|
||||
|
||||
ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true;
|
||||
});
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
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_DRAW_HEX;
|
||||
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_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_LEADING;
|
||||
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_SIZE;
|
||||
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.lsp.java.models.JavaServerSettings.KEY_COMPLETIONS_MATCH_LOWER;
|
||||
import static com.itsaky.lsp.java.models.JavaServerSettings.KEY_JAVA_PREF_GOOGLE_CODE_STYLE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.databinding.LayoutTextSizeSliderBinding;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.models.ConstantsBridge;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
|
||||
public class EditorPreferences extends BasePreferenceFragment
|
||||
implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle p1, String p2) {
|
||||
super.onCreatePreferences(p1, p2);
|
||||
|
||||
if (getContext() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var screen = getPreferenceScreen();
|
||||
final var commonCategory = new PreferenceCategory(getContext());
|
||||
final var javaCategory = new PreferenceCategory(getContext());
|
||||
|
||||
final var fontSize = new Preference(getContext());
|
||||
final var nonPrintable = new Preference(getContext());
|
||||
final var tabSize = new Preference(getContext());
|
||||
final var drawHex = new SwitchPreference(getContext());
|
||||
final var wordWrap = new SwitchPreference(getContext());
|
||||
final var magnifier = new SwitchPreference(getContext());
|
||||
final var fontLigatures = new SwitchPreference(getContext());
|
||||
final var autoSave = new SwitchPreference(getContext());
|
||||
final var useGoogleCodeStyle = new SwitchPreference(getContext());
|
||||
final var visiblePasswordFlag = new SwitchPreference(getContext());
|
||||
|
||||
final var completionsMatchLower = new SwitchPreference(getContext());
|
||||
|
||||
screen.addPreference(commonCategory);
|
||||
screen.addPreference(javaCategory);
|
||||
|
||||
fontSize.setIcon(R.drawable.ic_text_size);
|
||||
fontSize.setKey(KEY_EDITOR_FONT_SIZE);
|
||||
fontSize.setTitle(R.string.idepref_editor_fontsize_title);
|
||||
fontSize.setSummary(R.string.idepref_editor_fontsize_summary);
|
||||
|
||||
wordWrap.setIcon(R.drawable.ic_wrap_text);
|
||||
wordWrap.setKey(PreferenceManager.KEY_EDITOR_WORD_WRAP);
|
||||
wordWrap.setTitle(R.string.idepref_editor_word_wrap_title);
|
||||
wordWrap.setSummary(R.string.idepref_editor_word_wrap_summary);
|
||||
|
||||
magnifier.setIcon(R.drawable.ic_loupe);
|
||||
magnifier.setKey(PreferenceManager.KEY_EDITOR_USE_MAGNIFER);
|
||||
magnifier.setTitle(R.string.idepref_editor_use_magnifier_title);
|
||||
magnifier.setSummary(R.string.idepref_editor_use_magnifier_summary);
|
||||
|
||||
nonPrintable.setIcon(R.drawable.ic_drawing);
|
||||
nonPrintable.setKey(KEY_EDITOR_PRINTABLE_CHARS);
|
||||
nonPrintable.setTitle(R.string.idepref_editor_paintingflags_title);
|
||||
nonPrintable.setSummary(R.string.idepref_editor_paintingflags_summary);
|
||||
|
||||
tabSize.setIcon(R.drawable.ic_tab);
|
||||
tabSize.setKey(KEY_EDITOR_TAB_SIZE);
|
||||
tabSize.setTitle(R.string.title_tab_size);
|
||||
tabSize.setSummary(R.string.msg_tab_size);
|
||||
|
||||
drawHex.setIcon(R.drawable.ic_hexadecimal);
|
||||
drawHex.setKey(KEY_EDITOR_DRAW_HEX);
|
||||
drawHex.setTitle(R.string.idepref_editor_drawhexcolors_title);
|
||||
drawHex.setSummary(R.string.idepref_editor_drawhexcolors_summary);
|
||||
|
||||
fontLigatures.setIcon(R.drawable.ic_font_ligatures);
|
||||
fontLigatures.setKey(KEY_EDITOR_FONT_LIGATURES);
|
||||
fontLigatures.setTitle(getString(R.string.idepref_editor_ligatures_title));
|
||||
fontLigatures.setSummary(getString(R.string.idepref_editor_ligatures_summary));
|
||||
|
||||
autoSave.setIcon(R.drawable.ic_save);
|
||||
autoSave.setKey(KEY_EDITOR_AUTO_SAVE);
|
||||
autoSave.setTitle(getString(R.string.idepref_editor_autoSave_title));
|
||||
autoSave.setSummary(getString(R.string.idepref_editor_autoSave_summary));
|
||||
|
||||
completionsMatchLower.setKey(KEY_COMPLETIONS_MATCH_LOWER);
|
||||
completionsMatchLower.setIcon(R.drawable.ic_text_lower);
|
||||
completionsMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title));
|
||||
completionsMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary));
|
||||
|
||||
useGoogleCodeStyle.setIcon(R.drawable.ic_format_code);
|
||||
useGoogleCodeStyle.setKey(KEY_JAVA_PREF_GOOGLE_CODE_STYLE);
|
||||
useGoogleCodeStyle.setTitle(getString(R.string.idepref_java_useGoogleStyle_title));
|
||||
useGoogleCodeStyle.setSummary(getString(R.string.idepref_java_useGoogleStyle_summary));
|
||||
|
||||
visiblePasswordFlag.setIcon(R.drawable.ic_password_input);
|
||||
visiblePasswordFlag.setKey(KEY_EDITOR_FLAG_PASSWORD);
|
||||
visiblePasswordFlag.setTitle(getString(R.string.idepref_visiblePassword_title));
|
||||
visiblePasswordFlag.setSummary(getString(R.string.idepref_visiblePassword_summary));
|
||||
|
||||
commonCategory.setTitle(getString(R.string.idepref_editor_category_common));
|
||||
commonCategory.addPreference(fontSize);
|
||||
commonCategory.addPreference(fontLigatures);
|
||||
commonCategory.addPreference(tabSize);
|
||||
commonCategory.addPreference(wordWrap);
|
||||
commonCategory.addPreference(magnifier);
|
||||
commonCategory.addPreference(completionsMatchLower);
|
||||
commonCategory.addPreference(visiblePasswordFlag);
|
||||
commonCategory.addPreference(nonPrintable);
|
||||
commonCategory.addPreference(drawHex);
|
||||
commonCategory.addPreference(autoSave);
|
||||
|
||||
javaCategory.setTitle(getString(R.string.idepref_editor_category_java));
|
||||
javaCategory.addPreference(useGoogleCodeStyle);
|
||||
|
||||
setPreferenceScreen(screen);
|
||||
|
||||
fontSize.setOnPreferenceClickListener(this);
|
||||
fontLigatures.setOnPreferenceChangeListener(this);
|
||||
nonPrintable.setOnPreferenceClickListener(this);
|
||||
tabSize.setOnPreferenceClickListener(this);
|
||||
drawHex.setOnPreferenceChangeListener(this);
|
||||
autoSave.setOnPreferenceChangeListener(this);
|
||||
completionsMatchLower.setOnPreferenceChangeListener(this);
|
||||
useGoogleCodeStyle.setOnPreferenceChangeListener(this);
|
||||
visiblePasswordFlag.setOnPreferenceChangeListener(this);
|
||||
wordWrap.setOnPreferenceChangeListener(this);
|
||||
magnifier.setOnPreferenceChangeListener(this);
|
||||
|
||||
fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true));
|
||||
drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true));
|
||||
autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false));
|
||||
completionsMatchLower.setChecked(
|
||||
getPrefManager().getBoolean(KEY_COMPLETIONS_MATCH_LOWER, false));
|
||||
useGoogleCodeStyle.setChecked(
|
||||
getPrefManager().getBoolean(KEY_JAVA_PREF_GOOGLE_CODE_STYLE, false));
|
||||
visiblePasswordFlag.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FLAG_PASSWORD, true));
|
||||
wordWrap.setChecked(getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_WORD_WRAP, false));
|
||||
magnifier.setChecked(
|
||||
getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_USE_MAGNIFER, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
|
||||
final boolean value = (boolean) newValue;
|
||||
|
||||
switch (preference.getKey()) {
|
||||
case KEY_EDITOR_FLAG_PASSWORD:
|
||||
ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED = true;
|
||||
break;
|
||||
case KEY_EDITOR_DRAW_HEX:
|
||||
ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true;
|
||||
break;
|
||||
case KEY_EDITOR_FONT_LIGATURES:
|
||||
ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true;
|
||||
break;
|
||||
case PreferenceManager.KEY_EDITOR_WORD_WRAP:
|
||||
ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED = true;
|
||||
break;
|
||||
case PreferenceManager.KEY_EDITOR_USE_MAGNIFER:
|
||||
ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED = true;
|
||||
break;
|
||||
}
|
||||
|
||||
getPrefManager().putBoolean(preference.getKey(), value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
final String key = preference.getKey();
|
||||
switch (key) {
|
||||
case KEY_EDITOR_FONT_SIZE:
|
||||
showTextSizeDialog();
|
||||
break;
|
||||
case KEY_EDITOR_PRINTABLE_CHARS:
|
||||
showPrintableCharsDialog();
|
||||
break;
|
||||
case KEY_EDITOR_TAB_SIZE:
|
||||
showTabSizeDialog();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showTabSizeDialog() {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
final String[] sizes = new String[] {"2", "4", "6", "8"};
|
||||
|
||||
int current = (getPrefManager().getEditorTabSize() / 2) - 1;
|
||||
if (current < 0 || current >= sizes.length) {
|
||||
current = 1;
|
||||
}
|
||||
|
||||
builder.setTitle(R.string.title_tab_size);
|
||||
builder.setSingleChoiceItems(
|
||||
sizes,
|
||||
current,
|
||||
(d, i) -> {
|
||||
d.dismiss();
|
||||
|
||||
int tabSize = (i + 1) * 2;
|
||||
if (tabSize < 2 || tabSize > 8) {
|
||||
tabSize = 4;
|
||||
}
|
||||
getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize);
|
||||
});
|
||||
builder.setCancelable(true);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showTextSizeDialog() {
|
||||
final LayoutTextSizeSliderBinding binding =
|
||||
LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(getContext()));
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
float size = getPrefManager().getFloat(KEY_EDITOR_FONT_SIZE, 14);
|
||||
if (size < 6 || size > 32) {
|
||||
size = 14;
|
||||
}
|
||||
changeTextSize(binding, size);
|
||||
|
||||
binding.slider.setLabelFormatter(p1 -> String.valueOf((int) p1));
|
||||
builder.setTitle(R.string.title_change_text_size);
|
||||
builder.setMessage(R.string.msg_editor_font_size);
|
||||
builder.setView(binding.getRoot());
|
||||
builder.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
(p1, p2) -> {
|
||||
p1.dismiss();
|
||||
float s = binding.slider.getValue();
|
||||
changeTextSize(binding, s);
|
||||
});
|
||||
builder.setNeutralButton(R.string.reset, (p1, p2) -> changeTextSize(binding, 14));
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) {
|
||||
getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size);
|
||||
binding.slider.setValue(size);
|
||||
ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true;
|
||||
}
|
||||
|
||||
private void showPrintableCharsDialog() {
|
||||
String[] labels = {
|
||||
"Leading whitespaces",
|
||||
"Trailing whitespaces",
|
||||
"Inner whitespaces",
|
||||
"Whitespaces in empty lines",
|
||||
"Line breaks"
|
||||
};
|
||||
final boolean[] checked = {
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_LEADING, true),
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_TRAILING, false),
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_INNER, true),
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, true),
|
||||
getPrefManager().getBoolean(KEY_EDITOR_FLAG_LINE_BREAK, true)
|
||||
};
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext());
|
||||
builder.setTitle(R.string.idepref_editor_paintingflags_title);
|
||||
builder.setMultiChoiceItems(
|
||||
labels,
|
||||
checked,
|
||||
(dialog, which, isChecked) -> {
|
||||
if (which == 0) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_LEADING, isChecked);
|
||||
} else if (which == 1) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_TRAILING, isChecked);
|
||||
} else if (which == 2) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_INNER, isChecked);
|
||||
} else if (which == 3) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked);
|
||||
} else if (which == 4) {
|
||||
getPrefManager().putBoolean(KEY_EDITOR_FLAG_LINE_BREAK, isChecked);
|
||||
}
|
||||
|
||||
ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true;
|
||||
});
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
|
||||
+192
-192
@@ -1,192 +1,192 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.sheets;
|
||||
|
||||
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_DIMENSION;
|
||||
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_FLOAT;
|
||||
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_REFERENCE;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_STRING;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.adapters.AttrValueFormatTabAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutAttrValueEditorBinding;
|
||||
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
|
||||
import com.itsaky.androidide.fragments.attr.BooleanEditor;
|
||||
import com.itsaky.androidide.fragments.attr.ColorEditor;
|
||||
import com.itsaky.androidide.fragments.attr.DimensionEditor;
|
||||
import com.itsaky.androidide.fragments.attr.EnumEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FlagEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FloatEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FractionEditor;
|
||||
import com.itsaky.androidide.fragments.attr.IntegerEditor;
|
||||
import com.itsaky.androidide.fragments.attr.ReferenceEditor;
|
||||
import com.itsaky.androidide.fragments.attr.StringEditor;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Sheet for editing value of an attribute.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class AttrValueEditorSheet extends BottomSheetDialogFragment
|
||||
implements BaseValueEditorFragment.OnValueChangeListener {
|
||||
|
||||
private static final String KEY_ATTRIBUTE = "attrEditorSheet_attribute";
|
||||
private BaseValueEditorFragment.OnValueChangeListener valueChangeListener;
|
||||
private LayoutAttrValueEditorBinding binding;
|
||||
private AttrValueFormatTabAdapter adapter;
|
||||
private XMLAttribute attribute;
|
||||
|
||||
@NonNull
|
||||
public static AttrValueEditorSheet newInstance(final XMLAttribute attribute) {
|
||||
Objects.requireNonNull(attribute);
|
||||
|
||||
final var args = new Bundle();
|
||||
args.putParcelable(KEY_ATTRIBUTE, attribute);
|
||||
|
||||
final var fragment = new AttrValueEditorSheet();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
this.valueChangeListener = (BaseValueEditorFragment.OnValueChangeListener) getParentFragment();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutAttrValueEditorBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (attribute == null) {
|
||||
this.attribute = requireArguments().getParcelable(KEY_ATTRIBUTE);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(this.attribute);
|
||||
|
||||
final var namespace = this.attribute.getNamespace();
|
||||
final var formatText = Attr.createFormatText(attribute.getFormat());
|
||||
this.binding.attributeName.setText(
|
||||
String.format("%s:%s", namespace.getPrefix(), attribute.getAttributeName()));
|
||||
this.binding.attributeFormat.setText(formatText);
|
||||
|
||||
setupTabs(formatText);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void setupTabs(@NonNull String formatText) {
|
||||
this.binding.tabs.removeAllTabs();
|
||||
this.binding.pager.setSaveEnabled(false);
|
||||
|
||||
if (this.adapter != null) {
|
||||
this.adapter.removeAll();
|
||||
this.adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
this.adapter = new AttrValueFormatTabAdapter(getChildFragmentManager(), attribute);
|
||||
|
||||
final var formats = formatText.split("\\|");
|
||||
for (var formatName : formats) {
|
||||
if (TextUtils.isEmpty(formatName) || formatName.length() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var frag = getFragmentClass(formatName);
|
||||
if (frag != null) {
|
||||
this.adapter.addFragment(frag, formatName);
|
||||
}
|
||||
}
|
||||
|
||||
binding.pager.setAdapter(this.adapter);
|
||||
binding.tabs.setupWithViewPager(binding.pager);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Contract(pure = true)
|
||||
private Class<? extends BaseValueEditorFragment> getFragmentClass(@NonNull String name) {
|
||||
switch (name) {
|
||||
case FORMAT_REFERENCE:
|
||||
return ReferenceEditor.class;
|
||||
case FORMAT_COLOR:
|
||||
return ColorEditor.class;
|
||||
case FORMAT_BOOLEAN:
|
||||
return BooleanEditor.class;
|
||||
case FORMAT_DIMENSION:
|
||||
return DimensionEditor.class;
|
||||
case FORMAT_FLOAT:
|
||||
return FloatEditor.class;
|
||||
case FORMAT_INTEGER:
|
||||
return IntegerEditor.class;
|
||||
case FORMAT_FRACTION:
|
||||
return FractionEditor.class;
|
||||
case FORMAT_STRING:
|
||||
return StringEditor.class;
|
||||
case FORMAT_ENUM:
|
||||
return EnumEditor.class;
|
||||
case FORMAT_FLAG:
|
||||
return FlagEditor.class;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAttribute(XMLAttribute attribute) {
|
||||
Objects.requireNonNull(attribute);
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onValueChanged(IAttribute attribute, String newValue) {
|
||||
if (valueChangeListener != null) {
|
||||
valueChangeListener.onValueChanged(attribute, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.fragments.sheets;
|
||||
|
||||
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_DIMENSION;
|
||||
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_FLOAT;
|
||||
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_REFERENCE;
|
||||
import static com.itsaky.attrinfo.models.Attr.FORMAT_STRING;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.itsaky.androidide.adapters.AttrValueFormatTabAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutAttrValueEditorBinding;
|
||||
import com.itsaky.androidide.fragments.attr.BaseValueEditorFragment;
|
||||
import com.itsaky.androidide.fragments.attr.BooleanEditor;
|
||||
import com.itsaky.androidide.fragments.attr.ColorEditor;
|
||||
import com.itsaky.androidide.fragments.attr.DimensionEditor;
|
||||
import com.itsaky.androidide.fragments.attr.EnumEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FlagEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FloatEditor;
|
||||
import com.itsaky.androidide.fragments.attr.FractionEditor;
|
||||
import com.itsaky.androidide.fragments.attr.IntegerEditor;
|
||||
import com.itsaky.androidide.fragments.attr.ReferenceEditor;
|
||||
import com.itsaky.androidide.fragments.attr.StringEditor;
|
||||
import com.itsaky.androidide.models.XMLAttribute;
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Sheet for editing value of an attribute.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class AttrValueEditorSheet extends BottomSheetDialogFragment
|
||||
implements BaseValueEditorFragment.OnValueChangeListener {
|
||||
|
||||
private static final String KEY_ATTRIBUTE = "attrEditorSheet_attribute";
|
||||
private BaseValueEditorFragment.OnValueChangeListener valueChangeListener;
|
||||
private LayoutAttrValueEditorBinding binding;
|
||||
private AttrValueFormatTabAdapter adapter;
|
||||
private XMLAttribute attribute;
|
||||
|
||||
@NonNull
|
||||
public static AttrValueEditorSheet newInstance(final XMLAttribute attribute) {
|
||||
Objects.requireNonNull(attribute);
|
||||
|
||||
final var args = new Bundle();
|
||||
args.putParcelable(KEY_ATTRIBUTE, attribute);
|
||||
|
||||
final var fragment = new AttrValueEditorSheet();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
this.valueChangeListener = (BaseValueEditorFragment.OnValueChangeListener) getParentFragment();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
this.binding = LayoutAttrValueEditorBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (attribute == null) {
|
||||
this.attribute = requireArguments().getParcelable(KEY_ATTRIBUTE);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(this.attribute);
|
||||
|
||||
final var namespace = this.attribute.getNamespace();
|
||||
final var formatText = Attr.createFormatText(attribute.getFormat());
|
||||
this.binding.attributeName.setText(
|
||||
String.format("%s:%s", namespace.getPrefix(), attribute.getAttributeName()));
|
||||
this.binding.attributeFormat.setText(formatText);
|
||||
|
||||
setupTabs(formatText);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void setupTabs(@NonNull String formatText) {
|
||||
this.binding.tabs.removeAllTabs();
|
||||
this.binding.pager.setSaveEnabled(false);
|
||||
|
||||
if (this.adapter != null) {
|
||||
this.adapter.removeAll();
|
||||
this.adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
this.adapter = new AttrValueFormatTabAdapter(getChildFragmentManager(), attribute);
|
||||
|
||||
final var formats = formatText.split("\\|");
|
||||
for (var formatName : formats) {
|
||||
if (TextUtils.isEmpty(formatName) || formatName.length() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var frag = getFragmentClass(formatName);
|
||||
if (frag != null) {
|
||||
this.adapter.addFragment(frag, formatName);
|
||||
}
|
||||
}
|
||||
|
||||
binding.pager.setAdapter(this.adapter);
|
||||
binding.tabs.setupWithViewPager(binding.pager);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Contract(pure = true)
|
||||
private Class<? extends BaseValueEditorFragment> getFragmentClass(@NonNull String name) {
|
||||
switch (name) {
|
||||
case FORMAT_REFERENCE:
|
||||
return ReferenceEditor.class;
|
||||
case FORMAT_COLOR:
|
||||
return ColorEditor.class;
|
||||
case FORMAT_BOOLEAN:
|
||||
return BooleanEditor.class;
|
||||
case FORMAT_DIMENSION:
|
||||
return DimensionEditor.class;
|
||||
case FORMAT_FLOAT:
|
||||
return FloatEditor.class;
|
||||
case FORMAT_INTEGER:
|
||||
return IntegerEditor.class;
|
||||
case FORMAT_FRACTION:
|
||||
return FractionEditor.class;
|
||||
case FORMAT_STRING:
|
||||
return StringEditor.class;
|
||||
case FORMAT_ENUM:
|
||||
return EnumEditor.class;
|
||||
case FORMAT_FLAG:
|
||||
return FlagEditor.class;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAttribute(XMLAttribute attribute) {
|
||||
Objects.requireNonNull(attribute);
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onValueChanged(IAttribute attribute, String newValue) {
|
||||
if (valueChangeListener != null) {
|
||||
valueChangeListener.onValueChanged(attribute, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,169 +1,169 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.handlers
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.text.Layout.Alignment.ALIGN_CENTER
|
||||
import android.text.Layout.Alignment.ALIGN_NORMAL
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.AlignmentSpan
|
||||
import android.view.View
|
||||
import android.webkit.URLUtil
|
||||
import androidx.core.view.GravityCompat
|
||||
import com.itsaky.androidide.EditorActivity
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.managers.PreferenceManager
|
||||
import com.itsaky.androidide.services.GradleBuildService
|
||||
import com.itsaky.androidide.tooling.events.ProgressEvent
|
||||
import com.itsaky.androidide.tooling.events.StatusEvent
|
||||
import com.itsaky.androidide.tooling.events.download.FileDownloadFinishEvent
|
||||
import com.itsaky.androidide.tooling.events.download.FileDownloadOperationDescriptor
|
||||
import com.itsaky.androidide.tooling.events.task.TaskProgressEvent
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.StudioUtils
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Handles events received from [GradleBuildService] updates [EditorActivity].
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class EditorEventListener : GradleBuildService.EventListener {
|
||||
|
||||
private var activityReference: WeakReference<EditorActivity?> = WeakReference(null)
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
fun setActivity(activity: EditorActivity) {
|
||||
this.activityReference = WeakReference(activity)
|
||||
}
|
||||
|
||||
override fun prepareBuild() {
|
||||
val isFirstBuild =
|
||||
activity()
|
||||
.app.prefManager.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true)
|
||||
activity()
|
||||
.setStatus(
|
||||
activity()
|
||||
.getString(if (isFirstBuild) string.preparing_first else string.preparing))
|
||||
|
||||
if (isFirstBuild) {
|
||||
activity().showFirstBuildNotice()
|
||||
}
|
||||
|
||||
activity().binding.buildProgressIndicator.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onBuildSuccessful(tasks: MutableList<String>) {
|
||||
analyzeCurrentFile()
|
||||
appendOutputSeparator()
|
||||
|
||||
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
|
||||
activity().binding.buildProgressIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onProgressEvent(event: ProgressEvent) {
|
||||
if (event is FileDownloadFinishEvent) {
|
||||
activity().setStatus("")
|
||||
} else if (event is TaskProgressEvent) {
|
||||
activity()
|
||||
.setStatus(activity().getString(string.msg_running_task, event.descriptor.taskPath))
|
||||
} else if (event is StatusEvent && event.descriptor is FileDownloadOperationDescriptor) {
|
||||
val total = StudioUtils.bytesToSizeString(event.total)
|
||||
val progress = StudioUtils.bytesToSizeString(event.progress)
|
||||
val fileName =
|
||||
URLUtil.guessFileName(
|
||||
(event.descriptor as FileDownloadOperationDescriptor).uri.path, null, null)
|
||||
|
||||
val status = SpannableStringBuilder()
|
||||
status.append(
|
||||
"[$progress/$total]",
|
||||
AlignmentSpan.Standard(ALIGN_NORMAL),
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
status.append(
|
||||
" Download $fileName",
|
||||
AlignmentSpan.Standard(ALIGN_CENTER),
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
activity().setStatus(status, GravityCompat.START)
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendOutputSeparator() {
|
||||
activity().appendBuildOut("\n\n")
|
||||
}
|
||||
|
||||
override fun onBuildFailed(tasks: MutableList<String>) {
|
||||
analyzeCurrentFile()
|
||||
appendOutputSeparator()
|
||||
|
||||
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
|
||||
activity().binding.buildProgressIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onOutput(line: String?) {
|
||||
activity().appendBuildOut(line)
|
||||
|
||||
// TODO This can be handled better when ProgressEvents are received from Tooling API server
|
||||
if (line!!.contains("BUILD SUCCESSFUL") || line.contains("BUILD FAILED")) {
|
||||
activity().setStatus(line)
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeCurrentFile() {
|
||||
val editorView = activity().currentEditor
|
||||
if (editorView != null) {
|
||||
val editor = editorView.editor
|
||||
editor?.analyze()
|
||||
}
|
||||
}
|
||||
|
||||
fun activity(): EditorActivity {
|
||||
return activityReference.get()
|
||||
?: throw IllegalStateException("Activity reference has been destroyed!")
|
||||
}
|
||||
|
||||
private fun installApks(apks: Set<File>?) {
|
||||
if (apks == null || apks.isEmpty()) {
|
||||
IDEHandler.LOG.error("Cannot install APKs: $apks")
|
||||
return
|
||||
}
|
||||
|
||||
if (apks.size == 1) {
|
||||
activity().install(apks.iterator().next())
|
||||
} else {
|
||||
IDEHandler.LOG.info("Multiple APKs found. Let the user select...")
|
||||
val files: List<File> = ArrayList(apks)
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(activity())
|
||||
builder.setTitle(activity().getString(string.title_install_apks))
|
||||
builder.setItems(getNames(files)) { d: DialogInterface, w: Int ->
|
||||
d.dismiss()
|
||||
activity().install(files[w])
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNames(apks: Collection<File>): Array<String?> {
|
||||
val names = arrayOfNulls<String>(apks.size)
|
||||
for ((i, apk) in apks.withIndex()) {
|
||||
names[i] = apk.name
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.handlers
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.text.Layout.Alignment.ALIGN_CENTER
|
||||
import android.text.Layout.Alignment.ALIGN_NORMAL
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.AlignmentSpan
|
||||
import android.view.View
|
||||
import android.webkit.URLUtil
|
||||
import androidx.core.view.GravityCompat
|
||||
import com.itsaky.androidide.EditorActivity
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.managers.PreferenceManager
|
||||
import com.itsaky.androidide.services.GradleBuildService
|
||||
import com.itsaky.androidide.tooling.events.ProgressEvent
|
||||
import com.itsaky.androidide.tooling.events.StatusEvent
|
||||
import com.itsaky.androidide.tooling.events.download.FileDownloadFinishEvent
|
||||
import com.itsaky.androidide.tooling.events.download.FileDownloadOperationDescriptor
|
||||
import com.itsaky.androidide.tooling.events.task.TaskProgressEvent
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.StudioUtils
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Handles events received from [GradleBuildService] updates [EditorActivity].
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class EditorEventListener : GradleBuildService.EventListener {
|
||||
|
||||
private var activityReference: WeakReference<EditorActivity?> = WeakReference(null)
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
fun setActivity(activity: EditorActivity) {
|
||||
this.activityReference = WeakReference(activity)
|
||||
}
|
||||
|
||||
override fun prepareBuild() {
|
||||
val isFirstBuild =
|
||||
activity()
|
||||
.app.prefManager.getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true)
|
||||
activity()
|
||||
.setStatus(
|
||||
activity()
|
||||
.getString(if (isFirstBuild) string.preparing_first else string.preparing))
|
||||
|
||||
if (isFirstBuild) {
|
||||
activity().showFirstBuildNotice()
|
||||
}
|
||||
|
||||
activity().binding.buildProgressIndicator.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onBuildSuccessful(tasks: MutableList<String>) {
|
||||
analyzeCurrentFile()
|
||||
appendOutputSeparator()
|
||||
|
||||
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
|
||||
activity().binding.buildProgressIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onProgressEvent(event: ProgressEvent) {
|
||||
if (event is FileDownloadFinishEvent) {
|
||||
activity().setStatus("")
|
||||
} else if (event is TaskProgressEvent) {
|
||||
activity()
|
||||
.setStatus(activity().getString(string.msg_running_task, event.descriptor.taskPath))
|
||||
} else if (event is StatusEvent && event.descriptor is FileDownloadOperationDescriptor) {
|
||||
val total = StudioUtils.bytesToSizeString(event.total)
|
||||
val progress = StudioUtils.bytesToSizeString(event.progress)
|
||||
val fileName =
|
||||
URLUtil.guessFileName(
|
||||
(event.descriptor as FileDownloadOperationDescriptor).uri.path, null, null)
|
||||
|
||||
val status = SpannableStringBuilder()
|
||||
status.append(
|
||||
"[$progress/$total]",
|
||||
AlignmentSpan.Standard(ALIGN_NORMAL),
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
status.append(
|
||||
" Download $fileName",
|
||||
AlignmentSpan.Standard(ALIGN_CENTER),
|
||||
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
activity().setStatus(status, GravityCompat.START)
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendOutputSeparator() {
|
||||
activity().appendBuildOut("\n\n")
|
||||
}
|
||||
|
||||
override fun onBuildFailed(tasks: MutableList<String>) {
|
||||
analyzeCurrentFile()
|
||||
appendOutputSeparator()
|
||||
|
||||
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
|
||||
activity().binding.buildProgressIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onOutput(line: String?) {
|
||||
activity().appendBuildOut(line)
|
||||
|
||||
// TODO This can be handled better when ProgressEvents are received from Tooling API server
|
||||
if (line!!.contains("BUILD SUCCESSFUL") || line.contains("BUILD FAILED")) {
|
||||
activity().setStatus(line)
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeCurrentFile() {
|
||||
val editorView = activity().currentEditor
|
||||
if (editorView != null) {
|
||||
val editor = editorView.editor
|
||||
editor?.analyze()
|
||||
}
|
||||
}
|
||||
|
||||
fun activity(): EditorActivity {
|
||||
return activityReference.get()
|
||||
?: throw IllegalStateException("Activity reference has been destroyed!")
|
||||
}
|
||||
|
||||
private fun installApks(apks: Set<File>?) {
|
||||
if (apks == null || apks.isEmpty()) {
|
||||
IDEHandler.LOG.error("Cannot install APKs: $apks")
|
||||
return
|
||||
}
|
||||
|
||||
if (apks.size == 1) {
|
||||
activity().install(apks.iterator().next())
|
||||
} else {
|
||||
IDEHandler.LOG.info("Multiple APKs found. Let the user select...")
|
||||
val files: List<File> = ArrayList(apks)
|
||||
val builder = DialogUtils.newMaterialDialogBuilder(activity())
|
||||
builder.setTitle(activity().getString(string.title_install_apks))
|
||||
builder.setItems(getNames(files)) { d: DialogInterface, w: Int ->
|
||||
d.dismiss()
|
||||
activity().install(files[w])
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNames(apks: Collection<File>): Array<String?> {
|
||||
val names = arrayOfNulls<String>(apks.size)
|
||||
for ((i, apk) in apks.withIndex()) {
|
||||
names[i] = apk.name
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
/************************************************************************************
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.androidide.handlers;
|
||||
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
/**
|
||||
* A handler is an implementation that handles a specific feature, data or anything else in
|
||||
* AndroidIDE
|
||||
*/
|
||||
public abstract class IDEHandler {
|
||||
|
||||
protected static final ILogger LOG = ILogger.instance();
|
||||
|
||||
protected Provider provider;
|
||||
|
||||
public IDEHandler(Provider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public abstract void start();
|
||||
|
||||
public abstract void stop();
|
||||
|
||||
protected EditorActivity activity() {
|
||||
return provider.provideEditorActivity();
|
||||
}
|
||||
|
||||
/** An interface to communicate between a handler and its client */
|
||||
public interface Provider {
|
||||
|
||||
/**
|
||||
* Called by handler to get a reference to {@link EditorActivity}
|
||||
*
|
||||
* @throws NullPointerException is this is required
|
||||
*/
|
||||
EditorActivity provideEditorActivity();
|
||||
}
|
||||
}
|
||||
/************************************************************************************
|
||||
* 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/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
package com.itsaky.androidide.handlers;
|
||||
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
/**
|
||||
* A handler is an implementation that handles a specific feature, data or anything else in
|
||||
* AndroidIDE
|
||||
*/
|
||||
public abstract class IDEHandler {
|
||||
|
||||
protected static final ILogger LOG = ILogger.instance();
|
||||
|
||||
protected Provider provider;
|
||||
|
||||
public IDEHandler(Provider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public abstract void start();
|
||||
|
||||
public abstract void stop();
|
||||
|
||||
protected EditorActivity activity() {
|
||||
return provider.provideEditorActivity();
|
||||
}
|
||||
|
||||
/** An interface to communicate between a handler and its client */
|
||||
public interface Provider {
|
||||
|
||||
/**
|
||||
* Called by handler to get a reference to {@link EditorActivity}
|
||||
*
|
||||
* @throws NullPointerException is this is required
|
||||
*/
|
||||
EditorActivity provideEditorActivity();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +1,128 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.language;
|
||||
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
import com.itsaky.lsp.models.CompletionItem;
|
||||
import com.itsaky.lsp.models.CompletionParams;
|
||||
import com.itsaky.lsp.models.CompletionResult;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionHelper;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.util.MyCharacter;
|
||||
|
||||
/**
|
||||
* Common implementation of completion provider which requests completions to provided language
|
||||
* server.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class CommonCompletionProvider {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("CommonCompletionProvider");
|
||||
private final ILanguageServer server;
|
||||
private CompletableFuture<CompletionResult> future;
|
||||
|
||||
public CommonCompletionProvider(ILanguageServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public static boolean checkJavaCompletionChar(char c) {
|
||||
return MyCharacter.isJavaIdentifierPart(c) || c == '.';
|
||||
}
|
||||
|
||||
public static boolean checkXMLCompletionChar(char c) {
|
||||
return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes completion items using the provided language server instance.
|
||||
*
|
||||
* @param content The reference to the content of the editor.
|
||||
* @param file The file to compute completions for.
|
||||
* @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
|
||||
* computing the completion items.
|
||||
*/
|
||||
public List<CompletionItem> complete(
|
||||
ContentReference content,
|
||||
Path file,
|
||||
CharPosition position,
|
||||
Predicate<Character> prefixMatcher) {
|
||||
if (this.future != null && !this.future.isDone()) {
|
||||
try {
|
||||
this.future.cancel(true);
|
||||
} catch (CancellationException e) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
this.future =
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
final var prefix =
|
||||
CompletionHelper.computePrefix(content, position, prefixMatcher::test);
|
||||
final var completer = server.getCompletionProvider();
|
||||
|
||||
if (!completer.canComplete(file)) {
|
||||
return CompletionResult.EMPTY;
|
||||
}
|
||||
|
||||
IdeGradleProject fileModule = null;
|
||||
try {
|
||||
fileModule = ProjectManager.INSTANCE.findModuleForFile(file.toFile()).get();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to find module for current file", e);
|
||||
}
|
||||
|
||||
final var params =
|
||||
new CompletionParams(
|
||||
new Position(position.line, position.column, position.index), file);
|
||||
params.setContent(content);
|
||||
params.setPrefix(prefix);
|
||||
params.setModule(fileModule);
|
||||
return completer.complete(params);
|
||||
});
|
||||
|
||||
try {
|
||||
final var items = future.get ().getItems ();
|
||||
return items;
|
||||
} catch (Throwable e) {
|
||||
// Do not log if completion was interrupted or cancelled
|
||||
if (!(e instanceof InterruptedException || e instanceof CompletionCancelledException)) {
|
||||
LOG.error("Unable to compute completions", e);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.language;
|
||||
|
||||
import com.itsaky.androidide.projects.ProjectManager;
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
import com.itsaky.lsp.models.CompletionItem;
|
||||
import com.itsaky.lsp.models.CompletionParams;
|
||||
import com.itsaky.lsp.models.CompletionResult;
|
||||
import com.itsaky.lsp.models.Position;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionHelper;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.util.MyCharacter;
|
||||
|
||||
/**
|
||||
* Common implementation of completion provider which requests completions to provided language
|
||||
* server.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class CommonCompletionProvider {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("CommonCompletionProvider");
|
||||
private final ILanguageServer server;
|
||||
private CompletableFuture<CompletionResult> future;
|
||||
|
||||
public CommonCompletionProvider(ILanguageServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public static boolean checkJavaCompletionChar(char c) {
|
||||
return MyCharacter.isJavaIdentifierPart(c) || c == '.';
|
||||
}
|
||||
|
||||
public static boolean checkXMLCompletionChar(char c) {
|
||||
return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes completion items using the provided language server instance.
|
||||
*
|
||||
* @param content The reference to the content of the editor.
|
||||
* @param file The file to compute completions for.
|
||||
* @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
|
||||
* computing the completion items.
|
||||
*/
|
||||
public List<CompletionItem> complete(
|
||||
ContentReference content,
|
||||
Path file,
|
||||
CharPosition position,
|
||||
Predicate<Character> prefixMatcher) {
|
||||
if (this.future != null && !this.future.isDone()) {
|
||||
try {
|
||||
this.future.cancel(true);
|
||||
} catch (CancellationException e) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
this.future =
|
||||
CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
final var prefix =
|
||||
CompletionHelper.computePrefix(content, position, prefixMatcher::test);
|
||||
final var completer = server.getCompletionProvider();
|
||||
|
||||
if (!completer.canComplete(file)) {
|
||||
return CompletionResult.EMPTY;
|
||||
}
|
||||
|
||||
IdeGradleProject fileModule = null;
|
||||
try {
|
||||
fileModule = ProjectManager.INSTANCE.findModuleForFile(file.toFile()).get();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to find module for current file", e);
|
||||
}
|
||||
|
||||
final var params =
|
||||
new CompletionParams(
|
||||
new Position(position.line, position.column, position.index), file);
|
||||
params.setContent(content);
|
||||
params.setPrefix(prefix);
|
||||
params.setModule(fileModule);
|
||||
return completer.complete(params);
|
||||
});
|
||||
|
||||
try {
|
||||
final var items = future.get ().getItems ();
|
||||
return items;
|
||||
} catch (Throwable e) {
|
||||
// Do not log if completion was interrupted or cancelled
|
||||
if (!(e instanceof InterruptedException || e instanceof CompletionCancelledException)) {
|
||||
LOG.error("Unable to compute completions", e);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.language;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class for analyze managers.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IAnalyzeManager {
|
||||
|
||||
/**
|
||||
* Update the diagnostic items.
|
||||
*
|
||||
* @param diagnostics The new list of diagnostics.
|
||||
*/
|
||||
default void updateDiagnostics(@NonNull List<DiagnosticItem> diagnostics) {}
|
||||
|
||||
/**
|
||||
* Get the diagnostic items.
|
||||
*
|
||||
* @return The diagnostic items. Must not be <code>null</code>.
|
||||
*/
|
||||
@NonNull
|
||||
List<DiagnosticItem> getDiagnostics();
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.language;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class for analyze managers.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public interface IAnalyzeManager {
|
||||
|
||||
/**
|
||||
* Update the diagnostic items.
|
||||
*
|
||||
* @param diagnostics The new list of diagnostics.
|
||||
*/
|
||||
default void updateDiagnostics(@NonNull List<DiagnosticItem> diagnostics) {}
|
||||
|
||||
/**
|
||||
* Get the diagnostic items.
|
||||
*
|
||||
* @return The diagnostic items. Must not be <code>null</code>.
|
||||
*/
|
||||
@NonNull
|
||||
List<DiagnosticItem> getDiagnostics();
|
||||
}
|
||||
|
||||
@@ -1,154 +1,154 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.language.xml;
|
||||
|
||||
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.get;
|
||||
import static io.github.rosemoe.sora.widget.schemes.EditorColorScheme.LITERAL;
|
||||
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.CharSequenceReader;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CodePointCharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Stack;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.styling.CodeBlock;
|
||||
import io.github.rosemoe.sora.lang.styling.MappedSpans;
|
||||
import io.github.rosemoe.sora.lang.styling.Styles;
|
||||
|
||||
/**
|
||||
* Simple analyzer implementation for the XML files.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class XMLAnalyzer extends SimpleAnalyzeManager<Void> {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("XMLAnalyzer");
|
||||
|
||||
@Override
|
||||
protected Styles analyze(StringBuilder text, Delegate<Void> delegate) {
|
||||
final var styles = new Styles();
|
||||
final var colors = new MappedSpans.Builder();
|
||||
|
||||
CodePointCharStream stream;
|
||||
try {
|
||||
stream = CharStreams.fromReader(new CharSequenceReader(text));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Unable to create stream for analyze", e);
|
||||
return styles;
|
||||
}
|
||||
|
||||
XMLLexer lexer = new XMLLexer(stream);
|
||||
Token token;
|
||||
int previous = 0;
|
||||
int line, column, lastLine = 1;
|
||||
var first = true;
|
||||
|
||||
final var stack = new Stack<CodeBlock>();
|
||||
|
||||
while (!delegate.isCancelled()) {
|
||||
token = lexer.nextToken();
|
||||
if (token == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.getType() == XMLLexer.EOF) {
|
||||
lastLine = token.getLine() - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
line = token.getLine() - 1;
|
||||
column = token.getCharPositionInLine();
|
||||
lastLine = line;
|
||||
|
||||
switch (token.getType()) {
|
||||
case XMLLexer.S:
|
||||
case XMLLexer.SEA_WS:
|
||||
if (first) {
|
||||
colors.addNormalIfNull();
|
||||
}
|
||||
|
||||
break;
|
||||
case XMLLexer.COMMENT:
|
||||
colors.addIfNeeded(line, column, forComment());
|
||||
break;
|
||||
case XMLLexer.OPEN:
|
||||
case XMLLexer.OPEN_SLASH:
|
||||
case XMLLexer.CLOSE:
|
||||
case XMLLexer.SLASH:
|
||||
case XMLLexer.SPECIAL_CLOSE:
|
||||
case XMLLexer.EQUALS:
|
||||
case XMLLexer.COLON:
|
||||
case XMLLexer.XMLDeclOpen:
|
||||
colors.addIfNeeded(line, column, get(OPERATOR));
|
||||
break;
|
||||
case XMLLexer.SLASH_CLOSE:
|
||||
colors.addIfNeeded(line, column, get(OPERATOR));
|
||||
final var closeBlock = stack.pop();
|
||||
closeBlock.endLine = line;
|
||||
closeBlock.endColumn = column;
|
||||
styles.addCodeBlock(closeBlock);
|
||||
break;
|
||||
case XMLLexer.STRING:
|
||||
colors.addIfNeeded(line, column, LITERAL);
|
||||
break;
|
||||
case XMLLexer.Name:
|
||||
var type = SchemeAndroidIDE.TEXT_NORMAL;
|
||||
if (previous == XMLLexer.OPEN) {
|
||||
type = SchemeAndroidIDE.XML_TAG;
|
||||
final var block = styles.obtainNewBlock();
|
||||
block.startLine = line;
|
||||
block.startColumn = column;
|
||||
stack.push(block);
|
||||
}
|
||||
|
||||
if (previous == XMLLexer.OPEN_SLASH) {
|
||||
type = SchemeAndroidIDE.XML_TAG;
|
||||
final var block = stack.pop();
|
||||
block.endLine = line;
|
||||
block.endColumn = column;
|
||||
styles.addCodeBlock(block);
|
||||
}
|
||||
|
||||
colors.addIfNeeded(line, column, get(type));
|
||||
break;
|
||||
case XMLLexer.TEXT:
|
||||
default:
|
||||
colors.addIfNeeded(line, column, get(SchemeAndroidIDE.TEXT_NORMAL));
|
||||
break;
|
||||
}
|
||||
first = false;
|
||||
if (token.getType() != XMLLexer.SEA_WS) {
|
||||
previous = token.getType();
|
||||
}
|
||||
}
|
||||
colors.determine(lastLine);
|
||||
|
||||
styles.spans = colors.build();
|
||||
|
||||
return styles;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.language.xml;
|
||||
|
||||
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.get;
|
||||
import static io.github.rosemoe.sora.widget.schemes.EditorColorScheme.LITERAL;
|
||||
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
|
||||
import com.itsaky.androidide.utils.CharSequenceReader;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CodePointCharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Stack;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.styling.CodeBlock;
|
||||
import io.github.rosemoe.sora.lang.styling.MappedSpans;
|
||||
import io.github.rosemoe.sora.lang.styling.Styles;
|
||||
|
||||
/**
|
||||
* Simple analyzer implementation for the XML files.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class XMLAnalyzer extends SimpleAnalyzeManager<Void> {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("XMLAnalyzer");
|
||||
|
||||
@Override
|
||||
protected Styles analyze(StringBuilder text, Delegate<Void> delegate) {
|
||||
final var styles = new Styles();
|
||||
final var colors = new MappedSpans.Builder();
|
||||
|
||||
CodePointCharStream stream;
|
||||
try {
|
||||
stream = CharStreams.fromReader(new CharSequenceReader(text));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Unable to create stream for analyze", e);
|
||||
return styles;
|
||||
}
|
||||
|
||||
XMLLexer lexer = new XMLLexer(stream);
|
||||
Token token;
|
||||
int previous = 0;
|
||||
int line, column, lastLine = 1;
|
||||
var first = true;
|
||||
|
||||
final var stack = new Stack<CodeBlock>();
|
||||
|
||||
while (!delegate.isCancelled()) {
|
||||
token = lexer.nextToken();
|
||||
if (token == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.getType() == XMLLexer.EOF) {
|
||||
lastLine = token.getLine() - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
line = token.getLine() - 1;
|
||||
column = token.getCharPositionInLine();
|
||||
lastLine = line;
|
||||
|
||||
switch (token.getType()) {
|
||||
case XMLLexer.S:
|
||||
case XMLLexer.SEA_WS:
|
||||
if (first) {
|
||||
colors.addNormalIfNull();
|
||||
}
|
||||
|
||||
break;
|
||||
case XMLLexer.COMMENT:
|
||||
colors.addIfNeeded(line, column, forComment());
|
||||
break;
|
||||
case XMLLexer.OPEN:
|
||||
case XMLLexer.OPEN_SLASH:
|
||||
case XMLLexer.CLOSE:
|
||||
case XMLLexer.SLASH:
|
||||
case XMLLexer.SPECIAL_CLOSE:
|
||||
case XMLLexer.EQUALS:
|
||||
case XMLLexer.COLON:
|
||||
case XMLLexer.XMLDeclOpen:
|
||||
colors.addIfNeeded(line, column, get(OPERATOR));
|
||||
break;
|
||||
case XMLLexer.SLASH_CLOSE:
|
||||
colors.addIfNeeded(line, column, get(OPERATOR));
|
||||
final var closeBlock = stack.pop();
|
||||
closeBlock.endLine = line;
|
||||
closeBlock.endColumn = column;
|
||||
styles.addCodeBlock(closeBlock);
|
||||
break;
|
||||
case XMLLexer.STRING:
|
||||
colors.addIfNeeded(line, column, LITERAL);
|
||||
break;
|
||||
case XMLLexer.Name:
|
||||
var type = SchemeAndroidIDE.TEXT_NORMAL;
|
||||
if (previous == XMLLexer.OPEN) {
|
||||
type = SchemeAndroidIDE.XML_TAG;
|
||||
final var block = styles.obtainNewBlock();
|
||||
block.startLine = line;
|
||||
block.startColumn = column;
|
||||
stack.push(block);
|
||||
}
|
||||
|
||||
if (previous == XMLLexer.OPEN_SLASH) {
|
||||
type = SchemeAndroidIDE.XML_TAG;
|
||||
final var block = stack.pop();
|
||||
block.endLine = line;
|
||||
block.endColumn = column;
|
||||
styles.addCodeBlock(block);
|
||||
}
|
||||
|
||||
colors.addIfNeeded(line, column, get(type));
|
||||
break;
|
||||
case XMLLexer.TEXT:
|
||||
default:
|
||||
colors.addIfNeeded(line, column, get(SchemeAndroidIDE.TEXT_NORMAL));
|
||||
break;
|
||||
}
|
||||
first = false;
|
||||
if (token.getType() != XMLLexer.SEA_WS) {
|
||||
previous = token.getType();
|
||||
}
|
||||
}
|
||||
colors.determine(lastLine);
|
||||
|
||||
styles.spans = colors.build();
|
||||
|
||||
return styles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,147 +1,147 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.language.xml;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.language.CommonCompletionProvider;
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.JavaCharacter;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
|
||||
import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.widget.SymbolPairMatch;
|
||||
|
||||
public class XMLLanguage extends IDELanguage {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("XMLLanguage");
|
||||
private final CommonCompletionProvider completer;
|
||||
private final NewlineHandler[] newlineHandlers;
|
||||
private XMLAnalyzer analyzer;
|
||||
|
||||
public XMLLanguage() {
|
||||
this.completer = new CommonCompletionProvider(getLanguageServer());
|
||||
this.analyzer = new XMLAnalyzer();
|
||||
this.newlineHandlers = new NewlineHandler[0];
|
||||
}
|
||||
|
||||
public boolean isAutoCompleteChar(char ch) {
|
||||
return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull String content) {
|
||||
try {
|
||||
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(content)));
|
||||
Token token;
|
||||
int advance = 0;
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
switch (token.getType()) {
|
||||
case XMLLexer.OPEN:
|
||||
advance++;
|
||||
break;
|
||||
case XMLLexer.CLOSE:
|
||||
case XMLLexer.SLASH_CLOSE:
|
||||
advance--;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
advance = Math.max(0, advance);
|
||||
return advance * getTabSize();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Failed to compute indent advance", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolPairMatch getSymbolPairs() {
|
||||
return new SymbolPairMatch.DefaultSymbolPairs();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AnalyzeManager getAnalyzeManager() {
|
||||
return analyzer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInterruptionLevel() {
|
||||
return INTERRUPTION_LEVEL_STRONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requireAutoComplete(
|
||||
@NonNull ContentReference content,
|
||||
@NonNull CharPosition position,
|
||||
@NonNull CompletionPublisher publisher,
|
||||
@NonNull Bundle extraArguments)
|
||||
throws CompletionCancelledException {
|
||||
if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE));
|
||||
publisher.setUpdateThreshold(0);
|
||||
publisher.addItems(
|
||||
new ArrayList<>(
|
||||
completer.complete(
|
||||
content, file, position, CommonCompletionProvider::checkXMLCompletionChar)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull ContentReference content, int line, int column) {
|
||||
final var text = content.getLine(line).substring(0, column);
|
||||
return getIndentAdvance(text.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ILanguageServer getLanguageServer() {
|
||||
return StudioApp.getInstance().getXMLLanguageServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewlineHandler[] getNewlineHandlers() {
|
||||
return newlineHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
analyzer = null;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.language.xml;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.language.CommonCompletionProvider;
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
import com.itsaky.androidide.lexers.xml.XMLLexer;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.JavaCharacter;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.lsp.api.ILanguageServer;
|
||||
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException;
|
||||
import io.github.rosemoe.sora.lang.completion.CompletionPublisher;
|
||||
import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler;
|
||||
import io.github.rosemoe.sora.text.CharPosition;
|
||||
import io.github.rosemoe.sora.text.ContentReference;
|
||||
import io.github.rosemoe.sora.widget.SymbolPairMatch;
|
||||
|
||||
public class XMLLanguage extends IDELanguage {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("XMLLanguage");
|
||||
private final CommonCompletionProvider completer;
|
||||
private final NewlineHandler[] newlineHandlers;
|
||||
private XMLAnalyzer analyzer;
|
||||
|
||||
public XMLLanguage() {
|
||||
this.completer = new CommonCompletionProvider(getLanguageServer());
|
||||
this.analyzer = new XMLAnalyzer();
|
||||
this.newlineHandlers = new NewlineHandler[0];
|
||||
}
|
||||
|
||||
public boolean isAutoCompleteChar(char ch) {
|
||||
return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull String content) {
|
||||
try {
|
||||
XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(content)));
|
||||
Token token;
|
||||
int advance = 0;
|
||||
while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) {
|
||||
switch (token.getType()) {
|
||||
case XMLLexer.OPEN:
|
||||
advance++;
|
||||
break;
|
||||
case XMLLexer.CLOSE:
|
||||
case XMLLexer.SLASH_CLOSE:
|
||||
advance--;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
advance = Math.max(0, advance);
|
||||
return advance * getTabSize();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Failed to compute indent advance", e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolPairMatch getSymbolPairs() {
|
||||
return new SymbolPairMatch.DefaultSymbolPairs();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AnalyzeManager getAnalyzeManager() {
|
||||
return analyzer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInterruptionLevel() {
|
||||
return INTERRUPTION_LEVEL_STRONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requireAutoComplete(
|
||||
@NonNull ContentReference content,
|
||||
@NonNull CharPosition position,
|
||||
@NonNull CompletionPublisher publisher,
|
||||
@NonNull Bundle extraArguments)
|
||||
throws CompletionCancelledException {
|
||||
if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE));
|
||||
publisher.setUpdateThreshold(0);
|
||||
publisher.addItems(
|
||||
new ArrayList<>(
|
||||
completer.complete(
|
||||
content, file, position, CommonCompletionProvider::checkXMLCompletionChar)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(@NonNull ContentReference content, int line, int column) {
|
||||
final var text = content.getLine(line).substring(0, column);
|
||||
return getIndentAdvance(text.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ILanguageServer getLanguageServer() {
|
||||
return StudioApp.getInstance().getXMLLanguageServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewlineHandler[] getNewlineHandlers() {
|
||||
return newlineHandlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
analyzer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,407 +1,407 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.gson.Gson;
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.DiagnosticsAdapter;
|
||||
import com.itsaky.androidide.adapters.SearchListAdapter;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.views.editor.CodeEditorView;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.lsp.api.ILanguageClient;
|
||||
import com.itsaky.lsp.models.CodeActionItem;
|
||||
import com.itsaky.lsp.models.Command;
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.lsp.models.DiagnosticResult;
|
||||
import com.itsaky.lsp.models.Location;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
|
||||
/** AndroidIDE specific implementation of the LanguageClient */
|
||||
public class IDELanguageClientImpl implements ILanguageClient {
|
||||
|
||||
protected static final Gson gson = new Gson();
|
||||
protected static final ILogger LOG = ILogger.newInstance("AbstractLanguageClient");
|
||||
private static IDELanguageClientImpl mInstance;
|
||||
private final Map<File, List<DiagnosticItem>> diagnostics = new HashMap<>();
|
||||
protected EditorActivityProvider activityProvider;
|
||||
|
||||
private IDELanguageClientImpl(EditorActivityProvider provider) {
|
||||
setActivityProvider(provider);
|
||||
}
|
||||
|
||||
public static IDELanguageClientImpl getInstance() {
|
||||
if (mInstance == null) {
|
||||
throw new IllegalStateException("Client not initialized");
|
||||
}
|
||||
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
public static IDELanguageClientImpl initialize(EditorActivityProvider provider) {
|
||||
if (mInstance != null) {
|
||||
throw new IllegalStateException("Client is already initialized");
|
||||
}
|
||||
|
||||
mInstance = new IDELanguageClientImpl(provider);
|
||||
|
||||
return getInstance();
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
mInstance = null;
|
||||
}
|
||||
|
||||
public static boolean isInitialized() {
|
||||
return mInstance != null;
|
||||
}
|
||||
|
||||
public void setActivityProvider(EditorActivityProvider provider) {
|
||||
this.activityProvider = provider;
|
||||
}
|
||||
|
||||
protected EditorActivity activity() {
|
||||
if (activityProvider == null) return null;
|
||||
return activityProvider.provide();
|
||||
}
|
||||
|
||||
private CodeEditorView findEditorByFile(File file) {
|
||||
return activity().getEditorForFile(file);
|
||||
}
|
||||
|
||||
public void hideDiagnostics() {
|
||||
if (activity() == null || activity().getDiagnosticBinding() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
activity().getDiagnosticBinding().getRoot().setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void hideBottomDiagnosticView(final File file) {
|
||||
if (activity() == null || file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var frag = findEditorByFile(file);
|
||||
if (frag == null || frag.getBinding() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
frag.getBinding().diagnosticTextContainer.setVisibility(View.GONE);
|
||||
frag.getBinding().diagnosticText.setVisibility(View.GONE);
|
||||
frag.getBinding().diagnosticText.setClickable(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishDiagnostics(DiagnosticResult result) {
|
||||
boolean error = result == null;
|
||||
activity().handleDiagnosticsResultVisibility(error || result.getDiagnostics().isEmpty());
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
File file = result.getFile().toFile();
|
||||
if (!file.exists() || !file.isFile()) return;
|
||||
|
||||
final var editorView = activity().getEditorForFile(file);
|
||||
if (editorView != null) {
|
||||
final var editor = editorView.getEditor();
|
||||
if (editor != null) {
|
||||
final var editorLanguage = editor.getEditorLanguage();
|
||||
if (editorLanguage instanceof IDELanguage) {
|
||||
((IDELanguage) editorLanguage).setDiagnostics(result.getDiagnostics());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diagnostics.put(file, result.getDiagnostics());
|
||||
activity().setDiagnosticsAdapter(newDiagnosticsAdapter());
|
||||
}
|
||||
|
||||
/** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */
|
||||
public void showLocations(List<Location> locations) {
|
||||
|
||||
// Cannot show anything if the activity() is null
|
||||
if (activity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean error = locations == null || locations.isEmpty();
|
||||
activity().handleSearchResultVisibility(error);
|
||||
|
||||
if (error) {
|
||||
activity().setSearchResultAdapter(new SearchListAdapter(null, null, null));
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<File, List<SearchResult>> results = new HashMap<>();
|
||||
for (int i = 0; i < locations.size(); i++) {
|
||||
try {
|
||||
final Location loc = locations.get(i);
|
||||
if (loc == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final File file = loc.getFile().toFile();
|
||||
if (!file.exists() || !file.isFile()) continue;
|
||||
var frag = findEditorByFile(file);
|
||||
Content content;
|
||||
if (frag != null && frag.getEditor() != null) content = frag.getEditor().getText();
|
||||
else content = new Content(FileIOUtils.readFile2String(file));
|
||||
final List<SearchResult> matches =
|
||||
results.containsKey(file) ? results.get(file) : new ArrayList<>();
|
||||
Objects.requireNonNull(matches)
|
||||
.add(
|
||||
new SearchResult(
|
||||
loc.getRange(),
|
||||
file,
|
||||
content.getLineString(loc.getRange().getStart().getLine()),
|
||||
content
|
||||
.subContent(
|
||||
loc.getRange().getStart().getLine(),
|
||||
loc.getRange().getStart().getColumn(),
|
||||
loc.getRange().getEnd().getLine(),
|
||||
loc.getRange().getEnd().getColumn())
|
||||
.toString()));
|
||||
results.put(file, matches);
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Failed to show file location", th);
|
||||
}
|
||||
}
|
||||
|
||||
activity().handleSearchResults(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performCodeAction(File file, CodeActionItem actionItem) {
|
||||
final var editor = activity().getEditorForFile(file);
|
||||
if (editor != null) {
|
||||
performCodeAction(editor.getEditor(), actionItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given {@link CodeActionItem}
|
||||
*
|
||||
* @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.
|
||||
* @param action The action to perform
|
||||
*/
|
||||
public void performCodeAction(IDEEditor editor, CodeActionItem action) {
|
||||
if (activity() == null || editor == null || action == null) {
|
||||
LOG.error(
|
||||
"Unable to perform code action",
|
||||
"activity=" + activity(),
|
||||
"editor=" + editor,
|
||||
"action=" + action);
|
||||
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
final ProgressSheet progress = new ProgressSheet();
|
||||
progress.setSubMessageEnabled(false);
|
||||
progress.setWelcomeTextEnabled(false);
|
||||
progress.setCancelable(false);
|
||||
progress.setMessage(activity().getString(R.string.msg_performing_actions));
|
||||
progress.show(activity().getSupportFragmentManager(), "quick_fix_progress");
|
||||
|
||||
new TaskExecutor()
|
||||
.executeAsyncProvideError(
|
||||
() -> performCodeActionAsync(editor, action),
|
||||
(result, throwable) -> {
|
||||
ThreadUtils.runOnUiThread(progress::dismiss);
|
||||
if (result == null || throwable != null || !result) {
|
||||
LOG.error(
|
||||
"Unable to perform code action", "result=" + result, "throwable=" + throwable);
|
||||
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
} else {
|
||||
editor.executeCommand(action.getCommand());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<DiagnosticGroup> mapAsGroup(Map<File, List<DiagnosticItem>> diags) {
|
||||
List<DiagnosticGroup> groups = new ArrayList<>();
|
||||
if (diags == null || diags.size() <= 0) return groups;
|
||||
for (File file : diags.keySet()) {
|
||||
var fileDiags = diags.get(file);
|
||||
if (fileDiags == null || fileDiags.size() <= 0) continue;
|
||||
DiagnosticGroup group = new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags);
|
||||
groups.add(group);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
private void showAvailableQuickfixes(IDEEditor editor, List<CodeActionItem> actions) {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
|
||||
builder.setTitle(R.string.msg_code_actions);
|
||||
builder.setItems(
|
||||
asArray(actions),
|
||||
(d, w) -> {
|
||||
d.dismiss();
|
||||
hideDiagnostics();
|
||||
hideBottomDiagnosticView(editor.getFile());
|
||||
performCodeAction(editor, actions.get(w));
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private CharSequence[] asArray(List<CodeActionItem> actions) {
|
||||
final String[] arr = new String[actions.size()];
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
arr[i] = actions.get(i).getTitle();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private Boolean performCodeActionAsync(final IDEEditor editor, final CodeActionItem action) {
|
||||
LOG.debug("Performing code action:", action);
|
||||
final var changes = action.getChanges();
|
||||
if (changes.isEmpty()) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
for (var change : changes) {
|
||||
final var path = change.getFile();
|
||||
if (path == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final File file = path.toFile();
|
||||
if (!file.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (TextEdit edit : change.getEdits()) {
|
||||
final String editorFilepath =
|
||||
editor.getFile() == null ? "" : editor.getFile().getAbsolutePath();
|
||||
if (file.getAbsolutePath().equals(editorFilepath)) {
|
||||
// Edit is in the same editor which requested the code action
|
||||
editInEditor(editor, edit);
|
||||
} else {
|
||||
var openedFrag = findEditorByFile(file);
|
||||
|
||||
if (openedFrag != null && openedFrag.getEditor() != null) {
|
||||
// Edit is in another 'opened' file
|
||||
editInEditor(openedFrag.getEditor(), edit);
|
||||
} else {
|
||||
// Edit is in some other file which is not opened
|
||||
// We should open that file and perform the edit
|
||||
openedFrag = activity().openFile(file);
|
||||
if (openedFrag != null && openedFrag.getEditor() != null) {
|
||||
editInEditor(openedFrag.getEditor(), edit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
private void execCommand(IDEEditor editor, Command command) {
|
||||
editor.executeCommand(command);
|
||||
}
|
||||
|
||||
private void editInEditor(final IDEEditor editor, final TextEdit edit) {
|
||||
final Range range = edit.getRange();
|
||||
final int startLine = range.getStart().getLine();
|
||||
final int startCol = range.getStart().getColumn();
|
||||
final int endLine = range.getEnd().getLine();
|
||||
final int endCol = range.getEnd().getColumn();
|
||||
|
||||
activity()
|
||||
.runOnUiThread(
|
||||
() -> {
|
||||
if (startLine == endLine && startCol == endCol) {
|
||||
editor.getText().insert(startLine, startCol, edit.getNewText());
|
||||
} else {
|
||||
editor.getText().replace(startLine, startCol, endLine, endCol, edit.getNewText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public DiagnosticsAdapter newDiagnosticsAdapter() {
|
||||
return new DiagnosticsAdapter(mapAsGroup(this.diagnostics), activity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.itsaky.lsp.models.ShowDocumentResult showDocument(
|
||||
com.itsaky.lsp.models.ShowDocumentParams params) {
|
||||
boolean success = false;
|
||||
final var result = new com.itsaky.lsp.models.ShowDocumentResult(false);
|
||||
if (activity() == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (params != null) {
|
||||
File file = params.getFile().toFile();
|
||||
if (file.exists() && file.isFile() && FileUtils.isUtf8(file)) {
|
||||
final var range = params.getSelection();
|
||||
var frag =
|
||||
activity().getEditorAtIndex(activity().getBinding().tabs.getSelectedTabPosition());
|
||||
if (frag != null
|
||||
&& frag.getFile() != null
|
||||
&& frag.getEditor() != null
|
||||
&& frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) {
|
||||
if (LSPUtils.isEqual(range.getStart(), range.getEnd())) {
|
||||
frag.getEditor().setSelection(range.getStart().getLine(), range.getStart().getColumn());
|
||||
} else {
|
||||
frag.getEditor().setSelection(range);
|
||||
}
|
||||
} else {
|
||||
activity().openFileAndSelect(file, range);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
result.setSuccess(success);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.lsp;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.gson.Gson;
|
||||
import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.DiagnosticsAdapter;
|
||||
import com.itsaky.androidide.adapters.SearchListAdapter;
|
||||
import com.itsaky.androidide.app.StudioApp;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.language.IDELanguage;
|
||||
import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
import com.itsaky.androidide.models.SearchResult;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.LSPUtils;
|
||||
import com.itsaky.androidide.views.editor.CodeEditorView;
|
||||
import com.itsaky.androidide.views.editor.IDEEditor;
|
||||
import com.itsaky.lsp.api.ILanguageClient;
|
||||
import com.itsaky.lsp.models.CodeActionItem;
|
||||
import com.itsaky.lsp.models.Command;
|
||||
import com.itsaky.lsp.models.DiagnosticItem;
|
||||
import com.itsaky.lsp.models.DiagnosticResult;
|
||||
import com.itsaky.lsp.models.Location;
|
||||
import com.itsaky.lsp.models.Range;
|
||||
import com.itsaky.lsp.models.TextEdit;
|
||||
import com.itsaky.toaster.Toaster;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.github.rosemoe.sora.text.Content;
|
||||
|
||||
/** AndroidIDE specific implementation of the LanguageClient */
|
||||
public class IDELanguageClientImpl implements ILanguageClient {
|
||||
|
||||
protected static final Gson gson = new Gson();
|
||||
protected static final ILogger LOG = ILogger.newInstance("AbstractLanguageClient");
|
||||
private static IDELanguageClientImpl mInstance;
|
||||
private final Map<File, List<DiagnosticItem>> diagnostics = new HashMap<>();
|
||||
protected EditorActivityProvider activityProvider;
|
||||
|
||||
private IDELanguageClientImpl(EditorActivityProvider provider) {
|
||||
setActivityProvider(provider);
|
||||
}
|
||||
|
||||
public static IDELanguageClientImpl getInstance() {
|
||||
if (mInstance == null) {
|
||||
throw new IllegalStateException("Client not initialized");
|
||||
}
|
||||
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
public static IDELanguageClientImpl initialize(EditorActivityProvider provider) {
|
||||
if (mInstance != null) {
|
||||
throw new IllegalStateException("Client is already initialized");
|
||||
}
|
||||
|
||||
mInstance = new IDELanguageClientImpl(provider);
|
||||
|
||||
return getInstance();
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
mInstance = null;
|
||||
}
|
||||
|
||||
public static boolean isInitialized() {
|
||||
return mInstance != null;
|
||||
}
|
||||
|
||||
public void setActivityProvider(EditorActivityProvider provider) {
|
||||
this.activityProvider = provider;
|
||||
}
|
||||
|
||||
protected EditorActivity activity() {
|
||||
if (activityProvider == null) return null;
|
||||
return activityProvider.provide();
|
||||
}
|
||||
|
||||
private CodeEditorView findEditorByFile(File file) {
|
||||
return activity().getEditorForFile(file);
|
||||
}
|
||||
|
||||
public void hideDiagnostics() {
|
||||
if (activity() == null || activity().getDiagnosticBinding() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
activity().getDiagnosticBinding().getRoot().setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void hideBottomDiagnosticView(final File file) {
|
||||
if (activity() == null || file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var frag = findEditorByFile(file);
|
||||
if (frag == null || frag.getBinding() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
frag.getBinding().diagnosticTextContainer.setVisibility(View.GONE);
|
||||
frag.getBinding().diagnosticText.setVisibility(View.GONE);
|
||||
frag.getBinding().diagnosticText.setClickable(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishDiagnostics(DiagnosticResult result) {
|
||||
boolean error = result == null;
|
||||
activity().handleDiagnosticsResultVisibility(error || result.getDiagnostics().isEmpty());
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
File file = result.getFile().toFile();
|
||||
if (!file.exists() || !file.isFile()) return;
|
||||
|
||||
final var editorView = activity().getEditorForFile(file);
|
||||
if (editorView != null) {
|
||||
final var editor = editorView.getEditor();
|
||||
if (editor != null) {
|
||||
final var editorLanguage = editor.getEditorLanguage();
|
||||
if (editorLanguage instanceof IDELanguage) {
|
||||
((IDELanguage) editorLanguage).setDiagnostics(result.getDiagnostics());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diagnostics.put(file, result.getDiagnostics());
|
||||
activity().setDiagnosticsAdapter(newDiagnosticsAdapter());
|
||||
}
|
||||
|
||||
/** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */
|
||||
public void showLocations(List<Location> locations) {
|
||||
|
||||
// Cannot show anything if the activity() is null
|
||||
if (activity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean error = locations == null || locations.isEmpty();
|
||||
activity().handleSearchResultVisibility(error);
|
||||
|
||||
if (error) {
|
||||
activity().setSearchResultAdapter(new SearchListAdapter(null, null, null));
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<File, List<SearchResult>> results = new HashMap<>();
|
||||
for (int i = 0; i < locations.size(); i++) {
|
||||
try {
|
||||
final Location loc = locations.get(i);
|
||||
if (loc == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final File file = loc.getFile().toFile();
|
||||
if (!file.exists() || !file.isFile()) continue;
|
||||
var frag = findEditorByFile(file);
|
||||
Content content;
|
||||
if (frag != null && frag.getEditor() != null) content = frag.getEditor().getText();
|
||||
else content = new Content(FileIOUtils.readFile2String(file));
|
||||
final List<SearchResult> matches =
|
||||
results.containsKey(file) ? results.get(file) : new ArrayList<>();
|
||||
Objects.requireNonNull(matches)
|
||||
.add(
|
||||
new SearchResult(
|
||||
loc.getRange(),
|
||||
file,
|
||||
content.getLineString(loc.getRange().getStart().getLine()),
|
||||
content
|
||||
.subContent(
|
||||
loc.getRange().getStart().getLine(),
|
||||
loc.getRange().getStart().getColumn(),
|
||||
loc.getRange().getEnd().getLine(),
|
||||
loc.getRange().getEnd().getColumn())
|
||||
.toString()));
|
||||
results.put(file, matches);
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Failed to show file location", th);
|
||||
}
|
||||
}
|
||||
|
||||
activity().handleSearchResults(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performCodeAction(File file, CodeActionItem actionItem) {
|
||||
final var editor = activity().getEditorForFile(file);
|
||||
if (editor != null) {
|
||||
performCodeAction(editor.getEditor(), actionItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given {@link CodeActionItem}
|
||||
*
|
||||
* @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.
|
||||
* @param action The action to perform
|
||||
*/
|
||||
public void performCodeAction(IDEEditor editor, CodeActionItem action) {
|
||||
if (activity() == null || editor == null || action == null) {
|
||||
LOG.error(
|
||||
"Unable to perform code action",
|
||||
"activity=" + activity(),
|
||||
"editor=" + editor,
|
||||
"action=" + action);
|
||||
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
final ProgressSheet progress = new ProgressSheet();
|
||||
progress.setSubMessageEnabled(false);
|
||||
progress.setWelcomeTextEnabled(false);
|
||||
progress.setCancelable(false);
|
||||
progress.setMessage(activity().getString(R.string.msg_performing_actions));
|
||||
progress.show(activity().getSupportFragmentManager(), "quick_fix_progress");
|
||||
|
||||
new TaskExecutor()
|
||||
.executeAsyncProvideError(
|
||||
() -> performCodeActionAsync(editor, action),
|
||||
(result, throwable) -> {
|
||||
ThreadUtils.runOnUiThread(progress::dismiss);
|
||||
if (result == null || throwable != null || !result) {
|
||||
LOG.error(
|
||||
"Unable to perform code action", "result=" + result, "throwable=" + throwable);
|
||||
StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR);
|
||||
} else {
|
||||
editor.executeCommand(action.getCommand());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<DiagnosticGroup> mapAsGroup(Map<File, List<DiagnosticItem>> diags) {
|
||||
List<DiagnosticGroup> groups = new ArrayList<>();
|
||||
if (diags == null || diags.size() <= 0) return groups;
|
||||
for (File file : diags.keySet()) {
|
||||
var fileDiags = diags.get(file);
|
||||
if (fileDiags == null || fileDiags.size() <= 0) continue;
|
||||
DiagnosticGroup group = new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags);
|
||||
groups.add(group);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
private void showAvailableQuickfixes(IDEEditor editor, List<CodeActionItem> actions) {
|
||||
final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity());
|
||||
builder.setTitle(R.string.msg_code_actions);
|
||||
builder.setItems(
|
||||
asArray(actions),
|
||||
(d, w) -> {
|
||||
d.dismiss();
|
||||
hideDiagnostics();
|
||||
hideBottomDiagnosticView(editor.getFile());
|
||||
performCodeAction(editor, actions.get(w));
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private CharSequence[] asArray(List<CodeActionItem> actions) {
|
||||
final String[] arr = new String[actions.size()];
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
arr[i] = actions.get(i).getTitle();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private Boolean performCodeActionAsync(final IDEEditor editor, final CodeActionItem action) {
|
||||
LOG.debug("Performing code action:", action);
|
||||
final var changes = action.getChanges();
|
||||
if (changes.isEmpty()) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
for (var change : changes) {
|
||||
final var path = change.getFile();
|
||||
if (path == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final File file = path.toFile();
|
||||
if (!file.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (TextEdit edit : change.getEdits()) {
|
||||
final String editorFilepath =
|
||||
editor.getFile() == null ? "" : editor.getFile().getAbsolutePath();
|
||||
if (file.getAbsolutePath().equals(editorFilepath)) {
|
||||
// Edit is in the same editor which requested the code action
|
||||
editInEditor(editor, edit);
|
||||
} else {
|
||||
var openedFrag = findEditorByFile(file);
|
||||
|
||||
if (openedFrag != null && openedFrag.getEditor() != null) {
|
||||
// Edit is in another 'opened' file
|
||||
editInEditor(openedFrag.getEditor(), edit);
|
||||
} else {
|
||||
// Edit is in some other file which is not opened
|
||||
// We should open that file and perform the edit
|
||||
openedFrag = activity().openFile(file);
|
||||
if (openedFrag != null && openedFrag.getEditor() != null) {
|
||||
editInEditor(openedFrag.getEditor(), edit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
private void execCommand(IDEEditor editor, Command command) {
|
||||
editor.executeCommand(command);
|
||||
}
|
||||
|
||||
private void editInEditor(final IDEEditor editor, final TextEdit edit) {
|
||||
final Range range = edit.getRange();
|
||||
final int startLine = range.getStart().getLine();
|
||||
final int startCol = range.getStart().getColumn();
|
||||
final int endLine = range.getEnd().getLine();
|
||||
final int endCol = range.getEnd().getColumn();
|
||||
|
||||
activity()
|
||||
.runOnUiThread(
|
||||
() -> {
|
||||
if (startLine == endLine && startCol == endCol) {
|
||||
editor.getText().insert(startLine, startCol, edit.getNewText());
|
||||
} else {
|
||||
editor.getText().replace(startLine, startCol, endLine, endCol, edit.getNewText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public DiagnosticsAdapter newDiagnosticsAdapter() {
|
||||
return new DiagnosticsAdapter(mapAsGroup(this.diagnostics), activity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.itsaky.lsp.models.ShowDocumentResult showDocument(
|
||||
com.itsaky.lsp.models.ShowDocumentParams params) {
|
||||
boolean success = false;
|
||||
final var result = new com.itsaky.lsp.models.ShowDocumentResult(false);
|
||||
if (activity() == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (params != null) {
|
||||
File file = params.getFile().toFile();
|
||||
if (file.exists() && file.isFile() && FileUtils.isUtf8(file)) {
|
||||
final var range = params.getSelection();
|
||||
var frag =
|
||||
activity().getEditorAtIndex(activity().getBinding().tabs.getSelectedTabPosition());
|
||||
if (frag != null
|
||||
&& frag.getFile() != null
|
||||
&& frag.getEditor() != null
|
||||
&& frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) {
|
||||
if (LSPUtils.isEqual(range.getStart(), range.getEnd())) {
|
||||
frag.getEditor().setSelection(range.getStart().getLine(), range.getStart().getColumn());
|
||||
} else {
|
||||
frag.getEditor().setSelection(range);
|
||||
}
|
||||
} else {
|
||||
activity().openFileAndSelect(file, range);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
result.setSuccess(success);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,335 +1,335 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import static com.itsaky.androidide.utils.ILogger.newInstance;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.JSONUtility;
|
||||
import com.itsaky.androidide.utils.ListingFileRedirect;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ApkMetadata {
|
||||
|
||||
private static final ILogger LOG = newInstance("ApkMetadata");
|
||||
|
||||
@SerializedName("version")
|
||||
private int version;
|
||||
|
||||
@SerializedName("artifactType")
|
||||
private ArtifactType artifactType;
|
||||
|
||||
@SerializedName("applicationId")
|
||||
private String applicationId;
|
||||
|
||||
@SerializedName("variantName")
|
||||
private String variantName;
|
||||
|
||||
@SerializedName("elements")
|
||||
private List<Element> elements;
|
||||
|
||||
@SerializedName("elementType")
|
||||
private String elementType;
|
||||
|
||||
@Nullable
|
||||
public static File findApkFile(@NonNull File listingFIle) {
|
||||
try {
|
||||
|
||||
// This sometimes might be a redirect to the listing file
|
||||
// So we have to handle this case too.
|
||||
final var redirectedFile = ListingFileRedirect.maybeExtractRedirectedFile(listingFIle);
|
||||
if (redirectedFile != null) {
|
||||
listingFIle = redirectedFile;
|
||||
}
|
||||
|
||||
final var dir = listingFIle.getParentFile();
|
||||
final var metadata =
|
||||
JSONUtility.gson.fromJson(new FileReader(listingFIle), ApkMetadata.class);
|
||||
if (!isValid(metadata)) {
|
||||
LOG.warn("Invalid APK metadata:", metadata);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Element element : metadata.getElements()) {
|
||||
if (element == null || element.getOutputFile() == null) {
|
||||
LOG.warn("No output file specified in APK metadata element:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
final File apk = new File(dir, element.getOutputFile());
|
||||
if (apk.exists() && apk.isFile()) {
|
||||
LOG.info("Found apk in metadata:", apk);
|
||||
return apk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (FileNotFoundException e) {
|
||||
LOG.error("Metadata file not found...", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValid(ApkMetadata metadata) {
|
||||
|
||||
// Null checks
|
||||
if (metadata == null
|
||||
|| metadata.getArtifactType() == null
|
||||
|| metadata.getArtifactType().getType() == null
|
||||
|| metadata.getElements() == null) {
|
||||
LOG.warn("APK metadata null check failed. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
final ArtifactType type = metadata.getArtifactType();
|
||||
final List<Element> elements = metadata.getElements();
|
||||
|
||||
if (!type.getType().equals(ArtifactType.TYPE_APK)) {
|
||||
LOG.warn("Artifact is not of type APK. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elements.isEmpty()) {
|
||||
LOG.warn("No output elements found for metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean atLeastOneApk = false;
|
||||
for (Element element : elements) {
|
||||
if (element == null
|
||||
|| element.getOutputFile() == null
|
||||
|| !element.getOutputFile().endsWith(".apk")) {
|
||||
LOG.warn("Skipping output element because file is not APK:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
atLeastOneApk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("Output metadata validation succeeded");
|
||||
return atLeastOneApk;
|
||||
}
|
||||
|
||||
public List<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public ArtifactType getArtifactType() {
|
||||
return artifactType;
|
||||
}
|
||||
|
||||
public void setArtifactType(ArtifactType artifactType) {
|
||||
this.artifactType = artifactType;
|
||||
}
|
||||
|
||||
public void setElements(List<Element> elements) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public void setApplicationId(String applicationId) {
|
||||
this.applicationId = applicationId;
|
||||
}
|
||||
|
||||
public String getVariantName() {
|
||||
return variantName;
|
||||
}
|
||||
|
||||
public void setVariantName(String variantName) {
|
||||
this.variantName = variantName;
|
||||
}
|
||||
|
||||
public String getElementType() {
|
||||
return elementType;
|
||||
}
|
||||
|
||||
public void setElementType(String elementType) {
|
||||
this.elementType = elementType;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApkMetadata{"
|
||||
+ "version="
|
||||
+ version
|
||||
+ ", artifactType="
|
||||
+ artifactType
|
||||
+ ", applicationId='"
|
||||
+ applicationId
|
||||
+ '\''
|
||||
+ ", variantName='"
|
||||
+ variantName
|
||||
+ '\''
|
||||
+ ", elements="
|
||||
+ elements
|
||||
+ ", elementType='"
|
||||
+ elementType
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public static class ArtifactType {
|
||||
|
||||
public static String TYPE_APK = "APK";
|
||||
|
||||
@SerializedName("type")
|
||||
private String type;
|
||||
|
||||
@SerializedName("kind")
|
||||
private String kind;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public void setKind(String kind) {
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArtifactType{" + "type='" + type + '\'' + ", kind='" + kind + '\'' + '}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Element {
|
||||
|
||||
@SerializedName("type")
|
||||
private String type;
|
||||
|
||||
@SerializedName("filters")
|
||||
private List<Object> filters;
|
||||
|
||||
@SerializedName("attributes")
|
||||
private List<Object> attributes;
|
||||
|
||||
@SerializedName("versionCode")
|
||||
private int versionCode;
|
||||
|
||||
@SerializedName("versionName")
|
||||
private String versionName;
|
||||
|
||||
@SerializedName("outputFile")
|
||||
private String outputFile;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<Object> getFilters() {
|
||||
return filters;
|
||||
}
|
||||
|
||||
public void setFilters(List<Object> filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
public List<Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(List<Object> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public int getVersionCode() {
|
||||
return versionCode;
|
||||
}
|
||||
|
||||
public void setVersionCode(int versionCode) {
|
||||
this.versionCode = versionCode;
|
||||
}
|
||||
|
||||
public String getVersionName() {
|
||||
return versionName;
|
||||
}
|
||||
|
||||
public void setVersionName(String versionName) {
|
||||
this.versionName = versionName;
|
||||
}
|
||||
|
||||
public String getOutputFile() {
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
public void setOutputFile(String outputFile) {
|
||||
this.outputFile = outputFile;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Element{"
|
||||
+ "type='"
|
||||
+ type
|
||||
+ '\''
|
||||
+ ", filters="
|
||||
+ filters
|
||||
+ ", attributes="
|
||||
+ attributes
|
||||
+ ", versionCode="
|
||||
+ versionCode
|
||||
+ ", versionName='"
|
||||
+ versionName
|
||||
+ '\''
|
||||
+ ", outputFile='"
|
||||
+ outputFile
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import static com.itsaky.androidide.utils.ILogger.newInstance;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.JSONUtility;
|
||||
import com.itsaky.androidide.utils.ListingFileRedirect;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ApkMetadata {
|
||||
|
||||
private static final ILogger LOG = newInstance("ApkMetadata");
|
||||
|
||||
@SerializedName("version")
|
||||
private int version;
|
||||
|
||||
@SerializedName("artifactType")
|
||||
private ArtifactType artifactType;
|
||||
|
||||
@SerializedName("applicationId")
|
||||
private String applicationId;
|
||||
|
||||
@SerializedName("variantName")
|
||||
private String variantName;
|
||||
|
||||
@SerializedName("elements")
|
||||
private List<Element> elements;
|
||||
|
||||
@SerializedName("elementType")
|
||||
private String elementType;
|
||||
|
||||
@Nullable
|
||||
public static File findApkFile(@NonNull File listingFIle) {
|
||||
try {
|
||||
|
||||
// This sometimes might be a redirect to the listing file
|
||||
// So we have to handle this case too.
|
||||
final var redirectedFile = ListingFileRedirect.maybeExtractRedirectedFile(listingFIle);
|
||||
if (redirectedFile != null) {
|
||||
listingFIle = redirectedFile;
|
||||
}
|
||||
|
||||
final var dir = listingFIle.getParentFile();
|
||||
final var metadata =
|
||||
JSONUtility.gson.fromJson(new FileReader(listingFIle), ApkMetadata.class);
|
||||
if (!isValid(metadata)) {
|
||||
LOG.warn("Invalid APK metadata:", metadata);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Element element : metadata.getElements()) {
|
||||
if (element == null || element.getOutputFile() == null) {
|
||||
LOG.warn("No output file specified in APK metadata element:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
final File apk = new File(dir, element.getOutputFile());
|
||||
if (apk.exists() && apk.isFile()) {
|
||||
LOG.info("Found apk in metadata:", apk);
|
||||
return apk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (FileNotFoundException e) {
|
||||
LOG.error("Metadata file not found...", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValid(ApkMetadata metadata) {
|
||||
|
||||
// Null checks
|
||||
if (metadata == null
|
||||
|| metadata.getArtifactType() == null
|
||||
|| metadata.getArtifactType().getType() == null
|
||||
|| metadata.getElements() == null) {
|
||||
LOG.warn("APK metadata null check failed. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
final ArtifactType type = metadata.getArtifactType();
|
||||
final List<Element> elements = metadata.getElements();
|
||||
|
||||
if (!type.getType().equals(ArtifactType.TYPE_APK)) {
|
||||
LOG.warn("Artifact is not of type APK. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elements.isEmpty()) {
|
||||
LOG.warn("No output elements found for metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean atLeastOneApk = false;
|
||||
for (Element element : elements) {
|
||||
if (element == null
|
||||
|| element.getOutputFile() == null
|
||||
|| !element.getOutputFile().endsWith(".apk")) {
|
||||
LOG.warn("Skipping output element because file is not APK:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
atLeastOneApk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("Output metadata validation succeeded");
|
||||
return atLeastOneApk;
|
||||
}
|
||||
|
||||
public List<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public ArtifactType getArtifactType() {
|
||||
return artifactType;
|
||||
}
|
||||
|
||||
public void setArtifactType(ArtifactType artifactType) {
|
||||
this.artifactType = artifactType;
|
||||
}
|
||||
|
||||
public void setElements(List<Element> elements) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public void setApplicationId(String applicationId) {
|
||||
this.applicationId = applicationId;
|
||||
}
|
||||
|
||||
public String getVariantName() {
|
||||
return variantName;
|
||||
}
|
||||
|
||||
public void setVariantName(String variantName) {
|
||||
this.variantName = variantName;
|
||||
}
|
||||
|
||||
public String getElementType() {
|
||||
return elementType;
|
||||
}
|
||||
|
||||
public void setElementType(String elementType) {
|
||||
this.elementType = elementType;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApkMetadata{"
|
||||
+ "version="
|
||||
+ version
|
||||
+ ", artifactType="
|
||||
+ artifactType
|
||||
+ ", applicationId='"
|
||||
+ applicationId
|
||||
+ '\''
|
||||
+ ", variantName='"
|
||||
+ variantName
|
||||
+ '\''
|
||||
+ ", elements="
|
||||
+ elements
|
||||
+ ", elementType='"
|
||||
+ elementType
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public static class ArtifactType {
|
||||
|
||||
public static String TYPE_APK = "APK";
|
||||
|
||||
@SerializedName("type")
|
||||
private String type;
|
||||
|
||||
@SerializedName("kind")
|
||||
private String kind;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public void setKind(String kind) {
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArtifactType{" + "type='" + type + '\'' + ", kind='" + kind + '\'' + '}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Element {
|
||||
|
||||
@SerializedName("type")
|
||||
private String type;
|
||||
|
||||
@SerializedName("filters")
|
||||
private List<Object> filters;
|
||||
|
||||
@SerializedName("attributes")
|
||||
private List<Object> attributes;
|
||||
|
||||
@SerializedName("versionCode")
|
||||
private int versionCode;
|
||||
|
||||
@SerializedName("versionName")
|
||||
private String versionName;
|
||||
|
||||
@SerializedName("outputFile")
|
||||
private String outputFile;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<Object> getFilters() {
|
||||
return filters;
|
||||
}
|
||||
|
||||
public void setFilters(List<Object> filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
public List<Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(List<Object> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public int getVersionCode() {
|
||||
return versionCode;
|
||||
}
|
||||
|
||||
public void setVersionCode(int versionCode) {
|
||||
this.versionCode = versionCode;
|
||||
}
|
||||
|
||||
public String getVersionName() {
|
||||
return versionName;
|
||||
}
|
||||
|
||||
public void setVersionName(String versionName) {
|
||||
this.versionName = versionName;
|
||||
}
|
||||
|
||||
public String getOutputFile() {
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
public void setOutputFile(String outputFile) {
|
||||
this.outputFile = outputFile;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Element{"
|
||||
+ "type='"
|
||||
+ type
|
||||
+ '\''
|
||||
+ ", filters="
|
||||
+ filters
|
||||
+ ", attributes="
|
||||
+ attributes
|
||||
+ ", versionCode="
|
||||
+ versionCode
|
||||
+ ", versionName='"
|
||||
+ versionName
|
||||
+ '\''
|
||||
+ ", outputFile='"
|
||||
+ outputFile
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,155 +1,155 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.projects
|
||||
|
||||
import com.itsaky.androidide.tooling.api.IProject
|
||||
import com.itsaky.androidide.tooling.api.IProject.Type
|
||||
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.SimpleVariantData
|
||||
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleTask
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.io.File
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* A project which lazily caches some required properties of the given project.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class CachingProject(val project: IProject) : IProject {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
private val mName: String by lazy { this.project.name.get() }
|
||||
private val mDescription: String by lazy { this.project.description.get() }
|
||||
private val mProjectPath: String by lazy { this.project.projectPath.get() }
|
||||
private val mProjectDir: File by lazy { this.project.projectDir.get() }
|
||||
private val mBuildDir: File by lazy { this.project.buildDir.get() }
|
||||
private val mBuildScript: File by lazy { this.project.buildScript.get() }
|
||||
private val mProjectType: Type by lazy { this.project.type.get() }
|
||||
|
||||
private var mFirstAppModule: IdeAndroidModule? = null
|
||||
private val mCachedVariants: MutableMap<VariantDataRequest, SimpleVariantData> = mutableMapOf()
|
||||
private val mModules: MutableList<SimpleModuleData> = mutableListOf()
|
||||
|
||||
override fun isProjectInitialized(): CompletableFuture<Boolean> {
|
||||
return CompletableFuture.completedFuture(true)
|
||||
}
|
||||
|
||||
override fun getName(): CompletableFuture<String> {
|
||||
return CompletableFuture.completedFuture(this.mName)
|
||||
}
|
||||
|
||||
override fun getDescription(): CompletableFuture<String> {
|
||||
return CompletableFuture.completedFuture(mDescription)
|
||||
}
|
||||
|
||||
override fun getProjectPath(): CompletableFuture<String> {
|
||||
return CompletableFuture.completedFuture(mProjectPath)
|
||||
}
|
||||
|
||||
override fun getType(): CompletableFuture<Type> {
|
||||
return CompletableFuture.completedFuture(mProjectType)
|
||||
}
|
||||
|
||||
override fun getProjectDir(): CompletableFuture<File> {
|
||||
return CompletableFuture.completedFuture(mProjectDir)
|
||||
}
|
||||
|
||||
override fun getBuildDir(): CompletableFuture<File> {
|
||||
return CompletableFuture.completedFuture(mBuildDir)
|
||||
}
|
||||
|
||||
override fun getBuildScript(): CompletableFuture<File> {
|
||||
return CompletableFuture.completedFuture(mBuildScript)
|
||||
}
|
||||
|
||||
override fun getTasks(): CompletableFuture<MutableList<IdeGradleTask>> {
|
||||
return this.project.tasks
|
||||
}
|
||||
|
||||
override fun getModules(): CompletableFuture<MutableList<IdeGradleProject>> {
|
||||
return this.project.modules
|
||||
}
|
||||
|
||||
override fun listModules(): CompletableFuture<MutableList<SimpleModuleData>> {
|
||||
return if (this.mModules.isNotEmpty()) {
|
||||
log.debug("Using cached module data...")
|
||||
CompletableFuture.completedFuture(mModules)
|
||||
} else {
|
||||
return this.project.listModules().whenComplete { modules, err ->
|
||||
if (err != null || modules == null) {
|
||||
log.debug("Unable to fetch module data from tooling server", err)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
if (modules.isEmpty()) {
|
||||
log.debug("Empty module data returned by tooling server. Ignoring...")
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
mModules.clear()
|
||||
mModules.addAll(modules)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVariantData(request: VariantDataRequest): CompletableFuture<SimpleVariantData> {
|
||||
val cached = mCachedVariants[request]
|
||||
if (cached != null) {
|
||||
return CompletableFuture.completedFuture(cached)
|
||||
}
|
||||
|
||||
return this.project.getVariantData(request).whenComplete { t, u ->
|
||||
if (t == null) {
|
||||
log.warn(
|
||||
"Unable to get variant data for " +
|
||||
"variant ${request.variantName} ('${request.projectPath}') from tooling server",
|
||||
u)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
mCachedVariants[request] = t
|
||||
}
|
||||
}
|
||||
|
||||
override fun findByPath(path: String): CompletableFuture<IdeGradleProject> {
|
||||
return this.project.findByPath(path)
|
||||
}
|
||||
|
||||
override fun findAndroidModules(): CompletableFuture<MutableList<IdeAndroidModule>> {
|
||||
return this.project.findAndroidModules()
|
||||
}
|
||||
|
||||
override fun findFirstAndroidModule(): CompletableFuture<IdeAndroidModule> {
|
||||
return this.project.findFirstAndroidModule()
|
||||
}
|
||||
|
||||
override fun findFirstAndroidAppModule(): CompletableFuture<IdeAndroidModule> {
|
||||
if (mFirstAppModule != null) {
|
||||
return CompletableFuture.completedFuture(mFirstAppModule)
|
||||
}
|
||||
|
||||
return this.project.findFirstAndroidAppModule().whenComplete { module, _ ->
|
||||
this.mFirstAppModule = module
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.projects
|
||||
|
||||
import com.itsaky.androidide.tooling.api.IProject
|
||||
import com.itsaky.androidide.tooling.api.IProject.Type
|
||||
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.SimpleVariantData
|
||||
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleTask
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import java.io.File
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* A project which lazily caches some required properties of the given project.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class CachingProject(val project: IProject) : IProject {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
private val mName: String by lazy { this.project.name.get() }
|
||||
private val mDescription: String by lazy { this.project.description.get() }
|
||||
private val mProjectPath: String by lazy { this.project.projectPath.get() }
|
||||
private val mProjectDir: File by lazy { this.project.projectDir.get() }
|
||||
private val mBuildDir: File by lazy { this.project.buildDir.get() }
|
||||
private val mBuildScript: File by lazy { this.project.buildScript.get() }
|
||||
private val mProjectType: Type by lazy { this.project.type.get() }
|
||||
|
||||
private var mFirstAppModule: IdeAndroidModule? = null
|
||||
private val mCachedVariants: MutableMap<VariantDataRequest, SimpleVariantData> = mutableMapOf()
|
||||
private val mModules: MutableList<SimpleModuleData> = mutableListOf()
|
||||
|
||||
override fun isProjectInitialized(): CompletableFuture<Boolean> {
|
||||
return CompletableFuture.completedFuture(true)
|
||||
}
|
||||
|
||||
override fun getName(): CompletableFuture<String> {
|
||||
return CompletableFuture.completedFuture(this.mName)
|
||||
}
|
||||
|
||||
override fun getDescription(): CompletableFuture<String> {
|
||||
return CompletableFuture.completedFuture(mDescription)
|
||||
}
|
||||
|
||||
override fun getProjectPath(): CompletableFuture<String> {
|
||||
return CompletableFuture.completedFuture(mProjectPath)
|
||||
}
|
||||
|
||||
override fun getType(): CompletableFuture<Type> {
|
||||
return CompletableFuture.completedFuture(mProjectType)
|
||||
}
|
||||
|
||||
override fun getProjectDir(): CompletableFuture<File> {
|
||||
return CompletableFuture.completedFuture(mProjectDir)
|
||||
}
|
||||
|
||||
override fun getBuildDir(): CompletableFuture<File> {
|
||||
return CompletableFuture.completedFuture(mBuildDir)
|
||||
}
|
||||
|
||||
override fun getBuildScript(): CompletableFuture<File> {
|
||||
return CompletableFuture.completedFuture(mBuildScript)
|
||||
}
|
||||
|
||||
override fun getTasks(): CompletableFuture<MutableList<IdeGradleTask>> {
|
||||
return this.project.tasks
|
||||
}
|
||||
|
||||
override fun getModules(): CompletableFuture<MutableList<IdeGradleProject>> {
|
||||
return this.project.modules
|
||||
}
|
||||
|
||||
override fun listModules(): CompletableFuture<MutableList<SimpleModuleData>> {
|
||||
return if (this.mModules.isNotEmpty()) {
|
||||
log.debug("Using cached module data...")
|
||||
CompletableFuture.completedFuture(mModules)
|
||||
} else {
|
||||
return this.project.listModules().whenComplete { modules, err ->
|
||||
if (err != null || modules == null) {
|
||||
log.debug("Unable to fetch module data from tooling server", err)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
if (modules.isEmpty()) {
|
||||
log.debug("Empty module data returned by tooling server. Ignoring...")
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
mModules.clear()
|
||||
mModules.addAll(modules)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVariantData(request: VariantDataRequest): CompletableFuture<SimpleVariantData> {
|
||||
val cached = mCachedVariants[request]
|
||||
if (cached != null) {
|
||||
return CompletableFuture.completedFuture(cached)
|
||||
}
|
||||
|
||||
return this.project.getVariantData(request).whenComplete { t, u ->
|
||||
if (t == null) {
|
||||
log.warn(
|
||||
"Unable to get variant data for " +
|
||||
"variant ${request.variantName} ('${request.projectPath}') from tooling server",
|
||||
u)
|
||||
return@whenComplete
|
||||
}
|
||||
|
||||
mCachedVariants[request] = t
|
||||
}
|
||||
}
|
||||
|
||||
override fun findByPath(path: String): CompletableFuture<IdeGradleProject> {
|
||||
return this.project.findByPath(path)
|
||||
}
|
||||
|
||||
override fun findAndroidModules(): CompletableFuture<MutableList<IdeAndroidModule>> {
|
||||
return this.project.findAndroidModules()
|
||||
}
|
||||
|
||||
override fun findFirstAndroidModule(): CompletableFuture<IdeAndroidModule> {
|
||||
return this.project.findFirstAndroidModule()
|
||||
}
|
||||
|
||||
override fun findFirstAndroidAppModule(): CompletableFuture<IdeAndroidModule> {
|
||||
if (mFirstAppModule != null) {
|
||||
return CompletableFuture.completedFuture(mFirstAppModule)
|
||||
}
|
||||
|
||||
return this.project.findFirstAndroidAppModule().whenComplete { module, _ ->
|
||||
this.mFirstAppModule = module
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,207 +1,207 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.projects
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.services.BuildService
|
||||
import com.itsaky.androidide.tooling.api.IProject
|
||||
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
|
||||
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
|
||||
import com.itsaky.androidide.tooling.api.model.IdeModule
|
||||
import com.itsaky.androidide.tooling.api.util.ProjectDataCollector
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.lsp.java.models.JavaServerConfiguration
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* Manages projects in AndroidIDE.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
object ProjectManager {
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
var rootProject: IProject? = null
|
||||
set(value) {
|
||||
field = if (value == null) null else CachingProject(value)
|
||||
|
||||
// Cache the module data in advance for future use
|
||||
if (field != null) {
|
||||
field!!.listModules()
|
||||
}
|
||||
}
|
||||
var projectPath: String? = null
|
||||
|
||||
fun isInitialized() = this.rootProject != null && this.rootProject!!.isProjectInitialized.get()
|
||||
|
||||
fun checkInit(): Boolean {
|
||||
if (isInitialized()) {
|
||||
return true
|
||||
}
|
||||
|
||||
log.warn("Project is not initialized yet!")
|
||||
return false
|
||||
}
|
||||
|
||||
fun getProjectDir(): File? = if (!checkInit()) null else File(getProjectDirPath()!!)
|
||||
|
||||
fun getProjectDirPath(): String? {
|
||||
return projectPath
|
||||
}
|
||||
|
||||
fun generateSources(builder: BuildService?) {
|
||||
if (builder == null) {
|
||||
log.warn("Cannot generate sources. BuildService is null.")
|
||||
return
|
||||
}
|
||||
|
||||
getApplicationModule().whenCompleteAsync { app, _ ->
|
||||
if (app == null) {
|
||||
log.warn(
|
||||
"Cannot run resource and source generation task. No application module found.")
|
||||
return@whenCompleteAsync
|
||||
}
|
||||
|
||||
val debug = app.simpleVariants.firstOrNull { it.name == "debug" }
|
||||
if (debug == null) {
|
||||
log.warn("No debug variant found in application project ${app.name}")
|
||||
return@whenCompleteAsync
|
||||
}
|
||||
|
||||
val mainArtifact = app.simpleVariants.first { it.name == "debug" }.mainArtifact
|
||||
val genResourcesTask = mainArtifact.resGenTaskName
|
||||
val genSourcesTask = mainArtifact.sourceGenTaskName
|
||||
builder
|
||||
.executeProjectTasks(
|
||||
app.projectPath,
|
||||
genResourcesTask ?: "",
|
||||
genSourcesTask,
|
||||
|
||||
// If view binding is enabled, generate the view binding classes too
|
||||
if (app.viewBindingOptions != null && app.viewBindingOptions!!.isEnabled)
|
||||
"dataBindingGenBaseClassesDebug"
|
||||
else "")
|
||||
.whenComplete { result, taskErr ->
|
||||
if (!result.isSuccessful || taskErr != null) {
|
||||
log.warn(
|
||||
"Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.",
|
||||
taskErr ?: "")
|
||||
} else {
|
||||
notifyProjectUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getApplicationModule(): CompletableFuture<IdeAndroidModule?> {
|
||||
if (rootProject == null) {
|
||||
log.error(
|
||||
"No root project instance is set. Is the project initialization process finished?")
|
||||
return CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
return rootProject!!.findFirstAndroidAppModule().whenComplete { app, err ->
|
||||
if (app == null || err != null) {
|
||||
log.debug("An error occurred while fetching model for application module.")
|
||||
return@whenComplete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getApplicationResDirectories(): CompletableFuture<List<File>?> {
|
||||
return getApplicationModule().thenApplyAsync {
|
||||
if (it == null) null else collectResDirectories(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyProjectUpdate() {
|
||||
log.debug("Updating class paths in language servers...")
|
||||
getApplicationModule().whenCompleteAsync { it, _ ->
|
||||
if (it == null) {
|
||||
log.debug("Cannot find application module. Unable to update class paths to LSP.")
|
||||
return@whenCompleteAsync
|
||||
}
|
||||
|
||||
val server = StudioApp.getInstance().javaLanguageServer
|
||||
val classPaths = collectClassPaths(it)
|
||||
val sourceDirs = collectSourceDirs(it)
|
||||
val configuration = JavaServerConfiguration(classPaths, sourceDirs)
|
||||
server.configurationChanged(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
fun collectResDirectories(android: IdeAndroidModule) =
|
||||
ProjectDataCollector.collectResDirectories(this.rootProject!!, android)
|
||||
|
||||
fun collectClassPaths(app: IdeAndroidModule) =
|
||||
ProjectDataCollector.collectClassPaths(this.rootProject!!, app)
|
||||
|
||||
fun collectSourceDirs(app: IdeAndroidModule): Set<Path> =
|
||||
ProjectDataCollector.collectSourceDirs(this.rootProject!!, app)
|
||||
|
||||
@Suppress("unused")
|
||||
fun collectProjectDependencies(project: IProject, app: IdeAndroidModule) =
|
||||
ProjectDataCollector.collectProjectDependencies(project, app)
|
||||
|
||||
@Suppress("unused")
|
||||
fun collectSourceDirs(projects: List<IdeModule>) =
|
||||
ProjectDataCollector.collectSourceDirs(projects)
|
||||
|
||||
@Suppress("unused")
|
||||
fun collectSources(java: IdeJavaModule) = ProjectDataCollector.collectSources(java)
|
||||
|
||||
@Suppress("unused")
|
||||
fun collectSources(android: IdeAndroidModule) = ProjectDataCollector.collectSources(android)
|
||||
|
||||
fun findModuleForFile(file: File): CompletableFuture<IdeGradleProject?> {
|
||||
if (!checkInit()) {
|
||||
return CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
val notAvailable = ":::::"
|
||||
return rootProject!!
|
||||
.listModules()
|
||||
.thenApplyAsync { modules ->
|
||||
val path = file.canonicalPath
|
||||
|
||||
var longestPath = ""
|
||||
for (module in modules) {
|
||||
val modulePath = module.projectDir.canonicalPath
|
||||
if (path.startsWith(modulePath)) {
|
||||
if (longestPath.length < modulePath.length) {
|
||||
longestPath = modulePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (longestPath.isEmpty()) {
|
||||
return@thenApplyAsync notAvailable
|
||||
}
|
||||
|
||||
return@thenApplyAsync longestPath
|
||||
}
|
||||
.thenApplyAsync {
|
||||
if (it == notAvailable) {
|
||||
return@thenApplyAsync null
|
||||
} else return@thenApplyAsync rootProject!!.findByPath(it).get()
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.projects
|
||||
|
||||
import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.services.BuildService
|
||||
import com.itsaky.androidide.tooling.api.IProject
|
||||
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
|
||||
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
|
||||
import com.itsaky.androidide.tooling.api.model.IdeModule
|
||||
import com.itsaky.androidide.tooling.api.util.ProjectDataCollector
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.lsp.java.models.JavaServerConfiguration
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* Manages projects in AndroidIDE.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
object ProjectManager {
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
var rootProject: IProject? = null
|
||||
set(value) {
|
||||
field = if (value == null) null else CachingProject(value)
|
||||
|
||||
// Cache the module data in advance for future use
|
||||
if (field != null) {
|
||||
field!!.listModules()
|
||||
}
|
||||
}
|
||||
var projectPath: String? = null
|
||||
|
||||
fun isInitialized() = this.rootProject != null && this.rootProject!!.isProjectInitialized.get()
|
||||
|
||||
fun checkInit(): Boolean {
|
||||
if (isInitialized()) {
|
||||
return true
|
||||
}
|
||||
|
||||
log.warn("Project is not initialized yet!")
|
||||
return false
|
||||
}
|
||||
|
||||
fun getProjectDir(): File? = if (!checkInit()) null else File(getProjectDirPath()!!)
|
||||
|
||||
fun getProjectDirPath(): String? {
|
||||
return projectPath
|
||||
}
|
||||
|
||||
fun generateSources(builder: BuildService?) {
|
||||
if (builder == null) {
|
||||
log.warn("Cannot generate sources. BuildService is null.")
|
||||
return
|
||||
}
|
||||
|
||||
getApplicationModule().whenCompleteAsync { app, _ ->
|
||||
if (app == null) {
|
||||
log.warn(
|
||||
"Cannot run resource and source generation task. No application module found.")
|
||||
return@whenCompleteAsync
|
||||
}
|
||||
|
||||
val debug = app.simpleVariants.firstOrNull { it.name == "debug" }
|
||||
if (debug == null) {
|
||||
log.warn("No debug variant found in application project ${app.name}")
|
||||
return@whenCompleteAsync
|
||||
}
|
||||
|
||||
val mainArtifact = app.simpleVariants.first { it.name == "debug" }.mainArtifact
|
||||
val genResourcesTask = mainArtifact.resGenTaskName
|
||||
val genSourcesTask = mainArtifact.sourceGenTaskName
|
||||
builder
|
||||
.executeProjectTasks(
|
||||
app.projectPath,
|
||||
genResourcesTask ?: "",
|
||||
genSourcesTask,
|
||||
|
||||
// If view binding is enabled, generate the view binding classes too
|
||||
if (app.viewBindingOptions != null && app.viewBindingOptions!!.isEnabled)
|
||||
"dataBindingGenBaseClassesDebug"
|
||||
else "")
|
||||
.whenComplete { result, taskErr ->
|
||||
if (!result.isSuccessful || taskErr != null) {
|
||||
log.warn(
|
||||
"Execution for tasks '$genResourcesTask' and '$genSourcesTask' failed.",
|
||||
taskErr ?: "")
|
||||
} else {
|
||||
notifyProjectUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getApplicationModule(): CompletableFuture<IdeAndroidModule?> {
|
||||
if (rootProject == null) {
|
||||
log.error(
|
||||
"No root project instance is set. Is the project initialization process finished?")
|
||||
return CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
return rootProject!!.findFirstAndroidAppModule().whenComplete { app, err ->
|
||||
if (app == null || err != null) {
|
||||
log.debug("An error occurred while fetching model for application module.")
|
||||
return@whenComplete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getApplicationResDirectories(): CompletableFuture<List<File>?> {
|
||||
return getApplicationModule().thenApplyAsync {
|
||||
if (it == null) null else collectResDirectories(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyProjectUpdate() {
|
||||
log.debug("Updating class paths in language servers...")
|
||||
getApplicationModule().whenCompleteAsync { it, _ ->
|
||||
if (it == null) {
|
||||
log.debug("Cannot find application module. Unable to update class paths to LSP.")
|
||||
return@whenCompleteAsync
|
||||
}
|
||||
|
||||
val server = StudioApp.getInstance().javaLanguageServer
|
||||
val classPaths = collectClassPaths(it)
|
||||
val sourceDirs = collectSourceDirs(it)
|
||||
val configuration = JavaServerConfiguration(classPaths, sourceDirs)
|
||||
server.configurationChanged(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
fun collectResDirectories(android: IdeAndroidModule) =
|
||||
ProjectDataCollector.collectResDirectories(this.rootProject!!, android)
|
||||
|
||||
fun collectClassPaths(app: IdeAndroidModule) =
|
||||
ProjectDataCollector.collectClassPaths(this.rootProject!!, app)
|
||||
|
||||
fun collectSourceDirs(app: IdeAndroidModule): Set<Path> =
|
||||
ProjectDataCollector.collectSourceDirs(this.rootProject!!, app)
|
||||
|
||||
@Suppress("unused")
|
||||
fun collectProjectDependencies(project: IProject, app: IdeAndroidModule) =
|
||||
ProjectDataCollector.collectProjectDependencies(project, app)
|
||||
|
||||
@Suppress("unused")
|
||||
fun collectSourceDirs(projects: List<IdeModule>) =
|
||||
ProjectDataCollector.collectSourceDirs(projects)
|
||||
|
||||
@Suppress("unused")
|
||||
fun collectSources(java: IdeJavaModule) = ProjectDataCollector.collectSources(java)
|
||||
|
||||
@Suppress("unused")
|
||||
fun collectSources(android: IdeAndroidModule) = ProjectDataCollector.collectSources(android)
|
||||
|
||||
fun findModuleForFile(file: File): CompletableFuture<IdeGradleProject?> {
|
||||
if (!checkInit()) {
|
||||
return CompletableFuture.completedFuture(null)
|
||||
}
|
||||
|
||||
val notAvailable = ":::::"
|
||||
return rootProject!!
|
||||
.listModules()
|
||||
.thenApplyAsync { modules ->
|
||||
val path = file.canonicalPath
|
||||
|
||||
var longestPath = ""
|
||||
for (module in modules) {
|
||||
val modulePath = module.projectDir.canonicalPath
|
||||
if (path.startsWith(modulePath)) {
|
||||
if (longestPath.length < modulePath.length) {
|
||||
longestPath = modulePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (longestPath.isEmpty()) {
|
||||
return@thenApplyAsync notAvailable
|
||||
}
|
||||
|
||||
return@thenApplyAsync longestPath
|
||||
}
|
||||
.thenApplyAsync {
|
||||
if (it == notAvailable) {
|
||||
return@thenApplyAsync null
|
||||
} else return@thenApplyAsync rootProject!!.findByPath(it).get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,312 +1,312 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.projects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ProjectResourceTable implements IResourceTable {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ProjectResourceFinder");
|
||||
private File resDir;
|
||||
private File color;
|
||||
private File layout;
|
||||
private File[] drawables;
|
||||
private File[] mipmaps;
|
||||
|
||||
@Override
|
||||
public File findDrawable(@NonNull String name) {
|
||||
for (File drawable : drawables) {
|
||||
final File file = findFileWithName(drawable.listFiles(), name);
|
||||
if (file != null) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File findLayout(@NonNull String name) {
|
||||
return findFileWithName(this.layout.listFiles(), name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findString(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findString(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@string/")) {
|
||||
var ref = value.substring("@string/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findBoolean(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findBoolean(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var val = resource.getValue();
|
||||
if (val.startsWith("@bool/")) {
|
||||
final var ref = val.substring("@bool/".length());
|
||||
if (ref.equals(name)) {
|
||||
// recursive reference
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findInteger(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findInteger(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var val = resource.getValue();
|
||||
if (val.startsWith("@integer/")) {
|
||||
final var ref = val.substring("@integer/".length());
|
||||
if (ref.equals(name)) {
|
||||
// recursive reference
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findColor(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findColor(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@color/")) {
|
||||
var ref = value.substring("@color/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] findArray(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
if (table == null) {
|
||||
return new String[] {name};
|
||||
}
|
||||
|
||||
final var resource = table.findArray(name);
|
||||
if (resource == null) {
|
||||
return new String[] {name};
|
||||
}
|
||||
|
||||
return resource.getValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findDimension(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findDimension(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@dimen/")) {
|
||||
var ref = value.substring("@dimen/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Collection<String> listResourceNames(@Nullable String type) {
|
||||
if (type == null) {
|
||||
return listAllResourceNames();
|
||||
} else {
|
||||
final var set = new HashSet<String>();
|
||||
switch (type) {
|
||||
case "drawable":
|
||||
for (var folder : this.drawables) {
|
||||
listAndAddTo(set, folder, type);
|
||||
}
|
||||
break;
|
||||
case "layout":
|
||||
listAndAddTo(set, this.layout, type);
|
||||
break;
|
||||
case "color":
|
||||
listAndAddTo(set, this.color, type);
|
||||
break;
|
||||
case "mipmap":
|
||||
for (var folder : this.mipmaps) {
|
||||
listAndAddTo(set, folder, type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
private void listAndAddTo(HashSet<String> set, @NonNull File dir, String type) {
|
||||
final var files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var file : files) {
|
||||
var name = file.getName();
|
||||
if (!name.contains(".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
name = name.substring(0, name.lastIndexOf('.'));
|
||||
name = String.format(Locale.ROOT, "@%s/%s", type, name);
|
||||
set.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Collection<String> listAllResourceNames() {
|
||||
final var set = new HashSet<>(listResourceNames("color"));
|
||||
set.addAll(listResourceNames("drawable"));
|
||||
set.addAll(listResourceNames("mipmap"));
|
||||
set.addAll(listResourceNames("layout"));
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInflatingFile(@NonNull File file) {
|
||||
if (file.getParentFile() == null) {
|
||||
throw new IllegalArgumentException("Invalid inflating file");
|
||||
}
|
||||
|
||||
setupDirectories(
|
||||
file // layout file
|
||||
.getParentFile() // layout dir
|
||||
.getParentFile()); // res dir
|
||||
}
|
||||
|
||||
private void setupDirectories(File resDir) {
|
||||
|
||||
if (resDir == null) {
|
||||
LOG.error("Null resource directory passed to resource finder. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.resDir = resDir;
|
||||
this.color = new File(resDir, "color");
|
||||
this.layout = new File(resDir, "layout");
|
||||
|
||||
final File[] drawables = resDir.listFiles(new NameStartsWith("drawable"));
|
||||
this.drawables = drawables == null ? new File[0] : drawables;
|
||||
|
||||
final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap"));
|
||||
this.mipmaps = mipmaps == null ? new File[0] : mipmaps;
|
||||
}
|
||||
|
||||
private File findFileWithName(File[] files, String name) {
|
||||
if (files == null || name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
var simpleName = file.getName();
|
||||
|
||||
if (simpleName.startsWith(name)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class NameStartsWith implements FileFilter {
|
||||
|
||||
private final String name;
|
||||
|
||||
public NameStartsWith(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(@NonNull File file) {
|
||||
return file.getName().startsWith(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.projects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.inflater.IResourceTable;
|
||||
import com.itsaky.inflater.values.ValuesTableFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ProjectResourceTable implements IResourceTable {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ProjectResourceFinder");
|
||||
private File resDir;
|
||||
private File color;
|
||||
private File layout;
|
||||
private File[] drawables;
|
||||
private File[] mipmaps;
|
||||
|
||||
@Override
|
||||
public File findDrawable(@NonNull String name) {
|
||||
for (File drawable : drawables) {
|
||||
final File file = findFileWithName(drawable.listFiles(), name);
|
||||
if (file != null) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File findLayout(@NonNull String name) {
|
||||
return findFileWithName(this.layout.listFiles(), name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findString(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findString(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@string/")) {
|
||||
var ref = value.substring("@string/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findBoolean(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findBoolean(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var val = resource.getValue();
|
||||
if (val.startsWith("@bool/")) {
|
||||
final var ref = val.substring("@bool/".length());
|
||||
if (ref.equals(name)) {
|
||||
// recursive reference
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findInteger(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findInteger(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var val = resource.getValue();
|
||||
if (val.startsWith("@integer/")) {
|
||||
final var ref = val.substring("@integer/".length());
|
||||
if (ref.equals(name)) {
|
||||
// recursive reference
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findColor(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findColor(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@color/")) {
|
||||
var ref = value.substring("@color/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] findArray(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
if (table == null) {
|
||||
return new String[] {name};
|
||||
}
|
||||
|
||||
final var resource = table.findArray(name);
|
||||
if (resource == null) {
|
||||
return new String[] {name};
|
||||
}
|
||||
|
||||
return resource.getValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findDimension(@NonNull String name) {
|
||||
final var table = ValuesTableFactory.getTable(resDir);
|
||||
|
||||
if (table == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var resource = table.findDimension(name);
|
||||
if (resource == null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final var value = resource.getValue();
|
||||
if (value.startsWith("@dimen/")) {
|
||||
var ref = value.substring("@dimen/".length());
|
||||
if (name.equals(ref)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Collection<String> listResourceNames(@Nullable String type) {
|
||||
if (type == null) {
|
||||
return listAllResourceNames();
|
||||
} else {
|
||||
final var set = new HashSet<String>();
|
||||
switch (type) {
|
||||
case "drawable":
|
||||
for (var folder : this.drawables) {
|
||||
listAndAddTo(set, folder, type);
|
||||
}
|
||||
break;
|
||||
case "layout":
|
||||
listAndAddTo(set, this.layout, type);
|
||||
break;
|
||||
case "color":
|
||||
listAndAddTo(set, this.color, type);
|
||||
break;
|
||||
case "mipmap":
|
||||
for (var folder : this.mipmaps) {
|
||||
listAndAddTo(set, folder, type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
private void listAndAddTo(HashSet<String> set, @NonNull File dir, String type) {
|
||||
final var files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var file : files) {
|
||||
var name = file.getName();
|
||||
if (!name.contains(".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
name = name.substring(0, name.lastIndexOf('.'));
|
||||
name = String.format(Locale.ROOT, "@%s/%s", type, name);
|
||||
set.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Collection<String> listAllResourceNames() {
|
||||
final var set = new HashSet<>(listResourceNames("color"));
|
||||
set.addAll(listResourceNames("drawable"));
|
||||
set.addAll(listResourceNames("mipmap"));
|
||||
set.addAll(listResourceNames("layout"));
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInflatingFile(@NonNull File file) {
|
||||
if (file.getParentFile() == null) {
|
||||
throw new IllegalArgumentException("Invalid inflating file");
|
||||
}
|
||||
|
||||
setupDirectories(
|
||||
file // layout file
|
||||
.getParentFile() // layout dir
|
||||
.getParentFile()); // res dir
|
||||
}
|
||||
|
||||
private void setupDirectories(File resDir) {
|
||||
|
||||
if (resDir == null) {
|
||||
LOG.error("Null resource directory passed to resource finder. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.resDir = resDir;
|
||||
this.color = new File(resDir, "color");
|
||||
this.layout = new File(resDir, "layout");
|
||||
|
||||
final File[] drawables = resDir.listFiles(new NameStartsWith("drawable"));
|
||||
this.drawables = drawables == null ? new File[0] : drawables;
|
||||
|
||||
final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap"));
|
||||
this.mipmaps = mipmaps == null ? new File[0] : mipmaps;
|
||||
}
|
||||
|
||||
private File findFileWithName(File[] files, String name) {
|
||||
if (files == null || name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
var simpleName = file.getName();
|
||||
|
||||
if (simpleName.startsWith(name)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class NameStartsWith implements FileFilter {
|
||||
|
||||
private final String name;
|
||||
|
||||
public NameStartsWith(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(@NonNull File file) {
|
||||
return file.getName().startsWith(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.services
|
||||
|
||||
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.TaskExecutionResult
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* A build service provides API to initialize project, execute builds, query a build, cancel running
|
||||
* builds, etc.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface BuildService {
|
||||
|
||||
/**
|
||||
* Initialize the project.
|
||||
*
|
||||
* @param rootDir The root directory of the project to initialize.
|
||||
* @return A [CompletableFuture] which returns an [InitializeResult] when the project
|
||||
* initialization process finishes.
|
||||
*/
|
||||
fun initializeProject(rootDir: String): CompletableFuture<InitializeResult>
|
||||
|
||||
/**
|
||||
* Execute the given tasks.
|
||||
*
|
||||
* @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.
|
||||
* @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.
|
||||
* @see BuildService.executeProjectTasks
|
||||
*/
|
||||
fun executeTasks(vararg tasks: String): CompletableFuture<TaskExecutionResult>
|
||||
|
||||
/**
|
||||
* 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 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 path. For example, if the project path is ':app' and the task is 'assembleDebug',
|
||||
* then, ':app:assembleDebug' task will be executed.
|
||||
* @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.
|
||||
*/
|
||||
fun executeProjectTasks(
|
||||
projectPath: String,
|
||||
vararg tasks: String
|
||||
): CompletableFuture<TaskExecutionResult>
|
||||
|
||||
/** Cancel any running build. */
|
||||
fun cancelCurrentBuild(): CompletableFuture<BuildCancellationRequestResult>
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.services
|
||||
|
||||
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.TaskExecutionResult
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* A build service provides API to initialize project, execute builds, query a build, cancel running
|
||||
* builds, etc.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface BuildService {
|
||||
|
||||
/**
|
||||
* Initialize the project.
|
||||
*
|
||||
* @param rootDir The root directory of the project to initialize.
|
||||
* @return A [CompletableFuture] which returns an [InitializeResult] when the project
|
||||
* initialization process finishes.
|
||||
*/
|
||||
fun initializeProject(rootDir: String): CompletableFuture<InitializeResult>
|
||||
|
||||
/**
|
||||
* Execute the given tasks.
|
||||
*
|
||||
* @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.
|
||||
* @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.
|
||||
* @see BuildService.executeProjectTasks
|
||||
*/
|
||||
fun executeTasks(vararg tasks: String): CompletableFuture<TaskExecutionResult>
|
||||
|
||||
/**
|
||||
* 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 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 path. For example, if the project path is ':app' and the task is 'assembleDebug',
|
||||
* then, ':app:assembleDebug' task will be executed.
|
||||
* @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.
|
||||
*/
|
||||
fun executeProjectTasks(
|
||||
projectPath: String,
|
||||
vararg tasks: String
|
||||
): CompletableFuture<TaskExecutionResult>
|
||||
|
||||
/** Cancel any running build. */
|
||||
fun cancelCurrentBuild(): CompletableFuture<BuildCancellationRequestResult>
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,88 +1,88 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.ui.edit;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.lsp.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles editing dimension attributes.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class DimensionSuggestionProvider extends BaseSuggestionProvider {
|
||||
|
||||
/** The dimension units that will be suggested. */
|
||||
private final List<String> dimensions = new ArrayList<>();
|
||||
|
||||
public DimensionSuggestionProvider(File layout) {
|
||||
super(layout);
|
||||
|
||||
this.dimensions.add("dp");
|
||||
this.dimensions.add("sp");
|
||||
this.dimensions.add("px");
|
||||
this.dimensions.add("pt");
|
||||
this.dimensions.add("in");
|
||||
this.dimensions.add("mm");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkFormat(int format) {
|
||||
return (format & Attr.DIMENSION) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<String> suggest(IAttribute attribute, @NonNull String prefix) {
|
||||
final var list = new ArrayList<String>();
|
||||
if (prefix.length() > 0 && Character.isDigit(prefix.charAt(0))) {
|
||||
// Fixed dimension values
|
||||
// e.g. 10dp
|
||||
|
||||
var val = extractDimensionVal(prefix);
|
||||
for (var unit : this.dimensions) {
|
||||
final var dimension = val + unit;
|
||||
if (StringUtils.matchesFuzzy(dimension, prefix, true)) {
|
||||
list.add(dimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private int extractDimensionVal(@NonNull String prefix) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
var i = 0;
|
||||
while (i < prefix.length()) {
|
||||
if (Character.isDigit(prefix.charAt(i))) {
|
||||
s.append(prefix.charAt(i));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return Integer.parseInt(s.toString());
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.ui.edit;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.itsaky.attrinfo.models.Attr;
|
||||
import com.itsaky.inflater.IAttribute;
|
||||
import com.itsaky.lsp.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles editing dimension attributes.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
public class DimensionSuggestionProvider extends BaseSuggestionProvider {
|
||||
|
||||
/** The dimension units that will be suggested. */
|
||||
private final List<String> dimensions = new ArrayList<>();
|
||||
|
||||
public DimensionSuggestionProvider(File layout) {
|
||||
super(layout);
|
||||
|
||||
this.dimensions.add("dp");
|
||||
this.dimensions.add("sp");
|
||||
this.dimensions.add("px");
|
||||
this.dimensions.add("pt");
|
||||
this.dimensions.add("in");
|
||||
this.dimensions.add("mm");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkFormat(int format) {
|
||||
return (format & Attr.DIMENSION) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<String> suggest(IAttribute attribute, @NonNull String prefix) {
|
||||
final var list = new ArrayList<String>();
|
||||
if (prefix.length() > 0 && Character.isDigit(prefix.charAt(0))) {
|
||||
// Fixed dimension values
|
||||
// e.g. 10dp
|
||||
|
||||
var val = extractDimensionVal(prefix);
|
||||
for (var unit : this.dimensions) {
|
||||
final var dimension = val + unit;
|
||||
if (StringUtils.matchesFuzzy(dimension, prefix, true)) {
|
||||
list.add(dimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private int extractDimensionVal(@NonNull String prefix) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
var i = 0;
|
||||
while (i < prefix.length()) {
|
||||
if (Character.isDigit(prefix.charAt(i))) {
|
||||
s.append(prefix.charAt(i));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return Integer.parseInt(s.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.actions.build.CancelBuildAction
|
||||
import com.itsaky.androidide.actions.build.ProjectSyncAction
|
||||
import com.itsaky.androidide.actions.build.QuickRunAction
|
||||
import com.itsaky.androidide.actions.build.RunActionMenu
|
||||
import com.itsaky.androidide.actions.editor.CopyAction
|
||||
import com.itsaky.androidide.actions.editor.CutAction
|
||||
import com.itsaky.androidide.actions.editor.ExpandSelectionAction
|
||||
import com.itsaky.androidide.actions.editor.PasteAction
|
||||
import com.itsaky.androidide.actions.editor.SelectAllAction
|
||||
import com.itsaky.androidide.actions.editor.find.GoToNextAction
|
||||
import com.itsaky.androidide.actions.editor.find.GoToPreviousAction
|
||||
import com.itsaky.androidide.actions.editor.find.ReplaceAction
|
||||
import com.itsaky.androidide.actions.etc.DaemonStatusAction
|
||||
import com.itsaky.androidide.actions.etc.FileTreeAction
|
||||
import com.itsaky.androidide.actions.etc.FindActionMenu
|
||||
import com.itsaky.androidide.actions.etc.PreviewLayoutAction
|
||||
import com.itsaky.androidide.actions.file.FormatCodeAction
|
||||
import com.itsaky.androidide.actions.file.SaveFileAction
|
||||
import com.itsaky.androidide.actions.text.RedoAction
|
||||
import com.itsaky.androidide.actions.text.UndoAction
|
||||
|
||||
/**
|
||||
* Takes care of registering actions to the actions registry for the editor activity.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class EditorActivityActions {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun register(context: Context) {
|
||||
val registry = ActionsRegistry.getInstance()
|
||||
|
||||
// Toolbar actions
|
||||
registry.registerAction(UndoAction(context))
|
||||
registry.registerAction(RedoAction(context))
|
||||
registry.registerAction(QuickRunAction(context))
|
||||
registry.registerAction(RunActionMenu(context))
|
||||
registry.registerAction(SaveFileAction(context))
|
||||
registry.registerAction(PreviewLayoutAction(context))
|
||||
registry.registerAction(FindActionMenu(context))
|
||||
registry.registerAction(FileTreeAction(context))
|
||||
registry.registerAction(DaemonStatusAction(context))
|
||||
registry.registerAction(CancelBuildAction(context))
|
||||
registry.registerAction(ProjectSyncAction(context))
|
||||
|
||||
// editor text actions
|
||||
registry.registerAction(ExpandSelectionAction(context))
|
||||
registry.registerAction(SelectAllAction(context))
|
||||
registry.registerAction(CutAction(context))
|
||||
registry.registerAction(CopyAction(context))
|
||||
registry.registerAction(PasteAction(context))
|
||||
registry.registerAction(FormatCodeAction(context))
|
||||
|
||||
// editor search actions
|
||||
registry.registerAction(GoToNextAction(context))
|
||||
registry.registerAction(GoToPreviousAction(context))
|
||||
registry.registerAction(ReplaceAction(context))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun clear() = ActionsRegistry.getInstance().clearActions(ActionItem.Location.EDITOR_TOOLBAR)
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.itsaky.androidide.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.actions.build.CancelBuildAction
|
||||
import com.itsaky.androidide.actions.build.ProjectSyncAction
|
||||
import com.itsaky.androidide.actions.build.QuickRunAction
|
||||
import com.itsaky.androidide.actions.build.RunActionMenu
|
||||
import com.itsaky.androidide.actions.editor.CopyAction
|
||||
import com.itsaky.androidide.actions.editor.CutAction
|
||||
import com.itsaky.androidide.actions.editor.ExpandSelectionAction
|
||||
import com.itsaky.androidide.actions.editor.PasteAction
|
||||
import com.itsaky.androidide.actions.editor.SelectAllAction
|
||||
import com.itsaky.androidide.actions.editor.find.GoToNextAction
|
||||
import com.itsaky.androidide.actions.editor.find.GoToPreviousAction
|
||||
import com.itsaky.androidide.actions.editor.find.ReplaceAction
|
||||
import com.itsaky.androidide.actions.etc.DaemonStatusAction
|
||||
import com.itsaky.androidide.actions.etc.FileTreeAction
|
||||
import com.itsaky.androidide.actions.etc.FindActionMenu
|
||||
import com.itsaky.androidide.actions.etc.PreviewLayoutAction
|
||||
import com.itsaky.androidide.actions.file.FormatCodeAction
|
||||
import com.itsaky.androidide.actions.file.SaveFileAction
|
||||
import com.itsaky.androidide.actions.text.RedoAction
|
||||
import com.itsaky.androidide.actions.text.UndoAction
|
||||
|
||||
/**
|
||||
* Takes care of registering actions to the actions registry for the editor activity.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class EditorActivityActions {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun register(context: Context) {
|
||||
val registry = ActionsRegistry.getInstance()
|
||||
|
||||
// Toolbar actions
|
||||
registry.registerAction(UndoAction(context))
|
||||
registry.registerAction(RedoAction(context))
|
||||
registry.registerAction(QuickRunAction(context))
|
||||
registry.registerAction(RunActionMenu(context))
|
||||
registry.registerAction(SaveFileAction(context))
|
||||
registry.registerAction(PreviewLayoutAction(context))
|
||||
registry.registerAction(FindActionMenu(context))
|
||||
registry.registerAction(FileTreeAction(context))
|
||||
registry.registerAction(DaemonStatusAction(context))
|
||||
registry.registerAction(CancelBuildAction(context))
|
||||
registry.registerAction(ProjectSyncAction(context))
|
||||
|
||||
// editor text actions
|
||||
registry.registerAction(ExpandSelectionAction(context))
|
||||
registry.registerAction(SelectAllAction(context))
|
||||
registry.registerAction(CutAction(context))
|
||||
registry.registerAction(CopyAction(context))
|
||||
registry.registerAction(PasteAction(context))
|
||||
registry.registerAction(FormatCodeAction(context))
|
||||
|
||||
// editor search actions
|
||||
registry.registerAction(GoToNextAction(context))
|
||||
registry.registerAction(GoToPreviousAction(context))
|
||||
registry.registerAction(ReplaceAction(context))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun clear() = ActionsRegistry.getInstance().clearActions(ActionItem.Location.EDITOR_TOOLBAR)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher;
|
||||
|
||||
public class JSONUtility {
|
||||
public static final Gson gson = new Gson();
|
||||
public static final Gson toolingGson;
|
||||
public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
static {
|
||||
final var builder = new GsonBuilder();
|
||||
ToolingApiLauncher.configureGson(builder);
|
||||
toolingGson = builder.create();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher;
|
||||
|
||||
public class JSONUtility {
|
||||
public static final Gson gson = new Gson();
|
||||
public static final Gson toolingGson;
|
||||
public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
static {
|
||||
final var builder = new GsonBuilder();
|
||||
ToolingApiLauncher.configureGson(builder);
|
||||
toolingGson = builder.create();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.utils
|
||||
|
||||
import java.io.File
|
||||
import java.io.StringReader
|
||||
import java.util.*
|
||||
|
||||
class ListingFileRedirect {
|
||||
|
||||
companion object {
|
||||
/** Redirect file will have this marker as the first line as comment. */
|
||||
private const val REDIRECT_MARKER = "#- File Locator -"
|
||||
|
||||
/** Property name in a [Properties] for the metadata file location. */
|
||||
private const val REDIRECT_PROPERTY_NAME = "listingFile"
|
||||
|
||||
@JvmStatic
|
||||
fun maybeExtractRedirectedFile(redirectFile: File): File? {
|
||||
val fileContent = redirectFile.readText()
|
||||
return if (fileContent.startsWith(REDIRECT_MARKER)) {
|
||||
val fileLocator = Properties().also { it.load(StringReader(fileContent)) }
|
||||
val file = File(fileLocator.getProperty(REDIRECT_PROPERTY_NAME))
|
||||
if (!file.isAbsolute) redirectFile.parentFile!!.resolve(file) else file
|
||||
} else null
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.utils
|
||||
|
||||
import java.io.File
|
||||
import java.io.StringReader
|
||||
import java.util.*
|
||||
|
||||
class ListingFileRedirect {
|
||||
|
||||
companion object {
|
||||
/** Redirect file will have this marker as the first line as comment. */
|
||||
private const val REDIRECT_MARKER = "#- File Locator -"
|
||||
|
||||
/** Property name in a [Properties] for the metadata file location. */
|
||||
private const val REDIRECT_PROPERTY_NAME = "listingFile"
|
||||
|
||||
@JvmStatic
|
||||
fun maybeExtractRedirectedFile(redirectFile: File): File? {
|
||||
val fileContent = redirectFile.readText()
|
||||
return if (fileContent.startsWith(REDIRECT_MARKER)) {
|
||||
val fileLocator = Properties().also { it.load(StringReader(fileContent)) }
|
||||
val file = File(fileLocator.getProperty(REDIRECT_PROPERTY_NAME))
|
||||
if (!file.isAbsolute) redirectFile.parentFile!!.resolve(file) else file
|
||||
} 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.
|
||||
*
|
||||
@@ -473,4 +456,4 @@ public class CodeEditorView extends FrameLayout {
|
||||
isModified = false;
|
||||
notifySaved();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,391 +1,391 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.views.editor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.view.menu.MenuBuilder
|
||||
import androidx.appcompat.view.menu.MenuItemImpl
|
||||
import androidx.appcompat.view.menu.SubMenuBuilder
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.transition.ChangeBounds
|
||||
import androidx.transition.TransitionManager
|
||||
import com.blankj.utilcode.util.SizeUtils
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.actions.ActionsRegistry.Companion.getInstance
|
||||
import com.itsaky.androidide.actions.editor.SelectAllAction
|
||||
import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.language.IDELanguage
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.lsp.models.DiagnosticItem
|
||||
import com.itsaky.lsp.models.Range
|
||||
import com.itsaky.lsp.util.DiagnosticUtil
|
||||
import com.itsaky.lsp.xml.XMLLanguageServer
|
||||
import io.github.rosemoe.sora.event.HandleStateChangeEvent
|
||||
import io.github.rosemoe.sora.event.ScrollEvent
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent
|
||||
import io.github.rosemoe.sora.event.SubscriptionReceipt
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import io.github.rosemoe.sora.widget.EditorTouchEventHandler
|
||||
import io.github.rosemoe.sora.widget.base.EditorPopupWindow
|
||||
import java.io.File
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* PopupMenu for showing editor's text and code actions.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
open class EditorActionsMenu(val editor: IDEEditor) :
|
||||
EditorPopupWindow(editor, FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED),
|
||||
ActionsRegistry.ActionExecListener,
|
||||
MenuBuilder.Callback {
|
||||
|
||||
companion object {
|
||||
const val DELAY: Long = 200
|
||||
}
|
||||
|
||||
private val touchHandler: EditorTouchEventHandler = editor.eventHandler
|
||||
private val receipts: MutableList<SubscriptionReceipt<*>> = mutableListOf()
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
private val list: ListView = ListView(editor.context)
|
||||
private var mLastScroll: Long = 0
|
||||
private var mLastPosition: Int = 0
|
||||
|
||||
private val menu: MenuBuilder = MenuBuilder(editor.context)
|
||||
protected open var location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
open fun init() {
|
||||
subscribe()
|
||||
applyBackground()
|
||||
|
||||
list.clipChildren = true
|
||||
list.clipToOutline = true
|
||||
list.isVerticalFadingEdgeEnabled = true
|
||||
list.isVerticalScrollBarEnabled = true
|
||||
list.setFadingEdgeLength(SizeUtils.dp2px(42f))
|
||||
list.divider = null
|
||||
list.layoutParams =
|
||||
ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
|
||||
popup.contentView = this.list
|
||||
popup.animationStyle = R.style.PopupAnimation
|
||||
|
||||
val menu = getMenu()
|
||||
if (menu is MenuBuilder) {
|
||||
menu.setCallback(this)
|
||||
}
|
||||
}
|
||||
|
||||
open fun subscribe() {
|
||||
receipts.add(
|
||||
editor.subscribeEvent(SelectionChangeEvent::class.java) { event, _ ->
|
||||
this.onSelectionChanged(event)
|
||||
})
|
||||
receipts.add(
|
||||
editor.subscribeEvent(ScrollEvent::class.java) { _, _ -> this.onScrollEvent() })
|
||||
receipts.add(
|
||||
editor.subscribeEvent(HandleStateChangeEvent::class.java) { event, _ ->
|
||||
this.onHandleStateChanged(event)
|
||||
})
|
||||
}
|
||||
|
||||
open fun unsubscribeEvents() {
|
||||
for (receipt in receipts) {
|
||||
receipt.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onSelectionChanged(event: SelectionChangeEvent) {
|
||||
if (touchHandler.hasAnyHeldHandle()) {
|
||||
return
|
||||
}
|
||||
if (event.isSelected) {
|
||||
if (!isShowing) {
|
||||
editor.post(::displayWindow)
|
||||
}
|
||||
mLastPosition = -1
|
||||
} else {
|
||||
var show = false
|
||||
if (event.cause == SelectionChangeEvent.CAUSE_TAP &&
|
||||
event.left.index == mLastPosition &&
|
||||
!isShowing &&
|
||||
!editor.text.isInBatchEdit) {
|
||||
editor.post(::displayWindow)
|
||||
show = true
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
mLastPosition =
|
||||
if (event.cause == SelectionChangeEvent.CAUSE_TAP && !show) {
|
||||
event.left.index
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onScrollEvent() {
|
||||
val last = mLastScroll
|
||||
mLastScroll = System.currentTimeMillis()
|
||||
if (mLastScroll - last < DELAY) {
|
||||
postDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onHandleStateChanged(
|
||||
event: HandleStateChangeEvent,
|
||||
) {
|
||||
if (event.isHeld) {
|
||||
postDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun applyBackground() {
|
||||
val drawable = GradientDrawable()
|
||||
drawable.shape = GradientDrawable.RECTANGLE
|
||||
drawable.cornerRadius = 16f
|
||||
drawable.color =
|
||||
ColorStateList.valueOf(
|
||||
ContextCompat.getColor(editor.context, R.color.content_background))
|
||||
drawable.setStroke(
|
||||
SizeUtils.dp2px(1f), ContextCompat.getColor(editor.context, R.color.primaryLightColor))
|
||||
list.background = drawable
|
||||
}
|
||||
|
||||
private fun postDisplay() {
|
||||
if (!isShowing) {
|
||||
return
|
||||
}
|
||||
dismiss()
|
||||
if (!editor.cursor.isSelected) {
|
||||
return
|
||||
}
|
||||
editor.postDelayed(
|
||||
object : Runnable {
|
||||
override fun run() {
|
||||
if (!touchHandler.hasAnyHeldHandle() &&
|
||||
System.currentTimeMillis() - mLastScroll > DELAY &&
|
||||
touchHandler.scroller.isFinished) {
|
||||
displayWindow()
|
||||
} else {
|
||||
editor.postDelayed(this, DELAY)
|
||||
}
|
||||
}
|
||||
},
|
||||
DELAY)
|
||||
}
|
||||
|
||||
private fun selectTop(rect: RectF): Int {
|
||||
val rowHeight = editor.rowHeight
|
||||
return if (rect.top - rowHeight * 3 / 2f > height) {
|
||||
(rect.top - rowHeight * 3 / 2 - height).toInt()
|
||||
} else {
|
||||
(rect.bottom + rowHeight / 2).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
open fun displayWindow() {
|
||||
var top: Int
|
||||
val cursor = editor.cursor
|
||||
top =
|
||||
if (cursor.isSelected) {
|
||||
val leftRect = editor.leftHandleDescriptor.position
|
||||
val rightRect = editor.rightHandleDescriptor.position
|
||||
val top1 = selectTop(leftRect)
|
||||
val top2 = selectTop(rightRect)
|
||||
min(top1, top2)
|
||||
} else {
|
||||
selectTop(editor.insertHandleDescriptor.position)
|
||||
}
|
||||
top = max(0, min(top, editor.height - height - 5))
|
||||
val handleLeftX = editor.getOffset(editor.cursor.leftLine, editor.cursor.leftColumn)
|
||||
val handleRightX = editor.getOffset(editor.cursor.rightLine, editor.cursor.rightColumn)
|
||||
val panelX = ((handleLeftX + handleRightX) / 2f).toInt()
|
||||
setLocationAbsolutely(panelX, top)
|
||||
show()
|
||||
}
|
||||
|
||||
protected open fun fillMenu() {
|
||||
getMenu().clear()
|
||||
|
||||
val data = onCreateActionData()
|
||||
|
||||
val registry = getInstance()
|
||||
registry.registerActionExecListener(this)
|
||||
onFillMenu(registry, data)
|
||||
|
||||
this.list.adapter = ActionsListAdapter(getMenu(), editor.context)
|
||||
}
|
||||
|
||||
protected open fun onFillMenu(registry: ActionsRegistry, data: ActionData) {
|
||||
registry.fillMenu(data, onGetActionLocation(), getMenu())
|
||||
}
|
||||
|
||||
protected open fun onGetActionLocation() = location
|
||||
|
||||
protected open fun onCreateActionData(): ActionData {
|
||||
val data = ActionData()
|
||||
data.put(Context::class.java, editor.context)
|
||||
data.put(IDEEditor::class.java, this.editor)
|
||||
data.put(
|
||||
CodeEditor::class.java,
|
||||
editor) // For LSP actions, as they cannot access IDEEditor class
|
||||
data.put(File::class.java, editor.file)
|
||||
data.put(DiagnosticItem::class.java, getDiagnosticAtCursor())
|
||||
data.put(Range::class.java, editor.cursorRange)
|
||||
data.put(
|
||||
JavaLanguageServer::class.java,
|
||||
StudioApp.getInstance().javaLanguageServer as JavaLanguageServer)
|
||||
data.put(
|
||||
XMLLanguageServer::class.java,
|
||||
StudioApp.getInstance().xmlLanguageServer as XMLLanguageServer)
|
||||
return data
|
||||
}
|
||||
|
||||
protected open fun getMenu(): Menu = menu
|
||||
|
||||
private fun getDiagnosticAtCursor(): DiagnosticItem? {
|
||||
val language = editor.editorLanguage as? IDELanguage ?: return null
|
||||
val diagnostics = language.diagnostics
|
||||
return DiagnosticUtil.binarySearchDiagnostic(diagnostics, editor.cursorRange.start)
|
||||
}
|
||||
|
||||
override fun onExec(action: ActionItem, result: Any) {
|
||||
if (action !is SelectAllAction) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
if (list.parent != null) {
|
||||
(list.parent as ViewGroup).removeView(list)
|
||||
}
|
||||
|
||||
fillMenu()
|
||||
|
||||
val dp8 = SizeUtils.dp2px(8f)
|
||||
val dp16 = dp8 * 2
|
||||
this.list.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(editor.width - dp16 * 2, View.MeasureSpec.AT_MOST),
|
||||
View.MeasureSpec.makeMeasureSpec(
|
||||
(260 * editor.dpUnit).toInt() - dp16 * 2, View.MeasureSpec.AT_MOST))
|
||||
setSize(findWidestItem(), this.list.measuredHeight)
|
||||
super.show()
|
||||
}
|
||||
|
||||
private fun findWidestItem(): Int {
|
||||
var widest = 0
|
||||
val text =
|
||||
LayoutInflater.from(editor.context).inflate(R.layout.layout_popup_menu_item, null) as
|
||||
MaterialTextView
|
||||
val dp30 = SizeUtils.dp2px(30f)
|
||||
val paddingHorizontal = text.paddingStart + text.paddingEnd
|
||||
val drawablePadding = text.compoundDrawablePadding
|
||||
val extraWidth = dp30 * 2 // 30dp for start and end drawables both
|
||||
|
||||
for (i in 0 until getMenu().size()) {
|
||||
val item = getMenu().getItem(i)
|
||||
val title = item.title.toString()
|
||||
var width = paddingHorizontal + (drawablePadding * 2)
|
||||
width += text.paint.measureText(title).toInt()
|
||||
width += extraWidth
|
||||
|
||||
if (width > widest) {
|
||||
widest = width
|
||||
}
|
||||
}
|
||||
|
||||
return widest
|
||||
}
|
||||
|
||||
private class ActionsListAdapter(val menu: Menu, val context: Context) : BaseAdapter() {
|
||||
|
||||
override fun getCount(): Int = menu.size()
|
||||
|
||||
override fun getItem(position: Int): MenuItem = menu.getItem(position)
|
||||
|
||||
override fun getItemId(position: Int): Long = position.toLong()
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
val view: MaterialTextView =
|
||||
if (convertView == null || convertView !is MaterialButton) {
|
||||
(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
|
||||
.inflate(R.layout.layout_popup_menu_item, parent, false) as
|
||||
MaterialTextView
|
||||
} else {
|
||||
convertView as MaterialTextView
|
||||
}
|
||||
|
||||
val item = getItem(position)
|
||||
view.text = item.title
|
||||
|
||||
val start = item.icon ?: null
|
||||
var end: Drawable? = null
|
||||
if (item.hasSubMenu()) {
|
||||
end = ContextCompat.getDrawable(context, R.drawable.ic_submenu_arrow)
|
||||
}
|
||||
|
||||
view.setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null)
|
||||
view.setOnClickListener { (item as MenuItemImpl).invoke() }
|
||||
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menu: MenuBuilder, item: MenuItem): Boolean {
|
||||
// Click event of MenuItems without SubMenu is consumed by the ActionsRegistry
|
||||
// So we only need to handle click event of SubMenus
|
||||
if (!item.hasSubMenu()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (item.hasSubMenu() && item.subMenu is SubMenuBuilder) {
|
||||
(item.subMenu as SubMenuBuilder).setCallback(this)
|
||||
}
|
||||
|
||||
this.editor.post {
|
||||
TransitionManager.beginDelayedTransition(this.list, ChangeBounds())
|
||||
this.list.adapter = ActionsListAdapter(item.subMenu, editor.context)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuModeChange(menu: MenuBuilder) {}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.views.editor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.view.menu.MenuBuilder
|
||||
import androidx.appcompat.view.menu.MenuItemImpl
|
||||
import androidx.appcompat.view.menu.SubMenuBuilder
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.transition.ChangeBounds
|
||||
import androidx.transition.TransitionManager
|
||||
import com.blankj.utilcode.util.SizeUtils
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import com.itsaky.androidide.actions.ActionsRegistry.Companion.getInstance
|
||||
import com.itsaky.androidide.actions.editor.SelectAllAction
|
||||
import com.itsaky.androidide.app.StudioApp
|
||||
import com.itsaky.androidide.language.IDELanguage
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.lsp.java.JavaLanguageServer
|
||||
import com.itsaky.lsp.models.DiagnosticItem
|
||||
import com.itsaky.lsp.models.Range
|
||||
import com.itsaky.lsp.util.DiagnosticUtil
|
||||
import com.itsaky.lsp.xml.XMLLanguageServer
|
||||
import io.github.rosemoe.sora.event.HandleStateChangeEvent
|
||||
import io.github.rosemoe.sora.event.ScrollEvent
|
||||
import io.github.rosemoe.sora.event.SelectionChangeEvent
|
||||
import io.github.rosemoe.sora.event.SubscriptionReceipt
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import io.github.rosemoe.sora.widget.EditorTouchEventHandler
|
||||
import io.github.rosemoe.sora.widget.base.EditorPopupWindow
|
||||
import java.io.File
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* PopupMenu for showing editor's text and code actions.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
open class EditorActionsMenu(val editor: IDEEditor) :
|
||||
EditorPopupWindow(editor, FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED),
|
||||
ActionsRegistry.ActionExecListener,
|
||||
MenuBuilder.Callback {
|
||||
|
||||
companion object {
|
||||
const val DELAY: Long = 200
|
||||
}
|
||||
|
||||
private val touchHandler: EditorTouchEventHandler = editor.eventHandler
|
||||
private val receipts: MutableList<SubscriptionReceipt<*>> = mutableListOf()
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
private val list: ListView = ListView(editor.context)
|
||||
private var mLastScroll: Long = 0
|
||||
private var mLastPosition: Int = 0
|
||||
|
||||
private val menu: MenuBuilder = MenuBuilder(editor.context)
|
||||
protected open var location = ActionItem.Location.EDITOR_TEXT_ACTIONS
|
||||
|
||||
open fun init() {
|
||||
subscribe()
|
||||
applyBackground()
|
||||
|
||||
list.clipChildren = true
|
||||
list.clipToOutline = true
|
||||
list.isVerticalFadingEdgeEnabled = true
|
||||
list.isVerticalScrollBarEnabled = true
|
||||
list.setFadingEdgeLength(SizeUtils.dp2px(42f))
|
||||
list.divider = null
|
||||
list.layoutParams =
|
||||
ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
|
||||
popup.contentView = this.list
|
||||
popup.animationStyle = R.style.PopupAnimation
|
||||
|
||||
val menu = getMenu()
|
||||
if (menu is MenuBuilder) {
|
||||
menu.setCallback(this)
|
||||
}
|
||||
}
|
||||
|
||||
open fun subscribe() {
|
||||
receipts.add(
|
||||
editor.subscribeEvent(SelectionChangeEvent::class.java) { event, _ ->
|
||||
this.onSelectionChanged(event)
|
||||
})
|
||||
receipts.add(
|
||||
editor.subscribeEvent(ScrollEvent::class.java) { _, _ -> this.onScrollEvent() })
|
||||
receipts.add(
|
||||
editor.subscribeEvent(HandleStateChangeEvent::class.java) { event, _ ->
|
||||
this.onHandleStateChanged(event)
|
||||
})
|
||||
}
|
||||
|
||||
open fun unsubscribeEvents() {
|
||||
for (receipt in receipts) {
|
||||
receipt.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onSelectionChanged(event: SelectionChangeEvent) {
|
||||
if (touchHandler.hasAnyHeldHandle()) {
|
||||
return
|
||||
}
|
||||
if (event.isSelected) {
|
||||
if (!isShowing) {
|
||||
editor.post(::displayWindow)
|
||||
}
|
||||
mLastPosition = -1
|
||||
} else {
|
||||
var show = false
|
||||
if (event.cause == SelectionChangeEvent.CAUSE_TAP &&
|
||||
event.left.index == mLastPosition &&
|
||||
!isShowing &&
|
||||
!editor.text.isInBatchEdit) {
|
||||
editor.post(::displayWindow)
|
||||
show = true
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
mLastPosition =
|
||||
if (event.cause == SelectionChangeEvent.CAUSE_TAP && !show) {
|
||||
event.left.index
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onScrollEvent() {
|
||||
val last = mLastScroll
|
||||
mLastScroll = System.currentTimeMillis()
|
||||
if (mLastScroll - last < DELAY) {
|
||||
postDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onHandleStateChanged(
|
||||
event: HandleStateChangeEvent,
|
||||
) {
|
||||
if (event.isHeld) {
|
||||
postDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun applyBackground() {
|
||||
val drawable = GradientDrawable()
|
||||
drawable.shape = GradientDrawable.RECTANGLE
|
||||
drawable.cornerRadius = 16f
|
||||
drawable.color =
|
||||
ColorStateList.valueOf(
|
||||
ContextCompat.getColor(editor.context, R.color.content_background))
|
||||
drawable.setStroke(
|
||||
SizeUtils.dp2px(1f), ContextCompat.getColor(editor.context, R.color.primaryLightColor))
|
||||
list.background = drawable
|
||||
}
|
||||
|
||||
private fun postDisplay() {
|
||||
if (!isShowing) {
|
||||
return
|
||||
}
|
||||
dismiss()
|
||||
if (!editor.cursor.isSelected) {
|
||||
return
|
||||
}
|
||||
editor.postDelayed(
|
||||
object : Runnable {
|
||||
override fun run() {
|
||||
if (!touchHandler.hasAnyHeldHandle() &&
|
||||
System.currentTimeMillis() - mLastScroll > DELAY &&
|
||||
touchHandler.scroller.isFinished) {
|
||||
displayWindow()
|
||||
} else {
|
||||
editor.postDelayed(this, DELAY)
|
||||
}
|
||||
}
|
||||
},
|
||||
DELAY)
|
||||
}
|
||||
|
||||
private fun selectTop(rect: RectF): Int {
|
||||
val rowHeight = editor.rowHeight
|
||||
return if (rect.top - rowHeight * 3 / 2f > height) {
|
||||
(rect.top - rowHeight * 3 / 2 - height).toInt()
|
||||
} else {
|
||||
(rect.bottom + rowHeight / 2).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
open fun displayWindow() {
|
||||
var top: Int
|
||||
val cursor = editor.cursor
|
||||
top =
|
||||
if (cursor.isSelected) {
|
||||
val leftRect = editor.leftHandleDescriptor.position
|
||||
val rightRect = editor.rightHandleDescriptor.position
|
||||
val top1 = selectTop(leftRect)
|
||||
val top2 = selectTop(rightRect)
|
||||
min(top1, top2)
|
||||
} else {
|
||||
selectTop(editor.insertHandleDescriptor.position)
|
||||
}
|
||||
top = max(0, min(top, editor.height - height - 5))
|
||||
val handleLeftX = editor.getOffset(editor.cursor.leftLine, editor.cursor.leftColumn)
|
||||
val handleRightX = editor.getOffset(editor.cursor.rightLine, editor.cursor.rightColumn)
|
||||
val panelX = ((handleLeftX + handleRightX) / 2f).toInt()
|
||||
setLocationAbsolutely(panelX, top)
|
||||
show()
|
||||
}
|
||||
|
||||
protected open fun fillMenu() {
|
||||
getMenu().clear()
|
||||
|
||||
val data = onCreateActionData()
|
||||
|
||||
val registry = getInstance()
|
||||
registry.registerActionExecListener(this)
|
||||
onFillMenu(registry, data)
|
||||
|
||||
this.list.adapter = ActionsListAdapter(getMenu(), editor.context)
|
||||
}
|
||||
|
||||
protected open fun onFillMenu(registry: ActionsRegistry, data: ActionData) {
|
||||
registry.fillMenu(data, onGetActionLocation(), getMenu())
|
||||
}
|
||||
|
||||
protected open fun onGetActionLocation() = location
|
||||
|
||||
protected open fun onCreateActionData(): ActionData {
|
||||
val data = ActionData()
|
||||
data.put(Context::class.java, editor.context)
|
||||
data.put(IDEEditor::class.java, this.editor)
|
||||
data.put(
|
||||
CodeEditor::class.java,
|
||||
editor) // For LSP actions, as they cannot access IDEEditor class
|
||||
data.put(File::class.java, editor.file)
|
||||
data.put(DiagnosticItem::class.java, getDiagnosticAtCursor())
|
||||
data.put(Range::class.java, editor.cursorRange)
|
||||
data.put(
|
||||
JavaLanguageServer::class.java,
|
||||
StudioApp.getInstance().javaLanguageServer as JavaLanguageServer)
|
||||
data.put(
|
||||
XMLLanguageServer::class.java,
|
||||
StudioApp.getInstance().xmlLanguageServer as XMLLanguageServer)
|
||||
return data
|
||||
}
|
||||
|
||||
protected open fun getMenu(): Menu = menu
|
||||
|
||||
private fun getDiagnosticAtCursor(): DiagnosticItem? {
|
||||
val language = editor.editorLanguage as? IDELanguage ?: return null
|
||||
val diagnostics = language.diagnostics
|
||||
return DiagnosticUtil.binarySearchDiagnostic(diagnostics, editor.cursorRange.start)
|
||||
}
|
||||
|
||||
override fun onExec(action: ActionItem, result: Any) {
|
||||
if (action !is SelectAllAction) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
if (list.parent != null) {
|
||||
(list.parent as ViewGroup).removeView(list)
|
||||
}
|
||||
|
||||
fillMenu()
|
||||
|
||||
val dp8 = SizeUtils.dp2px(8f)
|
||||
val dp16 = dp8 * 2
|
||||
this.list.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(editor.width - dp16 * 2, View.MeasureSpec.AT_MOST),
|
||||
View.MeasureSpec.makeMeasureSpec(
|
||||
(260 * editor.dpUnit).toInt() - dp16 * 2, View.MeasureSpec.AT_MOST))
|
||||
setSize(findWidestItem(), this.list.measuredHeight)
|
||||
super.show()
|
||||
}
|
||||
|
||||
private fun findWidestItem(): Int {
|
||||
var widest = 0
|
||||
val text =
|
||||
LayoutInflater.from(editor.context).inflate(R.layout.layout_popup_menu_item, null) as
|
||||
MaterialTextView
|
||||
val dp30 = SizeUtils.dp2px(30f)
|
||||
val paddingHorizontal = text.paddingStart + text.paddingEnd
|
||||
val drawablePadding = text.compoundDrawablePadding
|
||||
val extraWidth = dp30 * 2 // 30dp for start and end drawables both
|
||||
|
||||
for (i in 0 until getMenu().size()) {
|
||||
val item = getMenu().getItem(i)
|
||||
val title = item.title.toString()
|
||||
var width = paddingHorizontal + (drawablePadding * 2)
|
||||
width += text.paint.measureText(title).toInt()
|
||||
width += extraWidth
|
||||
|
||||
if (width > widest) {
|
||||
widest = width
|
||||
}
|
||||
}
|
||||
|
||||
return widest
|
||||
}
|
||||
|
||||
private class ActionsListAdapter(val menu: Menu, val context: Context) : BaseAdapter() {
|
||||
|
||||
override fun getCount(): Int = menu.size()
|
||||
|
||||
override fun getItem(position: Int): MenuItem = menu.getItem(position)
|
||||
|
||||
override fun getItemId(position: Int): Long = position.toLong()
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
val view: MaterialTextView =
|
||||
if (convertView == null || convertView !is MaterialButton) {
|
||||
(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
|
||||
.inflate(R.layout.layout_popup_menu_item, parent, false) as
|
||||
MaterialTextView
|
||||
} else {
|
||||
convertView as MaterialTextView
|
||||
}
|
||||
|
||||
val item = getItem(position)
|
||||
view.text = item.title
|
||||
|
||||
val start = item.icon ?: null
|
||||
var end: Drawable? = null
|
||||
if (item.hasSubMenu()) {
|
||||
end = ContextCompat.getDrawable(context, R.drawable.ic_submenu_arrow)
|
||||
}
|
||||
|
||||
view.setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null)
|
||||
view.setOnClickListener { (item as MenuItemImpl).invoke() }
|
||||
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menu: MenuBuilder, item: MenuItem): Boolean {
|
||||
// Click event of MenuItems without SubMenu is consumed by the ActionsRegistry
|
||||
// So we only need to handle click event of SubMenus
|
||||
if (!item.hasSubMenu()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (item.hasSubMenu() && item.subMenu is SubMenuBuilder) {
|
||||
(item.subMenu as SubMenuBuilder).setCallback(this)
|
||||
}
|
||||
|
||||
this.editor.post {
|
||||
TransitionManager.beginDelayedTransition(this.list, ChangeBounds())
|
||||
this.list.adapter = ActionsListAdapter(item.subMenu, editor.context)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.views.editor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.view.menu.MenuBuilder
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import io.github.rosemoe.sora.R
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import io.github.rosemoe.sora.widget.EditorSearcher.SearchOptions
|
||||
|
||||
/**
|
||||
* Action mode for find and replace functions in editor.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class SearchActionMode(val editor: IDEEditor) : ActionMode.Callback {
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
|
||||
if (mode == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (menu is MenuBuilder) {
|
||||
menu.setOptionalIconsVisible(true)
|
||||
}
|
||||
|
||||
val search = SearchView(editor.context)
|
||||
search.setOnQueryTextListener(OnQueryListener(editor))
|
||||
search.isIconified = false
|
||||
search.queryHint = editor.context.getString(R.string.text_to_search)
|
||||
search.performClick()
|
||||
search.setIconifiedByDefault(false)
|
||||
mode.customView = search
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
if (mode == null || menu == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
menu.clear()
|
||||
val registry = ActionsRegistry.getInstance()
|
||||
registry.fillMenu(createData(mode), ActionItem.Location.EDITOR_SEARCH_ACTION_MODE, menu)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
editor.searcher.stopSearch()
|
||||
}
|
||||
|
||||
private fun createData(mode: ActionMode): ActionData {
|
||||
return ActionData().apply {
|
||||
put(Context::class.java, editor.context)
|
||||
put(IDEEditor::class.java, editor)
|
||||
put(CodeEditor::class.java, editor)
|
||||
put(ActionMode::class.java, mode)
|
||||
}
|
||||
}
|
||||
|
||||
private class OnQueryListener(val editor: IDEEditor) : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
editor.searcher.gotoNext()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
if (TextUtils.isEmpty(newText)) {
|
||||
editor.searcher.stopSearch()
|
||||
return false
|
||||
}
|
||||
|
||||
editor.searcher.search(newText!!, SearchOptions(false, false))
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.views.editor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.view.menu.MenuBuilder
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.ActionItem
|
||||
import com.itsaky.androidide.actions.ActionsRegistry
|
||||
import io.github.rosemoe.sora.R
|
||||
import io.github.rosemoe.sora.widget.CodeEditor
|
||||
import io.github.rosemoe.sora.widget.EditorSearcher.SearchOptions
|
||||
|
||||
/**
|
||||
* Action mode for find and replace functions in editor.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class SearchActionMode(val editor: IDEEditor) : ActionMode.Callback {
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
|
||||
if (mode == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (menu is MenuBuilder) {
|
||||
menu.setOptionalIconsVisible(true)
|
||||
}
|
||||
|
||||
val search = SearchView(editor.context)
|
||||
search.setOnQueryTextListener(OnQueryListener(editor))
|
||||
search.isIconified = false
|
||||
search.queryHint = editor.context.getString(R.string.text_to_search)
|
||||
search.performClick()
|
||||
search.setIconifiedByDefault(false)
|
||||
mode.customView = search
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
if (mode == null || menu == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
menu.clear()
|
||||
val registry = ActionsRegistry.getInstance()
|
||||
registry.fillMenu(createData(mode), ActionItem.Location.EDITOR_SEARCH_ACTION_MODE, menu)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
editor.searcher.stopSearch()
|
||||
}
|
||||
|
||||
private fun createData(mode: ActionMode): ActionData {
|
||||
return ActionData().apply {
|
||||
put(Context::class.java, editor.context)
|
||||
put(IDEEditor::class.java, editor)
|
||||
put(CodeEditor::class.java, editor)
|
||||
put(ActionMode::class.java, mode)
|
||||
}
|
||||
}
|
||||
|
||||
private class OnQueryListener(val editor: IDEEditor) : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
editor.searcher.gotoNext()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
if (TextUtils.isEmpty(newText)) {
|
||||
editor.searcher.stopSearch()
|
||||
return false
|
||||
}
|
||||
|
||||
editor.searcher.search(newText!!, SearchOptions(false, false))
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,117 +1,117 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package io.github.rosemoe.sora.widget
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.widget.Toast
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.views.editor.IDEEditor
|
||||
import io.github.rosemoe.sora.text.TextUtils
|
||||
import io.github.rosemoe.sora.util.IntPair
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class IDEEditorSearcher(editor: IDEEditor) : EditorSearcher(editor) {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
protected fun getEditor(): CodeEditor {
|
||||
try {
|
||||
val field = EditorSearcher::class.java.getDeclaredField("mEditor")
|
||||
field.isAccessible = true
|
||||
return field.get(this) as CodeEditor
|
||||
} catch (error: Throwable) {
|
||||
throw RuntimeException("Unable get instance of editor", error)
|
||||
}
|
||||
}
|
||||
|
||||
fun replaceAllAsync(replacement: String): CompletableFuture<Void> {
|
||||
val dialog =
|
||||
ProgressDialog.show(
|
||||
getEditor().context,
|
||||
getEditor().context.getString(string.replaceAll),
|
||||
getEditor().context.getString(string.msg_replacing),
|
||||
true,
|
||||
false)
|
||||
return CompletableFuture.runAsync { replaceAll(replacement) }.thenAccept {
|
||||
getEditor().post { dialog.dismiss() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceAll(replacement: String) {
|
||||
checkState()
|
||||
if (!isResultValid) {
|
||||
Toast.makeText(getEditor().context, "Editor is still preparing", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val res = mLastResults
|
||||
try {
|
||||
val sb = getEditor().text.toStringBuilder()
|
||||
val newLength = replacement.length
|
||||
if (mOptions.useRegex) {
|
||||
var delta = 0
|
||||
for (i in 0 until res.size()) {
|
||||
val region = res[i]
|
||||
val start = IntPair.getFirst(region)
|
||||
val end = IntPair.getSecond(region)
|
||||
val oldLength = end - start
|
||||
sb.replace(start + delta, end + delta, replacement)
|
||||
delta += newLength - oldLength
|
||||
}
|
||||
} else {
|
||||
var fromIndex = 0
|
||||
var foundIndex: Int
|
||||
while (TextUtils.indexOf(sb, mPattern, mOptions.ignoreCase, fromIndex).also {
|
||||
foundIndex = it
|
||||
} != -1) {
|
||||
sb.replace(foundIndex, foundIndex + mPattern.length, replacement)
|
||||
fromIndex = foundIndex + newLength
|
||||
}
|
||||
}
|
||||
getEditor().post {
|
||||
val pos = getEditor().cursor.left()
|
||||
// stopSearch();
|
||||
getEditor()
|
||||
.text
|
||||
.replace(
|
||||
0,
|
||||
0,
|
||||
getEditor().lineCount - 1,
|
||||
getEditor().text.getColumnCount(getEditor().lineCount - 1),
|
||||
sb)
|
||||
getEditor().setSelectionAround(pos.line, pos.column)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to replace", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkState() {
|
||||
check(hasQuery()) { "pattern not set" }
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package io.github.rosemoe.sora.widget
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.widget.Toast
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.views.editor.IDEEditor
|
||||
import io.github.rosemoe.sora.text.TextUtils
|
||||
import io.github.rosemoe.sora.util.IntPair
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class IDEEditorSearcher(editor: IDEEditor) : EditorSearcher(editor) {
|
||||
|
||||
private val log = ILogger.newInstance(javaClass.simpleName)
|
||||
|
||||
protected fun getEditor(): CodeEditor {
|
||||
try {
|
||||
val field = EditorSearcher::class.java.getDeclaredField("mEditor")
|
||||
field.isAccessible = true
|
||||
return field.get(this) as CodeEditor
|
||||
} catch (error: Throwable) {
|
||||
throw RuntimeException("Unable get instance of editor", error)
|
||||
}
|
||||
}
|
||||
|
||||
fun replaceAllAsync(replacement: String): CompletableFuture<Void> {
|
||||
val dialog =
|
||||
ProgressDialog.show(
|
||||
getEditor().context,
|
||||
getEditor().context.getString(string.replaceAll),
|
||||
getEditor().context.getString(string.msg_replacing),
|
||||
true,
|
||||
false)
|
||||
return CompletableFuture.runAsync { replaceAll(replacement) }.thenAccept {
|
||||
getEditor().post { dialog.dismiss() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceAll(replacement: String) {
|
||||
checkState()
|
||||
if (!isResultValid) {
|
||||
Toast.makeText(getEditor().context, "Editor is still preparing", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val res = mLastResults
|
||||
try {
|
||||
val sb = getEditor().text.toStringBuilder()
|
||||
val newLength = replacement.length
|
||||
if (mOptions.useRegex) {
|
||||
var delta = 0
|
||||
for (i in 0 until res.size()) {
|
||||
val region = res[i]
|
||||
val start = IntPair.getFirst(region)
|
||||
val end = IntPair.getSecond(region)
|
||||
val oldLength = end - start
|
||||
sb.replace(start + delta, end + delta, replacement)
|
||||
delta += newLength - oldLength
|
||||
}
|
||||
} else {
|
||||
var fromIndex = 0
|
||||
var foundIndex: Int
|
||||
while (TextUtils.indexOf(sb, mPattern, mOptions.ignoreCase, fromIndex).also {
|
||||
foundIndex = it
|
||||
} != -1) {
|
||||
sb.replace(foundIndex, foundIndex + mPattern.length, replacement)
|
||||
fromIndex = foundIndex + newLength
|
||||
}
|
||||
}
|
||||
getEditor().post {
|
||||
val pos = getEditor().cursor.left()
|
||||
// stopSearch();
|
||||
getEditor()
|
||||
.text
|
||||
.replace(
|
||||
0,
|
||||
0,
|
||||
getEditor().lineCount - 1,
|
||||
getEditor().text.getColumnCount(getEditor().lineCount - 1),
|
||||
sb)
|
||||
getEditor().setSelectionAround(pos.line, pos.column)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to replace", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkState() {
|
||||
check(hasQuery()) { "pattern not set" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
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"/>
|
||||
</vector>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
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"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!-- drawable/form_textbox_password.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="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" />
|
||||
<!-- drawable/form_textbox_password.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="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" />
|
||||
</vector>
|
||||
@@ -1,8 +1,8 @@
|
||||
<!-- drawable/eyedropper.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="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" />
|
||||
<!-- drawable/eyedropper.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="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" />
|
||||
</vector>
|
||||
@@ -1,10 +1,10 @@
|
||||
<!-- drawable/auto_fix.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
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" />
|
||||
<!-- drawable/auto_fix.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
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" />
|
||||
</vector>
|
||||
@@ -1,10 +1,10 @@
|
||||
<!-- drawable/menu_right.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M10,17L15,12L10,7V17Z" />
|
||||
<!-- drawable/menu_right.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M10,17L15,12L10,7V17Z" />
|
||||
</vector>
|
||||
@@ -1,10 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
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"/>
|
||||
</vector>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
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"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:padding="16dp">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/choice_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/bool_true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:padding="2dp"
|
||||
android:text="true"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/bool_false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:padding="2dp"
|
||||
android:text="false"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@color/primaryLightColor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/choice_group" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/boolean_res_input"
|
||||
style="@style/AppTheme.TextInputLayout.Outlined"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/hint_boolean_res"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider1">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@"
|
||||
android:padding="16dp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:padding="16dp">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/choice_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/bool_true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:padding="2dp"
|
||||
android:text="true"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/bool_false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:padding="2dp"
|
||||
android:text="false"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@color/primaryLightColor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/choice_group" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/boolean_res_input"
|
||||
style="@style/AppTheme.TextInputLayout.Outlined"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/hint_boolean_res"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider1">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@"
|
||||
android:padding="16dp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,29 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textSize="11sp"
|
||||
tools:text="Implement abstract methods">
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textSize="11sp"
|
||||
tools:text="Implement abstract methods">
|
||||
|
||||
</TextView>
|
||||
@@ -1,78 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:padding="16dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pick_color"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_color_picker"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
app:icon="@drawable/ic_pick_color"
|
||||
app:iconTint="@color/primaryIconColor"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:rippleColor="@color/secondaryColor" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/color_preview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/pick_color"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:strokeColor="@color/primaryDarkColor"
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@color/primaryLightColor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pick_color" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/color_res_input"
|
||||
style="@style/AppTheme.TextInputLayout.Outlined"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/hint_color_resource"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider1">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@"
|
||||
android:padding="16dp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:padding="16dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pick_color"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title_color_picker"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
app:icon="@drawable/ic_pick_color"
|
||||
app:iconTint="@color/primaryIconColor"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:rippleColor="@color/secondaryColor" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/color_preview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/pick_color"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:strokeColor="@color/primaryDarkColor"
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_margin="16dp"
|
||||
android:background="@color/primaryLightColor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pick_color" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/color_res_input"
|
||||
style="@style/AppTheme.TextInputLayout.Outlined"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/hint_color_resource"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider1">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:digits="abcdefghijklmnopqrstwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_/@"
|
||||
android:padding="16dp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user