From 1a2920cb4654149923d7399aadd32181972ed0ac Mon Sep 17 00:00:00 2001 From: github-actions <> Date: Fri, 4 Mar 2022 08:06:27 +0000 Subject: [PATCH] Format code --- .../com/itsaky/androidide/AboutActivity.java | 339 +- .../androidide/CrashHandlerActivity.java | 52 +- .../itsaky/androidide/DesignerActivity.java | 694 +- .../itsaky/androidide/DownloadActivity.java | 498 +- .../com/itsaky/androidide/EditorActivity.java | 2937 +- .../com/itsaky/androidide/MainActivity.java | 776 +- .../androidide/PreferencesActivity.java | 54 +- .../com/itsaky/androidide/SplashActivity.java | 113 +- .../itsaky/androidide/TerminalActivity.java | 867 +- .../androidide/adapters/AttrListAdapter.java | 80 +- .../adapters/CompletionListAdapter.java | 294 +- .../adapters/DiagnosticItemAdapter.java | 123 +- .../adapters/DiagnosticsAdapter.java | 106 +- .../adapters/EditorBottomSheetTabAdapter.java | 173 +- .../androidide/adapters/IconTextAdapter.java | 134 +- .../adapters/OptionsSheetAdapter.java | 91 +- .../adapters/ProjectsListAdapter.java | 226 +- .../adapters/SearchListAdapter.java | 201 +- .../adapters/SimpleIconTextAdapter.java | 46 +- .../adapters/SymbolInputAdapter.java | 168 +- .../adapters/TextActionItemAdapter.java | 123 +- .../adapters/WidgetGroupItemAdapter.java | 71 +- .../adapters/WidgetItemAdapter.java | 75 +- .../adapters/XMLAttributeListAdapter.java | 124 +- .../viewholders/FileTreeViewHolder.java | 131 +- .../itsaky/androidide/app/StudioActivity.java | 186 +- .../com/itsaky/androidide/app/StudioApp.java | 198 +- .../androidide/fragments/BaseFragment.java | 7 +- .../fragments/CrashReportFragment.java | 200 +- .../fragments/DiagnosticsListFragment.java | 85 +- .../fragments/FileTreeFragment.java | 397 +- .../androidide/fragments/IDELogFragment.java | 14 +- .../androidide/fragments/LogViewFragment.java | 66 +- .../fragments/NonEditableEditorFragment.java | 69 +- .../fragments/ProjectsFragment.java | 192 +- .../fragments/SearchResultFragment.java | 82 +- .../fragments/SimpleOutputFragment.java | 44 +- .../preferences/BasePreferenceFragment.java | 38 +- .../preferences/BuildPreferences.java | 215 +- .../preferences/EditorPreferences.java | 466 +- .../preferences/GeneralPreferences.java | 100 +- .../fragments/preferences/IDEPreferences.java | 294 +- .../fragments/sheets/AttrEditorSheet.java | 587 +- .../fragments/sheets/AttributeListSheet.java | 144 +- .../sheets/BaseBottomSheetFragment.java | 132 +- .../fragments/sheets/OptionsListFragment.java | 107 +- .../fragments/sheets/ProgressSheet.java | 166 +- .../fragments/sheets/TextSheetFragment.java | 184 +- .../handlers/BuildServiceHandler.java | 383 +- .../handlers/FileOptionsHandler.java | 716 +- .../androidide/handlers/IDEHandler.java | 80 +- .../interfaces/DiagnosticClickListener.java | 5 +- .../interfaces/EditorActivityProvider.java | 11 +- .../language/CommonCompletionProvider.java | 171 +- .../androidide/language/IDELanguage.java | 35 +- .../language/groovy/GroovyAnalyzer.java | 433 +- .../language/groovy/GroovyAutoComplete.java | 667 +- .../language/groovy/GroovyLanguage.java | 241 +- .../language/java/JavaAnalyzer.java | 623 +- .../language/java/JavaLanguage.java | 267 +- .../language/logs/LogLanguageImpl.java | 154 +- .../androidide/language/xml/XMLAnalyzer.java | 212 +- .../androidide/language/xml/XMLLanguage.java | 201 +- .../androidide/lsp/IDELanguageClientImpl.java | 594 +- .../itsaky/androidide/models/ApkMetadata.java | 375 +- .../androidide/models/DiagnosticGroup.java | 21 +- .../androidide/models/IconTextListItem.java | 28 +- .../com/itsaky/androidide/models/License.java | 22 +- .../com/itsaky/androidide/models/LogLine.java | 208 +- .../androidide/models/NewProjectDetails.java | 78 +- .../models/PrefBasedJavaServerSettings.java | 35 +- .../androidide/models/ProjectTemplate.java | 91 +- .../androidide/models/SearchResult.java | 19 +- .../itsaky/androidide/models/UIWidget.java | 150 +- .../androidide/models/UIWidgetGroup.java | 237 +- .../androidide/models/XMLAttribute.java | 248 +- .../androidide/project/AndroidProject.java | 320 +- .../itsaky/androidide/project/IDEModule.java | 62 +- .../itsaky/androidide/project/IDEProject.java | 107 +- .../itsaky/androidide/project/IDETask.java | 32 +- .../project/ProjectFileObserver.java | 16 +- .../project/ProjectResourceTable.java | 430 +- .../androidide/services/LogReceiver.java | 57 +- .../androidide/services/MessagingService.java | 175 +- .../services/builder/BuildListener.java | 95 +- .../services/builder/IDEService.java | 777 +- .../builder/MainThreadBuildListener.java | 84 +- .../syntax/colorschemes/SchemeAndroidIDE.java | 269 +- .../syntax/highlighters/Highlighter.java | 2 +- .../syntax/highlighters/JavaHighlighter.java | 348 +- .../androidide/tasks/BaseGradleTask.java | 32 +- .../itsaky/androidide/tasks/GradleTask.java | 64 +- .../itsaky/androidide/tasks/TaskExecutor.java | 82 +- .../tasks/callables/FileTreeCallable.java | 82 +- .../tasks/callables/LicenseReader.java | 34 +- .../callables/ListDirectoryCallable.java | 34 +- .../callables/ProjectCreatorCallable.java | 29 +- .../tasks/callables/UnzipCallable.java | 21 +- .../tasks/gradle/BaseGradleTasks.java | 22 +- .../tasks/gradle/build/ApkGeneratingTask.java | 187 +- .../tasks/gradle/build/AssembleDebug.java | 71 +- .../tasks/gradle/build/AssembleRelease.java | 61 +- .../androidide/tasks/gradle/build/Build.java | 63 +- .../androidide/tasks/gradle/build/Bundle.java | 49 +- .../androidide/tasks/gradle/build/Clean.java | 49 +- .../tasks/gradle/build/CleanBuild.java | 49 +- .../androidide/tasks/gradle/lint/Lint.java | 49 +- .../tasks/gradle/lint/LintDebug.java | 49 +- .../tasks/gradle/lint/LintRelease.java | 49 +- .../gradle/plugin/InitializeIDEProject.java | 49 +- .../DatabindingGenBaseClassesDebug.java | 57 +- .../resources/ProcessDebugResources.java | 57 +- .../resources/UpdateResourceClassesTask.java | 89 +- .../itsaky/androidide/ui/WidgetDragData.java | 42 +- .../androidide/ui/WidgetDragListener.java | 318 +- .../ui/WidgetDragShadowBuilder.java | 30 +- .../androidide/ui/WidgetTouchListener.java | 120 +- .../ui/edit/BaseSuggestionProvider.java | 37 +- .../ui/edit/DimensionSuggestionProvider.java | 86 +- .../ui/edit/IValueSuggestionProvider.java | 36 +- .../androidide/utils/AttributeDialogs.java | 512 +- .../itsaky/androidide/utils/DialogUtils.java | 125 +- .../utils/EditorBottomSheetBehavior.java | 60 +- .../utils/HighlightRangeHelper.java | 213 +- .../itsaky/androidide/utils/JSONUtility.java | 4 +- .../com/itsaky/androidide/utils/LSPUtils.java | 237 +- .../androidide/utils/ProjectFinder.java | 298 +- .../androidide/utils/ProjectWriter.java | 330 +- .../utils/RecursiveFileSearcher.java | 222 +- .../com/itsaky/androidide/utils/Symbols.java | 148 +- .../androidide/utils/TextWatcherAdapter.java | 13 +- .../androidide/utils/TransformUtils.java | 32 +- .../androidide/viewmodel/EditorViewModel.java | 270 +- .../views/DesignerLayoutContainer.java | 92 +- .../itsaky/androidide/views/EmptyView.java | 51 +- .../androidide/views/MaterialBanner.java | 290 +- .../androidide/views/SymbolInputView.java | 88 +- .../androidide/views/ValueEditorTextView.java | 61 +- .../views/editor/CodeEditorView.java | 584 +- .../views/editor/DiagnosticWindow.java | 57 +- .../views/editor/EditorTextActionMode.java | 294 +- .../views/editor/EditorTextActionWindow.java | 486 +- .../androidide/views/editor/IDEEditor.java | 1866 +- .../views/editor/SignatureHelpWindow.java | 251 +- .../views/editor/SimpleTextWindow.java | 101 +- .../views/transition/ArcMotionPlus.java | 159 +- .../views/transition/CubicBezierArc.java | 268 +- .../views/transition/ProgressTransition.java | 82 +- .../androidide/app/BaseApplication.java | 422 +- .../colorpicker/ColorPickerView.java | 323 +- .../itsaky/androidide/interfaces/Filter.java | 2 +- .../interfaces/ProjectWriterCallback.java | 8 +- .../managers/PreferenceManager.java | 250 +- .../androidide/managers/ToolsManager.java | 194 +- .../managers/VersionedFileManager.java | 30 +- .../androidide/models/ConstantsBridge.java | 70 +- .../com/itsaky/androidide/models/DexPair.java | 60 +- .../itsaky/androidide/models/SaveResult.java | 18 +- .../itsaky/androidide/models/SheetOption.java | 58 +- .../shell/CommonProcessExecutor.java | 180 +- .../androidide/shell/IProcessExecutor.java | 109 +- .../shell/IProcessExitListener.java | 2 +- .../shell/ProcessExecutorFactory.java | 9 +- .../shell/ProcessStreamsHolder.java | 6 +- .../itsaky/androidide/shell/ShellServer.java | 216 +- .../androidide/utils/BootstrapInstaller.java | 257 +- .../utils/CharSequenceInputStream.java | 471 +- .../androidide/utils/CharSequenceReader.java | 484 +- .../itsaky/androidide/utils/Environment.java | 427 +- .../com/itsaky/androidide/utils/FileUtil.java | 967 +- .../utils/InputStreamLineReader.java | 42 +- .../androidide/utils/InputStreamWriter.java | 50 +- .../itsaky/androidide/utils/IntProperty.java | 24 +- .../androidide/utils/JavaCharacter.java | 114 +- .../com/itsaky/androidide/utils/Logger.java | 192 +- .../androidide/utils/ReaderInputStream.java | 484 +- .../itsaky/androidide/utils/StudioUtils.java | 59 +- .../androidide/utils/TypefaceUtils.java | 25 +- .../java/com/itsaky/toaster/ToastWrapper.java | 94 +- .../main/java/com/itsaky/toaster/Toaster.java | 259 +- .../views/virtualkeys/SpecialButton.java | 71 +- .../views/virtualkeys/SpecialButtonState.java | 79 +- .../views/virtualkeys/VirtualKeyButton.java | 273 +- .../virtualkeys/VirtualKeysConstants.java | 448 +- .../views/virtualkeys/VirtualKeysInfo.java | 261 +- .../views/virtualkeys/VirtualKeysView.java | 1102 +- .../java/com/itsaky/terminal/ByteQueue.java | 191 +- .../main/java/com/itsaky/terminal/JNI.java | 86 +- .../java/com/itsaky/terminal/KeyHandler.java | 667 +- .../com/itsaky/terminal/TerminalBuffer.java | 1014 +- .../itsaky/terminal/TerminalColorScheme.java | 620 +- .../com/itsaky/terminal/TerminalColors.java | 133 +- .../com/itsaky/terminal/TerminalEmulator.java | 5464 ++-- .../com/itsaky/terminal/TerminalOutput.java | 40 +- .../java/com/itsaky/terminal/TerminalRow.java | 522 +- .../com/itsaky/terminal/TerminalSession.java | 634 +- .../terminal/TerminalSessionClient.java | 32 +- .../java/com/itsaky/terminal/TextStyle.java | 127 +- .../java/com/itsaky/terminal/WcWidth.java | 982 +- .../view/GestureAndScaleRecognizer.java | 210 +- .../terminal/view/TerminalRenderer.java | 563 +- .../itsaky/terminal/view/TerminalView.java | 2490 +- .../terminal/view/TerminalViewClient.java | 55 +- .../view/textselection/CursorController.java | 57 +- .../TextSelectionCursorController.java | 688 +- .../TextSelectionHandleView.java | 597 +- .../androidide/lexers/groovy/GroovyLexer.java | 1794 +- .../androidide/lexers/java/JavaLexer.java | 1775 +- .../androidide/lexers/java/JavaParser.java | 26692 ++++++++-------- .../lexers/java/JavaParserBaseListener.java | 3024 +- .../lexers/java/JavaParserBaseVisitor.java | 2113 +- .../lexers/java/JavaParserListener.java | 2532 +- .../lexers/java/JavaParserVisitor.java | 1472 +- .../lexers/javadoc/JavadocLexer.java | 375 +- .../lexers/javadoc/JavadocParser.java | 4607 +-- .../javadoc/JavadocParserBaseListener.java | 675 +- .../javadoc/JavadocParserBaseVisitor.java | 444 +- .../lexers/javadoc/JavadocParserListener.java | 528 +- .../lexers/javadoc/JavadocParserVisitor.java | 308 +- .../androidide/lexers/xml/XMLLexer.java | 503 +- .../androidide/lexers/xml/XMLParser.java | 2150 +- .../lexers/xml/XMLParserBaseListener.java | 364 +- .../lexers/xml/XMLParserBaseVisitor.java | 222 +- .../lexers/xml/XMLParserListener.java | 264 +- .../lexers/xml/XMLParserVisitor.java | 154 +- .../lsp/api/AbstractServiceProvider.java | 28 +- .../lsp/api/ConfigurableServiceProvider.java | 15 +- .../itsaky/lsp/api/ICodeActionProvider.java | 19 +- .../itsaky/lsp/api/ICompletionProvider.java | 26 +- .../itsaky/lsp/api/IDefinitionProvider.java | 17 +- .../itsaky/lsp/api/IDiagnosticProvider.java | 18 +- .../com/itsaky/lsp/api/IDocumentHandler.java | 58 +- .../com/itsaky/lsp/api/ILanguageClient.java | 30 +- .../com/itsaky/lsp/api/ILanguageServer.java | 227 +- .../itsaky/lsp/api/IReferenceProvider.java | 18 +- .../itsaky/lsp/api/ISelectionProvider.java | 21 +- .../com/itsaky/lsp/api/IServerSettings.java | 99 +- .../lsp/api/ISignatureHelpProvider.java | 17 +- .../itsaky/lsp/util/NoDocumentHandler.java | 16 +- .../java/com/itsaky/lsp/util/StringUtils.java | 51 +- .../itsaky/androidide/utils/ClassBuilder.java | 85 +- .../java/com/itsaky/lsp/java/FileStore.java | 536 +- .../itsaky/lsp/java/JavaLanguageServer.java | 338 +- .../lsp/java/compiler/CompileBatch.java | 240 +- .../itsaky/lsp/java/compiler/CompileTask.java | 74 +- .../lsp/java/compiler/CompilerProvider.java | 28 +- .../java/compiler/JavaCompilerService.java | 599 +- .../lsp/java/compiler/ReusableCompiler.java | 410 +- .../lsp/java/compiler/SourceFileManager.java | 176 +- .../lsp/java/compiler/SourceFileObject.java | 222 +- .../lsp/java/compiler/SynchronizedTask.java | 119 +- .../java/models/JavaServerConfiguration.java | 72 +- .../com/itsaky/lsp/java/parser/ParseTask.java | 12 +- .../com/itsaky/lsp/java/parser/Parser.java | 761 +- .../java/providers/CodeActionProvider.java | 753 +- .../java/providers/CompletionProvider.java | 1897 +- .../java/providers/DefinitionProvider.java | 263 +- .../java/providers/DiagnosticsProvider.java | 387 +- .../providers/JavaDiagnosticProvider.java | 42 +- .../java/providers/JavaSelectionProvider.java | 45 +- .../lsp/java/providers/ReferenceProvider.java | 143 +- .../providers/SemanticHighlightProvider.java | 55 +- .../lsp/java/providers/SignatureProvider.java | 531 +- .../itsaky/lsp/java/rewrite/AddException.java | 99 +- .../itsaky/lsp/java/rewrite/AddImport.java | 89 +- .../rewrite/AddSuppressWarningAnnotation.java | 73 +- .../lsp/java/rewrite/ConvertFieldToBlock.java | 84 +- .../rewrite/ConvertVariableToStatement.java | 109 +- .../lsp/java/rewrite/CreateMissingMethod.java | 414 +- .../rewrite/GenerateRecordConstructor.java | 186 +- .../rewrite/ImplementAbstractMethods.java | 121 +- .../java/rewrite/OverrideInheritedMethod.java | 150 +- .../itsaky/lsp/java/rewrite/RemoveClass.java | 28 +- .../lsp/java/rewrite/RemoveException.java | 267 +- .../itsaky/lsp/java/rewrite/RemoveMethod.java | 53 +- .../com/itsaky/lsp/java/rewrite/Rewrite.java | 11 +- .../lsp/java/rewrite/RewriteNotSupported.java | 9 +- .../java/com/itsaky/lsp/java/utils/Cache.java | 105 +- .../com/itsaky/lsp/java/utils/EditHelper.java | 242 +- .../com/itsaky/lsp/java/utils/Extractors.java | 35 +- .../com/itsaky/lsp/java/utils/FindHelper.java | 252 +- .../itsaky/lsp/java/utils/MarkdownHelper.java | 317 +- .../lsp/java/utils/NavigationHelper.java | 92 +- .../itsaky/lsp/java/utils/ScanClassPath.java | 62 +- .../itsaky/lsp/java/utils/ScopeHelper.java | 113 +- .../lsp/java/utils/ShortTypePrinter.java | 261 +- .../itsaky/lsp/java/utils/StringSearch.java | 863 +- .../lsp/java/visitors/DiagnosticVisitor.java | 462 +- .../lsp/java/visitors/FindBiggerRange.java | 131 +- .../lsp/java/visitors/FindCompletionsAt.java | 189 +- .../lsp/java/visitors/FindInvocationAt.java | 72 +- .../lsp/java/visitors/FindMethodCallAt.java | 402 +- .../visitors/FindMethodDeclarationAt.java | 54 +- .../itsaky/lsp/java/visitors/FindNameAt.java | 161 +- .../lsp/java/visitors/FindReferences.java | 72 +- .../java/visitors/FindTypeDeclarationAt.java | 62 +- .../visitors/FindTypeDeclarationNamed.java | 45 +- .../java/visitors/FindTypeDeclarations.java | 31 +- .../java/visitors/FindVariableAtCursor.java | 54 +- .../lsp/java/visitors/PruneMethodBodies.java | 81 +- .../java/visitors/SemanticHighlighter.java | 334 +- .../com/squareup/javapoet/AnnotationSpec.java | 432 +- .../com/squareup/javapoet/ArrayTypeName.java | 141 +- .../java/com/squareup/javapoet/ClassName.java | 530 +- .../java/com/squareup/javapoet/CodeBlock.java | 794 +- .../com/squareup/javapoet/CodeWriter.java | 940 +- .../java/com/squareup/javapoet/FieldSpec.java | 245 +- .../java/com/squareup/javapoet/JavaFile.java | 544 +- .../com/squareup/javapoet/LineWrapper.java | 255 +- .../com/squareup/javapoet/MethodSpec.java | 922 +- .../com/squareup/javapoet/NameAllocator.java | 157 +- .../com/squareup/javapoet/ParameterSpec.java | 318 +- .../javapoet/ParameterizedTypeName.java | 208 +- .../java/com/squareup/javapoet/TypeName.java | 614 +- .../java/com/squareup/javapoet/TypeSpec.java | 1604 +- .../squareup/javapoet/TypeVariableName.java | 269 +- .../main/java/com/squareup/javapoet/Util.java | 195 +- .../squareup/javapoet/WildcardTypeName.java | 187 +- .../java/com/sun/tools/javac/file/FSInfo.java | 141 +- .../com/sun/tools/javac/file/Locations.java | 3888 +-- .../java/javax/lang/model/SourceVersion.java | 622 +- .../main/java/jdk/internal/jmod/JmodFile.java | 305 +- .../internal/jrtfs/JrtFileSystemProvider.java | 511 +- .../java/jdk/internal/jrtfs/SystemImage.java | 122 +- .../com/itsaky/lsp/xml/XMLLanguageServer.java | 214 +- .../lsp/xml/providers/CompletionProvider.java | 513 +- .../com/itsaky/lsp/xml/utils/XmlUtils.java | 38 +- .../main/java/com/itsaky/apiinfo/ApiInfo.java | 265 +- .../com/itsaky/apiinfo/models/ClassInfo.java | 111 +- .../java/com/itsaky/apiinfo/models/Info.java | 16 +- .../com/itsaky/apiinfo/models/MethodInfo.java | 14 +- .../java/com/itsaky/attrinfo/AttrInfo.java | 333 +- .../java/com/itsaky/attrinfo/models/Attr.java | 294 +- .../com/itsaky/attrinfo/models/Styleable.java | 56 +- .../src/main/java/com/itsaky/sdk/SDKInfo.java | 39 +- .../java/com/itsaky/widgets/WidgetInfo.java | 127 +- .../com/itsaky/widgets/models/Widget.java | 32 +- .../main/java/com/itsaky/xml/INamespace.java | 45 +- .../java/com/itsaky/xml/impl/UiNamespace.java | 136 +- .../java/org/eclipse/jdt/core/Signature.java | 5575 ++-- .../jdt/core/compiler/CharOperation.java | 7751 ++--- .../jdt/internal/compiler/ast/ASTNode.java | 240 +- .../compiler/classfmt/ClassFileConstants.java | 357 +- .../compiler/parser/ScannerHelper.java | 1336 +- .../jdt/internal/compiler/util/Util.java | 1202 +- .../b/atv/holder/SimpleViewHolder.java | 23 +- .../com/unnamed/b/atv/model/TreeNode.java | 550 +- .../unnamed/b/atv/view/AndroidTreeView.java | 904 +- .../b/atv/view/TreeNodeWrapperView.java | 61 +- .../unnamed/b/atv/view/TwoDScrollView.java | 2048 +- .../java/com/itsaky/inflater/AttrType.java | 148 +- .../itsaky/inflater/BaseLayoutInflater.java | 78 +- .../java/com/itsaky/inflater/IAttribute.java | 127 +- .../itsaky/inflater/IAttributeAdapter.java | 46 +- .../java/com/itsaky/inflater/IDTable.java | 89 +- .../java/com/itsaky/inflater/IFixedValue.java | 36 +- .../com/itsaky/inflater/IInflateListener.java | 44 +- .../com/itsaky/inflater/ILayoutInflater.java | 281 +- .../com/itsaky/inflater/IResourceTable.java | 131 +- .../main/java/com/itsaky/inflater/IView.java | 321 +- .../java/com/itsaky/inflater/IViewGroup.java | 212 +- .../com/itsaky/inflater/InflateException.java | 18 +- .../inflater/LayoutInflaterConfiguration.java | 101 +- .../inflater/NotSupportedException.java | 20 +- .../itsaky/inflater/XMLLayoutInflater.java | 776 +- .../android/view/ViewAttrAdapter.java | 1038 +- .../android/view/ViewGroupAttrAdapter.java | 129 +- .../android/widget/AbsSeekBarAttrAdapter.java | 86 +- .../android/widget/AbsSpinnerAttrAdapter.java | 76 +- .../widget/AdapterViewAttrAdapter.java | 62 +- .../android/widget/ButtonAttrAdapter.java | 27 +- .../android/widget/CheckBoxAttrAdapter.java | 28 +- .../widget/CheckedTextViewAttrAdapter.java | 80 +- .../widget/CompondButtonAttrAdapter.java | 80 +- .../android/widget/EditTextAttrAdapter.java | 28 +- .../widget/FrameLayoutAttrAdapter.java | 70 +- .../widget/ImageButtonAttrAdapter.java | 28 +- .../android/widget/ImageViewAttrAdapter.java | 174 +- .../widget/LinearLayoutAttrAdapter.java | 112 +- .../widget/ProgressBarAttrAdapter.java | 194 +- .../widget/RadioButtonAttrAdapter.java | 28 +- .../widget/RelativeLayoutAttrAdapter.java | 74 +- .../android/widget/SeekBarAttrAdapter.java | 65 +- .../android/widget/SpinnerAttrAdapter.java | 95 +- .../android/widget/TextViewAttrAdapter.java | 377 +- .../drawable/BitmapDrawableParser.java | 207 +- .../inflater/drawable/ClipDrawableParser.java | 85 +- .../drawable/DrawableParserFactory.java | 303 +- .../inflater/drawable/IDrawableParser.java | 222 +- .../drawable/InsetDrawableParser.java | 70 +- .../inflater/drawable/LayerListParser.java | 179 +- .../drawable/ScaleDrawableParser.java | 127 +- .../drawable/ShapeDrawableParser.java | 572 +- .../inflater/drawable/StateListParser.java | 164 +- .../com/itsaky/inflater/impl/BaseView.java | 566 +- .../itsaky/inflater/impl/BaseViewGroup.java | 147 +- .../com/itsaky/inflater/impl/ErrorUiView.java | 115 +- .../itsaky/inflater/impl/IncludeLayout.java | 20 +- .../com/itsaky/inflater/impl/UiAttribute.java | 114 +- .../java/com/itsaky/inflater/impl/UiView.java | 12 +- .../com/itsaky/inflater/impl/UiViewGroup.java | 141 +- .../inflater/util/CommonParseUtils.java | 704 +- .../itsaky/inflater/util/Preconditions.java | 42 +- .../itsaky/inflater/util/TextDrawable.java | 86 +- .../inflater/values/IResourceValue.java | 30 +- .../itsaky/inflater/values/ValuesTable.java | 624 +- .../inflater/values/ValuesTableFactory.java | 174 +- .../values/models/AbstractResourceValue.java | 95 +- .../inflater/values/models/ArrayResource.java | 67 +- .../inflater/values/models/BooleanValue.java | 42 +- .../inflater/values/models/ColorValue.java | 31 +- .../values/models/DimensionValue.java | 6 +- .../inflater/values/models/IntegerValue.java | 30 +- .../inflater/values/models/StringValue.java | 6 +- .../harjot/vectormaster/DefaultValues.java | 78 +- .../vectormaster/VectorMasterDrawable.java | 1032 +- .../harjot/vectormaster/VectorMasterView.java | 796 +- .../harjot/vectormaster/enums/TintMode.java | 12 +- .../vectormaster/models/ClipPathModel.java | 125 +- .../vectormaster/models/GroupModel.java | 454 +- .../harjot/vectormaster/models/PathModel.java | 491 +- .../vectormaster/models/VectorModel.java | 318 +- .../harjot/vectormaster/utilities/Utils.java | 154 +- .../utilities/legacyparser/PathParser.java | 1414 +- .../utilities/parser/ParserHelper.java | 652 +- .../utilities/parser/PathParser.java | 645 +- 426 files changed, 86887 insertions(+), 84448 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/AboutActivity.java b/app/src/main/java/com/itsaky/androidide/AboutActivity.java index 7fb61a03d..fcfa99a49 100755 --- a/app/src/main/java/com/itsaky/androidide/AboutActivity.java +++ b/app/src/main/java/com/itsaky/androidide/AboutActivity.java @@ -21,178 +21,211 @@ 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 { - private ActivityAboutBinding binding; + private ActivityAboutBinding binding; - public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android"; + public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android"; - @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"); + @Override + protected View bindLayout() { + binding = ActivityAboutBinding.inflate(getLayoutInflater()); + return binding.getRoot(); } - binding.items.translations.setText(sb); - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - /** - * 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. - * - *

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 - * > - * - *

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> createTranslationsList() { - final var list = new ArrayList>(); + binding.items.footerText.setText(getFooter()); - list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani")); - list.add(Pair.create("\u4e2d\u6587", "Rosemoe")); // 中文 - 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")); + setupTranslations(); + setupLicenses(); - return list; - } - - private void setupLicenses() { - final var licenses = new ArrayList(); - 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("
"); - sb.append(getString(R.string.msg_about_licensed_under)); - sb.append(license.license); - sb.append("
"); - sb.append(license.url); - sb.append(""); - sb.append("

"); + 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(); + }); } - sb.append(getString(R.string.license_jdk)); - sb.append(String.format("
%1$s", JDK_SOURCE)); - sb.append("

"); - binding.items.licenses.setText(Html.fromHtml(sb.toString())); - binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance()); - } - private String getFooter() { - final String arch = android.os.Build.SUPPORTED_ABIS[0]; - try { - final String version = - getPackageManager().getPackageInfo("com.itsaky.androidide", 0).versionName; - return getString(R.string.about_footer, version, arch); - } catch (Throwable th) { - return getString(R.string.about_footer_alternate, arch); + 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. + * + *

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 > + * + *

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> createTranslationsList() { + final var list = new ArrayList>(); + + list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani")); + list.add(Pair.create("\u4e2d\u6587", "Rosemoe")); // 中文 + 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")); + + return list; + } + + private void setupLicenses() { + final var licenses = new ArrayList(); + 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("
"); + sb.append(getString(R.string.msg_about_licensed_under)); + sb.append(license.license); + sb.append("
"); + sb.append(license.url); + sb.append(""); + sb.append("

"); + } + sb.append(getString(R.string.license_jdk)); + sb.append(String.format("
%1$s", JDK_SOURCE)); + sb.append("

"); + binding.items.licenses.setText(Html.fromHtml(sb.toString())); + binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance()); + } + + private String getFooter() { + final String arch = android.os.Build.SUPPORTED_ABIS[0]; + try { + final String version = + getPackageManager().getPackageInfo("com.itsaky.androidide", 0).versionName; + return getString(R.string.about_footer, version, arch); + } catch (Throwable th) { + return getString(R.string.about_footer_alternate, arch); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/CrashHandlerActivity.java b/app/src/main/java/com/itsaky/androidide/CrashHandlerActivity.java index cf252f656..4e88ad6dc 100644 --- a/app/src/main/java/com/itsaky/androidide/CrashHandlerActivity.java +++ b/app/src/main/java/com/itsaky/androidide/CrashHandlerActivity.java @@ -17,43 +17,43 @@ package com.itsaky.androidide; - import android.os.Bundle; import android.view.View; + import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.databinding.ActivityCrashHandlerBinding; import com.itsaky.androidide.fragments.CrashReportFragment; public class CrashHandlerActivity extends StudioActivity { - private ActivityCrashHandlerBinding binding; + private ActivityCrashHandlerBinding binding; - public static final String REPORT_ACTION = "com.itsaky.androidide.REPORT_CRASH"; - public static final String TRACE_KEY = "crash_trace"; + public static final String REPORT_ACTION = "com.itsaky.androidide.REPORT_CRASH"; + public static final String TRACE_KEY = "crash_trace"; - @Override - protected View bindLayout() { - binding = ActivityCrashHandlerBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final var extra = getIntent().getExtras(); - if (extra == null) { - finishAffinity(); - return; + @Override + protected View bindLayout() { + binding = ActivityCrashHandlerBinding.inflate(getLayoutInflater()); + return binding.getRoot(); } - final var report = extra.getString(TRACE_KEY, "Unable to get logs."); - final var fragment = CrashReportFragment.newInstance(report); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - getSupportFragmentManager() - .beginTransaction() - .replace(binding.getRoot().getId(), fragment, "crash_report_fragment") - .addToBackStack(null) - .commit(); - } + final var extra = getIntent().getExtras(); + if (extra == null) { + finishAffinity(); + return; + } + + final var report = extra.getString(TRACE_KEY, "Unable to get logs."); + final var fragment = CrashReportFragment.newInstance(report); + + getSupportFragmentManager() + .beginTransaction() + .replace(binding.getRoot().getId(), fragment, "crash_report_fragment") + .addToBackStack(null) + .commit(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/DesignerActivity.java b/app/src/main/java/com/itsaky/androidide/DesignerActivity.java index 3e2a8ec7d..d5323ff69 100755 --- a/app/src/main/java/com/itsaky/androidide/DesignerActivity.java +++ b/app/src/main/java/com/itsaky/androidide/DesignerActivity.java @@ -38,6 +38,7 @@ import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.Spinner; import android.widget.TextView; + import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.core.content.ContextCompat; @@ -47,6 +48,7 @@ import androidx.transition.Fade; import androidx.transition.Slide; import androidx.transition.TransitionManager; import androidx.transition.TransitionSet; + import com.blankj.utilcode.util.DeviceUtils; import com.blankj.utilcode.util.ThrowableUtils; import com.itsaky.androidide.adapters.WidgetGroupItemAdapter; @@ -69,380 +71,410 @@ import com.itsaky.inflater.ILayoutInflater; import com.itsaky.inflater.IView; import com.itsaky.inflater.IViewGroup; import com.itsaky.inflater.impl.BaseView; + +import org.jetbrains.annotations.Contract; + import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import org.jetbrains.annotations.Contract; public class DesignerActivity extends StudioActivity - implements WidgetItemAdapter.OnDragStartListener { + implements WidgetItemAdapter.OnDragStartListener { - public static final String KEY_LAYOUT_PATH = "designer_layoutPath"; - public static final String KEY_GENERATED_CODE = "designer_xmlCode"; - public static final String DRAGGING_WIDGET_TAG = "DRAGGING_WIDGET"; - public static final String DRAGGING_WIDGET_MIME = "application/ide_widget"; - private static final Logger LOG = Logger.instance("DesignerActivity"); - private final boolean isTablet = DeviceUtils.isTablet(); - private final List widgetGroups = new ArrayList<>(); - private ActivityDesignerBinding mBinding; - private UIWidgetGroup checkedWidgetCategory; - private IViewGroup inflatedRoot; - private File layout; - private boolean inflationFailed = false; - private AttrEditorSheet mEditorSheet; - // This will make sure to apply listeners, background and data to view that are inflated from XML. - private final IInflateListener mInflateListener = - new IInflateListener() { + public static final String KEY_LAYOUT_PATH = "designer_layoutPath"; + public static final String KEY_GENERATED_CODE = "designer_xmlCode"; + public static final String DRAGGING_WIDGET_TAG = "DRAGGING_WIDGET"; + public static final String DRAGGING_WIDGET_MIME = "application/ide_widget"; + private static final Logger LOG = Logger.instance("DesignerActivity"); + private final boolean isTablet = DeviceUtils.isTablet(); + private final List widgetGroups = new ArrayList<>(); + private ActivityDesignerBinding mBinding; + private UIWidgetGroup checkedWidgetCategory; + private IViewGroup inflatedRoot; + private File layout; + private boolean inflationFailed = false; + private AttrEditorSheet mEditorSheet; + // This will make sure to apply listeners, background and data to view that are inflated from + // XML. + private final IInflateListener mInflateListener = + new IInflateListener() { - @Override - public void onBeginInflate() {} + @Override + public void onBeginInflate() {} - @Override - public void onInflateView(IView view, IViewGroup parent) { - if (view instanceof IViewGroup) { - View v = view.asView(); - v.setBackgroundResource(R.drawable.bg_design_container_background); - v.setOnDragListener(getOnDragListener((IViewGroup) view)); - } + @Override + public void onInflateView(IView view, IViewGroup parent) { + if (view instanceof IViewGroup) { + View v = view.asView(); + v.setBackgroundResource(R.drawable.bg_design_container_background); + v.setOnDragListener(getOnDragListener((IViewGroup) view)); + } - setupInflatedView(view); - setDragDataToInflatedView(view); - } + setupInflatedView(view); + setDragDataToInflatedView(view); + } - @Override - public void onApplyAttribute(IAttribute attr, IView view) {} + @Override + public void onApplyAttribute(IAttribute attr, IView view) {} - @Override - public void onFinishInflate(IView rootView) { - if (rootView instanceof IViewGroup) { - inflatedRoot = (IViewGroup) rootView; - } - } - }; + @Override + public void onFinishInflate(IView rootView) { + if (rootView instanceof IViewGroup) { + inflatedRoot = (IViewGroup) rootView; + } + } + }; - @Override - protected View bindLayout() { - this.mBinding = ActivityDesignerBinding.inflate(getLayoutInflater()); - return mBinding.getRoot(); - } + @Override + protected View bindLayout() { + this.mBinding = ActivityDesignerBinding.inflate(getLayoutInflater()); + return mBinding.getRoot(); + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - setSupportActionBar(mBinding.toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setSupportActionBar(mBinding.toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); - final var toggle = - new ActionBarDrawerToggle( - this, mBinding.getRoot(), mBinding.toolbar, R.string.app_name, R.string.app_name) { + final var toggle = + new ActionBarDrawerToggle( + this, + mBinding.getRoot(), + mBinding.toolbar, + R.string.app_name, + R.string.app_name) { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - super.onDrawerSlide(drawerView, slideOffset); - if (isTablet) { - mBinding.mainContainer.setTranslationX(drawerView.getWidth() * slideOffset); + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + super.onDrawerSlide(drawerView, slideOffset); + if (isTablet) { + mBinding.mainContainer.setTranslationX( + drawerView.getWidth() * slideOffset); + } + } + }; + mBinding.getRoot().addDrawerListener(toggle); + toggle.syncState(); + + final var extras = getIntent().getExtras(); + final var path = extras.getString(KEY_LAYOUT_PATH, null); + final var name = path.substring(path.lastIndexOf(File.separator) + 1); + getSupportActionBar().setTitle(name); + + try { + this.layout = new File(path); + + final ILayoutInflater inflater = getApp().getLayoutInflater(); + inflater.resetContextProvider(newContextProvider()); + inflater.registerInflateListener(this.mInflateListener); + final IView view = inflater.inflatePath(path, mBinding.layoutContainer); + + if (this.inflatedRoot != null) { + this.inflatedRoot.asView().setOnDragListener(getOnDragListener(this.inflatedRoot)); } - } - }; - mBinding.getRoot().addDrawerListener(toggle); - toggle.syncState(); - final var extras = getIntent().getExtras(); - final var path = extras.getString(KEY_LAYOUT_PATH, null); - final var name = path.substring(path.lastIndexOf(File.separator) + 1); - getSupportActionBar().setTitle(name); + mBinding.layoutContainer.addView(view.asView()); + mBinding.layoutContainer.setOnClickListener( + v -> mBinding.getRoot().openDrawer(GravityCompat.START)); + } catch (Throwable th) { + mBinding.layoutContainer.removeAllViews(); + mBinding.layoutContainer.addView(createErrorText(th)); + LOG.error(getString(R.string.err_cannot_inflate_layout), th); + inflationFailed = true; + } - try { - this.layout = new File(path); + setupWidgets(); - final ILayoutInflater inflater = getApp().getLayoutInflater(); - inflater.resetContextProvider(newContextProvider()); - inflater.registerInflateListener(this.mInflateListener); - final IView view = inflater.inflatePath(path, mBinding.layoutContainer); - - if (this.inflatedRoot != null) { - this.inflatedRoot.asView().setOnDragListener(getOnDragListener(this.inflatedRoot)); - } - - mBinding.layoutContainer.addView(view.asView()); - mBinding.layoutContainer.setOnClickListener( - v -> mBinding.getRoot().openDrawer(GravityCompat.START)); - } catch (Throwable th) { - mBinding.layoutContainer.removeAllViews(); - mBinding.layoutContainer.addView(createErrorText(th)); - LOG.error(getString(R.string.err_cannot_inflate_layout), th); - inflationFailed = true; + // Always open the drawer if this device is a tablet. + if (isTablet) { + mBinding.getRoot().setScrimColor(Color.TRANSPARENT); + mBinding.getRoot().openDrawer(GravityCompat.START); + mBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN); + } } - setupWidgets(); + @Override + public void onBackPressed() { - // Always open the drawer if this device is a tablet. - if (isTablet) { - mBinding.getRoot().setScrimColor(Color.TRANSPARENT); - mBinding.getRoot().openDrawer(GravityCompat.START); - mBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN); - } - } - - @Override - public void onBackPressed() { - - if (inflationFailed) { - super.onBackPressed(); - return; - } - - // When the user presses the back button, set the activity result and finish this activity - - final var progress = new ProgressSheet(); - progress.setSubMessageEnabled(true); - progress.setShowTitle(false); - progress.setMessage(getString(R.string.title_generating_xml)); - progress.setSubMessage(getString(R.string.please_wait)); - progress.show(getSupportFragmentManager(), "generate_code_progress"); - - final var future = - CompletableFuture.supplyAsync( - () -> BaseView.XML_HEADER.concat("\n").concat(inflatedRoot.generateCode())); - future.whenComplete( - (s, throwable) -> { - progress.dismiss(); - - if (throwable != null) { - notifyXmlGenerationFailed(throwable); + if (inflationFailed) { + super.onBackPressed(); return; - } + } - try { - finishWithResult(future.get()); - } catch (ExecutionException | InterruptedException e) { - notifyXmlGenerationFailed(e); - } - }); - } + // When the user presses the back button, set the activity result and finish this activity - private void notifyXmlGenerationFailed(Throwable error) { - final var errorMessage = getString(R.string.msg_generate_xml_failed); - LOG.error(errorMessage, error); + final var progress = new ProgressSheet(); + progress.setSubMessageEnabled(true); + progress.setShowTitle(false); + progress.setMessage(getString(R.string.title_generating_xml)); + progress.setSubMessage(getString(R.string.please_wait)); + progress.show(getSupportFragmentManager(), "generate_code_progress"); - DialogUtils.newYesNoDialog( - this, - getString(R.string.title_code_generation_failed), - getString(R.string.msg_code_generation_failed), - (dialog1, which) -> finishWithError(), - (dialog1, which) -> dialog1.dismiss()) - .show(); - } + final var future = + CompletableFuture.supplyAsync( + () -> BaseView.XML_HEADER.concat("\n").concat(inflatedRoot.generateCode())); + future.whenComplete( + (s, throwable) -> { + progress.dismiss(); - private void finishWithResult(String result) { - final var intent = new Intent(); - intent.putExtra(KEY_GENERATED_CODE, result); - setResult(RESULT_OK, intent); - finish(); - } + if (throwable != null) { + notifyXmlGenerationFailed(throwable); + return; + } - private void finishWithError() { - final var intent = new Intent(); - setResult(-123, intent); - finish(); - } - - @Override - protected void onDestroy() { - - if (isAttrEditorShowing()) { - getAttrEditorSheet().dismiss(); + try { + finishWithResult(future.get()); + } catch (ExecutionException | InterruptedException e) { + notifyXmlGenerationFailed(e); + } + }); } - this.mEditorSheet = null; + private void notifyXmlGenerationFailed(Throwable error) { + final var errorMessage = getString(R.string.msg_generate_xml_failed); + LOG.error(errorMessage, error); - // Release the reference to inflate listener and context from the layout inflater - // Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment manager - try { - final var layoutInflater = getApp().getLayoutInflater(); - layoutInflater.unregisterListener(mInflateListener); - layoutInflater.resetContextProvider(null); - } catch (Throwable e) { - e.printStackTrace(); + DialogUtils.newYesNoDialog( + this, + getString(R.string.title_code_generation_failed), + getString(R.string.msg_code_generation_failed), + (dialog1, which) -> finishWithError(), + (dialog1, which) -> dialog1.dismiss()) + .show(); } - super.onDestroy(); - } - - private void setupInflatedView(@NonNull IView view) { - final var listener = - new WidgetTouchListener(view, this::onLayoutViewClick, this::onLayoutViewLongClick); - view.asView().setOnTouchListener(listener); - - setDragDataToInflatedView(view); - } - - private boolean onLayoutViewLongClick(@NonNull IView view) { - final var shadow = new WidgetDragShadowBuilder(view.asView()); - final var item = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG); - final var clip = - new ClipData( - DesignerActivity.DRAGGING_WIDGET_TAG, - new String[] {DesignerActivity.DRAGGING_WIDGET_MIME}, - item); - final var data = view.getExtraData(); - - view.asView().startDragAndDrop(clip, shadow, data, 0); - view.removeFromParent(); - - return true; - } - - private void onLayoutViewClick(@NonNull IView view) { - if (isAttrEditorShowing()) { - getAttrEditorSheet().dismiss(); + private void finishWithResult(String result) { + final var intent = new Intent(); + intent.putExtra(KEY_GENERATED_CODE, result); + setResult(RESULT_OK, intent); + finish(); } - getAttrEditorSheet().setSelectedView(view); - - if (!getSupportFragmentManager().isDestroyed()) { - getAttrEditorSheet().show(getSupportFragmentManager(), "attribute_editor_dialog"); + private void finishWithError() { + final var intent = new Intent(); + setResult(-123, intent); + finish(); } - } - private boolean isAttrEditorShowing() { - return getAttrEditorSheet().getDialog() != null && getAttrEditorSheet().getDialog().isShowing(); - } + @Override + protected void onDestroy() { - private void setupWidgets() { - final var android = new UIWidgetGroup(getString(R.string.widget_group_android)); - android.addChild( - new UIWidget(getString(R.string.widget_button), R.drawable.ic_widget_button, Button.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_checkbox), R.drawable.ic_widget_checkbox, CheckBox.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_checked_textview), - R.drawable.ic_widget_checked_textview, - CheckedTextView.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_edittext), R.drawable.ic_widget_edittext, EditText.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_image_button), - R.drawable.ic_widget_image, - ImageButton.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_image_view), R.drawable.ic_widget_image, ImageView.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_progressbar), - R.drawable.ic_widget_progress, - ProgressBar.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_radio_button), - R.drawable.ic_widget_radio_button, - RadioButton.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_seekbar), R.drawable.ic_widget_seekbar, SeekBar.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_spinner), R.drawable.ic_widget_spinner, Spinner.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_textview), R.drawable.ic_widget_textview, TextView.class)); + if (isAttrEditorShowing()) { + getAttrEditorSheet().dismiss(); + } - // IMPORTANT: Do not select any other groups here - android.setSelected(true); - this.checkedWidgetCategory = android; - widgetGroups.add(android); + this.mEditorSheet = null; - final var layouts = new UIWidgetGroup(getString(R.string.widget_group_layouts)); - layouts.addChild( - new UIWidget( - getString(R.string.layout_linear), R.drawable.ic_widget_linear, LinearLayout.class)); - layouts.addChild( - new UIWidget( - getString(R.string.layout_relative), - R.drawable.ic_widget_relative, - RelativeLayout.class)); + // Release the reference to inflate listener and context from the layout inflater + // Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment + // manager + try { + final var layoutInflater = getApp().getLayoutInflater(); + layoutInflater.unregisterListener(mInflateListener); + layoutInflater.resetContextProvider(null); + } catch (Throwable e) { + e.printStackTrace(); + } - widgetGroups.add(layouts); - - final var adapter = new WidgetGroupItemAdapter(widgetGroups, this::showWidgetGroup); - mBinding.groupItems.setAdapter(adapter); - - showWidgetGroup(android); - } - - @SuppressLint("NotifyDataSetChanged") - private void showWidgetGroup(@NonNull UIWidgetGroup group) { - final var children = group.getChildren(); - this.mBinding.widgetItems.setAdapter(new WidgetItemAdapter(children, this)); - this.checkedWidgetCategory.setSelected(false); - this.checkedWidgetCategory = group; - this.checkedWidgetCategory.setSelected(true); - - TransitionManager.beginDelayedTransition( - mBinding.navigation, - new TransitionSet().addTransition(new Slide(Gravity.END)).addTransition(new Fade())); - - //noinspection ConstantConditions - this.mBinding.groupItems.getAdapter().notifyDataSetChanged(); - } - - @NonNull - @Contract(pure = true) - private ILayoutInflater.ContextProvider newContextProvider() { - return () -> DesignerActivity.this; - } - - @NonNull - private TextView createErrorText(Throwable th) { - final TextView error = new TextView(this); - error.setText(ThrowableUtils.getFullStackTrace(th)); - error.setLayoutParams(new ViewGroup.LayoutParams(-2, -2)); - error.setTextColor(ContextCompat.getColor(this, android.R.color.black)); - error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white)); - return error; - } - - @Override - public void onDragStarted(View view) { - mBinding.getRoot().closeDrawer(GravityCompat.START); - } - - @NonNull - @Contract("_ -> new") - private View.OnDragListener getOnDragListener(IViewGroup group) { - // WidgetDragListener cannot be reused - // This is because they keep a reference to the view group in which dragged view will be added - - return new WidgetDragListener(this, group, this::setupInflatedView); - } - - private void setDragDataToInflatedView(@NonNull IView view) { - view.setExtraData(new WidgetDragData(true, view, null)); - } - - private AttrEditorSheet getAttrEditorSheet() { - return this.mEditorSheet == null - ? mEditorSheet = - new AttrEditorSheet() - .setDeletionFailedListener(this::onViewDeletionFailed) - .setLayout(this.layout) - : mEditorSheet; - } - - private boolean onViewDeletionFailed(@NonNull IView view) { - final var v = view.asView(); - if (mBinding.layoutContainer.indexOfChild(v) >= 0) { - mBinding.layoutContainer.removeView(v); - return true; + super.onDestroy(); + } + + private void setupInflatedView(@NonNull IView view) { + final var listener = + new WidgetTouchListener(view, this::onLayoutViewClick, this::onLayoutViewLongClick); + view.asView().setOnTouchListener(listener); + + setDragDataToInflatedView(view); + } + + private boolean onLayoutViewLongClick(@NonNull IView view) { + final var shadow = new WidgetDragShadowBuilder(view.asView()); + final var item = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG); + final var clip = + new ClipData( + DesignerActivity.DRAGGING_WIDGET_TAG, + new String[] {DesignerActivity.DRAGGING_WIDGET_MIME}, + item); + final var data = view.getExtraData(); + + view.asView().startDragAndDrop(clip, shadow, data, 0); + view.removeFromParent(); + + return true; + } + + private void onLayoutViewClick(@NonNull IView view) { + if (isAttrEditorShowing()) { + getAttrEditorSheet().dismiss(); + } + + getAttrEditorSheet().setSelectedView(view); + + if (!getSupportFragmentManager().isDestroyed()) { + getAttrEditorSheet().show(getSupportFragmentManager(), "attribute_editor_dialog"); + } + } + + private boolean isAttrEditorShowing() { + return getAttrEditorSheet().getDialog() != null + && getAttrEditorSheet().getDialog().isShowing(); + } + + private void setupWidgets() { + final var android = new UIWidgetGroup(getString(R.string.widget_group_android)); + android.addChild( + new UIWidget( + getString(R.string.widget_button), + R.drawable.ic_widget_button, + Button.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_checkbox), + R.drawable.ic_widget_checkbox, + CheckBox.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_checked_textview), + R.drawable.ic_widget_checked_textview, + CheckedTextView.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_edittext), + R.drawable.ic_widget_edittext, + EditText.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_image_button), + R.drawable.ic_widget_image, + ImageButton.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_image_view), + R.drawable.ic_widget_image, + ImageView.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_progressbar), + R.drawable.ic_widget_progress, + ProgressBar.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_radio_button), + R.drawable.ic_widget_radio_button, + RadioButton.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_seekbar), + R.drawable.ic_widget_seekbar, + SeekBar.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_spinner), + R.drawable.ic_widget_spinner, + Spinner.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_textview), + R.drawable.ic_widget_textview, + TextView.class)); + + // IMPORTANT: Do not select any other groups here + android.setSelected(true); + this.checkedWidgetCategory = android; + widgetGroups.add(android); + + final var layouts = new UIWidgetGroup(getString(R.string.widget_group_layouts)); + layouts.addChild( + new UIWidget( + getString(R.string.layout_linear), + R.drawable.ic_widget_linear, + LinearLayout.class)); + layouts.addChild( + new UIWidget( + getString(R.string.layout_relative), + R.drawable.ic_widget_relative, + RelativeLayout.class)); + + widgetGroups.add(layouts); + + final var adapter = new WidgetGroupItemAdapter(widgetGroups, this::showWidgetGroup); + mBinding.groupItems.setAdapter(adapter); + + showWidgetGroup(android); + } + + @SuppressLint("NotifyDataSetChanged") + private void showWidgetGroup(@NonNull UIWidgetGroup group) { + final var children = group.getChildren(); + this.mBinding.widgetItems.setAdapter(new WidgetItemAdapter(children, this)); + this.checkedWidgetCategory.setSelected(false); + this.checkedWidgetCategory = group; + this.checkedWidgetCategory.setSelected(true); + + TransitionManager.beginDelayedTransition( + mBinding.navigation, + new TransitionSet() + .addTransition(new Slide(Gravity.END)) + .addTransition(new Fade())); + + //noinspection ConstantConditions + this.mBinding.groupItems.getAdapter().notifyDataSetChanged(); + } + + @NonNull + @Contract(pure = true) + private ILayoutInflater.ContextProvider newContextProvider() { + return () -> DesignerActivity.this; + } + + @NonNull + private TextView createErrorText(Throwable th) { + final TextView error = new TextView(this); + error.setText(ThrowableUtils.getFullStackTrace(th)); + error.setLayoutParams(new ViewGroup.LayoutParams(-2, -2)); + error.setTextColor(ContextCompat.getColor(this, android.R.color.black)); + error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white)); + return error; + } + + @Override + public void onDragStarted(View view) { + mBinding.getRoot().closeDrawer(GravityCompat.START); + } + + @NonNull + @Contract("_ -> new") + private View.OnDragListener getOnDragListener(IViewGroup group) { + // WidgetDragListener cannot be reused + // This is because they keep a reference to the view group in which dragged view will be + // added + + return new WidgetDragListener(this, group, this::setupInflatedView); + } + + private void setDragDataToInflatedView(@NonNull IView view) { + view.setExtraData(new WidgetDragData(true, view, null)); + } + + private AttrEditorSheet getAttrEditorSheet() { + return this.mEditorSheet == null + ? mEditorSheet = + new AttrEditorSheet() + .setDeletionFailedListener(this::onViewDeletionFailed) + .setLayout(this.layout) + : mEditorSheet; + } + + private boolean onViewDeletionFailed(@NonNull IView view) { + final var v = view.asView(); + if (mBinding.layoutContainer.indexOfChild(v) >= 0) { + mBinding.layoutContainer.removeView(v); + return true; + } + return false; } - return false; - } } diff --git a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java index c75679a62..ad06f6f6c 100755 --- a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java +++ b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java @@ -17,15 +17,18 @@ */ package com.itsaky.androidide; -import abhishekti7.unicorn.filepicker.UnicornFilePicker; 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 abhishekti7.unicorn.filepicker.UnicornFilePicker; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.SizeUtils; import com.blankj.utilcode.util.ThreadUtils; @@ -45,6 +48,7 @@ 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; @@ -52,266 +56,274 @@ import java.util.ArrayList; public class DownloadActivity extends StudioActivity { - private ActivityDownloadBinding binding; - private ProgressSheet progressSheet; - private File choosenDir = null; + private ActivityDownloadBinding binding; + private ProgressSheet progressSheet; + private File choosenDir = null; - @Override - protected View bindLayout() { - binding = ActivityDownloadBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } + @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); + @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(); + 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; } - } else { - onInstallationFailed(code); - } - }); - } - private void onInstallationFailed(int code) { - // TODO Can this be improved by adding some animations? + if (!choosenDir.exists()) { + getApp().toast(R.string.msg_file_doesnt_exist, Toaster.Type.ERROR); + return; + } - 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); + installAll(); } - 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); + private void installAll() { + showProgress(); + getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true); - final File script = new File(Environment.TMP_DIR, "extract_tools.sh"); - if (!FileIOUtils.writeFileFromString(script, sb.toString())) { - throw new InstallationException(2); - } + try { + final File script = createExtractScript(); + final ProcessStreamsHolder holder = new ProcessStreamsHolder(); + final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor(); - return script; - } + executor.execAsync( + holder, + this::onInstallProcessExit, + true, + Environment.BUSYBOX.getAbsolutePath(), + "sh", // We use busybox's sh, because Environment.SHELL is not installed yet... + script.getAbsolutePath()); - private void joiner(StringBuilder sb) { - sb.append(" && "); - } + this.output = new StringBuilder(); + final InputStreamLineReader reader = + new InputStreamLineReader(holder.in, this::onInstallationOutput); + new Thread(reader).start(); - 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 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); + } 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 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; + 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 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; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index f7847c1f6..fb1b7c853 100755 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -35,6 +35,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.LinearLayout; + import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -50,6 +51,7 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.transition.Slide; import androidx.transition.TransitionManager; + import com.blankj.utilcode.util.ClipboardUtils; import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.IntentUtils; @@ -115,8 +117,15 @@ import com.itsaky.lsp.models.InitializeParams; import com.itsaky.lsp.models.Range; import com.itsaky.toaster.Toaster; import com.unnamed.b.atv.model.TreeNode; + import io.github.rosemoe.sora.event.ContentChangeEvent; import io.github.rosemoe.sora.event.Unsubscribe; + +import me.piruin.quickaction.ActionItem; +import me.piruin.quickaction.QuickAction; + +import org.jetbrains.annotations.Contract; + import java.io.File; import java.nio.file.Paths; import java.util.ArrayList; @@ -130,1523 +139,1561 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.regex.Pattern; import java.util.stream.Collectors; -import me.piruin.quickaction.ActionItem; -import me.piruin.quickaction.QuickAction; -import org.jetbrains.annotations.Contract; public class EditorActivity extends StudioActivity - implements FileTreeFragment.FileActionListener, - TabLayout.OnTabSelectedListener, - NavigationView.OnNavigationItemSelectedListener, - DiagnosticClickListener, - IDEHandler.Provider, - EditorActivityProvider { + implements FileTreeFragment.FileActionListener, + TabLayout.OnTabSelectedListener, + NavigationView.OnNavigationItemSelectedListener, + DiagnosticClickListener, + IDEHandler.Provider, + EditorActivityProvider { - private ActivityEditorBinding mBinding; - private LayoutDiagnosticInfoBinding mDiagnosticInfoBinding; - private EditorBottomSheetTabAdapter bottomSheetTabAdapter; - private FileTreeFragment mFileTreeFragment; - private TreeNode mLastHeld; - private SymbolInputView symbolInput; - private BuildServiceHandler mBuildServiceHandler; - private FileOptionsHandler mFileOptionsHandler; - private QuickAction mTabCloseAction; - private TextSheetFragment mDaemonStatusFragment; - private OptionsListFragment mFileOptionsFragment; - private ProgressSheet mSearchingProgress; - private AlertDialog mFindInProjectDialog; - private ActivityResultLauncher mUIDesignerLauncher; - private EditorBottomSheetBehavior mEditorBottomSheet; + private ActivityEditorBinding mBinding; + private LayoutDiagnosticInfoBinding mDiagnosticInfoBinding; + private EditorBottomSheetTabAdapter bottomSheetTabAdapter; + private FileTreeFragment mFileTreeFragment; + private TreeNode mLastHeld; + private SymbolInputView symbolInput; + private BuildServiceHandler mBuildServiceHandler; + private FileOptionsHandler mFileOptionsHandler; + private QuickAction mTabCloseAction; + private TextSheetFragment mDaemonStatusFragment; + private OptionsListFragment mFileOptionsFragment; + private ProgressSheet mSearchingProgress; + private AlertDialog mFindInProjectDialog; + private ActivityResultLauncher mUIDesignerLauncher; + private EditorBottomSheetBehavior mEditorBottomSheet; - private EditorViewModel mViewModel; + private EditorViewModel mViewModel; - private static final String TAG_FILE_OPTIONS_FRAGMENT = "file_options_fragment"; - private static final int ACTION_ID_CLOSE = 100; - private static final int ACTION_ID_OTHERS = 101; - private static final int ACTION_ID_ALL = 102; + private static final String TAG_FILE_OPTIONS_FRAGMENT = "file_options_fragment"; + private static final int ACTION_ID_CLOSE = 100; + private static final int ACTION_ID_OTHERS = 101; + private static final int ACTION_ID_ALL = 102; - public static final String EXTRA_PROJECT = "project"; - public static final String KEY_BOTTOM_SHEET_SHOWN = "editor_bottomSheetShown"; + public static final String EXTRA_PROJECT = "project"; + public static final String KEY_BOTTOM_SHEET_SHOWN = "editor_bottomSheetShown"; - private final LogReceiver mLogReceiver = new LogReceiver().setLogListener(this::appendApkLog); + private final LogReceiver mLogReceiver = new LogReceiver().setLogListener(this::appendApkLog); - /** - * MenuItem(s) that are related to the build process - * - *

These items will be disabled once the build process starts and will be enabled after the - * execution - */ - private final int[] BUILD_IDS = { - R.id.menuEditor_quickRun, - R.id.menuEditor_runDebug, - R.id.menuEditor_runRelease, - R.id.menuEditor_runBuild, - R.id.menuEditor_runBundle, - R.id.menuEditor_runClean, - R.id.menuEditor_runCleanBuild, - R.id.menuEditor_lint, - R.id.menuEditor_lintDebug, - R.id.menuEditor_lintRelease - }; + /** + * MenuItem(s) that are related to the build process + * + *

These items will be disabled once the build process starts and will be enabled after the + * execution + */ + private final int[] BUILD_IDS = { + R.id.menuEditor_quickRun, + R.id.menuEditor_runDebug, + R.id.menuEditor_runRelease, + R.id.menuEditor_runBuild, + R.id.menuEditor_runBundle, + R.id.menuEditor_runClean, + R.id.menuEditor_runCleanBuild, + R.id.menuEditor_lint, + R.id.menuEditor_lintDebug, + R.id.menuEditor_lintRelease + }; - @Override - protected View bindLayout() { - mBinding = ActivityEditorBinding.inflate(getLayoutInflater()); - mDiagnosticInfoBinding = mBinding.diagnosticInfo; - return mBinding.getRoot(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setSupportActionBar(mBinding.editorToolbar); - - mViewModel = new ViewModelProvider(this).get(EditorViewModel.class); - getProjectFromIntent(); - - mFileTreeFragment = - FileTreeFragment.newInstance(this.getAndroidProject()).setFileActionListener(this); - mDaemonStatusFragment = new TextSheetFragment().setTextSelectable(true); - - setupDrawerToggle(); - loadFragment(mFileTreeFragment); - - symbolInput = new SymbolInputView(this); - mBinding.bottomSheet.textContainer.addView(symbolInput, 0, new ViewGroup.LayoutParams(-1, -2)); - mBinding.tabs.addOnTabSelectedListener(this); - - setupEditorBottomSheet(); - createQuickActions(); - - mBuildServiceHandler = new BuildServiceHandler(this); - mFileOptionsHandler = new FileOptionsHandler(this); - - startServices(); - - KeyboardUtils.registerSoftInputChangedListener(this, __ -> onSoftInputChanged()); - registerLogReceiver(); - setupContainers(); - setupDiagnosticInfo(); - - mUIDesignerLauncher = - registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), this::onGetUIDesignerResult); - } - - private void setupDrawerToggle() { - ActionBarDrawerToggle toggle = - new ActionBarDrawerToggle( - this, - mBinding.editorDrawerLayout, - mBinding.editorToolbar, - R.string.app_name, - R.string.app_name); - mBinding.editorDrawerLayout.addDrawerListener(toggle); - mBinding.startNav.setNavigationItemSelectedListener(this); - toggle.syncState(); - } - - private void setupEditorBottomSheet() { - bottomSheetTabAdapter = new EditorBottomSheetTabAdapter(this); - mBinding.bottomSheet.pager.setAdapter(bottomSheetTabAdapter); - - final var mediator = - new TabLayoutMediator( - mBinding.bottomSheet.tabs, - mBinding.bottomSheet.pager, - true, - true, - (tab, position) -> tab.setText(bottomSheetTabAdapter.getTitle(position))); - - mediator.attach(); - mBinding.bottomSheet.pager.setUserInputEnabled(false); - mBinding.bottomSheet.pager.setOffscreenPageLimit( - bottomSheetTabAdapter.getItemCount() - 1); // DO not remove any views - - mEditorBottomSheet = - (EditorBottomSheetBehavior) - EditorBottomSheetBehavior.from(mBinding.bottomSheet.getRoot()); - mEditorBottomSheet.setBinding(mBinding.bottomSheet); - mEditorBottomSheet.addBottomSheetCallback( - new BottomSheetBehavior.BottomSheetCallback() { - @Override - public void onStateChanged(@NonNull View bottomSheet, int newState) { - mBinding.bottomSheet.textContainer.setVisibility( - newState == BottomSheetBehavior.STATE_EXPANDED ? View.INVISIBLE : View.VISIBLE); - } - - @Override - public void onSlide(@NonNull View bottomSheet, float slideOffset) { - mBinding.bottomSheet.textContainer.setAlpha(1f - slideOffset); - } - }); - - mBinding.bottomSheet.tabs.addOnTabSelectedListener( - new TabLayout.OnTabSelectedListener() { - - @Override - public void onTabSelected(TabLayout.Tab tab) { - final var fragment = bottomSheetTabAdapter.getFragmentAtIndex(tab.getPosition()); - if (fragment instanceof NonEditableEditorFragment) { - mBinding.bottomSheet.clearFab.show(); - } else { - mBinding.bottomSheet.clearFab.hide(); - } - } - - @Override - public void onTabUnselected(TabLayout.Tab tab) {} - - @Override - public void onTabReselected(TabLayout.Tab tab) {} - }); - - TooltipCompat.setTooltipText( - mBinding.bottomSheet.clearFab, getString(R.string.title_clear_output)); - mBinding.bottomSheet.clearFab.setOnClickListener( - v -> { - final var fragment = - bottomSheetTabAdapter.getFragmentAtIndex( - mBinding.bottomSheet.tabs.getSelectedTabPosition()); - if (fragment instanceof NonEditableEditorFragment) { - final var editor = (NonEditableEditorFragment) fragment; - if (editor.getEditor() != null) { - editor.getEditor().setText(""); - } - } - }); - - if (!getApp().getPrefManager().getBoolean(KEY_BOTTOM_SHEET_SHOWN) - && mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) { - mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED); - new Handler(Looper.getMainLooper()) - .postDelayed( - () -> { - mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED); - getApp().getPrefManager().putBoolean(KEY_BOTTOM_SHEET_SHOWN, true); - }, - 1500); - } - } - - @Override - public void onBackPressed() { - if (mBinding.getRoot().isDrawerOpen(GravityCompat.END)) { - mBinding.getRoot().closeDrawer(GravityCompat.END); - } else if (mBinding.getRoot().isDrawerOpen(GravityCompat.START)) { - mBinding.getRoot().closeDrawer(GravityCompat.START); - } else if (getDaemonStatusFragment().isShowing()) { - getDaemonStatusFragment().dismiss(); - } else if (mFileOptionsFragment != null && mFileOptionsFragment.isShowing()) { - mFileOptionsFragment.dismiss(); - } else if (mEditorBottomSheet.getState() == BottomSheetBehavior.STATE_EXPANDED) { - mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED); - } else { - confirmProjectClose(); - } - } - - @Override - protected void onPause() { - dispatchOnPauseToEditors(); - super.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - - try { - checkForCompilerModule(); - dispatchOnResumeToEditors(); - mFileTreeFragment.listProjectFiles(); - } catch (Throwable th) { - LOG.error("Failed to update files list", th); - getApp().toast(R.string.msg_failed_list_files, Toaster.Type.ERROR); - } - } - - @Override - protected void onDestroy() { - closeProject(false); - try { - unregisterReceiver(mLogReceiver); - } catch (Throwable th) { - LOG.error("Failed to unregister LogReceiver", th); - } - super.onDestroy(); - } - - @Override - @SuppressLint("AlwaysShowAction") - public boolean onPrepareOptionsMenu(Menu menu) { - for (int id : BUILD_IDS) { - MenuItem item = menu.findItem(id); - if (item != null) { - boolean enabled = getBuildService() != null && !getBuildService().isBuilding(); - item.setEnabled(enabled); - item.getIcon().setAlpha(enabled ? 255 : 76); - } + @Override + protected View bindLayout() { + mBinding = ActivityEditorBinding.inflate(getLayoutInflater()); + mDiagnosticInfoBinding = mBinding.diagnosticInfo; + return mBinding.getRoot(); } - MenuItem run1 = menu.findItem(R.id.menuEditor_quickRun); - MenuItem run2 = menu.findItem(R.id.menuEditor_run); - MenuItem undo = menu.findItem(R.id.menuEditor_undo); - MenuItem redo = menu.findItem(R.id.menuEditor_redo); - MenuItem save = menu.findItem(R.id.menuEditor_save); - MenuItem def = menu.findItem(R.id.menuEditor_gotoDefinition); - MenuItem ref = menu.findItem(R.id.menuEditor_findReferences); - MenuItem comment = menu.findItem(R.id.menuEditor_commentLine); - MenuItem uncomment = menu.findItem(R.id.menuEditor_uncommentLine); - MenuItem findFile = menu.findItem(R.id.menuEditor_findFile); - MenuItem viewLayout = menu.findItem(R.id.menuEditor_viewLayout); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setSupportActionBar(mBinding.editorToolbar); - if (KeyboardUtils.isSoftInputVisible(this)) { - run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - undo.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + mViewModel = new ViewModelProvider(this).get(EditorViewModel.class); + getProjectFromIntent(); - viewLayout.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - } else { - run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - undo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + mFileTreeFragment = + FileTreeFragment.newInstance(this.getAndroidProject()).setFileActionListener(this); + mDaemonStatusFragment = new TextSheetFragment().setTextSelectable(true); - viewLayout.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + setupDrawerToggle(); + loadFragment(mFileTreeFragment); + + symbolInput = new SymbolInputView(this); + mBinding.bottomSheet.textContainer.addView( + symbolInput, 0, new ViewGroup.LayoutParams(-1, -2)); + mBinding.tabs.addOnTabSelectedListener(this); + + setupEditorBottomSheet(); + createQuickActions(); + + mBuildServiceHandler = new BuildServiceHandler(this); + mFileOptionsHandler = new FileOptionsHandler(this); + + startServices(); + + KeyboardUtils.registerSoftInputChangedListener(this, __ -> onSoftInputChanged()); + registerLogReceiver(); + setupContainers(); + setupDiagnosticInfo(); + + mUIDesignerLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + this::onGetUIDesignerResult); } - final var editor = getCurrentEditor(); - final var file = editor != null ? editor.getFile() : null; - final var notNull = editor != null && file != null; - final var isJava = notNull && file.getName().endsWith(".java"); - final var isXml = notNull && file.getName().endsWith(".xml"); - final var filesModified = notNull && mViewModel != null && mViewModel.areFilesModified(); - final var isLayout = - isXml - && file.getParentFile() != null - && Pattern.compile(FileOptionsHandler.LAYOUT_RES_PATH_REGEX) - .matcher(file.getParentFile().getAbsolutePath()) - .matches(); - final var nullableAlpha = notNull ? 255 : 76; - final var javaFileAlpha = isJava ? 255 : 76; - final var layoutFileAlpha = isLayout ? 255 : 76; - final var saveAlpha = filesModified ? 255 : 76; - - undo.setEnabled(notNull); - redo.setEnabled(notNull); - save.setEnabled(filesModified); - comment.setEnabled(notNull); - uncomment.setEnabled(notNull); - findFile.setEnabled(notNull); - def.setEnabled(isJava); - ref.setEnabled(isJava); - - undo.getIcon().setAlpha(nullableAlpha); - redo.getIcon().setAlpha(nullableAlpha); - save.getIcon().setAlpha(saveAlpha); - comment.getIcon().setAlpha(nullableAlpha); - uncomment.getIcon().setAlpha(nullableAlpha); - findFile.getIcon().setAlpha(nullableAlpha); - def.getIcon().setAlpha(javaFileAlpha); - ref.getIcon().setAlpha(javaFileAlpha); - - viewLayout.setEnabled(isLayout); - viewLayout.getIcon().setAlpha(layoutFileAlpha); - viewLayout.setShowAsActionFlags( - isLayout ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER); - - return true; - } - - @SuppressLint("RestrictedApi") - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (menu instanceof MenuBuilder) { - MenuBuilder builder = (MenuBuilder) menu; - builder.setOptionalIconsVisible(true); + private void setupDrawerToggle() { + ActionBarDrawerToggle toggle = + new ActionBarDrawerToggle( + this, + mBinding.editorDrawerLayout, + mBinding.editorToolbar, + R.string.app_name, + R.string.app_name); + mBinding.editorDrawerLayout.addDrawerListener(toggle); + mBinding.startNav.setNavigationItemSelectedListener(this); + toggle.syncState(); } - getMenuInflater().inflate(R.menu.menu_editor, menu); - return true; - } + private void setupEditorBottomSheet() { + bottomSheetTabAdapter = new EditorBottomSheetTabAdapter(this); + mBinding.bottomSheet.pager.setAdapter(bottomSheetTabAdapter); - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (getBuildService() == null) { - LOG.error("Build service is null. Cannot perform option menu actions."); - return false; + final var mediator = + new TabLayoutMediator( + mBinding.bottomSheet.tabs, + mBinding.bottomSheet.pager, + true, + true, + (tab, position) -> tab.setText(bottomSheetTabAdapter.getTitle(position))); + + mediator.attach(); + mBinding.bottomSheet.pager.setUserInputEnabled(false); + mBinding.bottomSheet.pager.setOffscreenPageLimit( + bottomSheetTabAdapter.getItemCount() - 1); // DO not remove any views + + mEditorBottomSheet = + (EditorBottomSheetBehavior) + EditorBottomSheetBehavior.from(mBinding.bottomSheet.getRoot()); + mEditorBottomSheet.setBinding(mBinding.bottomSheet); + mEditorBottomSheet.addBottomSheetCallback( + new BottomSheetBehavior.BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + mBinding.bottomSheet.textContainer.setVisibility( + newState == BottomSheetBehavior.STATE_EXPANDED + ? View.INVISIBLE + : View.VISIBLE); + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + mBinding.bottomSheet.textContainer.setAlpha(1f - slideOffset); + } + }); + + mBinding.bottomSheet.tabs.addOnTabSelectedListener( + new TabLayout.OnTabSelectedListener() { + + @Override + public void onTabSelected(TabLayout.Tab tab) { + final var fragment = + bottomSheetTabAdapter.getFragmentAtIndex(tab.getPosition()); + if (fragment instanceof NonEditableEditorFragment) { + mBinding.bottomSheet.clearFab.show(); + } else { + mBinding.bottomSheet.clearFab.hide(); + } + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) {} + + @Override + public void onTabReselected(TabLayout.Tab tab) {} + }); + + TooltipCompat.setTooltipText( + mBinding.bottomSheet.clearFab, getString(R.string.title_clear_output)); + mBinding.bottomSheet.clearFab.setOnClickListener( + v -> { + final var fragment = + bottomSheetTabAdapter.getFragmentAtIndex( + mBinding.bottomSheet.tabs.getSelectedTabPosition()); + if (fragment instanceof NonEditableEditorFragment) { + final var editor = (NonEditableEditorFragment) fragment; + if (editor.getEditor() != null) { + editor.getEditor().setText(""); + } + } + }); + + if (!getApp().getPrefManager().getBoolean(KEY_BOTTOM_SHEET_SHOWN) + && mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) { + mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED); + new Handler(Looper.getMainLooper()) + .postDelayed( + () -> { + mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED); + getApp().getPrefManager().putBoolean(KEY_BOTTOM_SHEET_SHOWN, true); + }, + 1500); + } } - int id = item.getItemId(); - if (id == R.id.menuEditor_runDebug || id == R.id.menuEditor_quickRun) { - getBuildServiceHandler().assembleDebug(true); - } else if (id == R.id.menuEditor_runRelease) { - getBuildService().assembleRelease(); - } else if (id == R.id.menuEditor_runClean) { - getBuildService().clean(); - } else if (id == R.id.menuEditor_runCleanBuild) { - getBuildService().cleanAndRebuild(); - } else if (id == R.id.menuEditor_runStopDaemons) { - getBuildService().stopAllDaemons(); - } else if (id == R.id.menuEditor_runBuild) { - getBuildService().build(); - } else if (id == R.id.menuEditor_runBundle) { - getBuildService().bundle(); - } else if (id == R.id.menuEditor_lint) { - getBuildService().lint(); - } else if (id == R.id.menuEditor_lintDebug) { - getBuildService().lintDebug(); - } else if (id == R.id.menuEditor_lintRelease) { - getBuildService().lintRelease(); - } else if (id == R.id.menuEditor_save) { - saveAll(true, true); - } else if (id == R.id.menuEditor_findProject) { - AlertDialog d = getFindInProjectDialog(); - if (d != null) { - d.show(); - } - } else if (id == R.id.menuEditor_viewLayout) { - previewLayout(); - } else if (id == R.id.menuEditor_daemonStatus) { - showDaemonStatus(); - } else if (getCurrentEditor() != null) { // Should be checked at last - if (id == R.id.menuEditor_undo) { - this.getCurrentEditor().undo(); - } else if (id == R.id.menuEditor_redo) { - this.getCurrentEditor().redo(); - } else if (id == R.id.menuEditor_gotoDefinition) { - this.getCurrentEditor().findDefinition(); - } else if (id == R.id.menuEditor_findReferences) { - this.getCurrentEditor().findReferences(); - } else if (id == R.id.menuEditor_commentLine) { - this.getCurrentEditor().commentLine(); - } else if (id == R.id.menuEditor_uncommentLine) { - this.getCurrentEditor().uncommentLine(); - } else if (id == R.id.menuEditor_findFile) { - this.getCurrentEditor().beginSearch(); - } - } - - invalidateOptionsMenu(); - return true; - } - - @Override - public EditorActivity provide() { - return this; - } - - @Override - public EditorActivity provideEditorActivity() { - return this; - } - - @Override - public AndroidProject provideAndroidProject() { - return getAndroidProject(); - } - - @Override - public IDEProject provideIDEProject() { - return getIDEProject(); - } - - public void handleSearchResults(Map> results) { - setSearchResultAdapter( - new com.itsaky.androidide.adapters.SearchListAdapter( - results, - file -> { - openFile(file); - hideViewOptions(); - }, - match -> { - openFileAndSelect(match.file, match); - hideViewOptions(); - })); - - showSearchResults(); - - if (mSearchingProgress != null && mSearchingProgress.isShowing()) { - mSearchingProgress.dismiss(); - } - } - - public void appendApkLog(LogLine line) { - bottomSheetTabAdapter.getLogFragment().appendLog(line); - } - - public void setIDEProject(IDEProject project) { - if (mViewModel != null) { - mViewModel.setIDEProject(project); - } - } - - public void appendBuildOut(final String str) { - bottomSheetTabAdapter.getBuildOutputFragment().appendOutput(str); - } - - public void showDaemonStatus() { - ShellServer shell = getApp().newShell(t -> getDaemonStatusFragment().append(t)); - shell.bgAppend(String.format("echo '%s'", getString(R.string.msg_getting_daemom_status))); - shell.bgAppend( - String.format( - "cd '%s' && sh gradlew --status", - Objects.requireNonNull(getAndroidProject()).getProjectPath())); - if (!getDaemonStatusFragment().isShowing()) { - getDaemonStatusFragment().show(getSupportFragmentManager(), "daemon_status"); - } - } - - public void hideViewOptions() { - if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_COLLAPSED) { - mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED); - } - } - - public void showSearchResults() { - if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) { - mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED); - } - - final int index = bottomSheetTabAdapter.findIndexOfFragmentByClass(SearchResultFragment.class); - if (index >= 0 && index < mBinding.bottomSheet.tabs.getTabCount()) { - final var tab = mBinding.bottomSheet.tabs.getTabAt(index); - if (tab != null) { - tab.select(); - } - } - } - - public void handleDiagnosticsResultVisibility(boolean errorVisible) { - runOnUiThread( - () -> bottomSheetTabAdapter.getDiagnosticsFragment().handleResultVisibility(errorVisible)); - } - - public void handleSearchResultVisibility(boolean errorVisible) { - runOnUiThread( - () -> bottomSheetTabAdapter.getSearchResultFragment().handleResultVisibility(errorVisible)); - } - - public void setStatus(final CharSequence text) { - try { - runOnUiThread(() -> mBinding.bottomSheet.statusText.setText(text)); - } catch (Throwable th) { - LOG.error("Failed to update status text", th); - } - } - - public void showFirstBuildNotice() { - DialogUtils.newMaterialDialogBuilder(this) - .setPositiveButton(android.R.string.ok, null) - .setTitle(R.string.title_first_build) - .setMessage(R.string.msg_first_build) - .setCancelable(false) - .create() - .show(); - } - - @Override - public void onGroupClick(DiagnosticGroup group) { - if (group != null - && group.file != null - && group.file.exists() - && FileUtils.isUtf8(group.file)) { - openFile(group.file); - hideViewOptions(); - } - } - - @Override - public void onDiagnosticClick(File file, @NonNull DiagnosticItem diagnostic) { - openFileAndSelect(file, diagnostic.getRange()); - hideViewOptions(); - } - - public ActivityEditorBinding getBinding() { - return mBinding; - } - - public LayoutDiagnosticInfoBinding getDiagnosticBinding() { - return mDiagnosticInfoBinding; - } - - public void openFileAndSelect(File file, Range selection) { - openFile(file, selection); - final var opened = getEditorForFile(file); - if (opened != null && opened.getEditor() != null) { - final var editor = opened.getEditor(); - final var range = editor.validateRange(selection); - editor.post( - () -> { - if (LSPUtils.isEqual(range.getStart(), range.getEnd())) { - editor.setSelection(range.getStart().getLine(), range.getEnd().getColumn()); - } else { - editor.setSelectionRegion( - range.getStart().getLine(), - range.getStart().getColumn(), - range.getEnd().getLine(), - range.getEnd().getColumn()); - } - }); - } - } - - @Override - public void onTabSelected(@NonNull TabLayout.Tab tab) { - final var position = tab.getPosition(); - mBinding.editorContainer.setDisplayedChild(position); - - final var editorView = getEditorAtIndex(position); - mViewModel.setCurrentFile(position, Objects.requireNonNull(editorView).getFile()); - refreshSymbolInput(editorView); - invalidateOptionsMenu(); - } - - @Override - public void onTabUnselected(@NonNull TabLayout.Tab tab) { - // unimplemented - } - - @Override - public void onTabReselected(@NonNull TabLayout.Tab tab) { - mTabCloseAction.show(tab.view); - } - - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem p1) { - final int id = p1.getItemId(); - if (id == R.id.editornav_discuss) { - getApp().openTelegramGroup(); - } else if (id == R.id.editornav_suggest) { - getApp().openGitHub(); - } else if (id == R.id.editornav_needHelp) { - showNeedHelpDialog(); - } else if (id == R.id.editornav_settings) { - startActivity(new Intent(this, PreferencesActivity.class)); - } else if (id == R.id.editornav_share) { - startActivity(IntentUtils.getShareTextIntent(getString(R.string.msg_share_app))); - } else if (id == R.id.editornav_close_project) { - confirmProjectClose(); - } else if (id == R.id.editornav_terminal) { - openTerminal(); - } - mBinding.getRoot().closeDrawer(GravityCompat.START); - return false; - } - - public void loadFragment(Fragment fragment) { - super.loadFragment(fragment, mBinding.editorFrameLayout.getId()); - } - - public FileTreeFragment getFileTreeFragment() { - return mFileTreeFragment; - } - - public TreeNode getLastHoldTreeNode() { - return mLastHeld; - } - - public OptionsListFragment getFileOptionsFragment(File file) { - mFileOptionsFragment = new OptionsListFragment(); - mFileOptionsFragment.addOption( - new SheetOption(0, R.drawable.ic_file_copy_path, R.string.copy_path, file)); - mFileOptionsFragment.addOption( - new SheetOption(1, R.drawable.ic_file_rename, R.string.rename_file, file)); - mFileOptionsFragment.addOption( - new SheetOption(2, R.drawable.ic_delete, R.string.delete_file, file)); - if (file.isDirectory()) { - mFileOptionsFragment.addOption( - new SheetOption(3, R.drawable.ic_new_file, R.string.new_file, file)); - mFileOptionsFragment.addOption( - new SheetOption(4, R.drawable.ic_new_folder, R.string.new_folder, file)); - } - mFileOptionsFragment.setOnOptionsClickListener(mFileOptionsHandler); - return mFileOptionsFragment; - } - - public ProgressSheet getProgressSheet(int msg) { - if (mSearchingProgress != null && mSearchingProgress.isShowing()) { - mSearchingProgress.dismiss(); - } - mSearchingProgress = new ProgressSheet(); - mSearchingProgress.setCancelable(false); - mSearchingProgress.setWelcomeTextEnabled(false); - mSearchingProgress.setMessage(getString(msg)); - mSearchingProgress.setSubMessageEnabled(false); - return mSearchingProgress; - } - - public MaterialBanner getSyncBanner() { - return mBinding - .syncBanner - .setContentTextColor(ContextCompat.getColor(this, R.color.primaryTextColor)) - .setBannerBackgroundColor(ContextCompat.getColor(this, R.color.primaryLightColor)) - .setButtonTextColor(ContextCompat.getColor(this, R.color.secondaryColor)) - .setIcon(R.drawable.ic_sync) - .setContentText(R.string.msg_sync_needed); - } - - public TextSheetFragment getDaemonStatusFragment() { - return mDaemonStatusFragment == null - ? mDaemonStatusFragment = - new TextSheetFragment() - .setTextSelectable(true) - .setTitleText(R.string.gradle_daemon_status) - : mDaemonStatusFragment; - } - - public void setDiagnosticsAdapter(@NonNull final DiagnosticsAdapter adapter) { - runOnUiThread(() -> bottomSheetTabAdapter.getDiagnosticsFragment().setAdapter(adapter)); - } - - public void setSearchResultAdapter(@NonNull final SearchListAdapter adapter) { - runOnUiThread(() -> bottomSheetTabAdapter.getSearchResultFragment().setAdapter(adapter)); - } - - @Override - public void showFileOptions(File thisFile, TreeNode node) { - mLastHeld = node; - getFileOptionsFragment(thisFile).show(getSupportFragmentManager(), TAG_FILE_OPTIONS_FRAGMENT); - } - - @SuppressWarnings("UnusedReturnValue") - public boolean saveAll() { - return saveAll(true); - } - - public boolean saveAll(boolean notify) { - return saveAll(notify, false); - } - - public boolean saveAll(boolean notify, boolean canProcessResources) { - SaveResult result = saveAllResult(); - - if (notify) { - getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS); - } - - if (result.gradleSaved) { - notifySyncNeeded(); - } - - if (result.xmlSaved && canProcessResources && getBuildService() != null) { - getBuildService().updateResourceClasses(); - } - - return result.gradleSaved; - } - - public SaveResult saveAllResult() { - SaveResult result = new SaveResult(); - for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { - saveResult(i, result); - } - - return result; - } - - public void saveResult(int index, SaveResult result) { - if (index >= 0 && index < mViewModel.getOpenedFileCount()) { - var frag = getEditorAtIndex(index); - if (frag == null || frag.getFile() == null) { - return; - } - - // Must be called before frag.save() - // Otherwise, it'll always return false - final boolean modified = frag.isModified(); - - frag.save(); - - final boolean isGradle = frag.getFile().getName().endsWith(".gradle"); - final boolean isXml = frag.getFile().getName().endsWith(".xml"); - - if (!result.gradleSaved) { - result.gradleSaved = modified && isGradle; - } - - if (!result.xmlSaved) { - result.xmlSaved = modified && isXml; - } - } - - var modified = false; - for (var file : mViewModel.getOpenedFiles()) { - var editor = getEditorForFile(file); - if (editor == null) { - continue; - } - modified = modified || editor.isModified(); - } - - mViewModel.setFilesModified(modified); - } - - public void install(@NonNull File apk) { - if (apk.exists()) { - Intent i = IntentUtils.getInstallAppIntent(apk); - if (i != null) { - startActivity(i); - } else { - getApp().toast(R.string.msg_apk_install_intent_failed, Toaster.Type.ERROR); - } - } - } - - public void updateServices() { - new TaskExecutor() - .executeAsync( - () -> { - List cps = Objects.requireNonNull(getAndroidProject()).getClassPaths(); - getApp() - .getJavaLanguageServer() - .configurationChanged( - new JavaServerConfiguration( - cps.stream().map(Paths::get).collect(Collectors.toSet()))); - return null; - }, - __ -> setStatus(getString(R.string.msg_service_started))); - } - - @Override - public CodeEditorView openFile(File file) { - return openFile(file, null); - } - - public CodeEditorView openFile(File file, com.itsaky.lsp.models.Range selection) { - if (selection == null) { - selection = com.itsaky.lsp.models.Range.NONE; - } - - int index = openFileAndGetIndex(file, selection); - final var tab = mBinding.tabs.getTabAt(index); - if (tab != null && index >= 0 && !tab.isSelected()) { - tab.select(); - } - - if (mBinding.editorDrawerLayout.isDrawerOpen(GravityCompat.END)) { - mBinding.editorDrawerLayout.closeDrawer(GravityCompat.END); - } - - mBinding.editorContainer.setDisplayedChild(index); - - invalidateOptionsMenu(); - try { - return getEditorAtIndex(index); - } catch (Throwable th) { - LOG.error("Unable to get editor fragment at opened file index", index); - LOG.error(th); - return null; - } - } - - @SuppressLint("NotifyDataSetChanged") - public int openFileAndGetIndex(File file, com.itsaky.lsp.models.Range selection) { - final var openedFileIndex = findIndexOfEditorByFile(file); - - if (openedFileIndex != -1) { - LOG.error("File is already opened. File: " + file); - return openedFileIndex; - } - - final var position = mViewModel.getOpenedFileCount(); - - LOG.info("Opening file at index:", position, "file: ", file); - - final var editor = new CodeEditorView(this, file, selection); - editor.getEditor().subscribeEvent(ContentChangeEvent.class, this::onEditorContentChanged); - editor.setLayoutParams( - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - - mBinding.editorContainer.addView(editor); - mBinding.editorContainer.setDisplayedChild(position); - mBinding.tabs.addTab(mBinding.tabs.newTab().setText(file.getName())); - - mViewModel.addFile(file); - mViewModel.setCurrentFile(position, file); - - return position; - } - - public void closeFile(int index) { - if (index >= 0 && index < mViewModel.getOpenedFileCount()) { - var opened = mViewModel.getOpenedFile(index); - LOG.info("Closing file:", opened); - final var editor = getEditorAtIndex(index); - - if (editor != null && editor.isModified()) { - notifyFilesUnsaved(Collections.singletonList(editor), () -> closeFile(index)); - return; - } - - if (editor != null && editor.getEditor() != null) { - editor.getEditor().close(); - } else { - LOG.error("Cannot save file before close. Editor instance is null"); - } - - mViewModel.removeFile(index); - mBinding.tabs.removeTabAt(index); - mBinding.editorContainer.removeViewAt(index); - } else { - LOG.error("Invalid file index. Cannot close."); - return; - } - - mBinding.tabs.requestLayout(); - invalidateOptionsMenu(); - } - - public void closeAll() { - closeAll(null); - } - - /** - * Close all opened files. If all the files are saved successfully, then the provided {@link - * Runnable} will be called. - * - * @param onSaved The {@link Runnable} to run when all the files are saved. - */ - public void closeAll(Runnable onSaved) { - final var count = mViewModel.getOpenedFileCount(); - final var unsavedFiles = - mViewModel.getOpenedFiles().stream() - .map(this::getEditorForFile) - .filter(editor -> editor != null && editor.isModified()) - .collect(Collectors.toList()); - - if (unsavedFiles.isEmpty()) { - // Files were already saved, close all files one by one - for (int i = 0; i < count; i++) { - final var editor = getEditorAtIndex(i); - if (editor != null && editor.getEditor() != null) { - editor.getEditor().close(); + @Override + public void onBackPressed() { + if (mBinding.getRoot().isDrawerOpen(GravityCompat.END)) { + mBinding.getRoot().closeDrawer(GravityCompat.END); + } else if (mBinding.getRoot().isDrawerOpen(GravityCompat.START)) { + mBinding.getRoot().closeDrawer(GravityCompat.START); + } else if (getDaemonStatusFragment().isShowing()) { + getDaemonStatusFragment().dismiss(); + } else if (mFileOptionsFragment != null && mFileOptionsFragment.isShowing()) { + mFileOptionsFragment.dismiss(); + } else if (mEditorBottomSheet.getState() == BottomSheetBehavior.STATE_EXPANDED) { + mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED); } else { - LOG.error("Unable to close file at index:", i); + confirmProjectClose(); } - } - - mViewModel.removeAllFiles(); - mBinding.tabs.removeAllTabs(); - mBinding.tabs.requestLayout(); - mBinding.editorContainer.removeAllViews(); - - invalidateOptionsMenu(); - - if (onSaved != null) { - onSaved.run(); - } - } else { - // There are unsaved files - notifyFilesUnsaved(unsavedFiles, () -> closeAll(onSaved)); } - } - private void notifyFilesUnsaved(List unsavedEditors, Runnable invokeAfter) { - //noinspection ConstantConditions - final var mapped = - unsavedEditors.stream() - .map(CodeEditorView::getFile) - .filter(Objects::nonNull) - .map(File::getAbsolutePath) - .collect(Collectors.toList()); - final var builder = - DialogUtils.newYesNoDialog( - this, - getString(R.string.title_files_unsaved), // title - getString(R.string.msg_files_unsaved, TextUtils.join("\n", mapped)), // message - (dialog, which) -> { // 'yes' click - dialog.dismiss(); - saveAll(true); - invokeAfter.run(); - }, - (dialog, which) -> { // 'no' click - dialog.dismiss(); - // Mark all the files as saved, then try to close them all - for (var editor : unsavedEditors) { - editor.markAsSaved(); - } + @Override + protected void onPause() { + dispatchOnPauseToEditors(); + super.onPause(); + } - invokeAfter.run(); - }); - builder.show(); - } + @Override + protected void onResume() { + super.onResume(); - public void closeOthers() { - final var unsavedFiles = - mViewModel.getOpenedFiles().stream() - .map(this::getEditorForFile) - .filter(editor -> editor != null && editor.isModified()) - .collect(Collectors.toList()); + try { + checkForCompilerModule(); + dispatchOnResumeToEditors(); + mFileTreeFragment.listProjectFiles(); + } catch (Throwable th) { + LOG.error("Failed to update files list", th); + getApp().toast(R.string.msg_failed_list_files, Toaster.Type.ERROR); + } + } - if (unsavedFiles.isEmpty()) { - final var file = mViewModel.getCurrentFile(); - for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { - final var editor = getEditorAtIndex(i); + @Override + protected void onDestroy() { + closeProject(false); + try { + unregisterReceiver(mLogReceiver); + } catch (Throwable th) { + LOG.error("Failed to unregister LogReceiver", th); + } + super.onDestroy(); + } - // Index of files changes as we keep close files - // So we compare the files instead of index - if (editor != null) { - if (!file.equals(editor.getFile())) { - closeFile(i); - } + @Override + @SuppressLint("AlwaysShowAction") + public boolean onPrepareOptionsMenu(Menu menu) { + for (int id : BUILD_IDS) { + MenuItem item = menu.findItem(id); + if (item != null) { + boolean enabled = getBuildService() != null && !getBuildService().isBuilding(); + item.setEnabled(enabled); + item.getIcon().setAlpha(enabled ? 255 : 76); + } + } + + MenuItem run1 = menu.findItem(R.id.menuEditor_quickRun); + MenuItem run2 = menu.findItem(R.id.menuEditor_run); + MenuItem undo = menu.findItem(R.id.menuEditor_undo); + MenuItem redo = menu.findItem(R.id.menuEditor_redo); + MenuItem save = menu.findItem(R.id.menuEditor_save); + MenuItem def = menu.findItem(R.id.menuEditor_gotoDefinition); + MenuItem ref = menu.findItem(R.id.menuEditor_findReferences); + MenuItem comment = menu.findItem(R.id.menuEditor_commentLine); + MenuItem uncomment = menu.findItem(R.id.menuEditor_uncommentLine); + MenuItem findFile = menu.findItem(R.id.menuEditor_findFile); + MenuItem viewLayout = menu.findItem(R.id.menuEditor_viewLayout); + + if (KeyboardUtils.isSoftInputVisible(this)) { + run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + undo.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + + viewLayout.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } else { - LOG.error("Unable to save file at index:", i); + run1.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + run2.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + undo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + redo.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + + viewLayout.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } - } - } else { - notifyFilesUnsaved(unsavedFiles, this::closeOthers); - } - } - @Nullable - public CodeEditorView getCurrentEditor() { - if (mViewModel.getCurrentFileIndex() != -1) { - return getEditorAtIndex(mViewModel.getCurrentFileIndex()); + final var editor = getCurrentEditor(); + final var file = editor != null ? editor.getFile() : null; + final var notNull = editor != null && file != null; + final var isJava = notNull && file.getName().endsWith(".java"); + final var isXml = notNull && file.getName().endsWith(".xml"); + final var filesModified = notNull && mViewModel != null && mViewModel.areFilesModified(); + final var isLayout = + isXml + && file.getParentFile() != null + && Pattern.compile(FileOptionsHandler.LAYOUT_RES_PATH_REGEX) + .matcher(file.getParentFile().getAbsolutePath()) + .matches(); + final var nullableAlpha = notNull ? 255 : 76; + final var javaFileAlpha = isJava ? 255 : 76; + final var layoutFileAlpha = isLayout ? 255 : 76; + final var saveAlpha = filesModified ? 255 : 76; + + undo.setEnabled(notNull); + redo.setEnabled(notNull); + save.setEnabled(filesModified); + comment.setEnabled(notNull); + uncomment.setEnabled(notNull); + findFile.setEnabled(notNull); + def.setEnabled(isJava); + ref.setEnabled(isJava); + + undo.getIcon().setAlpha(nullableAlpha); + redo.getIcon().setAlpha(nullableAlpha); + save.getIcon().setAlpha(saveAlpha); + comment.getIcon().setAlpha(nullableAlpha); + uncomment.getIcon().setAlpha(nullableAlpha); + findFile.getIcon().setAlpha(nullableAlpha); + def.getIcon().setAlpha(javaFileAlpha); + ref.getIcon().setAlpha(javaFileAlpha); + + viewLayout.setEnabled(isLayout); + viewLayout.getIcon().setAlpha(layoutFileAlpha); + viewLayout.setShowAsActionFlags( + isLayout ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER); + + return true; } - return null; - } - - @Nullable - public CodeEditorView getEditorAtIndex(final int index) { - return (CodeEditorView) mBinding.editorContainer.getChildAt(index); - } - - @Nullable - public CodeEditorView getEditorForFile(@NonNull final File file) { - for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { - final CodeEditorView editor = (CodeEditorView) mBinding.editorContainer.getChildAt(i); - if (file.equals(editor.getFile())) { - return editor; - } - } - - return null; - } - - public int findIndexOfEditorByFile(File file) { - if (file == null) { - LOG.error("Cannot find index of a null file."); - return -1; - } - - for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { - final var opened = mViewModel.getOpenedFile(i); - if (opened.equals(file)) { - return i; - } - } - - return -1; - } - - ///////////////////////////////////////////////// - ////////////// PRIVATE APIS ///////////////////// - ///////////////////////////////////////////////// - - private void onEditorContentChanged(ContentChangeEvent event, Unsubscribe unsubscribe) { - if (event.getAction() != ContentChangeEvent.ACTION_SET_NEW_TEXT) { - mViewModel.setFilesModified(true); - } - } - - @SuppressWarnings("deprecation") - private void checkForCompilerModule() { - if (!Environment.isCompilerModuleInstalled()) { - final var pd = - ProgressDialog.show( - this, - getString(R.string.title_compiler_module_install), - getString(R.string.msg_compiler_module_install), - true, - false); - - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> { - final var tmpModule = new File(Environment.TMP_DIR, "compiler-module.zip"); - if (!ResourceUtils.copyFileFromAssets( - ToolsManager.getCommonAsset("compiler-module.zip"), - tmpModule.getAbsolutePath())) { - throw new CompletionException( - new RuntimeException("Unable to copy compiler-module.zip")); - } - - try { - ZipUtils.unzipFile(tmpModule, Environment.COMPILER_MODULE); - } catch (Throwable e) { - throw new CompletionException(e); - } - - if (!Environment.isCompilerModuleInstalled()) { - throw new CompletionException(new RuntimeException("Unknown error")); - } - - try { - FileUtils.delete(tmpModule); - } catch (Exception e) { - // ignored - } - - return true; - }); - - future.whenComplete( - (result, error) -> { - pd.dismiss(); - - if (error != null) { - showCompilerModuleInstallError(error); - return; - } - - getApp().toast(getString(R.string.msg_compiler_module_installed), Toaster.Type.SUCCESS); - }); - } - } - - private void showCompilerModuleInstallError(Throwable error) { - final var stacktrace = ThrowableUtils.getFullStackTrace(error); - final var builder = DialogUtils.newMaterialDialogBuilder(this); - builder.setTitle(R.string.title_installation_failed); - builder.setMessage(getString(R.string.msg_compiler_module_install_failed, stacktrace)); - builder.setCancelable(false); - builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); - builder.setNegativeButton( - R.string.copy, - (dialog, which) -> { - ClipboardUtils.copyText(stacktrace); - dialog.dismiss(); - }); - builder.show(); - } - - private void dispatchOnPauseToEditors() { - CompletableFuture.runAsync( - () -> { - for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { - final var editor = getEditorAtIndex(i); - if (editor != null) { - editor.onPause(); - } - } - }); - } - - private void dispatchOnResumeToEditors() { - CompletableFuture.runAsync( - () -> { - for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { - final var editor = getEditorAtIndex(i); - if (editor != null) { - editor.onResume(); - } - } - }); - } - - @Contract(pure = true) - private void onGetUIDesignerResult(@NonNull ActivityResult result) { - final var index = mBinding.editorContainer.getDisplayedChild(); - final var editor = getEditorAtIndex(index); - if (editor != null && result.getResultCode() == RESULT_OK) { - final var data = result.getData(); - if (data != null && data.hasExtra(DesignerActivity.KEY_GENERATED_CODE)) { - final var code = data.getStringExtra(DesignerActivity.KEY_GENERATED_CODE); - editor.getEditor().setText(code); - saveAll(); - } else { - final var msg = getString(R.string.msg_invalid_designer_result); - getApp().toast(msg, Toaster.Type.ERROR); - LOG.error(msg, "Data returned by UI Designer is null or is invalid."); - } - } else { - LOG.error( - "UI Designer returned an invalid result code.", "Result code: " + result.getResultCode()); - } - } - - private void previewLayout() { - try { - - if (getCurrentEditor() == null || getCurrentEditor().getFile() == null) { - LOG.error("No file is opened. Cannot preview layout."); - return; - } - - saveAll(false); - if (getApp().getLayoutInflater() == null) { - LOG.info("Creating layout inflater instance..."); - getApp() - .createInflater( - getApp().createInflaterConfig(getContextProvider(), getResourceDirectories())); - } - - final Intent intent = new Intent(this, DesignerActivity.class); - intent.putExtra( - DesignerActivity.KEY_LAYOUT_PATH, getCurrentEditor().getFile().getAbsolutePath()); - - LOG.info("Launching UI Designer..."); - mUIDesignerLauncher.launch(intent); - } catch (Throwable th) { - LOG.error(getString(R.string.err_cannot_preview_layout), th); - getApp().toast(R.string.msg_cannot_preview_layout, Toaster.Type.ERROR); - } - } - - @NonNull - @Contract(" -> new") - private ILayoutInflater.ContextProvider getContextProvider() { - return this::provide; - } - - @NonNull - private Set getResourceDirectories() { - final Set dirs = new HashSet<>(); - if (getAndroidProject() != null - && getAndroidProject().getModulePaths() != null - && !getAndroidProject().getModulePaths().isEmpty()) { - for (String path : getAndroidProject().getModulePaths()) { - if (path != null && new File(path).exists()) { - File res = new File(path, "src/main/res"); - if (res.exists()) { - dirs.add(res); - } + @SuppressLint("RestrictedApi") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (menu instanceof MenuBuilder) { + MenuBuilder builder = (MenuBuilder) menu; + builder.setOptionalIconsVisible(true); } - } - } - return dirs; - } - private void openTerminal() { - final Intent intent = new Intent(this, TerminalActivity.class); - intent.putExtra( - TerminalActivity.KEY_WORKING_DIRECTORY, - Objects.requireNonNull(getAndroidProject()).getProjectPath()); - startActivity(intent); - } - - private void getProjectFromIntent() { - final var project = (AndroidProject) getIntent().getParcelableExtra(EXTRA_PROJECT); - if (mViewModel != null) { - mViewModel.setAndroidProject(project); - } else { - LOG.error("ViewModel is null. Cannot set project."); - } - getApp() - .getPrefManager() - .setOpenedProject(Objects.requireNonNull(this.getAndroidProject()).getProjectPath()); - - try { - //noinspection ConstantConditions - getSupportActionBar().setSubtitle(new File(getAndroidProject().getProjectPath()).getName()); - } catch (Throwable th) { - // ignored + getMenuInflater().inflate(R.menu.menu_editor, menu); + return true; } - CompletableFuture.runAsync( - () -> - ValuesTableFactory.setupWithResDirectories( - getResourceDirectories().toArray(new File[0]))); - } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (getBuildService() == null) { + LOG.error("Build service is null. Cannot perform option menu actions."); + return false; + } - @Nullable - private AndroidProject getAndroidProject() { - if (mViewModel == null) { - return null; + int id = item.getItemId(); + if (id == R.id.menuEditor_runDebug || id == R.id.menuEditor_quickRun) { + getBuildServiceHandler().assembleDebug(true); + } else if (id == R.id.menuEditor_runRelease) { + getBuildService().assembleRelease(); + } else if (id == R.id.menuEditor_runClean) { + getBuildService().clean(); + } else if (id == R.id.menuEditor_runCleanBuild) { + getBuildService().cleanAndRebuild(); + } else if (id == R.id.menuEditor_runStopDaemons) { + getBuildService().stopAllDaemons(); + } else if (id == R.id.menuEditor_runBuild) { + getBuildService().build(); + } else if (id == R.id.menuEditor_runBundle) { + getBuildService().bundle(); + } else if (id == R.id.menuEditor_lint) { + getBuildService().lint(); + } else if (id == R.id.menuEditor_lintDebug) { + getBuildService().lintDebug(); + } else if (id == R.id.menuEditor_lintRelease) { + getBuildService().lintRelease(); + } else if (id == R.id.menuEditor_save) { + saveAll(true, true); + } else if (id == R.id.menuEditor_findProject) { + AlertDialog d = getFindInProjectDialog(); + if (d != null) { + d.show(); + } + } else if (id == R.id.menuEditor_viewLayout) { + previewLayout(); + } else if (id == R.id.menuEditor_daemonStatus) { + showDaemonStatus(); + } else if (getCurrentEditor() != null) { // Should be checked at last + if (id == R.id.menuEditor_undo) { + this.getCurrentEditor().undo(); + } else if (id == R.id.menuEditor_redo) { + this.getCurrentEditor().redo(); + } else if (id == R.id.menuEditor_gotoDefinition) { + this.getCurrentEditor().findDefinition(); + } else if (id == R.id.menuEditor_findReferences) { + this.getCurrentEditor().findReferences(); + } else if (id == R.id.menuEditor_commentLine) { + this.getCurrentEditor().commentLine(); + } else if (id == R.id.menuEditor_uncommentLine) { + this.getCurrentEditor().uncommentLine(); + } else if (id == R.id.menuEditor_findFile) { + this.getCurrentEditor().beginSearch(); + } + } + + invalidateOptionsMenu(); + return true; } - return mViewModel.getAndroidProject(); - } - - @Nullable - private IDEProject getIDEProject() { - if (mViewModel == null) { - return null; + @Override + public EditorActivity provide() { + return this; } - return mViewModel.getIDEProject(); - } - - private void setupDiagnosticInfo() { - GradientDrawable gd = new GradientDrawable(); - gd.setShape(GradientDrawable.RECTANGLE); - gd.setColor(0xff212121); - gd.setStroke(1, 0xffffffff); - gd.setCornerRadius(8); - mDiagnosticInfoBinding.getRoot().setBackground(gd); - mDiagnosticInfoBinding.getRoot().setVisibility(View.GONE); - } - - private void setupContainers() { - handleDiagnosticsResultVisibility(true); - handleSearchResultVisibility(true); - } - - private void startServices() { - - if (!IDELanguageClientImpl.isInitialized()) { - IDELanguageClientImpl.initialize(this); + @Override + public EditorActivity provideEditorActivity() { + return this; } - initializeLanguageServers(); - - // Actually, we don't need to start FileOptionsHandler - // Because it would work anyway - // But still we do... - mFileOptionsHandler.start(); - mBuildServiceHandler.start(); - getBuildServiceHandler().assembleDebug(false); - } - - private void initializeLanguageServers() { - final var client = IDELanguageClientImpl.getInstance(); - final var javaLanguageServer = getApp().getJavaLanguageServer(); - final var workspaceRoots = - Collections.singleton( - new File(Objects.requireNonNull(getAndroidProject()).getProjectPath()).toPath()); - - final var params = new InitializeParams(workspaceRoots); - - javaLanguageServer.connectClient(client); - javaLanguageServer.applySettings(PrefBasedJavaServerSettings.getInstance()); - javaLanguageServer.initialize(params); - } - - private void stopServices() { - - if (IDELanguageClientImpl.isInitialized()) { - IDELanguageClientImpl.shutdown(); + @Override + public AndroidProject provideAndroidProject() { + return getAndroidProject(); } - shutdownLanguageServers(); - - if (getBuildService() != null) { - getBuildService().setListener(null); - if (mBuildServiceHandler != null) { - mBuildServiceHandler.stop(); - } else { - getBuildService().exit(); - } + @Override + public IDEProject provideIDEProject() { + return getIDEProject(); } - if (mFileOptionsHandler != null) { - mFileOptionsHandler.stop(); + public void handleSearchResults(Map> results) { + setSearchResultAdapter( + new com.itsaky.androidide.adapters.SearchListAdapter( + results, + file -> { + openFile(file); + hideViewOptions(); + }, + match -> { + openFileAndSelect(match.file, match); + hideViewOptions(); + })); + + showSearchResults(); + + if (mSearchingProgress != null && mSearchingProgress.isShowing()) { + mSearchingProgress.dismiss(); + } } - } - private void shutdownLanguageServers() { - final var javaServer = getApp().getJavaLanguageServer(); - javaServer.shutdown(); - } - - private void onSoftInputChanged() { - invalidateOptionsMenu(); - if (KeyboardUtils.isSoftInputVisible(this)) { - TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.TOP)); - symbolInput.setVisibility(View.VISIBLE); - mBinding.bottomSheet.statusText.setVisibility(View.GONE); - mBinding.bottomSheet.swipeHint.setVisibility(View.GONE); - } else { - TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.BOTTOM)); - symbolInput.setVisibility(View.GONE); - mBinding.bottomSheet.statusText.setVisibility(View.VISIBLE); - mBinding.bottomSheet.swipeHint.setVisibility(View.VISIBLE); + public void appendApkLog(LogLine line) { + bottomSheetTabAdapter.getLogFragment().appendLog(line); } - } - private void refreshSymbolInput(@NonNull CodeEditorView frag) { - symbolInput.bindEditor(frag.getEditor()); - symbolInput.setSymbols(Symbols.forFile(frag.getFile())); - } - - private void notifySyncNeeded() { - if (getBuildService() != null && !getBuildService().isBuilding()) { - getSyncBanner() - .setNegative(android.R.string.cancel, null) - .setPositive(android.R.string.ok, v -> getBuildServiceHandler().assembleDebug(false)) - .show(); + public void setIDEProject(IDEProject project) { + if (mViewModel != null) { + mViewModel.setIDEProject(project); + } } - } - private void closeProject(boolean manualFinish) { - stopServices(); - - // Make sure we close files - // This fill further make sure that file contents are not erased. - closeAll( - () -> { - getApp().getPrefManager().setOpenedProject(PreferenceManager.NO_OPENED_PROJECT); - getApp().stopAllDaemons(); - - if (manualFinish) { - finish(); - } - }); - } - - private void confirmProjectClose() { - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); - builder.setTitle(R.string.title_confirm_project_close); - builder.setMessage(R.string.msg_confirm_project_close); - builder.setNegativeButton(R.string.no, null); - builder.setPositiveButton( - R.string.yes, - (d, w) -> { - d.dismiss(); - closeProject(true); - }); - builder.show(); - } - - private AlertDialog getFindInProjectDialog() { - return mFindInProjectDialog == null ? createFindInProjectDialog() : mFindInProjectDialog; - } - - @Nullable - private AlertDialog createFindInProjectDialog() { - if (getAndroidProject() == null - || getAndroidProject().getModulePaths() == null - || getAndroidProject().getModulePaths().size() <= 0) { - getApp().toast(R.string.msg_no_modules, Toaster.Type.ERROR); - return null; + public void appendBuildOut(final String str) { + bottomSheetTabAdapter.getBuildOutputFragment().appendOutput(str); } - final List modules = getAndroidProject().getModulePaths(); - final List srcDirs = new ArrayList<>(); - final LayoutSearchProjectBinding binding = - LayoutSearchProjectBinding.inflate(getLayoutInflater()); - binding.modulesContainer.removeAllViews(); - for (int i = 0; i < modules.size(); i++) { - final File file = new File(modules.get(i)); - final File src = new File(file, "src"); - if (!file.exists() || !file.isDirectory() || !src.exists() || !src.isDirectory()) { - continue; - } - CheckBox check = new CheckBox(this); - check.setText(file.getName()); - check.setChecked(true); - - LinearLayout.MarginLayoutParams params = new LinearLayout.MarginLayoutParams(-2, -2); - params.bottomMargin = SizeUtils.dp2px(4); - binding.modulesContainer.addView(check, params); - - srcDirs.add(src); + public void showDaemonStatus() { + ShellServer shell = getApp().newShell(t -> getDaemonStatusFragment().append(t)); + shell.bgAppend(String.format("echo '%s'", getString(R.string.msg_getting_daemom_status))); + shell.bgAppend( + String.format( + "cd '%s' && sh gradlew --status", + Objects.requireNonNull(getAndroidProject()).getProjectPath())); + if (!getDaemonStatusFragment().isShowing()) { + getDaemonStatusFragment().show(getSupportFragmentManager(), "daemon_status"); + } } - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); - builder.setTitle(R.string.menu_find_project); - builder.setView(binding.getRoot()); - builder.setCancelable(false); - builder.setPositiveButton( - R.string.menu_find, - (dialog, which) -> { - final String text = - Objects.requireNonNull(binding.input.getEditText()).getText().toString().trim(); - if (text.isEmpty()) { - getApp().toast(R.string.msg_empty_search_query, Toaster.Type.ERROR); + + public void hideViewOptions() { + if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_COLLAPSED) { + mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED); + } + } + + public void showSearchResults() { + if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) { + mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED); + } + + final int index = + bottomSheetTabAdapter.findIndexOfFragmentByClass(SearchResultFragment.class); + if (index >= 0 && index < mBinding.bottomSheet.tabs.getTabCount()) { + final var tab = mBinding.bottomSheet.tabs.getTabAt(index); + if (tab != null) { + tab.select(); + } + } + } + + public void handleDiagnosticsResultVisibility(boolean errorVisible) { + runOnUiThread( + () -> + bottomSheetTabAdapter + .getDiagnosticsFragment() + .handleResultVisibility(errorVisible)); + } + + public void handleSearchResultVisibility(boolean errorVisible) { + runOnUiThread( + () -> + bottomSheetTabAdapter + .getSearchResultFragment() + .handleResultVisibility(errorVisible)); + } + + public void setStatus(final CharSequence text) { + try { + runOnUiThread(() -> mBinding.bottomSheet.statusText.setText(text)); + } catch (Throwable th) { + LOG.error("Failed to update status text", th); + } + } + + public void showFirstBuildNotice() { + DialogUtils.newMaterialDialogBuilder(this) + .setPositiveButton(android.R.string.ok, null) + .setTitle(R.string.title_first_build) + .setMessage(R.string.msg_first_build) + .setCancelable(false) + .create() + .show(); + } + + @Override + public void onGroupClick(DiagnosticGroup group) { + if (group != null + && group.file != null + && group.file.exists() + && FileUtils.isUtf8(group.file)) { + openFile(group.file); + hideViewOptions(); + } + } + + @Override + public void onDiagnosticClick(File file, @NonNull DiagnosticItem diagnostic) { + openFileAndSelect(file, diagnostic.getRange()); + hideViewOptions(); + } + + public ActivityEditorBinding getBinding() { + return mBinding; + } + + public LayoutDiagnosticInfoBinding getDiagnosticBinding() { + return mDiagnosticInfoBinding; + } + + public void openFileAndSelect(File file, Range selection) { + openFile(file, selection); + final var opened = getEditorForFile(file); + if (opened != null && opened.getEditor() != null) { + final var editor = opened.getEditor(); + final var range = editor.validateRange(selection); + editor.post( + () -> { + if (LSPUtils.isEqual(range.getStart(), range.getEnd())) { + editor.setSelection( + range.getStart().getLine(), range.getEnd().getColumn()); + } else { + editor.setSelectionRegion( + range.getStart().getLine(), + range.getStart().getColumn(), + range.getEnd().getLine(), + range.getEnd().getColumn()); + } + }); + } + } + + @Override + public void onTabSelected(@NonNull TabLayout.Tab tab) { + final var position = tab.getPosition(); + mBinding.editorContainer.setDisplayedChild(position); + + final var editorView = getEditorAtIndex(position); + mViewModel.setCurrentFile(position, Objects.requireNonNull(editorView).getFile()); + refreshSymbolInput(editorView); + invalidateOptionsMenu(); + } + + @Override + public void onTabUnselected(@NonNull TabLayout.Tab tab) { + // unimplemented + } + + @Override + public void onTabReselected(@NonNull TabLayout.Tab tab) { + mTabCloseAction.show(tab.view); + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem p1) { + final int id = p1.getItemId(); + if (id == R.id.editornav_discuss) { + getApp().openTelegramGroup(); + } else if (id == R.id.editornav_suggest) { + getApp().openGitHub(); + } else if (id == R.id.editornav_needHelp) { + showNeedHelpDialog(); + } else if (id == R.id.editornav_settings) { + startActivity(new Intent(this, PreferencesActivity.class)); + } else if (id == R.id.editornav_share) { + startActivity(IntentUtils.getShareTextIntent(getString(R.string.msg_share_app))); + } else if (id == R.id.editornav_close_project) { + confirmProjectClose(); + } else if (id == R.id.editornav_terminal) { + openTerminal(); + } + mBinding.getRoot().closeDrawer(GravityCompat.START); + return false; + } + + public void loadFragment(Fragment fragment) { + super.loadFragment(fragment, mBinding.editorFrameLayout.getId()); + } + + public FileTreeFragment getFileTreeFragment() { + return mFileTreeFragment; + } + + public TreeNode getLastHoldTreeNode() { + return mLastHeld; + } + + public OptionsListFragment getFileOptionsFragment(File file) { + mFileOptionsFragment = new OptionsListFragment(); + mFileOptionsFragment.addOption( + new SheetOption(0, R.drawable.ic_file_copy_path, R.string.copy_path, file)); + mFileOptionsFragment.addOption( + new SheetOption(1, R.drawable.ic_file_rename, R.string.rename_file, file)); + mFileOptionsFragment.addOption( + new SheetOption(2, R.drawable.ic_delete, R.string.delete_file, file)); + if (file.isDirectory()) { + mFileOptionsFragment.addOption( + new SheetOption(3, R.drawable.ic_new_file, R.string.new_file, file)); + mFileOptionsFragment.addOption( + new SheetOption(4, R.drawable.ic_new_folder, R.string.new_folder, file)); + } + mFileOptionsFragment.setOnOptionsClickListener(mFileOptionsHandler); + return mFileOptionsFragment; + } + + public ProgressSheet getProgressSheet(int msg) { + if (mSearchingProgress != null && mSearchingProgress.isShowing()) { + mSearchingProgress.dismiss(); + } + mSearchingProgress = new ProgressSheet(); + mSearchingProgress.setCancelable(false); + mSearchingProgress.setWelcomeTextEnabled(false); + mSearchingProgress.setMessage(getString(msg)); + mSearchingProgress.setSubMessageEnabled(false); + return mSearchingProgress; + } + + public MaterialBanner getSyncBanner() { + return mBinding.syncBanner + .setContentTextColor(ContextCompat.getColor(this, R.color.primaryTextColor)) + .setBannerBackgroundColor(ContextCompat.getColor(this, R.color.primaryLightColor)) + .setButtonTextColor(ContextCompat.getColor(this, R.color.secondaryColor)) + .setIcon(R.drawable.ic_sync) + .setContentText(R.string.msg_sync_needed); + } + + public TextSheetFragment getDaemonStatusFragment() { + return mDaemonStatusFragment == null + ? mDaemonStatusFragment = + new TextSheetFragment() + .setTextSelectable(true) + .setTitleText(R.string.gradle_daemon_status) + : mDaemonStatusFragment; + } + + public void setDiagnosticsAdapter(@NonNull final DiagnosticsAdapter adapter) { + runOnUiThread(() -> bottomSheetTabAdapter.getDiagnosticsFragment().setAdapter(adapter)); + } + + public void setSearchResultAdapter(@NonNull final SearchListAdapter adapter) { + runOnUiThread(() -> bottomSheetTabAdapter.getSearchResultFragment().setAdapter(adapter)); + } + + @Override + public void showFileOptions(File thisFile, TreeNode node) { + mLastHeld = node; + getFileOptionsFragment(thisFile) + .show(getSupportFragmentManager(), TAG_FILE_OPTIONS_FRAGMENT); + } + + @SuppressWarnings("UnusedReturnValue") + public boolean saveAll() { + return saveAll(true); + } + + public boolean saveAll(boolean notify) { + return saveAll(notify, false); + } + + public boolean saveAll(boolean notify, boolean canProcessResources) { + SaveResult result = saveAllResult(); + + if (notify) { + getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS); + } + + if (result.gradleSaved) { + notifySyncNeeded(); + } + + if (result.xmlSaved && canProcessResources && getBuildService() != null) { + getBuildService().updateResourceClasses(); + } + + return result.gradleSaved; + } + + public SaveResult saveAllResult() { + SaveResult result = new SaveResult(); + for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { + saveResult(i, result); + } + + return result; + } + + public void saveResult(int index, SaveResult result) { + if (index >= 0 && index < mViewModel.getOpenedFileCount()) { + var frag = getEditorAtIndex(index); + if (frag == null || frag.getFile() == null) { + return; + } + + // Must be called before frag.save() + // Otherwise, it'll always return false + final boolean modified = frag.isModified(); + + frag.save(); + + final boolean isGradle = frag.getFile().getName().endsWith(".gradle"); + final boolean isXml = frag.getFile().getName().endsWith(".xml"); + + if (!result.gradleSaved) { + result.gradleSaved = modified && isGradle; + } + + if (!result.xmlSaved) { + result.xmlSaved = modified && isXml; + } + } + + var modified = false; + for (var file : mViewModel.getOpenedFiles()) { + var editor = getEditorForFile(file); + if (editor == null) { + continue; + } + modified = modified || editor.isModified(); + } + + mViewModel.setFilesModified(modified); + } + + public void install(@NonNull File apk) { + if (apk.exists()) { + Intent i = IntentUtils.getInstallAppIntent(apk); + if (i != null) { + startActivity(i); + } else { + getApp().toast(R.string.msg_apk_install_intent_failed, Toaster.Type.ERROR); + } + } + } + + public void updateServices() { + new TaskExecutor() + .executeAsync( + () -> { + List cps = + Objects.requireNonNull(getAndroidProject()).getClassPaths(); + getApp().getJavaLanguageServer() + .configurationChanged( + new JavaServerConfiguration( + cps.stream() + .map(Paths::get) + .collect(Collectors.toSet()))); + return null; + }, + __ -> setStatus(getString(R.string.msg_service_started))); + } + + @Override + public CodeEditorView openFile(File file) { + return openFile(file, null); + } + + public CodeEditorView openFile(File file, com.itsaky.lsp.models.Range selection) { + if (selection == null) { + selection = com.itsaky.lsp.models.Range.NONE; + } + + int index = openFileAndGetIndex(file, selection); + final var tab = mBinding.tabs.getTabAt(index); + if (tab != null && index >= 0 && !tab.isSelected()) { + tab.select(); + } + + if (mBinding.editorDrawerLayout.isDrawerOpen(GravityCompat.END)) { + mBinding.editorDrawerLayout.closeDrawer(GravityCompat.END); + } + + mBinding.editorContainer.setDisplayedChild(index); + + invalidateOptionsMenu(); + try { + return getEditorAtIndex(index); + } catch (Throwable th) { + LOG.error("Unable to get editor fragment at opened file index", index); + LOG.error(th); + return null; + } + } + + @SuppressLint("NotifyDataSetChanged") + public int openFileAndGetIndex(File file, com.itsaky.lsp.models.Range selection) { + final var openedFileIndex = findIndexOfEditorByFile(file); + + if (openedFileIndex != -1) { + LOG.error("File is already opened. File: " + file); + return openedFileIndex; + } + + final var position = mViewModel.getOpenedFileCount(); + + LOG.info("Opening file at index:", position, "file: ", file); + + final var editor = new CodeEditorView(this, file, selection); + editor.getEditor().subscribeEvent(ContentChangeEvent.class, this::onEditorContentChanged); + editor.setLayoutParams( + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + mBinding.editorContainer.addView(editor); + mBinding.editorContainer.setDisplayedChild(position); + mBinding.tabs.addTab(mBinding.tabs.newTab().setText(file.getName())); + + mViewModel.addFile(file); + mViewModel.setCurrentFile(position, file); + + return position; + } + + public void closeFile(int index) { + if (index >= 0 && index < mViewModel.getOpenedFileCount()) { + var opened = mViewModel.getOpenedFile(index); + LOG.info("Closing file:", opened); + final var editor = getEditorAtIndex(index); + + if (editor != null && editor.isModified()) { + notifyFilesUnsaved(Collections.singletonList(editor), () -> closeFile(index)); + return; + } + + if (editor != null && editor.getEditor() != null) { + editor.getEditor().close(); + } else { + LOG.error("Cannot save file before close. Editor instance is null"); + } + + mViewModel.removeFile(index); + mBinding.tabs.removeTabAt(index); + mBinding.editorContainer.removeViewAt(index); + } else { + LOG.error("Invalid file index. Cannot close."); return; - } + } - final List searchDirs = new ArrayList<>(); - for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { - CheckBox check = (CheckBox) binding.modulesContainer.getChildAt(i); - if (check.isChecked()) { - searchDirs.add(srcDirs.get(i)); - } - } + mBinding.tabs.requestLayout(); + invalidateOptionsMenu(); + } - final String extensions = - Objects.requireNonNull(binding.filter.getEditText()).getText().toString().trim(); - final List extensionList = new ArrayList<>(); - if (!extensions.isEmpty()) { - if (extensions.contains("|")) { - for (String str : extensions.split(Pattern.quote("|"))) { - if (str == null || str.trim().isEmpty()) { - continue; + public void closeAll() { + closeAll(null); + } + + /** + * Close all opened files. If all the files are saved successfully, then the provided {@link + * Runnable} will be called. + * + * @param onSaved The {@link Runnable} to run when all the files are saved. + */ + public void closeAll(Runnable onSaved) { + final var count = mViewModel.getOpenedFileCount(); + final var unsavedFiles = + mViewModel.getOpenedFiles().stream() + .map(this::getEditorForFile) + .filter(editor -> editor != null && editor.isModified()) + .collect(Collectors.toList()); + + if (unsavedFiles.isEmpty()) { + // Files were already saved, close all files one by one + for (int i = 0; i < count; i++) { + final var editor = getEditorAtIndex(i); + if (editor != null && editor.getEditor() != null) { + editor.getEditor().close(); + } else { + LOG.error("Unable to close file at index:", i); } - - extensionList.add(str); - } - } else { - extensionList.add(extensions); } - } - if (searchDirs.isEmpty()) { - getApp().toast(R.string.msg_select_search_modules, Toaster.Type.ERROR); - } else { - dialog.dismiss(); - getProgressSheet(R.string.msg_searching_project) - .show(getSupportFragmentManager(), "search_in_project_progress"); - RecursiveFileSearcher.searchRecursiveAsync( - text, extensionList, searchDirs, this::handleSearchResults); - } - }); - builder.setNegativeButton(android.R.string.cancel, (__, ___) -> __.dismiss()); - mFindInProjectDialog = builder.create(); - return mFindInProjectDialog; - } + mViewModel.removeAllFiles(); + mBinding.tabs.removeAllTabs(); + mBinding.tabs.requestLayout(); + mBinding.editorContainer.removeAllViews(); - private void registerLogReceiver() { - IntentFilter filter = new IntentFilter(); - filter.addAction(LogReceiver.APPEND_LOG); - registerReceiver(mLogReceiver, filter); - } + invalidateOptionsMenu(); - private IDEService getBuildService() { - return mBuildServiceHandler.getService(); - } + if (onSaved != null) { + onSaved.run(); + } + } else { + // There are unsaved files + notifyFilesUnsaved(unsavedFiles, () -> closeAll(onSaved)); + } + } - public BuildServiceHandler getBuildServiceHandler() { - return mBuildServiceHandler; - } + private void notifyFilesUnsaved(List unsavedEditors, Runnable invokeAfter) { + //noinspection ConstantConditions + final var mapped = + unsavedEditors.stream() + .map(CodeEditorView::getFile) + .filter(Objects::nonNull) + .map(File::getAbsolutePath) + .collect(Collectors.toList()); + final var builder = + DialogUtils.newYesNoDialog( + this, + getString(R.string.title_files_unsaved), // title + getString( + R.string.msg_files_unsaved, + TextUtils.join("\n", mapped)), // message + (dialog, which) -> { // 'yes' click + dialog.dismiss(); + saveAll(true); + invokeAfter.run(); + }, + (dialog, which) -> { // 'no' click + dialog.dismiss(); + // Mark all the files as saved, then try to close them all + for (var editor : unsavedEditors) { + editor.markAsSaved(); + } - private void showNeedHelpDialog() { - MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); - builder.setTitle(R.string.need_help); - builder.setMessage(R.string.msg_need_help); - builder.setPositiveButton(android.R.string.ok, null); - builder.create().show(); - } + invokeAfter.run(); + }); + builder.show(); + } - private void createQuickActions() { - ActionItem closeThis = - new ActionItem( - ACTION_ID_CLOSE, getString(R.string.action_closeThis), R.drawable.ic_close_this); - ActionItem closeOthers = - new ActionItem( - ACTION_ID_OTHERS, getString(R.string.action_closeOthers), R.drawable.ic_close_others); - ActionItem closeAll = - new ActionItem(ACTION_ID_ALL, getString(R.string.action_closeAll), R.drawable.ic_close_all); - mTabCloseAction = new QuickAction(this, QuickAction.HORIZONTAL); - mTabCloseAction.addActionItem(closeThis, closeOthers, closeAll); - mTabCloseAction.setColorRes(R.color.tabAction_background); - mTabCloseAction.setTextColorRes(R.color.tabAction_text); + public void closeOthers() { + final var unsavedFiles = + mViewModel.getOpenedFiles().stream() + .map(this::getEditorForFile) + .filter(editor -> editor != null && editor.isModified()) + .collect(Collectors.toList()); - mTabCloseAction.setOnActionItemClickListener( - (item) -> { - final int id = item.getActionId(); - if (getApp().getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_AUTO_SAVE, false)) { - saveAll(); - } + if (unsavedFiles.isEmpty()) { + final var file = mViewModel.getCurrentFile(); + for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { + final var editor = getEditorAtIndex(i); - if (id == ACTION_ID_CLOSE) { - closeFile(mBinding.tabs.getSelectedTabPosition()); - } + // Index of files changes as we keep close files + // So we compare the files instead of index + if (editor != null) { + if (!file.equals(editor.getFile())) { + closeFile(i); + } + } else { + LOG.error("Unable to save file at index:", i); + } + } + } else { + notifyFilesUnsaved(unsavedFiles, this::closeOthers); + } + } - if (id == ACTION_ID_OTHERS) { - closeOthers(); - } + @Nullable + public CodeEditorView getCurrentEditor() { + if (mViewModel.getCurrentFileIndex() != -1) { + return getEditorAtIndex(mViewModel.getCurrentFileIndex()); + } - if (id == ACTION_ID_ALL) { - closeAll(); - } - }); - } + return null; + } - private static final Logger LOG = Logger.instance("EditorActivity"); + @Nullable + public CodeEditorView getEditorAtIndex(final int index) { + return (CodeEditorView) mBinding.editorContainer.getChildAt(index); + } + + @Nullable + public CodeEditorView getEditorForFile(@NonNull final File file) { + for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { + final CodeEditorView editor = (CodeEditorView) mBinding.editorContainer.getChildAt(i); + if (file.equals(editor.getFile())) { + return editor; + } + } + + return null; + } + + public int findIndexOfEditorByFile(File file) { + if (file == null) { + LOG.error("Cannot find index of a null file."); + return -1; + } + + for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { + final var opened = mViewModel.getOpenedFile(i); + if (opened.equals(file)) { + return i; + } + } + + return -1; + } + + ///////////////////////////////////////////////// + ////////////// PRIVATE APIS ///////////////////// + ///////////////////////////////////////////////// + + private void onEditorContentChanged(ContentChangeEvent event, Unsubscribe unsubscribe) { + if (event.getAction() != ContentChangeEvent.ACTION_SET_NEW_TEXT) { + mViewModel.setFilesModified(true); + } + } + + @SuppressWarnings("deprecation") + private void checkForCompilerModule() { + if (!Environment.isCompilerModuleInstalled()) { + final var pd = + ProgressDialog.show( + this, + getString(R.string.title_compiler_module_install), + getString(R.string.msg_compiler_module_install), + true, + false); + + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> { + final var tmpModule = + new File(Environment.TMP_DIR, "compiler-module.zip"); + if (!ResourceUtils.copyFileFromAssets( + ToolsManager.getCommonAsset("compiler-module.zip"), + tmpModule.getAbsolutePath())) { + throw new CompletionException( + new RuntimeException( + "Unable to copy compiler-module.zip")); + } + + try { + ZipUtils.unzipFile(tmpModule, Environment.COMPILER_MODULE); + } catch (Throwable e) { + throw new CompletionException(e); + } + + if (!Environment.isCompilerModuleInstalled()) { + throw new CompletionException( + new RuntimeException("Unknown error")); + } + + try { + FileUtils.delete(tmpModule); + } catch (Exception e) { + // ignored + } + + return true; + }); + + future.whenComplete( + (result, error) -> { + pd.dismiss(); + + if (error != null) { + showCompilerModuleInstallError(error); + return; + } + + getApp().toast( + getString(R.string.msg_compiler_module_installed), + Toaster.Type.SUCCESS); + }); + } + } + + private void showCompilerModuleInstallError(Throwable error) { + final var stacktrace = ThrowableUtils.getFullStackTrace(error); + final var builder = DialogUtils.newMaterialDialogBuilder(this); + builder.setTitle(R.string.title_installation_failed); + builder.setMessage(getString(R.string.msg_compiler_module_install_failed, stacktrace)); + builder.setCancelable(false); + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()); + builder.setNegativeButton( + R.string.copy, + (dialog, which) -> { + ClipboardUtils.copyText(stacktrace); + dialog.dismiss(); + }); + builder.show(); + } + + private void dispatchOnPauseToEditors() { + CompletableFuture.runAsync( + () -> { + for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { + final var editor = getEditorAtIndex(i); + if (editor != null) { + editor.onPause(); + } + } + }); + } + + private void dispatchOnResumeToEditors() { + CompletableFuture.runAsync( + () -> { + for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { + final var editor = getEditorAtIndex(i); + if (editor != null) { + editor.onResume(); + } + } + }); + } + + @Contract(pure = true) + private void onGetUIDesignerResult(@NonNull ActivityResult result) { + final var index = mBinding.editorContainer.getDisplayedChild(); + final var editor = getEditorAtIndex(index); + if (editor != null && result.getResultCode() == RESULT_OK) { + final var data = result.getData(); + if (data != null && data.hasExtra(DesignerActivity.KEY_GENERATED_CODE)) { + final var code = data.getStringExtra(DesignerActivity.KEY_GENERATED_CODE); + editor.getEditor().setText(code); + saveAll(); + } else { + final var msg = getString(R.string.msg_invalid_designer_result); + getApp().toast(msg, Toaster.Type.ERROR); + LOG.error(msg, "Data returned by UI Designer is null or is invalid."); + } + } else { + LOG.error( + "UI Designer returned an invalid result code.", + "Result code: " + result.getResultCode()); + } + } + + private void previewLayout() { + try { + + if (getCurrentEditor() == null || getCurrentEditor().getFile() == null) { + LOG.error("No file is opened. Cannot preview layout."); + return; + } + + saveAll(false); + if (getApp().getLayoutInflater() == null) { + LOG.info("Creating layout inflater instance..."); + getApp().createInflater( + getApp().createInflaterConfig( + getContextProvider(), getResourceDirectories())); + } + + final Intent intent = new Intent(this, DesignerActivity.class); + intent.putExtra( + DesignerActivity.KEY_LAYOUT_PATH, + getCurrentEditor().getFile().getAbsolutePath()); + + LOG.info("Launching UI Designer..."); + mUIDesignerLauncher.launch(intent); + } catch (Throwable th) { + LOG.error(getString(R.string.err_cannot_preview_layout), th); + getApp().toast(R.string.msg_cannot_preview_layout, Toaster.Type.ERROR); + } + } + + @NonNull + @Contract(" -> new") + private ILayoutInflater.ContextProvider getContextProvider() { + return this::provide; + } + + @NonNull + private Set getResourceDirectories() { + final Set dirs = new HashSet<>(); + if (getAndroidProject() != null + && getAndroidProject().getModulePaths() != null + && !getAndroidProject().getModulePaths().isEmpty()) { + for (String path : getAndroidProject().getModulePaths()) { + if (path != null && new File(path).exists()) { + File res = new File(path, "src/main/res"); + if (res.exists()) { + dirs.add(res); + } + } + } + } + return dirs; + } + + private void openTerminal() { + final Intent intent = new Intent(this, TerminalActivity.class); + intent.putExtra( + TerminalActivity.KEY_WORKING_DIRECTORY, + Objects.requireNonNull(getAndroidProject()).getProjectPath()); + startActivity(intent); + } + + private void getProjectFromIntent() { + final var project = (AndroidProject) getIntent().getParcelableExtra(EXTRA_PROJECT); + if (mViewModel != null) { + mViewModel.setAndroidProject(project); + } else { + LOG.error("ViewModel is null. Cannot set project."); + } + getApp().getPrefManager() + .setOpenedProject( + Objects.requireNonNull(this.getAndroidProject()).getProjectPath()); + + try { + //noinspection ConstantConditions + getSupportActionBar() + .setSubtitle(new File(getAndroidProject().getProjectPath()).getName()); + } catch (Throwable th) { + // ignored + } + + CompletableFuture.runAsync( + () -> + ValuesTableFactory.setupWithResDirectories( + getResourceDirectories().toArray(new File[0]))); + } + + @Nullable + private AndroidProject getAndroidProject() { + if (mViewModel == null) { + return null; + } + + return mViewModel.getAndroidProject(); + } + + @Nullable + private IDEProject getIDEProject() { + if (mViewModel == null) { + return null; + } + + return mViewModel.getIDEProject(); + } + + private void setupDiagnosticInfo() { + GradientDrawable gd = new GradientDrawable(); + gd.setShape(GradientDrawable.RECTANGLE); + gd.setColor(0xff212121); + gd.setStroke(1, 0xffffffff); + gd.setCornerRadius(8); + mDiagnosticInfoBinding.getRoot().setBackground(gd); + mDiagnosticInfoBinding.getRoot().setVisibility(View.GONE); + } + + private void setupContainers() { + handleDiagnosticsResultVisibility(true); + handleSearchResultVisibility(true); + } + + private void startServices() { + + if (!IDELanguageClientImpl.isInitialized()) { + IDELanguageClientImpl.initialize(this); + } + + initializeLanguageServers(); + + // Actually, we don't need to start FileOptionsHandler + // Because it would work anyway + // But still we do... + mFileOptionsHandler.start(); + mBuildServiceHandler.start(); + getBuildServiceHandler().assembleDebug(false); + } + + private void initializeLanguageServers() { + final var client = IDELanguageClientImpl.getInstance(); + final var javaLanguageServer = getApp().getJavaLanguageServer(); + final var workspaceRoots = + Collections.singleton( + new File(Objects.requireNonNull(getAndroidProject()).getProjectPath()) + .toPath()); + + final var params = new InitializeParams(workspaceRoots); + + javaLanguageServer.connectClient(client); + javaLanguageServer.applySettings(PrefBasedJavaServerSettings.getInstance()); + javaLanguageServer.initialize(params); + } + + private void stopServices() { + + if (IDELanguageClientImpl.isInitialized()) { + IDELanguageClientImpl.shutdown(); + } + + shutdownLanguageServers(); + + if (getBuildService() != null) { + getBuildService().setListener(null); + if (mBuildServiceHandler != null) { + mBuildServiceHandler.stop(); + } else { + getBuildService().exit(); + } + } + + if (mFileOptionsHandler != null) { + mFileOptionsHandler.stop(); + } + } + + private void shutdownLanguageServers() { + final var javaServer = getApp().getJavaLanguageServer(); + javaServer.shutdown(); + } + + private void onSoftInputChanged() { + invalidateOptionsMenu(); + if (KeyboardUtils.isSoftInputVisible(this)) { + TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.TOP)); + symbolInput.setVisibility(View.VISIBLE); + mBinding.bottomSheet.statusText.setVisibility(View.GONE); + mBinding.bottomSheet.swipeHint.setVisibility(View.GONE); + } else { + TransitionManager.beginDelayedTransition(mBinding.getRoot(), new Slide(Gravity.BOTTOM)); + symbolInput.setVisibility(View.GONE); + mBinding.bottomSheet.statusText.setVisibility(View.VISIBLE); + mBinding.bottomSheet.swipeHint.setVisibility(View.VISIBLE); + } + } + + private void refreshSymbolInput(@NonNull CodeEditorView frag) { + symbolInput.bindEditor(frag.getEditor()); + symbolInput.setSymbols(Symbols.forFile(frag.getFile())); + } + + private void notifySyncNeeded() { + if (getBuildService() != null && !getBuildService().isBuilding()) { + getSyncBanner() + .setNegative(android.R.string.cancel, null) + .setPositive( + android.R.string.ok, v -> getBuildServiceHandler().assembleDebug(false)) + .show(); + } + } + + private void closeProject(boolean manualFinish) { + stopServices(); + + // Make sure we close files + // This fill further make sure that file contents are not erased. + closeAll( + () -> { + getApp().getPrefManager().setOpenedProject(PreferenceManager.NO_OPENED_PROJECT); + getApp().stopAllDaemons(); + + if (manualFinish) { + finish(); + } + }); + } + + private void confirmProjectClose() { + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); + builder.setTitle(R.string.title_confirm_project_close); + builder.setMessage(R.string.msg_confirm_project_close); + builder.setNegativeButton(R.string.no, null); + builder.setPositiveButton( + R.string.yes, + (d, w) -> { + d.dismiss(); + closeProject(true); + }); + builder.show(); + } + + private AlertDialog getFindInProjectDialog() { + return mFindInProjectDialog == null ? createFindInProjectDialog() : mFindInProjectDialog; + } + + @Nullable + private AlertDialog createFindInProjectDialog() { + if (getAndroidProject() == null + || getAndroidProject().getModulePaths() == null + || getAndroidProject().getModulePaths().size() <= 0) { + getApp().toast(R.string.msg_no_modules, Toaster.Type.ERROR); + return null; + } + final List modules = getAndroidProject().getModulePaths(); + final List srcDirs = new ArrayList<>(); + final LayoutSearchProjectBinding binding = + LayoutSearchProjectBinding.inflate(getLayoutInflater()); + binding.modulesContainer.removeAllViews(); + for (int i = 0; i < modules.size(); i++) { + final File file = new File(modules.get(i)); + final File src = new File(file, "src"); + if (!file.exists() || !file.isDirectory() || !src.exists() || !src.isDirectory()) { + continue; + } + + CheckBox check = new CheckBox(this); + check.setText(file.getName()); + check.setChecked(true); + + LinearLayout.MarginLayoutParams params = new LinearLayout.MarginLayoutParams(-2, -2); + params.bottomMargin = SizeUtils.dp2px(4); + binding.modulesContainer.addView(check, params); + + srcDirs.add(src); + } + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); + builder.setTitle(R.string.menu_find_project); + builder.setView(binding.getRoot()); + builder.setCancelable(false); + builder.setPositiveButton( + R.string.menu_find, + (dialog, which) -> { + final String text = + Objects.requireNonNull(binding.input.getEditText()) + .getText() + .toString() + .trim(); + if (text.isEmpty()) { + getApp().toast(R.string.msg_empty_search_query, Toaster.Type.ERROR); + return; + } + + final List searchDirs = new ArrayList<>(); + for (int i = 0; i < mViewModel.getOpenedFileCount(); i++) { + CheckBox check = (CheckBox) binding.modulesContainer.getChildAt(i); + if (check.isChecked()) { + searchDirs.add(srcDirs.get(i)); + } + } + + final String extensions = + Objects.requireNonNull(binding.filter.getEditText()) + .getText() + .toString() + .trim(); + final List extensionList = new ArrayList<>(); + if (!extensions.isEmpty()) { + if (extensions.contains("|")) { + for (String str : extensions.split(Pattern.quote("|"))) { + if (str == null || str.trim().isEmpty()) { + continue; + } + + extensionList.add(str); + } + } else { + extensionList.add(extensions); + } + } + + if (searchDirs.isEmpty()) { + getApp().toast(R.string.msg_select_search_modules, Toaster.Type.ERROR); + } else { + dialog.dismiss(); + getProgressSheet(R.string.msg_searching_project) + .show(getSupportFragmentManager(), "search_in_project_progress"); + RecursiveFileSearcher.searchRecursiveAsync( + text, extensionList, searchDirs, this::handleSearchResults); + } + }); + builder.setNegativeButton(android.R.string.cancel, (__, ___) -> __.dismiss()); + mFindInProjectDialog = builder.create(); + return mFindInProjectDialog; + } + + private void registerLogReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(LogReceiver.APPEND_LOG); + registerReceiver(mLogReceiver, filter); + } + + private IDEService getBuildService() { + return mBuildServiceHandler.getService(); + } + + public BuildServiceHandler getBuildServiceHandler() { + return mBuildServiceHandler; + } + + private void showNeedHelpDialog() { + MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); + builder.setTitle(R.string.need_help); + builder.setMessage(R.string.msg_need_help); + builder.setPositiveButton(android.R.string.ok, null); + builder.create().show(); + } + + private void createQuickActions() { + ActionItem closeThis = + new ActionItem( + ACTION_ID_CLOSE, + getString(R.string.action_closeThis), + R.drawable.ic_close_this); + ActionItem closeOthers = + new ActionItem( + ACTION_ID_OTHERS, + getString(R.string.action_closeOthers), + R.drawable.ic_close_others); + ActionItem closeAll = + new ActionItem( + ACTION_ID_ALL, + getString(R.string.action_closeAll), + R.drawable.ic_close_all); + mTabCloseAction = new QuickAction(this, QuickAction.HORIZONTAL); + mTabCloseAction.addActionItem(closeThis, closeOthers, closeAll); + mTabCloseAction.setColorRes(R.color.tabAction_background); + mTabCloseAction.setTextColorRes(R.color.tabAction_text); + + mTabCloseAction.setOnActionItemClickListener( + (item) -> { + final int id = item.getActionId(); + if (getApp().getPrefManager() + .getBoolean(PreferenceManager.KEY_EDITOR_AUTO_SAVE, false)) { + saveAll(); + } + + if (id == ACTION_ID_CLOSE) { + closeFile(mBinding.tabs.getSelectedTabPosition()); + } + + if (id == ACTION_ID_OTHERS) { + closeOthers(); + } + + if (id == ACTION_ID_ALL) { + closeAll(); + } + }); + } + + private static final Logger LOG = Logger.instance("EditorActivity"); } diff --git a/app/src/main/java/com/itsaky/androidide/MainActivity.java b/app/src/main/java/com/itsaky/androidide/MainActivity.java index c87555a0d..fdf28a768 100755 --- a/app/src/main/java/com/itsaky/androidide/MainActivity.java +++ b/app/src/main/java/com/itsaky/androidide/MainActivity.java @@ -20,12 +20,15 @@ package com.itsaky.androidide; -import abhishekti7.unicorn.filepicker.UnicornFilePicker; import android.content.Intent; import android.os.Bundle; import android.os.Environment; import android.view.View; + import androidx.transition.TransitionManager; + +import abhishekti7.unicorn.filepicker.UnicornFilePicker; + import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.transition.MaterialContainerTransform; import com.itsaky.androidide.app.StudioActivity; @@ -45,400 +48,409 @@ import com.itsaky.androidide.utils.DialogUtils; import com.itsaky.androidide.utils.ProjectFinder; import com.itsaky.androidide.utils.TransformUtils; import com.itsaky.toaster.Toaster; + import java.io.File; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Callable; public class MainActivity extends StudioActivity - implements View.OnClickListener, ProjectWriterCallback { + implements View.OnClickListener, ProjectWriterCallback { - private ActivityMainBinding binding; - private LayoutCreateProjectBinding createBinding; - private LayoutCreateProjectContentBinding createLayoutBinding; + private ActivityMainBinding binding; + private LayoutCreateProjectBinding createBinding; + private LayoutCreateProjectContentBinding createLayoutBinding; - private ProgressSheet mProgressSheet; + private ProgressSheet mProgressSheet; - private ArrayList mTemplates = new ArrayList<>(); + private ArrayList mTemplates = new ArrayList<>(); - private int currentTemplateIndex = 0; + private int currentTemplateIndex = 0; - @Override - protected View bindLayout() { - binding = ActivityMainBinding.inflate(getLayoutInflater()); - createBinding = binding.createLayout; - createLayoutBinding = createBinding.createProjectContent; - return binding.getRoot(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding.createProject.setOnClickListener(this); - binding.openProject.setOnClickListener(this); - binding.openTerminal.setOnClickListener(this); - createBinding.createprojectClose.setOnClickListener(this); - createLayoutBinding.createprojectCreate.setOnClickListener(this); - createLayoutBinding.previousCard.setOnClickListener(this); - createLayoutBinding.nextCard.setOnClickListener(this); - binding.createNewCard.setVisibility(View.GONE); - - currentTemplateIndex = 0; - createTemplates(); - createProgressSheet(); - showTemplate(currentTemplateIndex); - - binding.gotoPreferences.setOnClickListener(this); - - binding - .getRoot() - .post( - () -> { - PreferenceManager manager = getApp().getPrefManager(); - if (manager.autoOpenProject() - && manager.wasProjectOpened() - && ConstantsBridge.SPLASH_TO_MAIN) { - String path = manager.getOpenedProject(); - if (path == null || path.trim().isEmpty()) { - getApp().toast(R.string.msg_opened_project_does_not_exist, Toaster.Type.INFO); - } else { - File root = new File(manager.getOpenedProject()); - if (!root.exists()) { - getApp().toast(R.string.msg_opened_project_does_not_exist, Toaster.Type.INFO); - } else { - if (manager.confirmProjectOpen()) { - askProjectOpenPermission(root); - } else { - openProject(root); - } - } - } - } - - ConstantsBridge.SPLASH_TO_MAIN = false; - }); - } - - private void askProjectOpenPermission(File root) { - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); - builder.setTitle(R.string.title_confirm_open_project); - builder.setMessage(getString(R.string.msg_confirm_open_project, root.getAbsolutePath())); - builder.setCancelable(false); - builder.setPositiveButton(R.string.yes, (d, w) -> openProject(root)); - builder.setNegativeButton(R.string.no, null); - builder.show(); - } - - private void gotoSettings() { - startActivity(new Intent(this, PreferencesActivity.class)); - } - - @Override - protected void onStorageGranted() {} - - @Override - protected void onStorageDenied() { - getApp().toast(R.string.msg_storage_denied, Toaster.Type.ERROR); - finishAffinity(); - } - - @Override - public void onClick(View p1) { - if (p1.getId() == binding.createProject.getId()) { - showCreateProject(); - } else if (p1.getId() == createBinding.createprojectClose.getId()) { - hideCreateProject(); - } else if (p1.getId() == createLayoutBinding.nextCard.getId()) { - currentTemplateIndex++; - if (currentTemplateIndex >= mTemplates.size()) { - currentTemplateIndex = 0; - } - showTemplate(currentTemplateIndex); - } else if (p1.getId() == createLayoutBinding.previousCard.getId()) { - currentTemplateIndex--; - if (currentTemplateIndex < 0) { - currentTemplateIndex = mTemplates.size() - 1; - } - showTemplate(currentTemplateIndex); - } else if (p1.getId() == createLayoutBinding.createprojectCreate.getId()) { - createNewProject(); - } else if (p1.getId() == binding.gotoPreferences.getId()) { - gotoSettings(); - } else if (p1.getId() == binding.openProject.getId()) { - pickProject(); - } else if (p1.getId() == binding.openTerminal.getId()) { - startActivity(new Intent(this, TerminalActivity.class)); - } - } - - @Override - public void beforeBegin() { - if (mProgressSheet == null) { - createProgressSheet(); - } - - setMessage(R.string.msg_begin_project_write); - } - - @Override - public void onProcessTask(String taskName) { - setMessage(taskName); - } - - @Override - public void onSuccess(File root) { - if (mProgressSheet == null) { - createProgressSheet(); - } - - if (mProgressSheet.isShowing()) { - mProgressSheet.dismiss(); - } - getApp().toast(R.string.project_created_successfully, Toaster.Type.SUCCESS); - - openProject(root); - } - - @Override - public void onFailed(String reason) { - if (mProgressSheet == null) { - createProgressSheet(); - } - - if (mProgressSheet.isShowing()) { - mProgressSheet.dismiss(); - } - getApp().toast(reason, Toaster.Type.ERROR); - } - - @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 files = data.getStringArrayListExtra("filePaths"); - if (files != null) { - if (files.size() == 1) { - File choseDir = new File(files.get(0)); - if (choseDir.exists() && choseDir.isDirectory()) { - openProject(choseDir); - } else { - getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR); - } - } else { - getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR); - } - } - } - } - - @Override - public void onBackPressed() { - if (binding.createNewCard.getVisibility() == View.VISIBLE) { - hideCreateProject(); - } else { - super.onBackPressed(); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (mProgressSheet != null && mProgressSheet.isShowing()) { - mProgressSheet.dismiss(); - } - } - - private void showTemplate(int index) { - if (index < 0 || index >= mTemplates.size()) { - return; - } - final ProjectTemplate template = mTemplates.get(index); - createLayoutBinding.createprojectTemplateLabel.setText(template.getName()); - createLayoutBinding.createprojectTemplateDescription.setText(template.getDescription()); - createLayoutBinding.createprojectTemplateImage.setImageResource(template.getImageId()); - } - - /** Must be called in/after onViewCreated */ - private void createTemplates() { - mTemplates = new ArrayList<>(); - - ProjectTemplate - empty = - new ProjectTemplate() - .setId(0) - .setName(this, R.string.template_empty) - .setDescription(this, R.string.template_description_empty) - .setImageId(R.drawable.template_empty), - basic = - new ProjectTemplate() - .setId(1) - .setName(this, R.string.template_basic) - .setDescription(this, R.string.template_description_basic) - .setImageId(R.drawable.template_basic), - drawer = - new ProjectTemplate() - .setId(2) - .setName(this, R.string.template_navigation_drawer) - .setDescription(this, R.string.template_description_navigation_drawer) - .setImageId(R.drawable.template_navigation_drawer), - kotlinBasic = - new ProjectTemplate() - .setId(3) - .setName(this, R.string.template_kotlin_basic) - .setDescription(this, R.string.template_description_kotlin_basic) - .setImageId(R.drawable.template_basic), - libgdx = - new ProjectTemplate() - .setId(4) - .setName(this, R.string.template_libgdx) - .setDescription(this, R.string.template_description_libgdx) - .setImageId(R.drawable.template_libgdx); - mTemplates.add(empty); - mTemplates.add(basic); - mTemplates.add(drawer); - mTemplates.add(kotlinBasic); - mTemplates.add(libgdx); - } - - private void createProgressSheet() { - mProgressSheet = new ProgressSheet(); - mProgressSheet.setShowShadow(false); - } - - private void setMessage(int msg) { - setMessage(getString(msg)); - } - - private void setMessage(String msg) { - mProgressSheet.setMessage(msg); - } - - private void openProject(File root) { - setMessage(R.string.msg_opening_project); - mProgressSheet.setCancelable(false); - mProgressSheet.show(getSupportFragmentManager(), "opening_project"); - - new TaskExecutor().executeAsync(new ProjectReader(root), this::openEditor); - } - - private void pickProject() { - UnicornFilePicker.from(this) - .addConfigBuilder() - .addItemDivider(false) - .selectMultipleFiles(false) - .setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath()) - .showHiddenFiles(true) - .showOnlyDirectory(true) - .theme(R.style.AppTheme_FilePicker) - .build() - .forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE); - } - - private void openEditor(AndroidProject project) { - if (project == null) { - getApp().toast(R.string.msg_invalid_project, Toaster.Type.ERROR); - } else { - startActivity( - new Intent(this, EditorActivity.class).putExtra(EditorActivity.EXTRA_PROJECT, project)); - } - } - - private void createNewProject() { - final String appName = - Objects.requireNonNull(createLayoutBinding.createprojectTextAppName.getEditText()) - .getText() - .toString() - .trim(); - final String packageName = - Objects.requireNonNull(createLayoutBinding.createprojectTextPackageName.getEditText()) - .getText() - .toString() - .trim(); - final int minSdk = getMinSdk(); - final int targetSdk = getTargetSdk(); - - if (!isValid(appName, packageName, minSdk, targetSdk)) { - getApp().toast(R.string.invalid_values, Toaster.Type.ERROR); - return; - } - - createProject(appName, packageName, minSdk, targetSdk); - } - - private void createProject(String appName, String packageName, int minSdk, int targetSdk) { - new TaskExecutor() - .executeAsync( - new ProjectCreatorCallable( - mTemplates.get(currentTemplateIndex), - new NewProjectDetails(appName, packageName, minSdk, targetSdk), - this), - r -> {}); - } - - private boolean isValid(String appName, String packageName, int minSdk, int targetSdk) { - return appName != null - && appName.length() > 0 - && packageName != null - && packageName.length() > 0 - && minSdk > 1 - && minSdk < 99 - && targetSdk > 1 - && targetSdk < 99; - } - - private int getMinSdk() { - try { - return Integer.parseInt( - Objects.requireNonNull(createLayoutBinding.createprojectTextMinSdk.getEditText()) - .getText() - .toString()); - } catch (Exception e) { - getApp().toast(e.getMessage(), Toaster.Type.ERROR); - } - return -1; - } - - private int getTargetSdk() { - try { - return Integer.parseInt( - Objects.requireNonNull(createLayoutBinding.createprojectTextTargetSdk.getEditText()) - .getText() - .toString()); - } catch (Exception e) { - getApp().toast(e.getMessage(), Toaster.Type.ERROR); - } - return -1; - } - - private void showCreateProject() { - MaterialContainerTransform transform = - TransformUtils.createContainerTransformFor( - binding.createProject, binding.createNewCard, binding.realContainer); - TransitionManager.beginDelayedTransition(binding.getRoot(), transform); - binding.createNewCard.setVisibility(View.VISIBLE); - } - - private void hideCreateProject() { - MaterialContainerTransform transform = - TransformUtils.createContainerTransformFor( - binding.createNewCard, binding.createProject, binding.realContainer); - TransitionManager.beginDelayedTransition(binding.getRoot(), transform); - binding.createNewCard.setVisibility(View.GONE); - } - - private static final class ProjectReader implements Callable { - - private final File root; - - public ProjectReader(File root) { - this.root = root; + @Override + protected View bindLayout() { + binding = ActivityMainBinding.inflate(getLayoutInflater()); + createBinding = binding.createLayout; + createLayoutBinding = createBinding.createProjectContent; + return binding.getRoot(); } @Override - public AndroidProject call() throws Exception { - return ProjectFinder.fromFolder(root); + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding.createProject.setOnClickListener(this); + binding.openProject.setOnClickListener(this); + binding.openTerminal.setOnClickListener(this); + createBinding.createprojectClose.setOnClickListener(this); + createLayoutBinding.createprojectCreate.setOnClickListener(this); + createLayoutBinding.previousCard.setOnClickListener(this); + createLayoutBinding.nextCard.setOnClickListener(this); + binding.createNewCard.setVisibility(View.GONE); + + currentTemplateIndex = 0; + createTemplates(); + createProgressSheet(); + showTemplate(currentTemplateIndex); + + binding.gotoPreferences.setOnClickListener(this); + + binding.getRoot() + .post( + () -> { + PreferenceManager manager = getApp().getPrefManager(); + if (manager.autoOpenProject() + && manager.wasProjectOpened() + && ConstantsBridge.SPLASH_TO_MAIN) { + String path = manager.getOpenedProject(); + if (path == null || path.trim().isEmpty()) { + getApp().toast( + R.string.msg_opened_project_does_not_exist, + Toaster.Type.INFO); + } else { + File root = new File(manager.getOpenedProject()); + if (!root.exists()) { + getApp().toast( + R.string.msg_opened_project_does_not_exist, + Toaster.Type.INFO); + } else { + if (manager.confirmProjectOpen()) { + askProjectOpenPermission(root); + } else { + openProject(root); + } + } + } + } + + ConstantsBridge.SPLASH_TO_MAIN = false; + }); + } + + private void askProjectOpenPermission(File root) { + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); + builder.setTitle(R.string.title_confirm_open_project); + builder.setMessage(getString(R.string.msg_confirm_open_project, root.getAbsolutePath())); + builder.setCancelable(false); + builder.setPositiveButton(R.string.yes, (d, w) -> openProject(root)); + builder.setNegativeButton(R.string.no, null); + builder.show(); + } + + private void gotoSettings() { + startActivity(new Intent(this, PreferencesActivity.class)); + } + + @Override + protected void onStorageGranted() {} + + @Override + protected void onStorageDenied() { + getApp().toast(R.string.msg_storage_denied, Toaster.Type.ERROR); + finishAffinity(); + } + + @Override + public void onClick(View p1) { + if (p1.getId() == binding.createProject.getId()) { + showCreateProject(); + } else if (p1.getId() == createBinding.createprojectClose.getId()) { + hideCreateProject(); + } else if (p1.getId() == createLayoutBinding.nextCard.getId()) { + currentTemplateIndex++; + if (currentTemplateIndex >= mTemplates.size()) { + currentTemplateIndex = 0; + } + showTemplate(currentTemplateIndex); + } else if (p1.getId() == createLayoutBinding.previousCard.getId()) { + currentTemplateIndex--; + if (currentTemplateIndex < 0) { + currentTemplateIndex = mTemplates.size() - 1; + } + showTemplate(currentTemplateIndex); + } else if (p1.getId() == createLayoutBinding.createprojectCreate.getId()) { + createNewProject(); + } else if (p1.getId() == binding.gotoPreferences.getId()) { + gotoSettings(); + } else if (p1.getId() == binding.openProject.getId()) { + pickProject(); + } else if (p1.getId() == binding.openTerminal.getId()) { + startActivity(new Intent(this, TerminalActivity.class)); + } + } + + @Override + public void beforeBegin() { + if (mProgressSheet == null) { + createProgressSheet(); + } + + setMessage(R.string.msg_begin_project_write); + } + + @Override + public void onProcessTask(String taskName) { + setMessage(taskName); + } + + @Override + public void onSuccess(File root) { + if (mProgressSheet == null) { + createProgressSheet(); + } + + if (mProgressSheet.isShowing()) { + mProgressSheet.dismiss(); + } + getApp().toast(R.string.project_created_successfully, Toaster.Type.SUCCESS); + + openProject(root); + } + + @Override + public void onFailed(String reason) { + if (mProgressSheet == null) { + createProgressSheet(); + } + + if (mProgressSheet.isShowing()) { + mProgressSheet.dismiss(); + } + getApp().toast(reason, Toaster.Type.ERROR); + } + + @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 files = data.getStringArrayListExtra("filePaths"); + if (files != null) { + if (files.size() == 1) { + File choseDir = new File(files.get(0)); + if (choseDir.exists() && choseDir.isDirectory()) { + openProject(choseDir); + } else { + getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR); + } + } else { + getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR); + } + } + } + } + + @Override + public void onBackPressed() { + if (binding.createNewCard.getVisibility() == View.VISIBLE) { + hideCreateProject(); + } else { + super.onBackPressed(); + } + } + + @Override + protected void onResume() { + super.onResume(); + if (mProgressSheet != null && mProgressSheet.isShowing()) { + mProgressSheet.dismiss(); + } + } + + private void showTemplate(int index) { + if (index < 0 || index >= mTemplates.size()) { + return; + } + final ProjectTemplate template = mTemplates.get(index); + createLayoutBinding.createprojectTemplateLabel.setText(template.getName()); + createLayoutBinding.createprojectTemplateDescription.setText(template.getDescription()); + createLayoutBinding.createprojectTemplateImage.setImageResource(template.getImageId()); + } + + /** Must be called in/after onViewCreated */ + private void createTemplates() { + mTemplates = new ArrayList<>(); + + ProjectTemplate + empty = + new ProjectTemplate() + .setId(0) + .setName(this, R.string.template_empty) + .setDescription(this, R.string.template_description_empty) + .setImageId(R.drawable.template_empty), + basic = + new ProjectTemplate() + .setId(1) + .setName(this, R.string.template_basic) + .setDescription(this, R.string.template_description_basic) + .setImageId(R.drawable.template_basic), + drawer = + new ProjectTemplate() + .setId(2) + .setName(this, R.string.template_navigation_drawer) + .setDescription( + this, R.string.template_description_navigation_drawer) + .setImageId(R.drawable.template_navigation_drawer), + kotlinBasic = + new ProjectTemplate() + .setId(3) + .setName(this, R.string.template_kotlin_basic) + .setDescription(this, R.string.template_description_kotlin_basic) + .setImageId(R.drawable.template_basic), + libgdx = + new ProjectTemplate() + .setId(4) + .setName(this, R.string.template_libgdx) + .setDescription(this, R.string.template_description_libgdx) + .setImageId(R.drawable.template_libgdx); + mTemplates.add(empty); + mTemplates.add(basic); + mTemplates.add(drawer); + mTemplates.add(kotlinBasic); + mTemplates.add(libgdx); + } + + private void createProgressSheet() { + mProgressSheet = new ProgressSheet(); + mProgressSheet.setShowShadow(false); + } + + private void setMessage(int msg) { + setMessage(getString(msg)); + } + + private void setMessage(String msg) { + mProgressSheet.setMessage(msg); + } + + private void openProject(File root) { + setMessage(R.string.msg_opening_project); + mProgressSheet.setCancelable(false); + mProgressSheet.show(getSupportFragmentManager(), "opening_project"); + + new TaskExecutor().executeAsync(new ProjectReader(root), this::openEditor); + } + + private void pickProject() { + UnicornFilePicker.from(this) + .addConfigBuilder() + .addItemDivider(false) + .selectMultipleFiles(false) + .setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath()) + .showHiddenFiles(true) + .showOnlyDirectory(true) + .theme(R.style.AppTheme_FilePicker) + .build() + .forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE); + } + + private void openEditor(AndroidProject project) { + if (project == null) { + getApp().toast(R.string.msg_invalid_project, Toaster.Type.ERROR); + } else { + startActivity( + new Intent(this, EditorActivity.class) + .putExtra(EditorActivity.EXTRA_PROJECT, project)); + } + } + + private void createNewProject() { + final String appName = + Objects.requireNonNull(createLayoutBinding.createprojectTextAppName.getEditText()) + .getText() + .toString() + .trim(); + final String packageName = + Objects.requireNonNull( + createLayoutBinding.createprojectTextPackageName.getEditText()) + .getText() + .toString() + .trim(); + final int minSdk = getMinSdk(); + final int targetSdk = getTargetSdk(); + + if (!isValid(appName, packageName, minSdk, targetSdk)) { + getApp().toast(R.string.invalid_values, Toaster.Type.ERROR); + return; + } + + createProject(appName, packageName, minSdk, targetSdk); + } + + private void createProject(String appName, String packageName, int minSdk, int targetSdk) { + new TaskExecutor() + .executeAsync( + new ProjectCreatorCallable( + mTemplates.get(currentTemplateIndex), + new NewProjectDetails(appName, packageName, minSdk, targetSdk), + this), + r -> {}); + } + + private boolean isValid(String appName, String packageName, int minSdk, int targetSdk) { + return appName != null + && appName.length() > 0 + && packageName != null + && packageName.length() > 0 + && minSdk > 1 + && minSdk < 99 + && targetSdk > 1 + && targetSdk < 99; + } + + private int getMinSdk() { + try { + return Integer.parseInt( + Objects.requireNonNull( + createLayoutBinding.createprojectTextMinSdk.getEditText()) + .getText() + .toString()); + } catch (Exception e) { + getApp().toast(e.getMessage(), Toaster.Type.ERROR); + } + return -1; + } + + private int getTargetSdk() { + try { + return Integer.parseInt( + Objects.requireNonNull( + createLayoutBinding.createprojectTextTargetSdk.getEditText()) + .getText() + .toString()); + } catch (Exception e) { + getApp().toast(e.getMessage(), Toaster.Type.ERROR); + } + return -1; + } + + private void showCreateProject() { + MaterialContainerTransform transform = + TransformUtils.createContainerTransformFor( + binding.createProject, binding.createNewCard, binding.realContainer); + TransitionManager.beginDelayedTransition(binding.getRoot(), transform); + binding.createNewCard.setVisibility(View.VISIBLE); + } + + private void hideCreateProject() { + MaterialContainerTransform transform = + TransformUtils.createContainerTransformFor( + binding.createNewCard, binding.createProject, binding.realContainer); + TransitionManager.beginDelayedTransition(binding.getRoot(), transform); + binding.createNewCard.setVisibility(View.GONE); + } + + private static final class ProjectReader implements Callable { + + private final File root; + + public ProjectReader(File root) { + this.root = root; + } + + @Override + public AndroidProject call() throws Exception { + return ProjectFinder.fromFolder(root); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java b/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java index ba208d436..a3e99d7a1 100755 --- a/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java +++ b/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java @@ -22,45 +22,47 @@ package com.itsaky.androidide; import android.os.Bundle; import android.view.View; + import androidx.fragment.app.Fragment; + import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.databinding.ActivityPreferencesBinding; import com.itsaky.androidide.fragments.preferences.IDEPreferences; public class PreferencesActivity extends StudioActivity { - private ActivityPreferencesBinding binding; - private IDEPreferences mPref; + private ActivityPreferencesBinding binding; + private IDEPreferences mPref; - @Override - protected View bindLayout() { - binding = ActivityPreferencesBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } + @Override + protected View bindLayout() { + binding = ActivityPreferencesBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - setSupportActionBar(binding.toolbar); - getSupportActionBar().setTitle(R.string.ide_preferences); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setSupportActionBar(binding.toolbar); + getSupportActionBar().setTitle(R.string.ide_preferences); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); - binding.toolbar.setNavigationOnClickListener(v -> onBackPressed()); + binding.toolbar.setNavigationOnClickListener(v -> onBackPressed()); - loadFragment(getPrefsFragment()); - } + loadFragment(getPrefsFragment()); + } - private IDEPreferences getPrefsFragment() { - return mPref == null ? mPref = new IDEPreferences() : mPref; - } + private IDEPreferences getPrefsFragment() { + return mPref == null ? mPref = new IDEPreferences() : mPref; + } - private void loadFragment(Fragment frag) { - super.loadFragment(frag, binding.fragmentContainer.getId()); - } + private void loadFragment(Fragment frag) { + super.loadFragment(frag, binding.fragmentContainer.getId()); + } - @Override - public void onBackPressed() { - super.onBackPressed(); - } + @Override + public void onBackPressed() { + super.onBackPressed(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/SplashActivity.java b/app/src/main/java/com/itsaky/androidide/SplashActivity.java index f1ecc7755..bdbe13296 100755 --- a/app/src/main/java/com/itsaky/androidide/SplashActivity.java +++ b/app/src/main/java/com/itsaky/androidide/SplashActivity.java @@ -24,6 +24,7 @@ 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; @@ -32,65 +33,65 @@ import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.utils.DialogUtils; public class SplashActivity extends StudioActivity { - private ActivitySplashBinding binding; + private ActivitySplashBinding binding; - private static final Handler mHandler = new Handler(); - private final Runnable mRunnable = - new Runnable() { + private static final Handler mHandler = new Handler(); + private final Runnable mRunnable = + new Runnable() { - @Override - public void run() { - ConstantsBridge.SPLASH_TO_MAIN = true; - startActivity(new Intent(SplashActivity.this, MainActivity.class)); - finish(); - } - }; + @Override + public void run() { + ConstantsBridge.SPLASH_TO_MAIN = true; + startActivity(new Intent(SplashActivity.this, MainActivity.class)); + finish(); + } + }; - @Override - protected View bindLayout() { - binding = ActivitySplashBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding.splashText.setText(getString(R.string.msg_checking_storage_permissions)); - } - - @Override - protected void onStorageGranted() { - super.onStorageGranted(); - proceed(); - } - - @Override - protected void onStorageAlreadyGranted() { - super.onStorageAlreadyGranted(); - proceed(); - } - - 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(); - } + @Override + protected View bindLayout() { + binding = ActivitySplashBinding.inflate(getLayoutInflater()); + return binding.getRoot(); } - } - private void goToMain() { - mHandler.removeCallbacks(mRunnable); - mHandler.postDelayed(mRunnable, 1000); - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding.splashText.setText(getString(R.string.msg_checking_storage_permissions)); + } + + @Override + protected void onStorageGranted() { + super.onStorageGranted(); + proceed(); + } + + @Override + protected void onStorageAlreadyGranted() { + super.onStorageAlreadyGranted(); + proceed(); + } + + 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); + } } diff --git a/app/src/main/java/com/itsaky/androidide/TerminalActivity.java b/app/src/main/java/com/itsaky/androidide/TerminalActivity.java index 385f3f978..2e9d96aa2 100755 --- a/app/src/main/java/com/itsaky/androidide/TerminalActivity.java +++ b/app/src/main/java/com/itsaky/androidide/TerminalActivity.java @@ -29,7 +29,9 @@ import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; + import androidx.annotation.NonNull; + import com.blankj.utilcode.util.ClipboardUtils; import com.blankj.utilcode.util.KeyboardUtils; import com.blankj.utilcode.util.SizeUtils; @@ -54,459 +56,466 @@ import com.itsaky.terminal.TerminalSessionClient; import com.itsaky.terminal.TextStyle; import com.itsaky.terminal.view.TerminalView; import com.itsaky.terminal.view.TerminalViewClient; + +import org.json.JSONException; + import java.io.File; import java.io.FileOutputStream; import java.util.Map; -import org.json.JSONException; public class TerminalActivity extends StudioActivity { - private ActivityTerminalBinding binding; - private TerminalView terminal; - private TerminalSession session; + private ActivityTerminalBinding binding; + private TerminalView terminal; + private TerminalSession session; - private boolean isVisible = false; + private boolean isVisible = false; - private KeyListener listener; - private final Client client = new Client(); - private static final Logger LOG = Logger.instance("TerminalActivity"); + private KeyListener listener; + private final Client client = new Client(); + private static final Logger LOG = Logger.instance("TerminalActivity"); - public static final String KEY_WORKING_DIRECTORY = "terminal_workingDirectory"; + public static final String KEY_WORKING_DIRECTORY = "terminal_workingDirectory"; - private static final byte[] SOURCES_LIST_CONTENT = - "deb https://androidide.com/packages/ stable main".getBytes(); + private static final byte[] SOURCES_LIST_CONTENT = + "deb https://androidide.com/packages/ stable main".getBytes(); - @Override - protected View bindLayout() { - binding = ActivityTerminalBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } + @Override + protected View bindLayout() { + binding = ActivityTerminalBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - final var bash = new File(BIN_DIR, "bash"); - final var useSystemShell = - getApp().getPrefManager().getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL); - if ((SYSROOT.exists() - && SYSROOT.isDirectory() - && bash.exists() - && bash.isFile() - && bash.canExecute()) - || useSystemShell) { - setupTerminalView(); - } else { - LOG.info("Bootstrap is not installed."); + final var bash = new File(BIN_DIR, "bash"); + final var useSystemShell = + getApp().getPrefManager() + .getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL); + if ((SYSROOT.exists() + && SYSROOT.isDirectory() + && bash.exists() + && bash.isFile() + && bash.canExecute()) + || useSystemShell) { + setupTerminalView(); + } else { + LOG.info("Bootstrap is not installed."); - // Show the progress sheet - final var progress = new ProgressSheet(); - progress.setShowShadow(false); - progress.setSubMessageEnabled(true); - progress.setShowTitle(false); - progress.setMessage(getString(R.string.please_wait)); - progress.setSubMessage(getString(R.string.msg_reading_bootstrap)); - progress.setCancelable(false); - progress.show(getSupportFragmentManager(), "extract_bootstrap_progress"); + // Show the progress sheet + final var progress = new ProgressSheet(); + progress.setShowShadow(false); + progress.setSubMessageEnabled(true); + progress.setShowTitle(false); + progress.setMessage(getString(R.string.please_wait)); + progress.setSubMessage(getString(R.string.msg_reading_bootstrap)); + progress.setCancelable(false); + progress.show(getSupportFragmentManager(), "extract_bootstrap_progress"); - LOG.debug("Starting installation..."); - // Install bootstrap asynchronously - final var future = - BootstrapInstaller.doInstall( - this, message -> runOnUiThread(() -> progress.setSubMessage(message))); + LOG.debug("Starting installation..."); + // Install bootstrap asynchronously + final var future = + BootstrapInstaller.doInstall( + this, message -> runOnUiThread(() -> progress.setSubMessage(message))); - future.whenComplete( - (voidResult, throwable) -> { - LOG.debug("Completable future has been complete.", throwable); + future.whenComplete( + (voidResult, throwable) -> { + LOG.debug("Completable future has been complete.", throwable); - runOnUiThread( - () -> { - progress.dismissAllowingStateLoss(); + runOnUiThread( + () -> { + progress.dismissAllowingStateLoss(); - if (future.isCompletedExceptionally() || throwable != null) { - LOG.error("Future has been completed exceptionally."); - showInstallationError(throwable); + if (future.isCompletedExceptionally() || throwable != null) { + LOG.error("Future has been completed exceptionally."); + showInstallationError(throwable); + return; + } + + setupTerminalView(); + }); + }); + } + } + + private void showInstallationError(Throwable throwable) { + + binding.virtualKeyTable.setVisibility(View.GONE); + + final var frag = + CrashReportFragment.newInstance( + getString(R.string.title_installation_failed), + getString(R.string.msg_bootstrap_installation_error), + ThrowableUtils.getFullStackTrace(throwable)) + .setCloseAppOnClick(false); + + getSupportFragmentManager() + .beginTransaction() + .replace(binding.root.getId(), frag, "bootstrap_install_error_fragment") + .addToBackStack(null) + .commit(); + } + + private void setupTerminalView() { + terminal = new TerminalView(this, null); + terminal.setTerminalViewClient(client); + terminal.attachSession(createSession(getWorkingDirectory())); + terminal.setKeepScreenOn(true); + terminal.setTextSize(SizeUtils.dp2px(10)); + terminal.setTypeface(TypefaceUtils.jetbrainsMono()); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, 0); + params.weight = 1f; + + binding.getRoot().addView(terminal, 0, params); + try { + binding.virtualKeyTable.setVirtualKeysViewClient(getKeyListener()); + binding.virtualKeyTable.reload( + new VirtualKeysInfo( + ConstantsBridge.VIRTUAL_KEYS, + "", + VirtualKeysConstants.CONTROL_CHARS_ALIASES)); + } catch (JSONException e) { + LOG.error("Unable to parse terminal virtual keys json data", e); + } + } + + @NonNull + private String getWorkingDirectory() { + final Bundle extras = getIntent().getExtras(); + if (extras != null && extras.containsKey(KEY_WORKING_DIRECTORY)) { + String directory = extras.getString(KEY_WORKING_DIRECTORY, null); + if (directory == null || directory.trim().length() <= 0) { + directory = HOME.getAbsolutePath(); + } + return directory; + } + return HOME.getAbsolutePath(); + } + + private KeyListener getKeyListener() { + return listener == null ? listener = new KeyListener(terminal) : listener; + } + + @Override + protected void onResume() { + super.onResume(); + setTerminalCursorBlinkingState(true); + } + + @Override + protected void onStart() { + super.onStart(); + isVisible = true; + } + + @Override + protected void onStop() { + super.onStop(); + isVisible = false; + setTerminalCursorBlinkingState(false); + } + + public boolean isVisible() { + return isVisible; + } + + private void setTerminalCursorBlinkingState(boolean start) { + if (terminal != null && terminal.mEmulator != null) { + terminal.setTerminalCursorBlinkerState(start, true); + } + } + + private TerminalSession createSession(final String workingDirectory) { + final Map envs = getEnvironment(); + final String[] env = new String[envs.size()]; + int i = 0; + for (Map.Entry entry : envs.entrySet()) { + env[i] = entry.getKey() + "=" + entry.getValue(); + i++; + } + + session = + new TerminalSession( + getShellPath(), // Shell command + workingDirectory, // Working directory + new String[] {}, // Arguments + env, // Environment variables + TerminalEmulator.DEFAULT_TERMINAL_TRANSCRIPT_ROWS, // Transcript rows + client // TerminalSessionClient + ); + + try { + final var file = new File(SYSROOT, "etc/apt/sources.list"); + final var out = new FileOutputStream(file); + out.write(SOURCES_LIST_CONTENT); + out.flush(); + out.close(); + } catch (Throwable th) { + LOG.error("Unable to update sources.list", th); + } + + return session; + } + + @NonNull + private String getShellPath() { + final var useSystemShell = + getApp().getPrefManager() + .getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL); + if (!useSystemShell && LOGIN_SHELL.exists() && LOGIN_SHELL.isFile()) { + return LOGIN_SHELL.getAbsolutePath(); + } + + if (!useSystemShell) { + LOG.error( + "Default shell does not exist. Falling back to '/system/bin/sh'.", + "This should not happen in normal circumstances."); + } + + return "/system/bin/sh"; + } + + private static final class KeyListener implements VirtualKeysView.IVirtualKeysView { + + private final TerminalView terminal; + + public KeyListener(TerminalView terminal) { + this.terminal = terminal; + } + + @Override + public void onVirtualKeyButtonClick(View view, VirtualKeyButton buttonInfo, Button button) { + if (terminal == null) { + return; + } + if (buttonInfo.isMacro()) { + String[] keys = buttonInfo.getKey().split(" "); + boolean ctrlDown = false; + boolean altDown = false; + boolean shiftDown = false; + boolean fnDown = false; + for (String key : keys) { + if (SpecialButton.CTRL.getKey().equals(key)) { + ctrlDown = true; + } else if (SpecialButton.ALT.getKey().equals(key)) { + altDown = true; + } else if (SpecialButton.SHIFT.getKey().equals(key)) { + shiftDown = true; + } else if (SpecialButton.FN.getKey().equals(key)) { + fnDown = true; + } else { + onTerminalExtraKeyButtonClick(key, ctrlDown, altDown, shiftDown, fnDown); + ctrlDown = false; + altDown = false; + shiftDown = false; + fnDown = false; + } + } + } else { + onTerminalExtraKeyButtonClick(buttonInfo.getKey(), false, false, false, false); + } + } + + protected void onTerminalExtraKeyButtonClick( + String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) { + if (VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.containsKey(key)) { + Integer keyCode = VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.get(key); + if (keyCode == null) { return; - } + } + int metaState = 0; + if (ctrlDown) { + metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON; + } + if (altDown) { + metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; + } + if (shiftDown) { + metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON; + } + if (fnDown) { + metaState |= KeyEvent.META_FUNCTION_ON; + } - setupTerminalView(); - }); - }); - } - } - - private void showInstallationError(Throwable throwable) { - - binding.virtualKeyTable.setVisibility(View.GONE); - - final var frag = - CrashReportFragment.newInstance( - getString(R.string.title_installation_failed), - getString(R.string.msg_bootstrap_installation_error), - ThrowableUtils.getFullStackTrace(throwable)) - .setCloseAppOnClick(false); - - getSupportFragmentManager() - .beginTransaction() - .replace(binding.root.getId(), frag, "bootstrap_install_error_fragment") - .addToBackStack(null) - .commit(); - } - - private void setupTerminalView() { - terminal = new TerminalView(this, null); - terminal.setTerminalViewClient(client); - terminal.attachSession(createSession(getWorkingDirectory())); - terminal.setKeepScreenOn(true); - terminal.setTextSize(SizeUtils.dp2px(10)); - terminal.setTypeface(TypefaceUtils.jetbrainsMono()); - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, 0); - params.weight = 1f; - - binding.getRoot().addView(terminal, 0, params); - try { - binding.virtualKeyTable.setVirtualKeysViewClient(getKeyListener()); - binding.virtualKeyTable.reload( - new VirtualKeysInfo( - ConstantsBridge.VIRTUAL_KEYS, "", VirtualKeysConstants.CONTROL_CHARS_ALIASES)); - } catch (JSONException e) { - LOG.error("Unable to parse terminal virtual keys json data", e); - } - } - - @NonNull - private String getWorkingDirectory() { - final Bundle extras = getIntent().getExtras(); - if (extras != null && extras.containsKey(KEY_WORKING_DIRECTORY)) { - String directory = extras.getString(KEY_WORKING_DIRECTORY, null); - if (directory == null || directory.trim().length() <= 0) { - directory = HOME.getAbsolutePath(); - } - return directory; - } - return HOME.getAbsolutePath(); - } - - private KeyListener getKeyListener() { - return listener == null ? listener = new KeyListener(terminal) : listener; - } - - @Override - protected void onResume() { - super.onResume(); - setTerminalCursorBlinkingState(true); - } - - @Override - protected void onStart() { - super.onStart(); - isVisible = true; - } - - @Override - protected void onStop() { - super.onStop(); - isVisible = false; - setTerminalCursorBlinkingState(false); - } - - public boolean isVisible() { - return isVisible; - } - - private void setTerminalCursorBlinkingState(boolean start) { - if (terminal != null && terminal.mEmulator != null) { - terminal.setTerminalCursorBlinkerState(start, true); - } - } - - private TerminalSession createSession(final String workingDirectory) { - final Map envs = getEnvironment(); - final String[] env = new String[envs.size()]; - int i = 0; - for (Map.Entry entry : envs.entrySet()) { - env[i] = entry.getKey() + "=" + entry.getValue(); - i++; - } - - session = - new TerminalSession( - getShellPath(), // Shell command - workingDirectory, // Working directory - new String[] {}, // Arguments - env, // Environment variables - TerminalEmulator.DEFAULT_TERMINAL_TRANSCRIPT_ROWS, // Transcript rows - client // TerminalSessionClient - ); - - try { - final var file = new File(SYSROOT, "etc/apt/sources.list"); - final var out = new FileOutputStream(file); - out.write(SOURCES_LIST_CONTENT); - out.flush(); - out.close(); - } catch (Throwable th) { - LOG.error("Unable to update sources.list", th); - } - - return session; - } - - @NonNull - private String getShellPath() { - final var useSystemShell = - getApp().getPrefManager().getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL); - if (!useSystemShell && LOGIN_SHELL.exists() && LOGIN_SHELL.isFile()) { - return LOGIN_SHELL.getAbsolutePath(); - } - - if (!useSystemShell) { - LOG.error( - "Default shell does not exist. Falling back to '/system/bin/sh'.", - "This should not happen in normal circumstances."); - } - - return "/system/bin/sh"; - } - - private static final class KeyListener implements VirtualKeysView.IVirtualKeysView { - - private final TerminalView terminal; - - public KeyListener(TerminalView terminal) { - this.terminal = terminal; - } - - @Override - public void onVirtualKeyButtonClick(View view, VirtualKeyButton buttonInfo, Button button) { - if (terminal == null) { - return; - } - if (buttonInfo.isMacro()) { - String[] keys = buttonInfo.getKey().split(" "); - boolean ctrlDown = false; - boolean altDown = false; - boolean shiftDown = false; - boolean fnDown = false; - for (String key : keys) { - if (SpecialButton.CTRL.getKey().equals(key)) { - ctrlDown = true; - } else if (SpecialButton.ALT.getKey().equals(key)) { - altDown = true; - } else if (SpecialButton.SHIFT.getKey().equals(key)) { - shiftDown = true; - } else if (SpecialButton.FN.getKey().equals(key)) { - fnDown = true; - } else { - onTerminalExtraKeyButtonClick(key, ctrlDown, altDown, shiftDown, fnDown); - ctrlDown = false; - altDown = false; - shiftDown = false; - fnDown = false; - } - } - } else { - onTerminalExtraKeyButtonClick(buttonInfo.getKey(), false, false, false, false); - } - } - - protected void onTerminalExtraKeyButtonClick( - String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) { - if (VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.containsKey(key)) { - Integer keyCode = VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.get(key); - if (keyCode == null) { - return; - } - int metaState = 0; - if (ctrlDown) { - metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON; - } - if (altDown) { - metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; - } - if (shiftDown) { - metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON; - } - if (fnDown) { - metaState |= KeyEvent.META_FUNCTION_ON; + KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState); + terminal.onKeyDown(keyCode, keyEvent); + } else { + // not a control char + for (int off = 0; off < key.length(); ) { + int codePoint = key.codePointAt(off); + terminal.inputCodePoint(codePoint, ctrlDown, altDown); + off += Character.charCount(codePoint); + } + } } - KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState); - terminal.onKeyDown(keyCode, keyEvent); - } else { - // not a control char - for (int off = 0; off < key.length(); ) { - int codePoint = key.codePointAt(off); - terminal.inputCodePoint(codePoint, ctrlDown, altDown); - off += Character.charCount(codePoint); + @Override + public boolean performVirtualKeyButtonHapticFeedback( + View view, VirtualKeyButton buttonInfo, Button button) { + // No need to handle this + // VirtualKeysView will take care of performing haptic feedback + return false; } - } } - @Override - public boolean performVirtualKeyButtonHapticFeedback( - View view, VirtualKeyButton buttonInfo, Button button) { - // No need to handle this - // VirtualKeysView will take care of performing haptic feedback - return false; + private final class Client implements TerminalViewClient, TerminalSessionClient { + + @Override + public boolean onKeyUp(int keyCode, KeyEvent e) { + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { + if (keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning()) { + finish(); + return true; + } + return false; + } + + @Override + public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) { + return false; + } + + @Override + public void onTextChanged(TerminalSession changedSession) { + terminal.onScreenUpdated(); + } + + @Override + public void onTitleChanged(TerminalSession changedSession) {} + + @Override + public void onSessionFinished(TerminalSession finishedSession) { + finish(); + } + + @Override + public void onCopyTextToClipboard(TerminalSession session, String text) { + ClipboardUtils.copyText("AndroidIDE Terminal", text); + } + + @Override + public void onPasteTextFromClipboard(TerminalSession session) { + String clip = ClipboardUtils.getText().toString(); + if (clip.trim().length() > 0 && terminal != null && terminal.mEmulator != null) { + terminal.mEmulator.paste(clip); + } + } + + @Override + public void onBell(TerminalSession session) {} + + @Override + public void onColorsChanged(TerminalSession session) {} + + @Override + public void onTerminalCursorStateChange(boolean state) {} + + @Override + public Integer getTerminalCursorStyle() { + return TerminalEmulator.DEFAULT_TERMINAL_CURSOR_STYLE; + } + + @Override + public float onScale(float scale) { + return 1.0f; + } + + @Override + public void onSingleTapUp(MotionEvent e) { + KeyboardUtils.showSoftInput(terminal); + } + + @Override + public boolean shouldBackButtonBeMappedToEscape() { + return false; + } + + @Override + public boolean shouldEnforceCharBasedInput() { + return true; + } + + @Override + public boolean shouldUseCtrlSpaceWorkaround() { + return false; + } + + @Override + public boolean isTerminalViewSelected() { + return true; + } + + @Override + public void copyModeChanged(boolean copyMode) {} + + @Override + public boolean onLongPress(MotionEvent event) { + return false; + } + + @Override + public boolean readControlKey() { + Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.CTRL, true); + return state != null && state; + } + + @Override + public boolean readAltKey() { + Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.ALT, true); + return state != null && state; + } + + @Override + public boolean readFnKey() { + return false; + } + + @Override + public boolean readShiftKey() { + return false; + } + + @Override + public void onEmulatorSet() { + setTerminalCursorBlinkingState(true); + + if (session != null) { + binding.getRoot() + .setBackgroundColor( + session.getEmulator() + .mColors + .mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); + } + } + + @Override + public void logError(String tag, String message) { + // No logs + } + + @Override + public void logWarn(String tag, String message) {} + + @Override + public void logInfo(String tag, String message) {} + + @Override + public void logDebug(String tag, String message) {} + + @Override + public void logVerbose(String tag, String message) {} + + @Override + public void logStackTraceWithMessage(String tag, String message, Exception e) {} + + @Override + public void logStackTrace(String tag, Exception e) {} } - } - - private final class Client implements TerminalViewClient, TerminalSessionClient { - - @Override - public boolean onKeyUp(int keyCode, KeyEvent e) { - return false; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { - if (keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning()) { - finish(); - return true; - } - return false; - } - - @Override - public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) { - return false; - } - - @Override - public void onTextChanged(TerminalSession changedSession) { - terminal.onScreenUpdated(); - } - - @Override - public void onTitleChanged(TerminalSession changedSession) {} - - @Override - public void onSessionFinished(TerminalSession finishedSession) { - finish(); - } - - @Override - public void onCopyTextToClipboard(TerminalSession session, String text) { - ClipboardUtils.copyText("AndroidIDE Terminal", text); - } - - @Override - public void onPasteTextFromClipboard(TerminalSession session) { - String clip = ClipboardUtils.getText().toString(); - if (clip.trim().length() > 0 && terminal != null && terminal.mEmulator != null) { - terminal.mEmulator.paste(clip); - } - } - - @Override - public void onBell(TerminalSession session) {} - - @Override - public void onColorsChanged(TerminalSession session) {} - - @Override - public void onTerminalCursorStateChange(boolean state) {} - - @Override - public Integer getTerminalCursorStyle() { - return TerminalEmulator.DEFAULT_TERMINAL_CURSOR_STYLE; - } - - @Override - public float onScale(float scale) { - return 1.0f; - } - - @Override - public void onSingleTapUp(MotionEvent e) { - KeyboardUtils.showSoftInput(terminal); - } - - @Override - public boolean shouldBackButtonBeMappedToEscape() { - return false; - } - - @Override - public boolean shouldEnforceCharBasedInput() { - return true; - } - - @Override - public boolean shouldUseCtrlSpaceWorkaround() { - return false; - } - - @Override - public boolean isTerminalViewSelected() { - return true; - } - - @Override - public void copyModeChanged(boolean copyMode) {} - - @Override - public boolean onLongPress(MotionEvent event) { - return false; - } - - @Override - public boolean readControlKey() { - Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.CTRL, true); - return state != null && state; - } - - @Override - public boolean readAltKey() { - Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.ALT, true); - return state != null && state; - } - - @Override - public boolean readFnKey() { - return false; - } - - @Override - public boolean readShiftKey() { - return false; - } - - @Override - public void onEmulatorSet() { - setTerminalCursorBlinkingState(true); - - if (session != null) { - binding - .getRoot() - .setBackgroundColor( - session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); - } - } - - @Override - public void logError(String tag, String message) { - // No logs - } - - @Override - public void logWarn(String tag, String message) {} - - @Override - public void logInfo(String tag, String message) {} - - @Override - public void logDebug(String tag, String message) {} - - @Override - public void logVerbose(String tag, String message) {} - - @Override - public void logStackTraceWithMessage(String tag, String message, Exception e) {} - - @Override - public void logStackTrace(String tag, Exception e) {} - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/AttrListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/AttrListAdapter.java index ab1ac9b49..de3f2c998 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/AttrListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/AttrListAdapter.java @@ -18,8 +18,10 @@ package com.itsaky.androidide.adapters; import androidx.annotation.NonNull; + import com.itsaky.androidide.R; import com.itsaky.attrinfo.models.Attr; + import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -32,46 +34,48 @@ import java.util.function.Consumer; */ public class AttrListAdapter extends IconTextAdapter { - private final Consumer clickConsumer; - private final List attrs; + private final Consumer clickConsumer; + private final List attrs; - public AttrListAdapter(Consumer clickConsumer, List attrs) { - this.clickConsumer = clickConsumer; - if (attrs == null) { - attrs = new ArrayList<>(); + public AttrListAdapter(Consumer clickConsumer, List attrs) { + this.clickConsumer = clickConsumer; + if (attrs == null) { + attrs = new ArrayList<>(); + } + + this.attrs = attrs; } - this.attrs = attrs; - } - - @NonNull - @Override - public Attr getItemAt(int index) { - return attrs.get(index); - } - - @Override - public int getIconResource(int index) { - return R.drawable.ic_xml_attribute; - } - - @NonNull - @Override - public String getItemText(int index) { - return getItemAt(index).name; - } - - @Override - public int getItemCount() { - return attrs.size(); - } - - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - super.onBindViewHolder(holder, position); - - if (clickConsumer != null) { - holder.binding.getRoot().setOnClickListener(v -> clickConsumer.accept(getItemAt(position))); + @NonNull + @Override + public Attr getItemAt(int index) { + return attrs.get(index); + } + + @Override + public int getIconResource(int index) { + return R.drawable.ic_xml_attribute; + } + + @NonNull + @Override + public String getItemText(int index) { + return getItemAt(index).name; + } + + @Override + public int getItemCount() { + return attrs.size(); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + super.onBindViewHolder(holder, position); + + if (clickConsumer != null) { + holder.binding + .getRoot() + .setOnClickListener(v -> clickConsumer.accept(getItemAt(position))); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/CompletionListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/CompletionListAdapter.java index 1df41f5e6..e1f69cd91 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/CompletionListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/CompletionListAdapter.java @@ -24,7 +24,9 @@ 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; @@ -42,167 +44,181 @@ 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 io.github.rosemoe.sora.widget.component.EditorCompletionAdapter; public class CompletionListAdapter extends EditorCompletionAdapter { - private static final Logger LOG = Logger.instance("CompletionListAdapter"); + private static final Logger LOG = Logger.instance("CompletionListAdapter"); - @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); + @Override + public int getItemHeight() { + return (int) + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics()); } - if (isCurrentCursorPosition) { - binding - .getRoot() - .setBackgroundColor( - ContextCompat.getColor(getContext(), R.color.completionList_backgroundSelected)); - } else { - binding - .getRoot() - .setBackgroundColor( - ContextCompat.getColor(getContext(), R.color.completionList_background)); + @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(); } - binding.completionApiInfo.setVisibility(View.GONE); + private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) { - showApiInfoIfNeeded(item, binding.completionApiInfo); + new Thread( + () -> { + final ApiInfo apiInfo = StudioApp.getInstance().apiInfo(); + boolean hasRead = apiInfo != null && apiInfo.hasRead(); + boolean isValid = isValidForApiVersion(item); - return binding.getRoot(); - } + if (hasRead && isValid) { - private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) { + JsonElement element = new Gson().toJsonTree(item.getData()); + if (element == null || !element.isJsonObject()) return; + JsonObject data = element.getAsJsonObject(); + if (!data.has("className")) return; - new Thread( - () -> { - final ApiInfo apiInfo = StudioApp.getInstance().apiInfo(); - boolean hasRead = apiInfo != null && apiInfo.hasRead(); - boolean isValid = isValidForApiVersion(item); + final String className = data.get("className").getAsString(); + CompletionItemKind kind = item.getKind(); - if (hasRead && isValid) { + ClassInfo clazz = apiInfo.getClassByName(className); + if (clazz == null) return; - JsonElement element = new Gson().toJsonTree(item.getData()); - if (element == null || !element.isJsonObject()) return; - JsonObject data = element.getAsJsonObject(); - if (!data.has("className")) return; + Info info = clazz; - final String className = data.get("className").getAsString(); - CompletionItemKind kind = item.getKind(); + // 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(); + } - ClassInfo clazz = apiInfo.getClassByName(className); - if (clazz == null) return; + MethodInfo method = clazz.getMethod(simpleName, paramTypes); - Info info = clazz; + 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 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(); - } + if (field != null) { + info = field; + } + } - MethodInfo method = clazz.getMethod(simpleName, paramTypes); + 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 (method != null) { - info = method; - } - } - } else if (kind == CompletionItemKind.FIELD && data.has("memberName")) { - String simpleName = data.get("memberName").getAsString(); - FieldInfo field = clazz.getFieldByName(simpleName); + if (info.removed > 0) { + infoBuilder.append( + completionApiInfo + .getContext() + .getString( + R.string.msg_api_info_removed, + info.removed)); + infoBuilder.append("\n"); + } - if (field != null) { - info = field; - } - } + if (info.deprecated > 0) { + infoBuilder.append( + completionApiInfo + .getContext() + .getString( + R.string.msg_api_info_deprecated, + info.deprecated)); + infoBuilder.append("\n"); + } - 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(); - JsonElement element = new Gson().toJsonTree(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) - && element != null - && element.isJsonObject()) { - JsonObject data = element.getAsJsonObject(); - return data.has("className"); + 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(); + JsonElement element = new Gson().toJsonTree(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) + && element != null + && element.isJsonObject()) { + JsonObject data = element.getAsJsonObject(); + return data.has("className"); + } + return false; } - return false; - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdapter.java index ea21d644f..d38bc038f 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdapter.java @@ -20,79 +20,82 @@ package com.itsaky.androidide.adapters; import android.graphics.PorterDuff; import android.view.LayoutInflater; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.R; import com.itsaky.androidide.databinding.LayoutDiagnosticItemBinding; import com.itsaky.androidide.interfaces.DiagnosticClickListener; import com.itsaky.lsp.models.DiagnosticItem; import com.itsaky.lsp.models.DiagnosticSeverity; + import java.io.File; import java.util.List; public class DiagnosticItemAdapter extends RecyclerView.Adapter { - private final List diags; - private final File file; - private final DiagnosticClickListener listener; + private final List diags; + private final File file; + private final DiagnosticClickListener listener; - public DiagnosticItemAdapter( - List diags, File file, DiagnosticClickListener listener) { - this.diags = diags; - this.file = file; - this.listener = listener; - } - - @NonNull - @Override - public DiagnosticItemAdapter.VH onCreateViewHolder(@NonNull ViewGroup p1, int p2) { - return new VH( - LayoutDiagnosticItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); - } - - @Override - public void onBindViewHolder(DiagnosticItemAdapter.VH p1, int p2) { - final DiagnosticItem diagnostic = diags.get(p2); - final LayoutDiagnosticItemBinding binding = p1.binding; - - binding.icon.setImageResource(getDiagnosticIconId(diagnostic)); - binding.icon.setColorFilter( - ContextCompat.getColor( - binding.icon.getContext(), - diagnostic.getSeverity() == DiagnosticSeverity.ERROR - ? R.color.diagnostic_error - : R.color.diagnostic_warning), - PorterDuff.Mode.SRC_ATOP); - binding.title.setText(diagnostic.getMessage()); - - binding - .getRoot() - .setOnClickListener( - v -> { - if (listener != null) { - listener.onDiagnosticClick(file, diagnostic); - } - }); - } - - @Override - public int getItemCount() { - return diags.size(); - } - - private int getDiagnosticIconId(DiagnosticItem diagnostic) { - if (diagnostic.getSeverity() == DiagnosticSeverity.ERROR) - return R.drawable.ic_compilation_error; - return R.drawable.ic_info; - } - - public static class VH extends RecyclerView.ViewHolder { - private LayoutDiagnosticItemBinding binding; - - public VH(LayoutDiagnosticItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; + public DiagnosticItemAdapter( + List diags, File file, DiagnosticClickListener listener) { + this.diags = diags; + this.file = file; + this.listener = listener; + } + + @NonNull + @Override + public DiagnosticItemAdapter.VH onCreateViewHolder(@NonNull ViewGroup p1, int p2) { + return new VH( + LayoutDiagnosticItemBinding.inflate( + LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(DiagnosticItemAdapter.VH p1, int p2) { + final DiagnosticItem diagnostic = diags.get(p2); + final LayoutDiagnosticItemBinding binding = p1.binding; + + binding.icon.setImageResource(getDiagnosticIconId(diagnostic)); + binding.icon.setColorFilter( + ContextCompat.getColor( + binding.icon.getContext(), + diagnostic.getSeverity() == DiagnosticSeverity.ERROR + ? R.color.diagnostic_error + : R.color.diagnostic_warning), + PorterDuff.Mode.SRC_ATOP); + binding.title.setText(diagnostic.getMessage()); + + binding.getRoot() + .setOnClickListener( + v -> { + if (listener != null) { + listener.onDiagnosticClick(file, diagnostic); + } + }); + } + + @Override + public int getItemCount() { + return diags.size(); + } + + private int getDiagnosticIconId(DiagnosticItem diagnostic) { + if (diagnostic.getSeverity() == DiagnosticSeverity.ERROR) + return R.drawable.ic_compilation_error; + return R.drawable.ic_info; + } + + public static class VH extends RecyclerView.ViewHolder { + private LayoutDiagnosticItemBinding binding; + + public VH(LayoutDiagnosticItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java index ad2cc5ac4..fa1a06c41 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java @@ -23,68 +23,72 @@ package com.itsaky.androidide.adapters; import android.graphics.PorterDuff; import android.view.LayoutInflater; import android.view.ViewGroup; + import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.R; import com.itsaky.androidide.databinding.LayoutDiagnosticGroupBinding; import com.itsaky.androidide.interfaces.DiagnosticClickListener; import com.itsaky.androidide.models.DiagnosticGroup; + import java.util.List; public class DiagnosticsAdapter extends RecyclerView.Adapter { - private final List diags; - private final DiagnosticClickListener listener; + private final List diags; + private final DiagnosticClickListener listener; - public DiagnosticsAdapter(List diags, DiagnosticClickListener listener) { - this.diags = diags; - this.listener = listener; - } - - @Override - public DiagnosticsAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { - return new VH( - LayoutDiagnosticGroupBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); - } - - @Override - public void onBindViewHolder(DiagnosticsAdapter.VH p1, int p2) { - final DiagnosticGroup group = diags.get(p2); - final LayoutDiagnosticGroupBinding binding = p1.binding; - - final int color = - ContextCompat.getColor(binding.info.icon.getContext(), R.color.secondaryColor); - binding.info.icon.setImageResource(group.icon); - binding.info.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - binding.info.title.setText(group.text); - binding.info.title.setTextColor(color); - binding.diagnostics.setLayoutManager(new LinearLayoutManager(binding.diagnostics.getContext())); - binding.diagnostics.setAdapter( - new DiagnosticItemAdapter(group.diagnostics, group.file, listener)); - - binding - .info - .getRoot() - .setOnClickListener( - v -> { - if (listener != null) { - listener.onGroupClick(group); - } - }); - } - - @Override - public int getItemCount() { - return diags.size(); - } - - public class VH extends RecyclerView.ViewHolder { - private LayoutDiagnosticGroupBinding binding; - - public VH(LayoutDiagnosticGroupBinding binding) { - super(binding.getRoot()); - this.binding = binding; + public DiagnosticsAdapter(List diags, DiagnosticClickListener listener) { + this.diags = diags; + this.listener = listener; + } + + @Override + public DiagnosticsAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH( + LayoutDiagnosticGroupBinding.inflate( + LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(DiagnosticsAdapter.VH p1, int p2) { + final DiagnosticGroup group = diags.get(p2); + final LayoutDiagnosticGroupBinding binding = p1.binding; + + final int color = + ContextCompat.getColor(binding.info.icon.getContext(), R.color.secondaryColor); + binding.info.icon.setImageResource(group.icon); + binding.info.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + binding.info.title.setText(group.text); + binding.info.title.setTextColor(color); + binding.diagnostics.setLayoutManager( + new LinearLayoutManager(binding.diagnostics.getContext())); + binding.diagnostics.setAdapter( + new DiagnosticItemAdapter(group.diagnostics, group.file, listener)); + + binding.info + .getRoot() + .setOnClickListener( + v -> { + if (listener != null) { + listener.onGroupClick(group); + } + }); + } + + @Override + public int getItemCount() { + return diags.size(); + } + + public class VH extends RecyclerView.ViewHolder { + private LayoutDiagnosticGroupBinding binding; + + public VH(LayoutDiagnosticGroupBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.java index bbc662848..98fb6595f 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.java @@ -22,105 +22,112 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.viewpager2.adapter.FragmentStateAdapter; + import com.itsaky.androidide.R; import com.itsaky.androidide.fragments.DiagnosticsListFragment; import com.itsaky.androidide.fragments.IDELogFragment; import com.itsaky.androidide.fragments.LogViewFragment; import com.itsaky.androidide.fragments.SearchResultFragment; import com.itsaky.androidide.fragments.SimpleOutputFragment; + import java.util.ArrayList; import java.util.List; import java.util.Objects; public class EditorBottomSheetTabAdapter extends FragmentStateAdapter { - private final List fragments; + private final List fragments; - public EditorBottomSheetTabAdapter(@NonNull FragmentActivity fragmentActivity) { - super(fragmentActivity); + public EditorBottomSheetTabAdapter(@NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); - this.fragments = new ArrayList<>(); - this.fragments.add( - new Tab(fragmentActivity.getString(R.string.build_output), new SimpleOutputFragment())); - this.fragments.add( - new Tab(fragmentActivity.getString(R.string.app_logs), new LogViewFragment())); - this.fragments.add( - new Tab(fragmentActivity.getString(R.string.ide_logs), new IDELogFragment())); - this.fragments.add( - new Tab(fragmentActivity.getString(R.string.view_diags), new DiagnosticsListFragment())); - this.fragments.add( - new Tab( - fragmentActivity.getString(R.string.view_search_results), new SearchResultFragment())); - } - - public Fragment getFragmentAtIndex(int index) { - return this.fragments.get(index).fragment; - } - - @NonNull - @Override - public Fragment createFragment(int position) { - return this.fragments.get(position).fragment; - } - - @Override - public int getItemCount() { - return fragments.size(); - } - - public String getTitle(int position) { - return fragments.get(position).title; - } - - @NonNull - public SimpleOutputFragment getBuildOutputFragment() { - return Objects.requireNonNull(findFragmentByClass(SimpleOutputFragment.class)); - } - - @NonNull - public LogViewFragment getLogFragment() { - return Objects.requireNonNull(findFragmentByClass(LogViewFragment.class)); - } - - @NonNull - public DiagnosticsListFragment getDiagnosticsFragment() { - return Objects.requireNonNull(findFragmentByClass(DiagnosticsListFragment.class)); - } - - @NonNull - public SearchResultFragment getSearchResultFragment() { - return Objects.requireNonNull(findFragmentByClass(SearchResultFragment.class)); - } - - @Nullable - private T findFragmentByClass(Class clazz) { - for (final var tab : this.fragments) { - if (tab.fragment.getClass() == clazz) { - return (T) tab.fragment; - } - } - return null; - } - - public int findIndexOfFragmentByClass(Class tClass) { - for (int i = 0; i < this.fragments.size(); i++) { - final var tab = this.fragments.get(i); - if (tab.fragment.getClass() == tClass) { - return i; - } + this.fragments = new ArrayList<>(); + this.fragments.add( + new Tab( + fragmentActivity.getString(R.string.build_output), + new SimpleOutputFragment())); + this.fragments.add( + new Tab(fragmentActivity.getString(R.string.app_logs), new LogViewFragment())); + this.fragments.add( + new Tab(fragmentActivity.getString(R.string.ide_logs), new IDELogFragment())); + this.fragments.add( + new Tab( + fragmentActivity.getString(R.string.view_diags), + new DiagnosticsListFragment())); + this.fragments.add( + new Tab( + fragmentActivity.getString(R.string.view_search_results), + new SearchResultFragment())); } - return -1; - } - - static class Tab { - - public Tab(String title, Fragment fragment) { - this.title = title; - this.fragment = fragment; + public Fragment getFragmentAtIndex(int index) { + return this.fragments.get(index).fragment; } - String title; - Fragment fragment; - } + @NonNull + @Override + public Fragment createFragment(int position) { + return this.fragments.get(position).fragment; + } + + @Override + public int getItemCount() { + return fragments.size(); + } + + public String getTitle(int position) { + return fragments.get(position).title; + } + + @NonNull + public SimpleOutputFragment getBuildOutputFragment() { + return Objects.requireNonNull(findFragmentByClass(SimpleOutputFragment.class)); + } + + @NonNull + public LogViewFragment getLogFragment() { + return Objects.requireNonNull(findFragmentByClass(LogViewFragment.class)); + } + + @NonNull + public DiagnosticsListFragment getDiagnosticsFragment() { + return Objects.requireNonNull(findFragmentByClass(DiagnosticsListFragment.class)); + } + + @NonNull + public SearchResultFragment getSearchResultFragment() { + return Objects.requireNonNull(findFragmentByClass(SearchResultFragment.class)); + } + + @Nullable + private T findFragmentByClass(Class clazz) { + for (final var tab : this.fragments) { + if (tab.fragment.getClass() == clazz) { + return (T) tab.fragment; + } + } + return null; + } + + public int findIndexOfFragmentByClass(Class tClass) { + for (int i = 0; i < this.fragments.size(); i++) { + final var tab = this.fragments.get(i); + if (tab.fragment.getClass() == tClass) { + return i; + } + } + + return -1; + } + + static class Tab { + + public Tab(String title, Fragment fragment) { + this.title = title; + this.fragment = fragment; + } + + String title; + Fragment fragment; + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/IconTextAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/IconTextAdapter.java index 8f67b9483..0b1b92bde 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/IconTextAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/IconTextAdapter.java @@ -20,8 +20,10 @@ package com.itsaky.androidide.adapters; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.databinding.LayoutSimpleIconTextBinding; /** @@ -31,86 +33,86 @@ import com.itsaky.androidide.databinding.LayoutSimpleIconTextBinding; */ public abstract class IconTextAdapter extends RecyclerView.Adapter { - private OnBindListener bindListener; + private OnBindListener bindListener; - public IconTextAdapter setOnBindListener(OnBindListener listener) { - this.bindListener = listener; - return this; - } - - @NonNull - @Override - public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new VH( - LayoutSimpleIconTextBinding.inflate( - LayoutInflater.from(parent.getContext()), parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - final var binding = holder.binding; - final var item = getItemAt(position); - - if (this.bindListener != null && this.bindListener.onBind(item, holder, position)) { - return; + public IconTextAdapter setOnBindListener(OnBindListener listener) { + this.bindListener = listener; + return this; } - final var icon = getIconResource(position); - - if (icon == -1) { - binding.icon.setVisibility(View.GONE); - } else { - binding.icon.setImageResource(icon); + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH( + LayoutSimpleIconTextBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); } - binding.text.setText(getItemText(position)); + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + final var binding = holder.binding; + final var item = getItemAt(position); - if (this.bindListener != null) { - this.bindListener.postBind(item, holder, position); - } - } + if (this.bindListener != null && this.bindListener.onBind(item, holder, position)) { + return; + } - /** - * Get the list item at the given position. - * - * @param index The index of the item to retrieve. - * @return The item at the given index. Must not be null. - */ - @NonNull - public abstract E getItemAt(int index); + final var icon = getIconResource(position); - /** - * Get the icon resource ID of the item the given index. - * - * @param index The index of the item. - * @return The icon resource id or -1 to hide the icon. - */ - public abstract int getIconResource(int index); + if (icon == -1) { + binding.icon.setVisibility(View.GONE); + } else { + binding.icon.setImageResource(icon); + } - /** - * Get the title of the item at the given index. - * - * @param index The index of the item. - * @return The title of the item. - */ - @NonNull - public abstract String getItemText(int index); + binding.text.setText(getItemText(position)); - public interface OnBindListener { - default boolean onBind(T item, VH holder, int position) { - return false; + if (this.bindListener != null) { + this.bindListener.postBind(item, holder, position); + } } - default void postBind(T item, VH holder, int position) {} - } + /** + * Get the list item at the given position. + * + * @param index The index of the item to retrieve. + * @return The item at the given index. Must not be null. + */ + @NonNull + public abstract E getItemAt(int index); - public static class VH extends RecyclerView.ViewHolder { + /** + * Get the icon resource ID of the item the given index. + * + * @param index The index of the item. + * @return The icon resource id or -1 to hide the icon. + */ + public abstract int getIconResource(int index); - public final LayoutSimpleIconTextBinding binding; + /** + * Get the title of the item at the given index. + * + * @param index The index of the item. + * @return The title of the item. + */ + @NonNull + public abstract String getItemText(int index); - public VH(@NonNull LayoutSimpleIconTextBinding binding) { - super(binding.getRoot()); - this.binding = binding; + public interface OnBindListener { + default boolean onBind(T item, VH holder, int position) { + return false; + } + + default void postBind(T item, VH holder, int position) {} + } + + public static class VH extends RecyclerView.ViewHolder { + + public final LayoutSimpleIconTextBinding binding; + + public VH(@NonNull LayoutSimpleIconTextBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/OptionsSheetAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/OptionsSheetAdapter.java index f0cfc5e57..a3425a86d 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/OptionsSheetAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/OptionsSheetAdapter.java @@ -22,58 +22,61 @@ package com.itsaky.androidide.adapters; import android.view.LayoutInflater; import android.view.ViewGroup; + import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.databinding.LayoutOptionssheetItemBinding; import com.itsaky.androidide.models.SheetOption; + import java.util.List; public class OptionsSheetAdapter extends RecyclerView.Adapter { - private OnOptionsClickListener listener; - private List options; + private OnOptionsClickListener listener; + private List options; - public OptionsSheetAdapter(List options, OnOptionsClickListener listener) { - this.options = options; - this.listener = listener; - } - - @Override - public OptionsSheetAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { - return new VH( - LayoutOptionssheetItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); - } - - @Override - public void onBindViewHolder(OptionsSheetAdapter.VH p1, int p2) { - final LayoutOptionssheetItemBinding binding = p1.binding; - final SheetOption option = options.get(p2); - - binding.text.setText(option.titleRes); - binding.icon.setImageResource(option.iconRes); - - binding - .getRoot() - .setOnClickListener( - v -> { - if (listener != null) listener.onOptionClick(option); - }); - } - - @Override - public int getItemCount() { - return options.size(); - } - - public class VH extends RecyclerView.ViewHolder { - private LayoutOptionssheetItemBinding binding; - - public VH(LayoutOptionssheetItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; + public OptionsSheetAdapter(List options, OnOptionsClickListener listener) { + this.options = options; + this.listener = listener; } - } - public static interface OnOptionsClickListener { - public void onOptionClick(SheetOption option); - } + @Override + public OptionsSheetAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH( + LayoutOptionssheetItemBinding.inflate( + LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(OptionsSheetAdapter.VH p1, int p2) { + final LayoutOptionssheetItemBinding binding = p1.binding; + final SheetOption option = options.get(p2); + + binding.text.setText(option.titleRes); + binding.icon.setImageResource(option.iconRes); + + binding.getRoot() + .setOnClickListener( + v -> { + if (listener != null) listener.onOptionClick(option); + }); + } + + @Override + public int getItemCount() { + return options.size(); + } + + public class VH extends RecyclerView.ViewHolder { + private LayoutOptionssheetItemBinding binding; + + public VH(LayoutOptionssheetItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + + public static interface OnOptionsClickListener { + public void onOptionClick(SheetOption option); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/ProjectsListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/ProjectsListAdapter.java index 7e7801f16..3db235826 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/ProjectsListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/ProjectsListAdapter.java @@ -24,9 +24,11 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; + import androidx.recyclerview.widget.RecyclerView; import androidx.transition.ChangeBounds; import androidx.transition.TransitionManager; + import com.blankj.utilcode.util.FileUtils; import com.bumptech.glide.Glide; import com.itsaky.androidide.R; @@ -35,131 +37,135 @@ import com.itsaky.androidide.databinding.LayoutProjectsItemBinding; import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.toaster.Toaster; + import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class ProjectsListAdapter extends RecyclerView.Adapter { - private ArrayList mProjects; - private OnProjectClickListener listener; + private ArrayList mProjects; + private OnProjectClickListener listener; - private final Comparator PROJECTS_SORTER = - new Comparator() { + private final Comparator PROJECTS_SORTER = + new Comparator() { - @Override - public int compare(AndroidProject p1, AndroidProject p2) { - return p1.getAppName().compareTo(p2.getAppName()); + @Override + public int compare(AndroidProject p1, AndroidProject p2) { + return p1.getAppName().compareTo(p2.getAppName()); + } + }; + + public ProjectsListAdapter( + ArrayList mProjects, OnProjectClickListener listener) { + this.mProjects = mProjects; + this.listener = listener; + Collections.sort(this.mProjects, PROJECTS_SORTER); + } + + @Override + public ProjectsListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH( + LayoutProjectsItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(ProjectsListAdapter.VH holder, int p2) { + final AndroidProject project = mProjects.get(p2); + holder.binding.projecsitemName.setText(project.getAppName()); + holder.binding.projecsitemPkgName.setText(project.getPackageName()); + holder.binding.deleteMsg.setText( + holder.binding + .deleteMsg + .getContext() + .getString(R.string.msg_confirm_delete, project.getAppName())); + if (!TextUtils.isEmpty(project.getIconPath())) + Glide.with(holder.itemView.getContext().getApplicationContext()) + .load(new File(project.getIconPath())) + .into(holder.binding.projecsitemIcon); + else holder.binding.projecsitemIcon.setImageResource(R.drawable.ic_android); + + holder.binding.projectsitemClickableContainer.setOnClickListener( + v -> sendClickEvent(project)); + holder.binding.projectsitemClickableContainer.setOnLongClickListener( + v -> holder.toggleDelete()); + holder.binding.delete.setOnClickListener(v -> holder.showConfirmDelete()); + holder.binding.deleteCancel.setOnClickListener(v -> holder.hideAllDelete()); + holder.binding.deleteOk.setOnClickListener(v -> deleteProject(project, p2)); + } + + private void deleteProject(AndroidProject project, int index) { + index = index >= mProjects.size() ? mProjects.size() - 1 : index; + if (index < 0) return; + mProjects.remove(index); + notifyItemRemoved(index); + + new TaskExecutor() + .executeAsync( + () -> FileUtils.delete(project.getProjectPath()), + __ -> { + StudioApp.getInstance() + .toast( + __ + ? R.string.msg_project_deleted + : R.string.msg_project_delete_failed, + Toaster.Type.SUCCESS); + }); + } + + private void sendClickEvent(AndroidProject project) { + if (listener != null) listener.onProjectClick(project); + } + + @Override + public int getItemCount() { + return mProjects.size(); + } + + public class VH extends RecyclerView.ViewHolder { + LayoutProjectsItemBinding binding; + + public VH(LayoutProjectsItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; } - }; - public ProjectsListAdapter(ArrayList mProjects, OnProjectClickListener listener) { - this.mProjects = mProjects; - this.listener = listener; - Collections.sort(this.mProjects, PROJECTS_SORTER); - } + public void showConfirmDelete() { + binding.confirmDeleteContainer.setEnabled(true); + TransitionManager.beginDelayedTransition( + binding.getRoot(), new ChangeBounds().setDuration(85)); + binding.confirmDeleteContainer.setVisibility(View.VISIBLE); + } - @Override - public ProjectsListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { - return new VH( - LayoutProjectsItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); - } + public void hideAllDelete() { + hideDelete(); + hideConfirmDelete(); + } - @Override - public void onBindViewHolder(ProjectsListAdapter.VH holder, int p2) { - final AndroidProject project = mProjects.get(p2); - holder.binding.projecsitemName.setText(project.getAppName()); - holder.binding.projecsitemPkgName.setText(project.getPackageName()); - holder.binding.deleteMsg.setText( - holder - .binding - .deleteMsg - .getContext() - .getString(R.string.msg_confirm_delete, project.getAppName())); - if (!TextUtils.isEmpty(project.getIconPath())) - Glide.with(holder.itemView.getContext().getApplicationContext()) - .load(new File(project.getIconPath())) - .into(holder.binding.projecsitemIcon); - else holder.binding.projecsitemIcon.setImageResource(R.drawable.ic_android); + public boolean toggleDelete() { + binding.delete.setEnabled(!binding.delete.isEnabled()); + TransitionManager.beginDelayedTransition( + binding.getRoot(), new ChangeBounds().setDuration(85)); + binding.delete.setVisibility(binding.delete.isEnabled() ? View.VISIBLE : View.GONE); + return true; + } - holder.binding.projectsitemClickableContainer.setOnClickListener(v -> sendClickEvent(project)); - holder.binding.projectsitemClickableContainer.setOnLongClickListener( - v -> holder.toggleDelete()); - holder.binding.delete.setOnClickListener(v -> holder.showConfirmDelete()); - holder.binding.deleteCancel.setOnClickListener(v -> holder.hideAllDelete()); - holder.binding.deleteOk.setOnClickListener(v -> deleteProject(project, p2)); - } + public void hideDelete() { + binding.delete.setEnabled(false); + TransitionManager.beginDelayedTransition( + binding.getRoot(), new ChangeBounds().setDuration(85)); + binding.delete.setVisibility(View.GONE); + } - private void deleteProject(AndroidProject project, int index) { - index = index >= mProjects.size() ? mProjects.size() - 1 : index; - if (index < 0) return; - mProjects.remove(index); - notifyItemRemoved(index); - - new TaskExecutor() - .executeAsync( - () -> FileUtils.delete(project.getProjectPath()), - __ -> { - StudioApp.getInstance() - .toast( - __ ? R.string.msg_project_deleted : R.string.msg_project_delete_failed, - Toaster.Type.SUCCESS); - }); - } - - private void sendClickEvent(AndroidProject project) { - if (listener != null) listener.onProjectClick(project); - } - - @Override - public int getItemCount() { - return mProjects.size(); - } - - public class VH extends RecyclerView.ViewHolder { - LayoutProjectsItemBinding binding; - - public VH(LayoutProjectsItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; + public void hideConfirmDelete() { + binding.confirmDeleteContainer.setEnabled(false); + TransitionManager.beginDelayedTransition( + binding.getRoot(), new ChangeBounds().setDuration(85)); + binding.confirmDeleteContainer.setVisibility(View.GONE); + } } - public void showConfirmDelete() { - binding.confirmDeleteContainer.setEnabled(true); - TransitionManager.beginDelayedTransition( - binding.getRoot(), new ChangeBounds().setDuration(85)); - binding.confirmDeleteContainer.setVisibility(View.VISIBLE); + public interface OnProjectClickListener { + public void onProjectClick(AndroidProject project); } - - public void hideAllDelete() { - hideDelete(); - hideConfirmDelete(); - } - - public boolean toggleDelete() { - binding.delete.setEnabled(!binding.delete.isEnabled()); - TransitionManager.beginDelayedTransition( - binding.getRoot(), new ChangeBounds().setDuration(85)); - binding.delete.setVisibility(binding.delete.isEnabled() ? View.VISIBLE : View.GONE); - return true; - } - - public void hideDelete() { - binding.delete.setEnabled(false); - TransitionManager.beginDelayedTransition( - binding.getRoot(), new ChangeBounds().setDuration(85)); - binding.delete.setVisibility(View.GONE); - } - - public void hideConfirmDelete() { - binding.confirmDeleteContainer.setEnabled(false); - TransitionManager.beginDelayedTransition( - binding.getRoot(), new ChangeBounds().setDuration(85)); - binding.confirmDeleteContainer.setVisibility(View.GONE); - } - } - - public interface OnProjectClickListener { - public void onProjectClick(AndroidProject project); - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java index 5178f691b..fd3868eed 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java @@ -24,14 +24,17 @@ import android.graphics.PorterDuff; import android.text.SpannableStringBuilder; import android.view.LayoutInflater; import android.view.ViewGroup; + import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.blankj.utilcode.util.ThreadUtils; import com.itsaky.androidide.databinding.LayoutSearchResultGroupBinding; import com.itsaky.androidide.databinding.LayoutSearchResultItemBinding; import com.itsaky.androidide.models.SearchResult; import com.itsaky.androidide.syntax.highlighters.JavaHighlighter; + import java.io.File; import java.util.ArrayList; import java.util.HashMap; @@ -40,124 +43,124 @@ import java.util.Map; public class SearchListAdapter extends RecyclerView.Adapter { - private final Map> results; - private final List keys; + private final Map> results; + private final List keys; - private final OnFileClickListener fileListener; - private final OnMatchClickListener matchListener; + private final OnFileClickListener fileListener; + private final OnMatchClickListener matchListener; - public SearchListAdapter( - Map> results, - OnFileClickListener fileListener, - OnMatchClickListener matchListener) { - if (results == null) results = new HashMap<>(); - this.results = results; - this.keys = new ArrayList(results.keySet()); - this.fileListener = fileListener; - this.matchListener = matchListener; - } - - @Override - public SearchListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { - return new VH(LayoutSearchResultGroupBinding.inflate(LayoutInflater.from(p1.getContext()))); - } - - @Override - public void onBindViewHolder(SearchListAdapter.VH p1, int p2) { - final LayoutSearchResultGroupBinding binding = p1.binding; - final File file = keys.get(p2); - final List matches = results.get(file); - final int color = - ContextCompat.getColor( - binding.icon.getContext(), com.itsaky.androidide.R.color.secondaryColor); - binding.title.setText(file.getName()); - binding.icon.setImageResource(com.itsaky.androidide.R.drawable.ic_language_java); - binding.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - binding.items.setLayoutManager(new LinearLayoutManager(binding.items.getContext())); - binding.items.setAdapter(new ChildAdapter(matches)); - - binding - .getRoot() - .setOnClickListener( - v -> { - if (fileListener != null) fileListener.onClick(file); - }); - } - - @Override - public int getItemCount() { - return results.size(); - } - - public class ChildAdapter extends RecyclerView.Adapter { - - private final List matches; - - public ChildAdapter(List matches) { - if (matches == null) matches = new ArrayList<>(); - this.matches = matches; + public SearchListAdapter( + Map> results, + OnFileClickListener fileListener, + OnMatchClickListener matchListener) { + if (results == null) results = new HashMap<>(); + this.results = results; + this.keys = new ArrayList(results.keySet()); + this.fileListener = fileListener; + this.matchListener = matchListener; } @Override - public SearchListAdapter.ChildVH onCreateViewHolder(ViewGroup p1, int p2) { - return new ChildVH( - LayoutSearchResultItemBinding.inflate(LayoutInflater.from(p1.getContext()))); + public SearchListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH(LayoutSearchResultGroupBinding.inflate(LayoutInflater.from(p1.getContext()))); } @Override - public void onBindViewHolder(SearchListAdapter.ChildVH p1, int p2) { - final SearchResult match = matches.get(p2); - final LayoutSearchResultItemBinding binding = p1.binding; - new Thread( - () -> { - try { - final SpannableStringBuilder sb = - new JavaHighlighter().highlight(match.line, match.match); - ThreadUtils.runOnUiThread(() -> binding.text.setText(sb)); - } catch (Exception e) { - ThreadUtils.runOnUiThread(() -> binding.text.setText(match.match)); - } - }) - .start(); - binding - .getRoot() - .setOnClickListener( - v -> { - if (matchListener != null) { - matchListener.onClick(match); - } - }); + public void onBindViewHolder(SearchListAdapter.VH p1, int p2) { + final LayoutSearchResultGroupBinding binding = p1.binding; + final File file = keys.get(p2); + final List matches = results.get(file); + final int color = + ContextCompat.getColor( + binding.icon.getContext(), com.itsaky.androidide.R.color.secondaryColor); + binding.title.setText(file.getName()); + binding.icon.setImageResource(com.itsaky.androidide.R.drawable.ic_language_java); + binding.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + binding.items.setLayoutManager(new LinearLayoutManager(binding.items.getContext())); + binding.items.setAdapter(new ChildAdapter(matches)); + + binding.getRoot() + .setOnClickListener( + v -> { + if (fileListener != null) fileListener.onClick(file); + }); } @Override public int getItemCount() { - return matches.size(); + return results.size(); } - } - public class VH extends RecyclerView.ViewHolder { - private LayoutSearchResultGroupBinding binding; + public class ChildAdapter extends RecyclerView.Adapter { - public VH(LayoutSearchResultGroupBinding binding) { - super(binding.getRoot()); - this.binding = binding; + private final List matches; + + public ChildAdapter(List matches) { + if (matches == null) matches = new ArrayList<>(); + this.matches = matches; + } + + @Override + public SearchListAdapter.ChildVH onCreateViewHolder(ViewGroup p1, int p2) { + return new ChildVH( + LayoutSearchResultItemBinding.inflate(LayoutInflater.from(p1.getContext()))); + } + + @Override + public void onBindViewHolder(SearchListAdapter.ChildVH p1, int p2) { + final SearchResult match = matches.get(p2); + final LayoutSearchResultItemBinding binding = p1.binding; + new Thread( + () -> { + try { + final SpannableStringBuilder sb = + new JavaHighlighter() + .highlight(match.line, match.match); + ThreadUtils.runOnUiThread(() -> binding.text.setText(sb)); + } catch (Exception e) { + ThreadUtils.runOnUiThread( + () -> binding.text.setText(match.match)); + } + }) + .start(); + binding.getRoot() + .setOnClickListener( + v -> { + if (matchListener != null) { + matchListener.onClick(match); + } + }); + } + + @Override + public int getItemCount() { + return matches.size(); + } } - } - public class ChildVH extends RecyclerView.ViewHolder { - private LayoutSearchResultItemBinding binding; + public class VH extends RecyclerView.ViewHolder { + private LayoutSearchResultGroupBinding binding; - public ChildVH(LayoutSearchResultItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; + public VH(LayoutSearchResultGroupBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } } - } - public static interface OnFileClickListener { - void onClick(File file); - } + public class ChildVH extends RecyclerView.ViewHolder { + private LayoutSearchResultItemBinding binding; - public static interface OnMatchClickListener { - void onClick(SearchResult match); - } + public ChildVH(LayoutSearchResultItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + + public static interface OnFileClickListener { + void onClick(File file); + } + + public static interface OnMatchClickListener { + void onClick(SearchResult match); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SimpleIconTextAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SimpleIconTextAdapter.java index a61f5a1b0..be64d6d00 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/SimpleIconTextAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SimpleIconTextAdapter.java @@ -18,7 +18,9 @@ package com.itsaky.androidide.adapters; import androidx.annotation.NonNull; + import com.itsaky.androidide.models.IconTextListItem; + import java.util.List; /** @@ -28,31 +30,31 @@ import java.util.List; */ public class SimpleIconTextAdapter extends IconTextAdapter { - private final List items; + private final List items; - public SimpleIconTextAdapter(List items) { - this.items = items; - } + public SimpleIconTextAdapter(List items) { + this.items = items; + } - @NonNull - @Override - public IconTextListItem getItemAt(int index) { - return items.get(index); - } + @NonNull + @Override + public IconTextListItem getItemAt(int index) { + return items.get(index); + } - @Override - public int getIconResource(int index) { - return getItemAt(index).getIconResource(); - } + @Override + public int getIconResource(int index) { + return getItemAt(index).getIconResource(); + } - @NonNull - @Override - public String getItemText(int index) { - return getItemAt(index).getText(); - } + @NonNull + @Override + public String getItemText(int index) { + return getItemAt(index).getText(); + } - @Override - public int getItemCount() { - return items.size(); - } + @Override + public int getItemCount() { + return items.size(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java index b984358df..b9a254ec6 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java @@ -20,100 +20,108 @@ package com.itsaky.androidide.adapters; import android.annotation.SuppressLint; import android.view.LayoutInflater; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.databinding.LayoutSymbolItemBinding; import com.itsaky.androidide.views.SymbolInputView.Symbol; import com.itsaky.androidide.views.editor.IDEEditor; + import java.util.ArrayList; import java.util.List; public class SymbolInputAdapter extends RecyclerView.Adapter { - private final IDEEditor editor; - private Symbol[] symbols; + private final IDEEditor editor; + private Symbol[] symbols; - public SymbolInputAdapter(IDEEditor editor) { - this(editor, null); - } - - public SymbolInputAdapter(IDEEditor editor, Symbol[] symbols) { - this.editor = editor; - this.symbols = symbols == null ? new Symbol[0] : symbols; - } - - @SuppressLint("NotifyDataSetChanged") - public void setSymbols(boolean notify, Symbol... symbols) { - this.symbols = symbols; - if (notify) { - notifyDataSetChanged(); - } - } - - @NonNull - @Override - public VH onCreateViewHolder(@NonNull ViewGroup parent, int itemType) { - return new VH( - LayoutSymbolItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - if (symbols == null || symbols[position] == null) return; - final Symbol symbol = symbols[position]; - holder.binding.symbol.setText(symbol.label); - holder.binding.symbol.setOnClickListener(__ -> insertSymbol(symbol.commit, symbol.offset)); - } - - @Override - public int getItemCount() { - return symbols == null ? 0 : symbols.length; - } - - public static class VH extends RecyclerView.ViewHolder { - LayoutSymbolItemBinding binding; - - public VH(LayoutSymbolItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - } - - void insertSymbol(String text, int selectionOffset) { - if (selectionOffset < 0 || selectionOffset > text.length()) { - return; - } - var cur = editor.getText().getCursor(); - if (cur.isSelected()) { - editor - .getText() - .delete(cur.getLeftLine(), cur.getLeftColumn(), cur.getRightLine(), cur.getRightColumn()); - editor.notifyIMEExternalCursorChange(); + public SymbolInputAdapter(IDEEditor editor) { + this(editor, null); } - if (cur.getLeftColumn() < editor.getText().getColumnCount(cur.getLeftLine()) - && text.length() == 1 - && text.charAt(0) == editor.getText().charAt(cur.getLeftLine(), cur.getLeftColumn()) - && pairs.contains(text.charAt(0))) { - editor.moveSelectionRight(); - } else { - editor.commitText(text); - if (selectionOffset != text.length()) { - editor.setSelection( - cur.getRightLine(), cur.getRightColumn() - (text.length() - selectionOffset)); - } + public SymbolInputAdapter(IDEEditor editor, Symbol[] symbols) { + this.editor = editor; + this.symbols = symbols == null ? new Symbol[0] : symbols; } - } - private static final List pairs; + @SuppressLint("NotifyDataSetChanged") + public void setSymbols(boolean notify, Symbol... symbols) { + this.symbols = symbols; + if (notify) { + notifyDataSetChanged(); + } + } - static { - pairs = new ArrayList<>(); - pairs.add('}'); - pairs.add(')'); - pairs.add(']'); - pairs.add('"'); - pairs.add('\''); - pairs.add('>'); - } + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int itemType) { + return new VH( + LayoutSymbolItemBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + if (symbols == null || symbols[position] == null) return; + final Symbol symbol = symbols[position]; + holder.binding.symbol.setText(symbol.label); + holder.binding.symbol.setOnClickListener(__ -> insertSymbol(symbol.commit, symbol.offset)); + } + + @Override + public int getItemCount() { + return symbols == null ? 0 : symbols.length; + } + + public static class VH extends RecyclerView.ViewHolder { + LayoutSymbolItemBinding binding; + + public VH(LayoutSymbolItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + + void insertSymbol(String text, int selectionOffset) { + if (selectionOffset < 0 || selectionOffset > text.length()) { + return; + } + var cur = editor.getText().getCursor(); + if (cur.isSelected()) { + editor.getText() + .delete( + cur.getLeftLine(), + cur.getLeftColumn(), + cur.getRightLine(), + cur.getRightColumn()); + editor.notifyIMEExternalCursorChange(); + } + + if (cur.getLeftColumn() < editor.getText().getColumnCount(cur.getLeftLine()) + && text.length() == 1 + && text.charAt(0) == editor.getText().charAt(cur.getLeftLine(), cur.getLeftColumn()) + && pairs.contains(text.charAt(0))) { + editor.moveSelectionRight(); + } else { + editor.commitText(text); + if (selectionOffset != text.length()) { + editor.setSelection( + cur.getRightLine(), + cur.getRightColumn() - (text.length() - selectionOffset)); + } + } + } + + private static final List pairs; + + static { + pairs = new ArrayList<>(); + pairs.add('}'); + pairs.add(')'); + pairs.add(']'); + pairs.add('"'); + pairs.add('\''); + pairs.add('>'); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/TextActionItemAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/TextActionItemAdapter.java index 0b132ed46..f95e9f95d 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/TextActionItemAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/TextActionItemAdapter.java @@ -20,12 +20,15 @@ package com.itsaky.androidide.adapters; import android.view.Gravity; import android.view.LayoutInflater; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.databinding.LayoutTextActionItemBinding; import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.androidide.views.editor.IDEEditor; + import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -37,68 +40,68 @@ import java.util.function.Consumer; */ public class TextActionItemAdapter extends RecyclerView.Adapter { - private final List actions; - private final Consumer onClick; - private final boolean isHorizontal; + private final List actions; + private final Consumer onClick; + private final boolean isHorizontal; - public TextActionItemAdapter( - List actions, Consumer onClick) { - Objects.requireNonNull(actions); - this.actions = actions; - this.onClick = onClick; - this.isHorizontal = - StudioApp.getInstance() - .getPrefManager() - .getBoolean(PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP, false); - } - - @NonNull - @Override - public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new VH( - LayoutTextActionItemBinding.inflate( - LayoutInflater.from(parent.getContext()), parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - final var binding = holder.binding; - final var action = actions.get(position); - final var button = binding.getRoot(); - - if (isHorizontal) { - // If not set to wrap_content, one item will take whole screen width. - final var params = button.getLayoutParams(); - params.width = ViewGroup.LayoutParams.WRAP_CONTENT; - button.setLayoutParams(params); - - // looks better - button.setGravity(Gravity.CENTER); + public TextActionItemAdapter( + List actions, Consumer onClick) { + Objects.requireNonNull(actions); + this.actions = actions; + this.onClick = onClick; + this.isHorizontal = + StudioApp.getInstance() + .getPrefManager() + .getBoolean(PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP, false); } - button.setText(action.titleId); - button.setCompoundDrawablesRelative(action.icon, null, null, null); - - button.setOnClickListener( - v -> { - if (onClick != null) { - onClick.accept(action); - } - }); - } - - @Override - public int getItemCount() { - return actions.size(); - } - - static class VH extends RecyclerView.ViewHolder { - - private final LayoutTextActionItemBinding binding; - - public VH(@NonNull LayoutTextActionItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH( + LayoutTextActionItemBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + final var binding = holder.binding; + final var action = actions.get(position); + final var button = binding.getRoot(); + + if (isHorizontal) { + // If not set to wrap_content, one item will take whole screen width. + final var params = button.getLayoutParams(); + params.width = ViewGroup.LayoutParams.WRAP_CONTENT; + button.setLayoutParams(params); + + // looks better + button.setGravity(Gravity.CENTER); + } + + button.setText(action.titleId); + button.setCompoundDrawablesRelative(action.icon, null, null, null); + + button.setOnClickListener( + v -> { + if (onClick != null) { + onClick.accept(action); + } + }); + } + + @Override + public int getItemCount() { + return actions.size(); + } + + static class VH extends RecyclerView.ViewHolder { + + private final LayoutTextActionItemBinding binding; + + public VH(@NonNull LayoutTextActionItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/WidgetGroupItemAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/WidgetGroupItemAdapter.java index ae7a58fd7..5c1d85431 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/WidgetGroupItemAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/WidgetGroupItemAdapter.java @@ -18,54 +18,57 @@ package com.itsaky.androidide.adapters; import android.graphics.Color; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; + import com.itsaky.androidide.R; import com.itsaky.androidide.models.IconTextListItem; import com.itsaky.androidide.models.UIWidgetGroup; + import java.util.List; public class WidgetGroupItemAdapter extends SimpleIconTextAdapter - implements SimpleIconTextAdapter.OnBindListener { + implements SimpleIconTextAdapter.OnBindListener { - private final OnGroupClickListener clickListener; + private final OnGroupClickListener clickListener; - public WidgetGroupItemAdapter( - @NonNull List groups, @Nullable OnGroupClickListener clickListener) { - super(groups); - this.clickListener = clickListener; + public WidgetGroupItemAdapter( + @NonNull List groups, @Nullable OnGroupClickListener clickListener) { + super(groups); + this.clickListener = clickListener; - super.setOnBindListener(this); - } - - @Override - public boolean onBind(IconTextListItem item, VH holder, int position) { - // Ignored - return false; - } - - @Override - public void postBind(IconTextListItem item, @NonNull VH holder, int position) { - final var binding = holder.binding; - final var group = (UIWidgetGroup) item; - - if (group.isSelected()) { - binding - .getRoot() - .setBackground( - ContextCompat.getDrawable( - binding.getRoot().getContext(), R.drawable.bg_widget_group_selected)); - } else { - binding.getRoot().setBackgroundColor(Color.TRANSPARENT); + super.setOnBindListener(this); } - if (this.clickListener != null) { - binding.getRoot().setOnClickListener(v -> this.clickListener.onGroupClick(group)); + @Override + public boolean onBind(IconTextListItem item, VH holder, int position) { + // Ignored + return false; } - } - public interface OnGroupClickListener { - void onGroupClick(UIWidgetGroup group); - } + @Override + public void postBind(IconTextListItem item, @NonNull VH holder, int position) { + final var binding = holder.binding; + final var group = (UIWidgetGroup) item; + + if (group.isSelected()) { + binding.getRoot() + .setBackground( + ContextCompat.getDrawable( + binding.getRoot().getContext(), + R.drawable.bg_widget_group_selected)); + } else { + binding.getRoot().setBackgroundColor(Color.TRANSPARENT); + } + + if (this.clickListener != null) { + binding.getRoot().setOnClickListener(v -> this.clickListener.onGroupClick(group)); + } + } + + public interface OnGroupClickListener { + void onGroupClick(UIWidgetGroup group); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/WidgetItemAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/WidgetItemAdapter.java index 506347f52..62502ed49 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/WidgetItemAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/WidgetItemAdapter.java @@ -19,58 +19,61 @@ package com.itsaky.androidide.adapters; import android.content.ClipData; import android.view.View; + import androidx.annotation.NonNull; + import com.itsaky.androidide.DesignerActivity; import com.itsaky.androidide.models.IconTextListItem; import com.itsaky.androidide.models.UIWidget; import com.itsaky.androidide.ui.WidgetDragData; import com.itsaky.androidide.ui.WidgetDragShadowBuilder; + import java.util.List; public class WidgetItemAdapter extends SimpleIconTextAdapter - implements SimpleIconTextAdapter.OnBindListener { + implements SimpleIconTextAdapter.OnBindListener { - private final OnDragStartListener dragStartListener; + private final OnDragStartListener dragStartListener; - public WidgetItemAdapter(List widgets, OnDragStartListener dragStartListener) { - super(widgets); - this.dragStartListener = dragStartListener; + public WidgetItemAdapter(List widgets, OnDragStartListener dragStartListener) { + super(widgets); + this.dragStartListener = dragStartListener; - setOnBindListener(this); - } + setOnBindListener(this); + } - @Override - public boolean onBind(IconTextListItem item, @NonNull VH holder, int position) { - return false; // Returning true will not set the text and icon - } + @Override + public boolean onBind(IconTextListItem item, @NonNull VH holder, int position) { + return false; // Returning true will not set the text and icon + } - @Override - public void postBind(IconTextListItem item, VH holder, int position) { - final var binding = holder.binding; - final var root = binding.getRoot(); - final var widget = (UIWidget) getItemAt(position); + @Override + public void postBind(IconTextListItem item, VH holder, int position) { + final var binding = holder.binding; + final var root = binding.getRoot(); + final var widget = (UIWidget) getItemAt(position); - root.setOnLongClickListener( - v -> { - final var dragData = new WidgetDragData(false, null, widget); - final var shadow = new WidgetDragShadowBuilder(binding.icon); - final var dataItem = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG); - final var data = - new ClipData( - DesignerActivity.DRAGGING_WIDGET_TAG, - new String[] {DesignerActivity.DRAGGING_WIDGET_MIME}, - dataItem); - binding.icon.startDragAndDrop(data, shadow, dragData, 0); + root.setOnLongClickListener( + v -> { + final var dragData = new WidgetDragData(false, null, widget); + final var shadow = new WidgetDragShadowBuilder(binding.icon); + final var dataItem = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG); + final var data = + new ClipData( + DesignerActivity.DRAGGING_WIDGET_TAG, + new String[] {DesignerActivity.DRAGGING_WIDGET_MIME}, + dataItem); + binding.icon.startDragAndDrop(data, shadow, dragData, 0); - if (this.dragStartListener != null) { - this.dragStartListener.onDragStarted(v); - } + if (this.dragStartListener != null) { + this.dragStartListener.onDragStarted(v); + } - return true; - }); - } + return true; + }); + } - public interface OnDragStartListener { - void onDragStarted(View view); - } + public interface OnDragStartListener { + void onDragStarted(View view); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/XMLAttributeListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/XMLAttributeListAdapter.java index 1abf22b2e..060504b47 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/XMLAttributeListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/XMLAttributeListAdapter.java @@ -24,77 +24,83 @@ import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.view.LayoutInflater; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.R; import com.itsaky.androidide.databinding.LayoutAttrEditorSheetItemBinding; import com.itsaky.androidide.models.XMLAttribute; + import java.util.List; public class XMLAttributeListAdapter extends RecyclerView.Adapter { - private final List attributes; - private final OnClickListener clickListener; + private final List attributes; + private final OnClickListener clickListener; - public XMLAttributeListAdapter(List attributes, OnClickListener clickListener) { - this.attributes = attributes; - this.clickListener = clickListener; - } - - @NonNull - @Override - public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new VH( - LayoutAttrEditorSheetItemBinding.inflate( - LayoutInflater.from(parent.getContext()), parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - final var binding = holder.binding; - final var attr = attributes.get(position); - final var sb = new SpannableStringBuilder(); - sb.append(attr.getAttributeName()); - if (attr.isApplied()) { - sb.append(" = "); - sb.append(attr.getValue(), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - final var length = attr.getValue().length(); - sb.setSpan( - new ForegroundColorSpan( - ContextCompat.getColor(binding.text.getContext(), R.color.primaryTextColor)), - sb.length() - length, - sb.length() - 1, - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + public XMLAttributeListAdapter(List attributes, OnClickListener clickListener) { + this.attributes = attributes; + this.clickListener = clickListener; } - binding.text.setText(sb); - binding - .getRoot() - .setOnClickListener( - v -> { - if (this.clickListener != null) { - this.clickListener.onClick(binding, attr); - } - }); - } - - @Override - public int getItemCount() { - return attributes.size(); - } - - public interface OnClickListener { - void onClick(LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute); - } - - static class VH extends RecyclerView.ViewHolder { - LayoutAttrEditorSheetItemBinding binding; - - VH(@NonNull LayoutAttrEditorSheetItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH( + LayoutAttrEditorSheetItemBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + final var binding = holder.binding; + final var attr = attributes.get(position); + final var sb = new SpannableStringBuilder(); + sb.append(attr.getAttributeName()); + if (attr.isApplied()) { + sb.append(" = "); + sb.append( + attr.getValue(), + new StyleSpan(Typeface.BOLD), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + final var length = attr.getValue().length(); + sb.setSpan( + new ForegroundColorSpan( + ContextCompat.getColor( + binding.text.getContext(), R.color.primaryTextColor)), + sb.length() - length, + sb.length() - 1, + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + binding.text.setText(sb); + binding.getRoot() + .setOnClickListener( + v -> { + if (this.clickListener != null) { + this.clickListener.onClick(binding, attr); + } + }); + } + + @Override + public int getItemCount() { + return attributes.size(); + } + + public interface OnClickListener { + void onClick(LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute); + } + + static class VH extends RecyclerView.ViewHolder { + LayoutAttrEditorSheetItemBinding binding; + + VH(@NonNull LayoutAttrEditorSheetItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java b/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java index 33ce5ea15..8462d8183 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java @@ -26,83 +26,88 @@ import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; + import androidx.core.content.ContextCompat; + import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioActivity; import com.itsaky.androidide.databinding.LayoutFiletreeItemBinding; import com.itsaky.androidide.utils.Environment; import com.unnamed.b.atv.model.TreeNode; + import java.io.File; public class FileTreeViewHolder extends TreeNode.BaseNodeViewHolder { - private Context context; - private ImageView chevron; - private LayoutFiletreeItemBinding binding; + private Context context; + private ImageView chevron; + private LayoutFiletreeItemBinding binding; - public FileTreeViewHolder(Context context) { - super(context); - this.context = context; - } + public FileTreeViewHolder(Context context) { + super(context); + this.context = context; + } - @Override - public View createNodeView(TreeNode node, File file) { - binding = LayoutFiletreeItemBinding.inflate(LayoutInflater.from(context)); - int dp15 = - context instanceof StudioActivity - ? ((StudioActivity) context).dpToPx(15) - : (int) - (TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 15, context.getResources().getDisplayMetrics())); - int icon = 0; - if (file.isDirectory()) icon = R.drawable.ic_folder; - else if (file.getName().endsWith(".java")) icon = R.drawable.ic_language_java; - else if (file.getName().endsWith(".kt")) icon = R.drawable.ic_language_kotlin; - else if (file.getName().endsWith(".xml")) icon = R.drawable.ic_language_xml; - else if (file.getName().endsWith(".gradle")) icon = R.drawable.ic_language_gradle; - else if (file.getName().endsWith(".json")) icon = R.drawable.ic_language_json; - else icon = R.drawable.ic_file_unknown; + @Override + public View createNodeView(TreeNode node, File file) { + binding = LayoutFiletreeItemBinding.inflate(LayoutInflater.from(context)); + int dp15 = + context instanceof StudioActivity + ? ((StudioActivity) context).dpToPx(15) + : (int) + (TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 15, + context.getResources().getDisplayMetrics())); + int icon = 0; + if (file.isDirectory()) icon = R.drawable.ic_folder; + else if (file.getName().endsWith(".java")) icon = R.drawable.ic_language_java; + else if (file.getName().endsWith(".kt")) icon = R.drawable.ic_language_kotlin; + else if (file.getName().endsWith(".xml")) icon = R.drawable.ic_language_xml; + else if (file.getName().endsWith(".gradle")) icon = R.drawable.ic_language_gradle; + else if (file.getName().endsWith(".json")) icon = R.drawable.ic_language_json; + else icon = R.drawable.ic_file_unknown; - final boolean isGradle = - file.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()); - chevron = binding.getRoot().findViewById(R.id.filetree_chevron); - binding.filetreeName.setText(isGradle ? "GRADLE_HOME" : file.getName()); - binding.filetreeIcon.setImageResource(icon); - binding - .getRoot() - .setPadding( - binding.getRoot().getPaddingLeft() + (dp15 * (node.getLevel() - 1)), - binding.getRoot().getPaddingTop(), - binding.getRoot().getPaddingRight(), - binding.getRoot().getPaddingBottom()); - chevron.setVisibility(file.isFile() ? View.INVISIBLE : View.VISIBLE); - chevron.setImageResource( - node.isExpanded() ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right); - chevron - .getDrawable() - .setColorFilter( - ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); - binding - .filetreeIcon - .getDrawable() - .setColorFilter( - ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); + final boolean isGradle = + file.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()); + chevron = binding.getRoot().findViewById(R.id.filetree_chevron); + binding.filetreeName.setText(isGradle ? "GRADLE_HOME" : file.getName()); + binding.filetreeIcon.setImageResource(icon); + binding.getRoot() + .setPadding( + binding.getRoot().getPaddingLeft() + (dp15 * (node.getLevel() - 1)), + binding.getRoot().getPaddingTop(), + binding.getRoot().getPaddingRight(), + binding.getRoot().getPaddingBottom()); + chevron.setVisibility(file.isFile() ? View.INVISIBLE : View.VISIBLE); + chevron.setImageResource( + node.isExpanded() ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right); + chevron.getDrawable() + .setColorFilter( + ContextCompat.getColor(context, R.color.secondaryLightColor), + PorterDuff.Mode.SRC_ATOP); + binding.filetreeIcon + .getDrawable() + .setColorFilter( + ContextCompat.getColor(context, R.color.secondaryLightColor), + PorterDuff.Mode.SRC_ATOP); - return binding.getRoot(); - } + return binding.getRoot(); + } - public void setLoading() { - chevron - .getDrawable() - .setColorFilter( - ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); - } + public void setLoading() { + chevron.getDrawable() + .setColorFilter( + ContextCompat.getColor(context, R.color.secondaryLightColor), + PorterDuff.Mode.SRC_ATOP); + } - public void updateChevron(boolean isExpanded) { - chevron.setImageResource(isExpanded ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right); - chevron - .getDrawable() - .setColorFilter( - ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); - } + public void updateChevron(boolean isExpanded) { + chevron.setImageResource( + isExpanded ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right); + chevron.getDrawable() + .setColorFilter( + ContextCompat.getColor(context, R.color.secondaryLightColor), + PorterDuff.Mode.SRC_ATOP); + } } diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java b/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java index 9417a211e..0d0488907 100755 --- a/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java @@ -22,121 +22,125 @@ import android.content.pm.PackageManager; import android.os.Bundle; import android.util.TypedValue; import android.view.View; + import androidx.annotation.AttrRes; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; + import com.itsaky.androidide.R; import com.itsaky.androidide.utils.Logger; public abstract class StudioActivity extends AppCompatActivity { - private boolean toRequestStorage = true; + private boolean toRequestStorage = true; - public static final String TAG = "StudioActivity"; - public static final int REQCODE_STORAGE = 1009; + public static final String TAG = "StudioActivity"; + public static final int REQCODE_STORAGE = 1009; - protected static Logger LOG = Logger.instance("StudioActivity"); + protected static Logger LOG = Logger.instance("StudioActivity"); - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(bindLayout()); - onSetContentView(); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(bindLayout()); + onSetContentView(); - if (toRequestStorage && !isStoragePermissionGranted()) { - requestStorage(); + if (toRequestStorage && !isStoragePermissionGranted()) { + requestStorage(); + } + + if (isStoragePermissionGranted()) { + onStorageAlreadyGranted(); + } else { + onStorageDenied(); + } } - if (isStoragePermissionGranted()) { - onStorageAlreadyGranted(); - } else { - onStorageDenied(); + public boolean isStoragePermissionGranted() { + return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission( + this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED; } - } - public boolean isStoragePermissionGranted() { - return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED - && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED; - } - - protected void doNotRequestStorage() { - this.toRequestStorage = false; - } - - protected void requestStorage() { - if (isStoragePermissionGranted()) { - onStorageGranted(); - return; + protected void doNotRequestStorage() { + this.toRequestStorage = false; } - ActivityCompat.requestPermissions( - this, - new String[] { - Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE - }, - REQCODE_STORAGE); - } - public void loadFrament(Fragment frag, View view) { - loadFragment(frag, view.getId()); - } - - public void loadFragment(Fragment fragment, int id) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(id, fragment); - transaction.commit(); - } - - public StudioApp getApp() { - return (StudioApp) getApplication(); - } - - public TypedValue getTypedValueForAttr(@AttrRes int attrRes) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(attrRes, typedValue, true); - return typedValue; - } - - public int getColorPrimary() { - return getTypedValueForAttr(R.attr.colorPrimary).data; - } - - public int getColorPrimaryDark() { - return getTypedValueForAttr(R.attr.colorPrimaryDark).data; - } - - public int getColorAccent() { - return getTypedValueForAttr(R.attr.colorAccent).data; - } - - public int dpToPx(int dp) { - return (int) - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); - } - - @Override - public void onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == REQCODE_STORAGE) { - if (grantResults != null - && grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) onStorageGranted(); - else onStorageDenied(); + protected void requestStorage() { + if (isStoragePermissionGranted()) { + onStorageGranted(); + return; + } + ActivityCompat.requestPermissions( + this, + new String[] { + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE + }, + REQCODE_STORAGE); } - } - protected void onStorageAlreadyGranted() {} + public void loadFrament(Fragment frag, View view) { + loadFragment(frag, view.getId()); + } - protected void onStorageGranted() {} + public void loadFragment(Fragment fragment, int id) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(id, fragment); + transaction.commit(); + } - protected void onStorageDenied() {} + public StudioApp getApp() { + return (StudioApp) getApplication(); + } - protected void onSetContentView() {} + public TypedValue getTypedValueForAttr(@AttrRes int attrRes) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(attrRes, typedValue, true); + return typedValue; + } - protected abstract View bindLayout(); + public int getColorPrimary() { + return getTypedValueForAttr(R.attr.colorPrimary).data; + } + + public int getColorPrimaryDark() { + return getTypedValueForAttr(R.attr.colorPrimaryDark).data; + } + + public int getColorAccent() { + return getTypedValueForAttr(R.attr.colorAccent).data; + } + + public int dpToPx(int dp) { + return (int) + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); + } + + @Override + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQCODE_STORAGE) { + if (grantResults != null + && grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) onStorageGranted(); + else onStorageDenied(); + } + } + + protected void onStorageAlreadyGranted() {} + + protected void onStorageGranted() {} + + protected void onStorageDenied() {} + + protected void onSetContentView() {} + + protected abstract View bindLayout(); } diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index 4290f2ae2..151b5133f 100755 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -18,7 +18,9 @@ package com.itsaky.androidide.app; import android.content.Intent; + import androidx.annotation.NonNull; + import com.blankj.utilcode.util.ThrowableUtils; import com.google.firebase.messaging.FirebaseMessaging; import com.itsaky.androidide.CrashHandlerActivity; @@ -36,117 +38,119 @@ import com.itsaky.lsp.java.JavaLanguageServer; import com.itsaky.lsp.xml.XMLLanguageServer; import com.itsaky.sdk.SDKInfo; import com.itsaky.widgets.WidgetInfo; + import java.io.File; import java.util.Set; public class StudioApp extends BaseApplication { - private static final Logger LOG = Logger.instance("StudioApp"); - private static StudioApp instance; - private static SDKInfo sdkInfo; - private final ILanguageServer mJavaLanguageServer = new JavaLanguageServer(); - private final ILanguageServer mXMLLanguageServer = new XMLLanguageServer(); - private IResourceTable mResTable; - private ILayoutInflater mLayoutInflater; - private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + private static final Logger LOG = Logger.instance("StudioApp"); + private static StudioApp instance; + private static SDKInfo sdkInfo; + private final ILanguageServer mJavaLanguageServer = new JavaLanguageServer(); + private final ILanguageServer mXMLLanguageServer = new XMLLanguageServer(); + private IResourceTable mResTable; + private ILayoutInflater mLayoutInflater; + private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; - public static StudioApp getInstance() { - return instance; - } - - @Override - public void onCreate() { - instance = this; - this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(this::handleCrash); - super.onCreate(); - - FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_UPDATE); - FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_DEV_MSGS); - - initializeApiInformation(); - } - - @NonNull - public ILanguageServer getJavaLanguageServer() { - return mJavaLanguageServer; - } - - @NonNull - public ILanguageServer getXMLLanguageServer() { - return mXMLLanguageServer; - } - - private void handleCrash(Thread thread, Throwable th) { - writeException(th); - - try { - final var intent = new Intent(); - intent.setAction(CrashHandlerActivity.REPORT_ACTION); - intent.putExtra(CrashHandlerActivity.TRACE_KEY, ThrowableUtils.getFullStackTrace(th)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - - if (this.uncaughtExceptionHandler != null) { - this.uncaughtExceptionHandler.uncaughtException(thread, th); - } - System.exit(1); - } catch (Throwable error) { - LOG.error("Unable to show crash handler activity", error); + public static StudioApp getInstance() { + return instance; } - } - public void createInflater(LayoutInflaterConfiguration config) { - this.mLayoutInflater = ILayoutInflater.newInstance(config); - } + @Override + public void onCreate() { + instance = this; + this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this::handleCrash); + super.onCreate(); - public LayoutInflaterConfiguration createInflaterConfig( - ILayoutInflater.ContextProvider contextProvider, Set resDirs) { - return new LayoutInflaterConfiguration.Builder() - .setAttrInfo(this.attrInfo()) - .setWidgetInfo(this.widgetInfo()) - .setResourceFinder(mResTable == null ? mResTable = new ProjectResourceTable() : mResTable) - .setResourceDirectories(resDirs) - .setContextProvider(contextProvider) - .create(); - } + FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_UPDATE); + FirebaseMessaging.getInstance().subscribeToTopic(MessagingService.TOPIC_DEV_MSGS); - public ILayoutInflater getLayoutInflater() { - return mLayoutInflater; - } + initializeApiInformation(); + } - public IResourceTable getResourceTable() { - return this.mResTable; - } + @NonNull + public ILanguageServer getJavaLanguageServer() { + return mJavaLanguageServer; + } - /** Reads API version information from api-versions.xml */ - public void initializeApiInformation() { - new Thread( - () -> { - try { - sdkInfo = new SDKInfo(StudioApp.this); - ((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo); - } catch (Throwable th) { - LOG.error(getString(R.string.err_init_sdkinfo), th); - } - }, - "SDKInfo Loader") - .start(); - } + @NonNull + public ILanguageServer getXMLLanguageServer() { + return mXMLLanguageServer; + } - public void stopAllDaemons() { - newShell(null).bgAppend("gradle --stop"); - } + private void handleCrash(Thread thread, Throwable th) { + writeException(th); - public ApiInfo apiInfo() { - return sdkInfo.getApiInfo(); - } + try { + final var intent = new Intent(); + intent.setAction(CrashHandlerActivity.REPORT_ACTION); + intent.putExtra(CrashHandlerActivity.TRACE_KEY, ThrowableUtils.getFullStackTrace(th)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); - public AttrInfo attrInfo() { - return sdkInfo.getAttrInfo(); - } + if (this.uncaughtExceptionHandler != null) { + this.uncaughtExceptionHandler.uncaughtException(thread, th); + } + System.exit(1); + } catch (Throwable error) { + LOG.error("Unable to show crash handler activity", error); + } + } - public WidgetInfo widgetInfo() { - return sdkInfo.getWidgetInfo(); - } + public void createInflater(LayoutInflaterConfiguration config) { + this.mLayoutInflater = ILayoutInflater.newInstance(config); + } + + public LayoutInflaterConfiguration createInflaterConfig( + ILayoutInflater.ContextProvider contextProvider, Set resDirs) { + return new LayoutInflaterConfiguration.Builder() + .setAttrInfo(this.attrInfo()) + .setWidgetInfo(this.widgetInfo()) + .setResourceFinder( + mResTable == null ? mResTable = new ProjectResourceTable() : mResTable) + .setResourceDirectories(resDirs) + .setContextProvider(contextProvider) + .create(); + } + + public ILayoutInflater getLayoutInflater() { + return mLayoutInflater; + } + + public IResourceTable getResourceTable() { + return this.mResTable; + } + + /** Reads API version information from api-versions.xml */ + public void initializeApiInformation() { + new Thread( + () -> { + try { + sdkInfo = new SDKInfo(StudioApp.this); + ((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo); + } catch (Throwable th) { + LOG.error(getString(R.string.err_init_sdkinfo), th); + } + }, + "SDKInfo Loader") + .start(); + } + + public void stopAllDaemons() { + newShell(null).bgAppend("gradle --stop"); + } + + public ApiInfo apiInfo() { + return sdkInfo.getApiInfo(); + } + + public AttrInfo attrInfo() { + return sdkInfo.getAttrInfo(); + } + + public WidgetInfo widgetInfo() { + return sdkInfo.getWidgetInfo(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/BaseFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/BaseFragment.java index d48112e3e..7319e9287 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/BaseFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/BaseFragment.java @@ -21,10 +21,11 @@ package com.itsaky.androidide.fragments; import androidx.fragment.app.Fragment; + import com.itsaky.androidide.app.StudioActivity; public class BaseFragment extends Fragment { - protected StudioActivity getStudioActivity() { - return (StudioActivity) getActivity(); - } + protected StudioActivity getStudioActivity() { + return (StudioActivity) getActivity(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/CrashReportFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/CrashReportFragment.java index d8a5fe66d..58eadca46 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/CrashReportFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/CrashReportFragment.java @@ -27,9 +27,11 @@ 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 androidx.fragment.app.Fragment; + import com.blankj.utilcode.util.ClipboardUtils; import com.itsaky.androidide.R; import com.itsaky.androidide.app.BaseApplication; @@ -37,116 +39,118 @@ import com.itsaky.androidide.databinding.LayoutCrashReportBinding; public class CrashReportFragment extends Fragment { - private LayoutCrashReportBinding binding; - private boolean closeAppOnClick = true; + private LayoutCrashReportBinding binding; + private boolean closeAppOnClick = true; - public static final String KEY_TITLE = "crash_title"; - public static final String KEY_MESSAGE = "crash_message"; - public static final String KEY_TRACE = "crash_trace"; + public static final String KEY_TITLE = "crash_title"; + public static final String KEY_MESSAGE = "crash_message"; + public static final String KEY_TRACE = "crash_trace"; - @NonNull - public static CrashReportFragment newInstance(@NonNull final String trace) { - return CrashReportFragment.newInstance(null, null, trace); - } - - @NonNull - public static CrashReportFragment newInstance( - @Nullable final String title, @Nullable final String message, @NonNull final String trace) { - final var frag = new CrashReportFragment(); - final var args = new Bundle(); - - args.putString(KEY_TRACE, trace); - - if (title != null) { - args.putString(KEY_TITLE, title); + @NonNull + public static CrashReportFragment newInstance(@NonNull final String trace) { + return CrashReportFragment.newInstance(null, null, trace); } - if (message != null) { - args.putString(KEY_MESSAGE, message); + @NonNull + public static CrashReportFragment newInstance( + @Nullable final String title, + @Nullable final String message, + @NonNull final String trace) { + final var frag = new CrashReportFragment(); + final var args = new Bundle(); + + args.putString(KEY_TRACE, trace); + + if (title != null) { + args.putString(KEY_TITLE, title); + } + + if (message != null) { + args.putString(KEY_MESSAGE, message); + } + + frag.setArguments(args); + return frag; } - frag.setArguments(args); - return frag; - } - - public CrashReportFragment setCloseAppOnClick(boolean closeAppOnClick) { - this.closeAppOnClick = closeAppOnClick; - return this; - } - - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - return (binding = LayoutCrashReportBinding.inflate(inflater, container, false)).getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - final var args = requireArguments(); - var title = getString(R.string.msg_ide_crashed); - var message = getString(R.string.msg_report_crash); - var trace = ""; - if (args.containsKey(KEY_TITLE)) { - title = args.getString(KEY_TITLE); + public CrashReportFragment setCloseAppOnClick(boolean closeAppOnClick) { + this.closeAppOnClick = closeAppOnClick; + return this; } - if (args.containsKey(KEY_MESSAGE)) { - message = args.getString(KEY_MESSAGE); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return (binding = LayoutCrashReportBinding.inflate(inflater, container, false)).getRoot(); } - if (args.containsKey(KEY_TRACE)) { - trace = args.getString(KEY_TRACE); - trace = buildReportText(trace); - } else { - trace = "No stack strace was provided for the report"; + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + final var args = requireArguments(); + var title = getString(R.string.msg_ide_crashed); + var message = getString(R.string.msg_report_crash); + var trace = ""; + if (args.containsKey(KEY_TITLE)) { + title = args.getString(KEY_TITLE); + } + + if (args.containsKey(KEY_MESSAGE)) { + message = args.getString(KEY_MESSAGE); + } + + if (args.containsKey(KEY_TRACE)) { + trace = args.getString(KEY_TRACE); + trace = buildReportText(trace); + } else { + trace = "No stack strace was provided for the report"; + } + + binding.crashTitle.setText(title); + binding.crashSubtitle.setText(message); + binding.logText.setText(trace); + + final var report = trace; + binding.closeButton.setOnClickListener( + v -> { + if (closeAppOnClick) { + requireActivity().finishAffinity(); + } else { + requireActivity().finish(); + } + }); + binding.reportButton.setOnClickListener(v -> reportTrace(report)); } - binding.crashTitle.setText(title); - binding.crashSubtitle.setText(message); - binding.logText.setText(trace); + private void reportTrace(String report) { + ClipboardUtils.copyText("AndroidIDE CrashLog", report); - final var report = trace; - binding.closeButton.setOnClickListener( - v -> { - if (closeAppOnClick) { - requireActivity().finishAffinity(); - } else { - requireActivity().finish(); - } - }); - binding.reportButton.setOnClickListener(v -> reportTrace(report)); - } + final var url = BaseApplication.GITHUB_URL.concat("/issues"); + final var intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } - private void reportTrace(String report) { - ClipboardUtils.copyText("AndroidIDE CrashLog", report); - - final var url = BaseApplication.GITHUB_URL.concat("/issues"); - final var intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - - @NonNull - private String buildReportText(String trace) { - return "AndroidIDE crash report\n" - + "Manufacturer: " - + getManufacturer() - + "\n" - + "Device: " - + getModel() - + "\n" - + "App version: " - + getAppVersionName() - + " (" - + getAppVersionCode() - + ")\n\n Stacktrace: \n" - + trace; - } + @NonNull + private String buildReportText(String trace) { + return "AndroidIDE crash report\n" + + "Manufacturer: " + + getManufacturer() + + "\n" + + "Device: " + + getModel() + + "\n" + + "App version: " + + getAppVersionName() + + " (" + + getAppVersionCode() + + ")\n\n Stacktrace: \n" + + trace; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/DiagnosticsListFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/DiagnosticsListFragment.java index 0ca758683..58e7cb9b4 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/DiagnosticsListFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/DiagnosticsListFragment.java @@ -22,63 +22,68 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.adapters.DiagnosticsAdapter; import com.itsaky.androidide.views.EmptyView; + import java.util.ArrayList; public class DiagnosticsListFragment extends Fragment { - private RecyclerView list; - private EmptyView emptyView; + private RecyclerView list; + private EmptyView emptyView; - private DiagnosticsAdapter unsavedAdapter; + private DiagnosticsAdapter unsavedAdapter; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup parent, - @Nullable Bundle savedInstanceState) { - final var container = new LinearLayout(inflater.getContext()); - container.setOrientation(LinearLayout.VERTICAL); - container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup parent, + @Nullable Bundle savedInstanceState) { + final var container = new LinearLayout(inflater.getContext()); + container.setOrientation(LinearLayout.VERTICAL); + container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); - this.list = new RecyclerView(inflater.getContext()); - this.emptyView = new EmptyView(inflater.getContext()); + this.list = new RecyclerView(inflater.getContext()); + this.emptyView = new EmptyView(inflater.getContext()); - container.addView(list, new LinearLayout.LayoutParams(-1, -1)); - container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1)); + container.addView(list, new LinearLayout.LayoutParams(-1, -1)); + container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1)); - return container; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - list.setLayoutManager(new LinearLayoutManager(list.getContext())); - list.setAdapter( - unsavedAdapter != null ? unsavedAdapter : new DiagnosticsAdapter(new ArrayList<>(), null)); - unsavedAdapter = null; - } - - public void setAdapter(@NonNull DiagnosticsAdapter adapter) { - if (list != null) { - list.setAdapter(adapter); - } else { - unsavedAdapter = adapter; + return container; } - } - public void handleResultVisibility(boolean error) { - if (emptyView != null && list != null) { - emptyView.setVisibility(error ? View.VISIBLE : View.GONE); - list.setVisibility(error ? View.GONE : View.VISIBLE); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + list.setLayoutManager(new LinearLayoutManager(list.getContext())); + list.setAdapter( + unsavedAdapter != null + ? unsavedAdapter + : new DiagnosticsAdapter(new ArrayList<>(), null)); + unsavedAdapter = null; + } + + public void setAdapter(@NonNull DiagnosticsAdapter adapter) { + if (list != null) { + list.setAdapter(adapter); + } else { + unsavedAdapter = adapter; + } + } + + public void handleResultVisibility(boolean error) { + if (emptyView != null && list != null) { + emptyView.setVisibility(error ? View.VISIBLE : View.GONE); + list.setVisibility(error ? View.GONE : View.VISIBLE); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java index 882d856e1..3bb002b3b 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java @@ -27,9 +27,11 @@ 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; @@ -46,223 +48,232 @@ 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; public class FileTreeFragment extends BottomSheetDialogFragment - implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener { + implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener { - private LayoutEditorFileTreeBinding binding; - private AndroidTreeView mFileTreeView; - private FileActionListener mFileActionListener; - private AndroidProject mProject; - private TreeNode mRoot; + private LayoutEditorFileTreeBinding binding; + private AndroidTreeView mFileTreeView; + private FileActionListener mFileActionListener; + private AndroidProject mProject; + private TreeNode mRoot; - public FileTreeFragment() {} + public FileTreeFragment() {} - public FileTreeFragment setFileActionListener(FileActionListener listener) { - this.mFileActionListener = listener; - return this; - } - - @NonNull - public static FileTreeFragment newInstance(AndroidProject project) { - Bundle bundle = new Bundle(); - bundle.putParcelable("project", project); - FileTreeFragment frag = new FileTreeFragment(); - frag.setArguments(bundle); - return frag; - } - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mProject = requireArguments().getParcelable("project"); - - listProjectFiles(); - } - - @Override - public void onClick(TreeNode node, Object p2) { - final File f = (File) p2; - if (!f.exists()) { - return; + public FileTreeFragment setFileActionListener(FileActionListener listener) { + this.mFileActionListener = listener; + return this; } - 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); + @NonNull + public static FileTreeFragment newInstance(AndroidProject project) { + Bundle bundle = new Bundle(); + bundle.putParcelable("project", project); + FileTreeFragment frag = new FileTreeFragment(); + frag.setArguments(bundle); + return frag; + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mProject = requireArguments().getParcelable("project"); + + listProjectFiles(); + } + + @Override + public void onClick(TreeNode node, Object p2) { + final File f = (File) p2; + if (!f.exists()) { + return; } - } 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); - } - } - } - private void listNode(@NonNull TreeNode node) { - node.getChildren().clear(); - node.setExpanded(false); - new TaskExecutor() - .executeAsync( - () -> { - getNodeFromFiles( - node.getValue().listFiles(/* new FileTreeCallable.HiddenFilesFilter() */ ), node); - TreeNode temp = node; - while (temp.size() == 1) { - temp = temp.childAt(0); - if (!temp.getValue().isDirectory()) { - break; + 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); } - getNodeFromFiles( - temp.getValue().listFiles(/* new FileTreeCallable.HiddenFilesFilter() */ ), - temp); - temp.setExpanded(true); - } - return null; - }, - __ -> { - updateChevron(node); - expandNode(node); - }); - } - - 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); - } - } - - private void setLoading(@NonNull TreeNode node) { - if (node.getViewHolder() instanceof FileTreeViewHolder) { - ((FileTreeViewHolder) node.getViewHolder()).setLoading(); - } - } - - private void updateChevron(@NonNull TreeNode node) { - if (node.getViewHolder() instanceof FileTreeViewHolder) { - ((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded()); - } - } - - @Override - public boolean onLongClick(TreeNode node, Object value) { - if (mFileActionListener != null) { - mFileActionListener.showFileOptions((File) value, node); - } - return true; - } - - public void expandNode(TreeNode node) { - if (mFileTreeView == null) { - return; + } 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); + } + } } - TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); - mFileTreeView.expandNode(node); - } - - public void collapseNode(TreeNode node) { - if (mFileTreeView == null) { - return; + private void listNode(@NonNull TreeNode node) { + node.getChildren().clear(); + node.setExpanded(false); + new TaskExecutor() + .executeAsync( + () -> { + getNodeFromFiles( + node.getValue() + .listFiles( + /* new FileTreeCallable.HiddenFilesFilter() */ ), + node); + TreeNode temp = node; + while (temp.size() == 1) { + temp = temp.childAt(0); + if (!temp.getValue().isDirectory()) { + break; + } + getNodeFromFiles( + temp.getValue() + .listFiles( + /* new FileTreeCallable.HiddenFilesFilter() */ ), + temp); + temp.setExpanded(true); + } + return null; + }, + __ -> { + updateChevron(node); + expandNode(node); + }); } - TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); - mFileTreeView.collapseNode(node); - } - - public void listProjectFiles() { - if (mProject == null) { - return; + 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); + } } - final File gradleProps = Environment.GRADLE_PROPS; - final File gradleHome = Environment.GRADLE_USER_HOME; - File projectDir = new File(mProject.getProjectPath()); - mRoot = TreeNode.root(projectDir); - if (gradleHome.exists() && gradleHome.isDirectory()) { - if (!gradleProps.exists()) { - FileIOUtils.writeFileFromString(gradleProps, Environment.SAMPLE_GRADLE_PROP_CONTENTS); - } - 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(); - getScrollView().addView(tree.getView()); - } - }); - } - - public AndroidTreeView createTreeView(TreeNode node) { - Context ctx = null; - if (getActivity() != null) { - ctx = getActivity(); - } else if (getContext() != null) { - ctx = getContext(); + private void setLoading(@NonNull TreeNode node) { + if (node.getViewHolder() instanceof FileTreeViewHolder) { + ((FileTreeViewHolder) node.getViewHolder()).setLoading(); + } } - if (ctx == null) { - return null; + private void updateChevron(@NonNull TreeNode node) { + if (node.getViewHolder() instanceof FileTreeViewHolder) { + ((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded()); + } } - return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple); - } + @Override + public boolean onLongClick(TreeNode node, Object value) { + if (mFileActionListener != null) { + mFileActionListener.showFileOptions((File) value, node); + } + return true; + } - public HorizontalScrollView getScrollView() { - return binding.filetreeHorizontalScrollView; - } + public void expandNode(TreeNode node) { + if (mFileTreeView == null) { + return; + } - public ProgressBar getLoadingProgress() { - return binding.fileTreeLoadingProgress; - } + TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); + mFileTreeView.expandNode(node); + } - public interface FileActionListener { - CodeEditorView openFile(File file); + public void collapseNode(TreeNode node) { + if (mFileTreeView == null) { + return; + } - void showFileOptions(File file, TreeNode node); - } + TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); + mFileTreeView.collapseNode(node); + } + + public void listProjectFiles() { + if (mProject == null) { + return; + } + + final File gradleProps = Environment.GRADLE_PROPS; + final File gradleHome = Environment.GRADLE_USER_HOME; + File projectDir = new File(mProject.getProjectPath()); + mRoot = TreeNode.root(projectDir); + if (gradleHome.exists() && gradleHome.isDirectory()) { + if (!gradleProps.exists()) { + FileIOUtils.writeFileFromString( + gradleProps, Environment.SAMPLE_GRADLE_PROP_CONTENTS); + } + 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(); + getScrollView().addView(tree.getView()); + } + }); + } + + 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; + } + + public interface FileActionListener { + CodeEditorView openFile(File file); + + void showFileOptions(File file, TreeNode node); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java index 8d778bc3e..afd2d1af8 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java @@ -21,13 +21,13 @@ import com.itsaky.androidide.utils.Logger; public class IDELogFragment extends SimpleOutputFragment { - private final Logger.LogListener listener = this::log; + private final Logger.LogListener listener = this::log; - public IDELogFragment() { - Logger.addLogListener(listener); - } + public IDELogFragment() { + Logger.addLogListener(listener); + } - private void log(int priority, String tag, String message) { - appendOutput(String.format("%s: %s", tag, message)); - } + private void log(int priority, String tag, String message) { + appendOutput(String.format("%s: %s", tag, message)); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/LogViewFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/LogViewFragment.java index 592e7cb1a..2c05531e9 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/LogViewFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/LogViewFragment.java @@ -19,55 +19,59 @@ package com.itsaky.androidide.fragments; import android.os.Bundle; import android.view.View; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.blankj.utilcode.util.ThreadUtils; import com.itsaky.androidide.language.logs.LogLanguageImpl; import com.itsaky.androidide.models.LogLine; + import io.github.rosemoe.sora.lang.Language; + import java.util.ArrayList; import java.util.List; import java.util.Objects; public class LogViewFragment extends NonEditableEditorFragment { - private final Language language = getLanguage(); - private final List unsavedLines = new ArrayList<>(); + private final Language language = getLanguage(); + private final List unsavedLines = new ArrayList<>(); - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - Objects.requireNonNull(getEditor()).setEditorLanguage(language); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Objects.requireNonNull(getEditor()).setEditorLanguage(language); + + if (!unsavedLines.isEmpty()) { + for (var line : unsavedLines) { + applyToLanguage(line); + getEditor().append(line.toString().trim() + "\n"); + } + unsavedLines.clear(); + } + } + + public void appendLog(LogLine line) { + if (getEditor() == null) { + unsavedLines.add(line); + return; + } - if (!unsavedLines.isEmpty()) { - for (var line : unsavedLines) { applyToLanguage(line); - getEditor().append(line.toString().trim() + "\n"); - } - unsavedLines.clear(); - } - } - public void appendLog(LogLine line) { - if (getEditor() == null) { - unsavedLines.add(line); - return; + final var lineString = line.toString(); + final var msg = lineString.endsWith("\n") ? lineString : lineString + "\n"; + ThreadUtils.runOnUiThread(() -> getEditor().append(msg)); } - applyToLanguage(line); - - final var lineString = line.toString(); - final var msg = lineString.endsWith("\n") ? lineString : lineString + "\n"; - ThreadUtils.runOnUiThread(() -> getEditor().append(msg)); - } - - private void applyToLanguage(LogLine line) { - if (language instanceof LogLanguageImpl) { - ((LogLanguageImpl) this.language).addLine(line); + private void applyToLanguage(LogLine line) { + if (language instanceof LogLanguageImpl) { + ((LogLanguageImpl) this.language).addLine(line); + } } - } - protected Language getLanguage() { - return new LogLanguageImpl(); - } + protected Language getLanguage() { + return new LogLanguageImpl(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/NonEditableEditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/NonEditableEditorFragment.java index 0524306f5..59dc8b5fc 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/NonEditableEditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/NonEditableEditorFragment.java @@ -21,50 +21,53 @@ 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 androidx.fragment.app.Fragment; + import com.itsaky.androidide.databinding.FragmentNonEditableEditorBinding; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; import com.itsaky.androidide.utils.TypefaceUtils; import com.itsaky.androidide.views.editor.IDEEditor; + import io.github.rosemoe.sora.lang.EmptyLanguage; public abstract class NonEditableEditorFragment extends Fragment { - private FragmentNonEditableEditorBinding binding; + private FragmentNonEditableEditorBinding binding; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = FragmentNonEditableEditorBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - final var editor = binding.getRoot(); - editor.setEditable(false); - editor.setDividerWidth(0); - editor.setEditorLanguage(new EmptyLanguage()); - editor.setWordwrap(false); - editor.setUndoEnabled(false); - editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono()); - editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); - editor.setTextSize(12); - editor.setColorScheme(new SchemeAndroidIDE()); - } - - @Nullable - public IDEEditor getEditor() { - if (binding == null) { - return null; + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = FragmentNonEditableEditorBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + final var editor = binding.getRoot(); + editor.setEditable(false); + editor.setDividerWidth(0); + editor.setEditorLanguage(new EmptyLanguage()); + editor.setWordwrap(false); + editor.setUndoEnabled(false); + editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono()); + editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); + editor.setTextSize(12); + editor.setColorScheme(new SchemeAndroidIDE()); + } + + @Nullable + public IDEEditor getEditor() { + if (binding == null) { + return null; + } + return binding.editor; } - return binding.editor; - } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java index 63556ff61..ce401b0cd 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/ProjectsFragment.java @@ -26,8 +26,10 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; + import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.EditorActivity; import com.itsaky.androidide.adapters.ProjectsListAdapter; import com.itsaky.androidide.app.StudioApp; @@ -35,6 +37,7 @@ import com.itsaky.androidide.databinding.LayoutProjectsBinding; import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.utils.ProjectFinder; + import java.io.File; import java.util.ArrayList; import java.util.List; @@ -42,99 +45,104 @@ import java.util.concurrent.Callable; public class ProjectsFragment extends BaseFragment { - private LayoutProjectsBinding binding; - private ArrayList mProjects; - private boolean isLoading = false; + private LayoutProjectsBinding binding; + private ArrayList mProjects; + private boolean isLoading = false; - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = LayoutProjectsBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (getActivity() == null) return; - - loadProjects(); - } - - public void loadProjects() { - if (!isLoading && getStudioActivity().isStoragePermissionGranted()) { - getProgressBar().setVisibility(View.VISIBLE); - getRecyclerView().setVisibility(View.GONE); - setLoading(true); - final ProjectsFragment frag = this; - new TaskExecutor() - .executeAsync( - new GetProjectsTask(), - (result) -> { - frag.setLoading(false); - - if (frag.getProjectsList() == null || frag.getProjectsList().size() == 0) { - frag.getProgressBar().setVisibility(View.GONE); - frag.getRecyclerView().setVisibility(View.GONE); - frag.getNothingLayout().setVisibility(View.VISIBLE); - } else { - frag.getNothingLayout().setVisibility(View.GONE); - frag.getProgressBar().setVisibility(View.GONE); - frag.getRecyclerView().setVisibility(View.VISIBLE); - frag.getRecyclerView().setLayoutManager(new LinearLayoutManager(getActivity())); - frag.getRecyclerView() - .setAdapter( - new ProjectsListAdapter(mProjects, (project) -> openProject(project))); - } - }); - } - } - - private void openProject(AndroidProject project) { - startActivity(new Intent(getActivity(), EditorActivity.class).putExtra("project", project)); - } - - public void getProjects() throws Exception { - mProjects = new ArrayList<>(); - File[] files = StudioApp.getInstance().listProjects(); - for (File file : files) { - AndroidProject project = ProjectFinder.fromFolder(file); - - if (project == null) continue; - - mProjects.add(project); - } - } - - public ProgressBar getProgressBar() { - return binding.projectsProgressBar; - } - - public RecyclerView getRecyclerView() { - return binding.mainProjectsRecyclerView; - } - - public boolean isLoading() { - return isLoading; - } - - public void setLoading(boolean isLoading) { - this.isLoading = isLoading; - } - - public ArrayList getProjectsList() { - return mProjects; - } - - public View getNothingLayout() { - return binding.projectsNothingLayout; - } - - private class GetProjectsTask implements Callable> { @Override - public List call() throws Exception { - getProjects(); - return null; + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = LayoutProjectsBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + if (getActivity() == null) return; + + loadProjects(); + } + + public void loadProjects() { + if (!isLoading && getStudioActivity().isStoragePermissionGranted()) { + getProgressBar().setVisibility(View.VISIBLE); + getRecyclerView().setVisibility(View.GONE); + setLoading(true); + final ProjectsFragment frag = this; + new TaskExecutor() + .executeAsync( + new GetProjectsTask(), + (result) -> { + frag.setLoading(false); + + if (frag.getProjectsList() == null + || frag.getProjectsList().size() == 0) { + frag.getProgressBar().setVisibility(View.GONE); + frag.getRecyclerView().setVisibility(View.GONE); + frag.getNothingLayout().setVisibility(View.VISIBLE); + } else { + frag.getNothingLayout().setVisibility(View.GONE); + frag.getProgressBar().setVisibility(View.GONE); + frag.getRecyclerView().setVisibility(View.VISIBLE); + frag.getRecyclerView() + .setLayoutManager( + new LinearLayoutManager(getActivity())); + frag.getRecyclerView() + .setAdapter( + new ProjectsListAdapter( + mProjects, + (project) -> openProject(project))); + } + }); + } + } + + private void openProject(AndroidProject project) { + startActivity(new Intent(getActivity(), EditorActivity.class).putExtra("project", project)); + } + + public void getProjects() throws Exception { + mProjects = new ArrayList<>(); + File[] files = StudioApp.getInstance().listProjects(); + for (File file : files) { + AndroidProject project = ProjectFinder.fromFolder(file); + + if (project == null) continue; + + mProjects.add(project); + } + } + + public ProgressBar getProgressBar() { + return binding.projectsProgressBar; + } + + public RecyclerView getRecyclerView() { + return binding.mainProjectsRecyclerView; + } + + public boolean isLoading() { + return isLoading; + } + + public void setLoading(boolean isLoading) { + this.isLoading = isLoading; + } + + public ArrayList getProjectsList() { + return mProjects; + } + + public View getNothingLayout() { + return binding.projectsNothingLayout; + } + + private class GetProjectsTask implements Callable> { + @Override + public List call() throws Exception { + getProjects(); + return null; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/SearchResultFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/SearchResultFragment.java index bb0344eaa..551587a49 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/SearchResultFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/SearchResultFragment.java @@ -22,62 +22,64 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.adapters.SearchListAdapter; import com.itsaky.androidide.views.EmptyView; public class SearchResultFragment extends Fragment { - private RecyclerView list; - private EmptyView emptyView; + private RecyclerView list; + private EmptyView emptyView; - private SearchListAdapter unsavedAdapter; + private SearchListAdapter unsavedAdapter; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup parent, - @Nullable Bundle savedInstanceState) { - final var container = new LinearLayout(inflater.getContext()); - container.setOrientation(LinearLayout.VERTICAL); - container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup parent, + @Nullable Bundle savedInstanceState) { + final var container = new LinearLayout(inflater.getContext()); + container.setOrientation(LinearLayout.VERTICAL); + container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); - this.list = new RecyclerView(inflater.getContext()); - this.emptyView = new EmptyView(inflater.getContext()); + this.list = new RecyclerView(inflater.getContext()); + this.emptyView = new EmptyView(inflater.getContext()); - container.addView(list, new LinearLayout.LayoutParams(-1, -1)); - container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1)); + container.addView(list, new LinearLayout.LayoutParams(-1, -1)); + container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1)); - return container; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - list.setLayoutManager(new LinearLayoutManager(list.getContext())); - list.setAdapter( - unsavedAdapter != null ? unsavedAdapter : new SearchListAdapter(null, null, null)); - unsavedAdapter = null; - } - - public void setAdapter(@NonNull final SearchListAdapter adapter) { - if (list == null) { - unsavedAdapter = adapter; - return; + return container; } - this.list.setAdapter(adapter); - } - public void handleResultVisibility(boolean error) { - if (emptyView != null && list != null) { - emptyView.setVisibility(error ? View.VISIBLE : View.GONE); - list.setVisibility(error ? View.GONE : View.VISIBLE); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + list.setLayoutManager(new LinearLayoutManager(list.getContext())); + list.setAdapter( + unsavedAdapter != null ? unsavedAdapter : new SearchListAdapter(null, null, null)); + unsavedAdapter = null; + } + + public void setAdapter(@NonNull final SearchListAdapter adapter) { + if (list == null) { + unsavedAdapter = adapter; + return; + } + this.list.setAdapter(adapter); + } + + public void handleResultVisibility(boolean error) { + if (emptyView != null && list != null) { + emptyView.setVisibility(error ? View.VISIBLE : View.GONE); + list.setVisibility(error ? View.GONE : View.VISIBLE); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/SimpleOutputFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/SimpleOutputFragment.java index daae22835..19c848477 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/SimpleOutputFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/SimpleOutputFragment.java @@ -19,38 +19,42 @@ package com.itsaky.androidide.fragments; import android.os.Bundle; import android.view.View; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.blankj.utilcode.util.ThreadUtils; + import java.util.ArrayList; import java.util.List; import java.util.Objects; public class SimpleOutputFragment extends NonEditableEditorFragment { - private final List unsavedLines = new ArrayList<>(); + private final List unsavedLines = new ArrayList<>(); - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); - if (!unsavedLines.isEmpty()) { - for (String line : unsavedLines) { - Objects.requireNonNull(getEditor()).append(line.trim() + "\n"); - } - unsavedLines.clear(); + if (!unsavedLines.isEmpty()) { + for (String line : unsavedLines) { + Objects.requireNonNull(getEditor()).append(line.trim() + "\n"); + } + unsavedLines.clear(); + } } - } - public void appendOutput(String output) { - if (getEditor() == null) { - unsavedLines.add(output); - return; + public void appendOutput(String output) { + if (getEditor() == null) { + unsavedLines.add(output); + return; + } + ThreadUtils.runOnUiThread( + () -> { + final var message = + output == null || output.endsWith("\n") ? output : output + "\n"; + getEditor().append(message); + }); } - ThreadUtils.runOnUiThread( - () -> { - final var message = output == null || output.endsWith("\n") ? output : output + "\n"; - getEditor().append(message); - }); - } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java index eae850770..f0b7cc9d6 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java @@ -24,31 +24,35 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; + import androidx.core.content.ContextCompat; import androidx.preference.PreferenceFragmentCompat; + import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.managers.PreferenceManager; public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { - protected PreferenceManager mPrefManager; + protected PreferenceManager mPrefManager; - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = super.onCreateView(inflater, container, savedInstanceState); - v.setBackgroundColor( - ContextCompat.getColor( - StudioApp.getInstance(), com.itsaky.androidide.R.color.content_background)); - return v; - } + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = super.onCreateView(inflater, container, savedInstanceState); + v.setBackgroundColor( + ContextCompat.getColor( + StudioApp.getInstance(), com.itsaky.androidide.R.color.content_background)); + return v; + } - @Override - public void onCreatePreferences(Bundle p1, String p2) { - setPreferencesFromResource(com.itsaky.androidide.R.xml.ide_prefs, p2); - } + @Override + public void onCreatePreferences(Bundle p1, String p2) { + setPreferencesFromResource(com.itsaky.androidide.R.xml.ide_prefs, p2); + } - protected PreferenceManager getPrefManager() { - return mPrefManager == null ? mPrefManager = new PreferenceManager(getContext()) : mPrefManager; - } + protected PreferenceManager getPrefManager() { + return mPrefManager == null + ? mPrefManager = new PreferenceManager(getContext()) + : mPrefManager; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java index 76ffe318d..5e3edc636 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java @@ -21,9 +21,11 @@ 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; @@ -31,124 +33,129 @@ 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 { + implements Preference.OnPreferenceClickListener { - public static final String KEY_GRADLE_COMMAMDS = "idepref_build_gradleCommands"; - public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache"; + public static final String KEY_GRADLE_COMMAMDS = "idepref_build_gradleCommands"; + public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache"; - private ProgressSheet progressSheet; + 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()); + @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); + screen.addPreference(categoryGradle); - customCommands.setKey(KEY_GRADLE_COMMAMDS); - customCommands.setIcon(R.drawable.ic_bash_commands); - customCommands.setTitle(R.string.idepref_build_customgradlecommands_title); - customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary); + customCommands.setKey(KEY_GRADLE_COMMAMDS); + 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); + 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); + categoryGradle.setTitle(R.string.gradle); + categoryGradle.addPreference(customCommands); + categoryGradle.addPreference(clearCache); - setPreferenceScreen(screen); + setPreferenceScreen(screen); - customCommands.setOnPreferenceClickListener(this); - clearCache.setOnPreferenceClickListener(this); - } - - @Override - public boolean onPreferenceClick(Preference p1) { - final String key = p1.getKey(); - if (key.equals(KEY_GRADLE_COMMAMDS)) { - showGradleCommandsDialog(); - } else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) { - showClearCacheDialog(); + customCommands.setOnPreferenceClickListener(this); + clearCache.setOnPreferenceClickListener(this); } - return true; - } - private void showGradleCommandsDialog() { - final String[] labels = {"--stacktrace", "--info", "--debug", "--scan", "--warning-mode all"}; - final boolean[] checked = { - getPrefManager().isStackTraceEnabled(), - getPrefManager().isGradleInfoEnabled(), - getPrefManager().isGradleDebugEnabled(), - getPrefManager().isGradleScanEnabled(), - getPrefManager().isGradleWarningEnabled() - }; - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); - builder.setTitle(R.string.idepref_build_customgradlecommands_title); - builder.setMultiChoiceItems( - labels, - checked, - (p1, p2, p3) -> { - boolean isChecked = p3; - int pos = p2; - if (pos == 0) { - getPrefManager().setGradleStacktraceEnabled(isChecked); - } else if (pos == 1) { - getPrefManager().setGradleInfoEnabled(isChecked); - } else if (pos == 2) { - getPrefManager().setGradleDebugEnabled(isChecked); - } else if (pos == 3) { - getPrefManager().setGradleScanEnabled(isChecked); - } else if (pos == 4) { - getPrefManager().setGradleWarningEnabled(isChecked); - } - }); - 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); + @Override + public boolean onPreferenceClick(Preference p1) { + final String key = p1.getKey(); + if (key.equals(KEY_GRADLE_COMMAMDS)) { + showGradleCommandsDialog(); + } else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) { + showClearCacheDialog(); + } + return true; } - return null; - } - private ProgressSheet getProgressSheet() { - return progressSheet == null - ? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait)) - : progressSheet; - } + private void showGradleCommandsDialog() { + final String[] labels = { + "--stacktrace", "--info", "--debug", "--scan", "--warning-mode all" + }; + final boolean[] checked = { + getPrefManager().isStackTraceEnabled(), + getPrefManager().isGradleInfoEnabled(), + getPrefManager().isGradleDebugEnabled(), + getPrefManager().isGradleScanEnabled(), + getPrefManager().isGradleWarningEnabled() + }; + final MaterialAlertDialogBuilder builder = + DialogUtils.newMaterialDialogBuilder(getContext()); + builder.setTitle(R.string.idepref_build_customgradlecommands_title); + builder.setMultiChoiceItems( + labels, + checked, + (p1, p2, p3) -> { + boolean isChecked = p3; + int pos = p2; + if (pos == 0) { + getPrefManager().setGradleStacktraceEnabled(isChecked); + } else if (pos == 1) { + getPrefManager().setGradleInfoEnabled(isChecked); + } else if (pos == 2) { + getPrefManager().setGradleDebugEnabled(isChecked); + } else if (pos == 3) { + getPrefManager().setGradleScanEnabled(isChecked); + } else if (pos == 4) { + getPrefManager().setGradleWarningEnabled(isChecked); + } + }); + 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; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java index db23f0b19..5e62cfe30 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java @@ -34,10 +34,12 @@ import static com.itsaky.androidide.models.PrefBasedJavaServerSettings.KEY_JAVA_ 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.app.StudioApp; @@ -48,254 +50,258 @@ import com.itsaky.androidide.utils.DialogUtils; public class EditorPreferences extends BasePreferenceFragment { - @Override - public void onCreatePreferences(Bundle p1, String p2) { - super.onCreatePreferences(p1, p2); + @Override + public void onCreatePreferences(Bundle p1, String p2) { + super.onCreatePreferences(p1, p2); - if (getContext() == null) { - return; + 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 fontLigatures = new SwitchPreference(getContext()); + final var autoSave = new SwitchPreference(getContext()); + final var usePopupActions = new SwitchPreference(getContext()); + final var horizontalPopup = new SwitchPreference(getContext()); + + final var javaMatchLower = 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); + + 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)); + + usePopupActions.setIcon(R.drawable.ic_text_actions); + usePopupActions.setKey(KEY_EDITOR_USE_POPUP); + usePopupActions.setTitle(getString(R.string.title_use_popup_actions)); + usePopupActions.setSummary(getString(R.string.msg_use_popup_actions)); + + horizontalPopup.setIcon(R.drawable.ic_text_actions_horizontal); + horizontalPopup.setKey(KEY_EDITOR_HORIZONTAL_POPUP); + horizontalPopup.setTitle(getString(R.string.title_use_horizontal_popup)); + horizontalPopup.setSummary(getString(R.string.msg_use_horizontal_popup)); + + 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)); + + javaMatchLower.setKey(KEY_JAVA_PREF_MATCH_LOWER); + javaMatchLower.setIcon(R.drawable.ic_text_lower); + javaMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title)); + javaMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary)); + + commonCategory.setTitle(getString(R.string.idepref_editor_category_common)); + commonCategory.addPreference(fontSize); + commonCategory.addPreference(fontLigatures); + commonCategory.addPreference(usePopupActions); + commonCategory.addPreference(horizontalPopup); + commonCategory.addPreference(nonPrintable); + commonCategory.addPreference(tabSize); + commonCategory.addPreference(drawHex); + commonCategory.addPreference(autoSave); + + javaCategory.setTitle(getString(R.string.idepref_editor_category_java)); + javaCategory.addPreference(javaMatchLower); + + setPreferenceScreen(screen); + + fontSize.setOnPreferenceClickListener(this::onPreferenceClick); + fontLigatures.setOnPreferenceChangeListener(this::onPreferenceChange); + usePopupActions.setOnPreferenceChangeListener(this::onPreferenceChange); + horizontalPopup.setOnPreferenceChangeListener(this::onPreferenceChange); + nonPrintable.setOnPreferenceClickListener(this::onPreferenceClick); + tabSize.setOnPreferenceClickListener(this::onPreferenceClick); + drawHex.setOnPreferenceChangeListener(this::onPreferenceChange); + autoSave.setOnPreferenceChangeListener(this::onPreferenceChange); + javaMatchLower.setOnPreferenceChangeListener(this::onPreferenceChange); + + fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true)); + usePopupActions.setChecked(getPrefManager().getBoolean(KEY_EDITOR_USE_POPUP, false)); + horizontalPopup.setChecked(getPrefManager().getBoolean(KEY_EDITOR_HORIZONTAL_POPUP, false)); + drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true)); + autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false)); + javaMatchLower.setChecked(getPrefManager().getBoolean(KEY_JAVA_PREF_MATCH_LOWER, false)); } - final var screen = getPreferenceScreen(); - final var commonCategory = new PreferenceCategory(getContext()); - final var javaCategory = new PreferenceCategory(getContext()); + public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { + final boolean value = (boolean) newValue; - 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 fontLigatures = new SwitchPreference(getContext()); - final var autoSave = new SwitchPreference(getContext()); - final var usePopupActions = new SwitchPreference(getContext()); - final var horizontalPopup = new SwitchPreference(getContext()); + switch (preference.getKey()) { + 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 KEY_JAVA_PREF_MATCH_LOWER: + final var javaServer = StudioApp.getInstance().getJavaLanguageServer(); + javaServer.applySettings(PrefBasedJavaServerSettings.getInstance()); + break; + } - final var javaMatchLower = new SwitchPreference(getContext()); + getPrefManager().putBoolean(preference.getKey(), value); - screen.addPreference(commonCategory); - screen.addPreference(javaCategory); + final var horizontalPopup = findPreference(KEY_EDITOR_HORIZONTAL_POPUP); + if (horizontalPopup != null) { + horizontalPopup.setEnabled(getPrefManager().getBoolean(KEY_EDITOR_USE_POPUP)); + } - 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); - - 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)); - - usePopupActions.setIcon(R.drawable.ic_text_actions); - usePopupActions.setKey(KEY_EDITOR_USE_POPUP); - usePopupActions.setTitle(getString(R.string.title_use_popup_actions)); - usePopupActions.setSummary(getString(R.string.msg_use_popup_actions)); - - horizontalPopup.setIcon(R.drawable.ic_text_actions_horizontal); - horizontalPopup.setKey(KEY_EDITOR_HORIZONTAL_POPUP); - horizontalPopup.setTitle(getString(R.string.title_use_horizontal_popup)); - horizontalPopup.setSummary(getString(R.string.msg_use_horizontal_popup)); - - 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)); - - javaMatchLower.setKey(KEY_JAVA_PREF_MATCH_LOWER); - javaMatchLower.setIcon(R.drawable.ic_text_lower); - javaMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title)); - javaMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary)); - - commonCategory.setTitle(getString(R.string.idepref_editor_category_common)); - commonCategory.addPreference(fontSize); - commonCategory.addPreference(fontLigatures); - commonCategory.addPreference(usePopupActions); - commonCategory.addPreference(horizontalPopup); - commonCategory.addPreference(nonPrintable); - commonCategory.addPreference(tabSize); - commonCategory.addPreference(drawHex); - commonCategory.addPreference(autoSave); - - javaCategory.setTitle(getString(R.string.idepref_editor_category_java)); - javaCategory.addPreference(javaMatchLower); - - setPreferenceScreen(screen); - - fontSize.setOnPreferenceClickListener(this::onPreferenceClick); - fontLigatures.setOnPreferenceChangeListener(this::onPreferenceChange); - usePopupActions.setOnPreferenceChangeListener(this::onPreferenceChange); - horizontalPopup.setOnPreferenceChangeListener(this::onPreferenceChange); - nonPrintable.setOnPreferenceClickListener(this::onPreferenceClick); - tabSize.setOnPreferenceClickListener(this::onPreferenceClick); - drawHex.setOnPreferenceChangeListener(this::onPreferenceChange); - autoSave.setOnPreferenceChangeListener(this::onPreferenceChange); - javaMatchLower.setOnPreferenceChangeListener(this::onPreferenceChange); - - fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true)); - usePopupActions.setChecked(getPrefManager().getBoolean(KEY_EDITOR_USE_POPUP, false)); - horizontalPopup.setChecked(getPrefManager().getBoolean(KEY_EDITOR_HORIZONTAL_POPUP, false)); - drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true)); - autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false)); - javaMatchLower.setChecked(getPrefManager().getBoolean(KEY_JAVA_PREF_MATCH_LOWER, false)); - } - - public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { - final boolean value = (boolean) newValue; - - switch (preference.getKey()) { - 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 KEY_JAVA_PREF_MATCH_LOWER: - final var javaServer = StudioApp.getInstance().getJavaLanguageServer(); - javaServer.applySettings(PrefBasedJavaServerSettings.getInstance()); - break; + return true; } - getPrefManager().putBoolean(preference.getKey(), value); - - final var horizontalPopup = findPreference(KEY_EDITOR_HORIZONTAL_POPUP); - if (horizontalPopup != null) { - horizontalPopup.setEnabled(getPrefManager().getBoolean(KEY_EDITOR_USE_POPUP)); + 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; } - return true; - } + private void showTabSizeDialog() { + final MaterialAlertDialogBuilder builder = + DialogUtils.newMaterialDialogBuilder(getContext()); + final String[] sizes = new String[] {"2", "4", "6", "8"}; - 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; + // We apply simple maths, + // assuming that tab size options have a difference of 2 + // And the array is sorted + // Like, 2, 4, 6, 8, 10, etc + // + // If current tab size if 4, 4/2 = 2 and 2 - 1 = 1, so option at index 1 will be selected + // which + // is current tab size (4)! + 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(); + + // Reversing the logic applied above... + int tabSize = (i + 1) * 2; + if (tabSize < 2 || tabSize > 8) { + tabSize = 4; + } + getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize); + }); + builder.setCancelable(true); + builder.show(); } - return true; - } - private void showTabSizeDialog() { - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); - final String[] sizes = new String[] {"2", "4", "6", "8"}; + 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); - // We apply simple maths, - // assuming that tab size options have a difference of 2 - // And the array is sorted - // Like, 2, 4, 6, 8, 10, etc - // - // If current tab size if 4, 4/2 = 2 and 2 - 1 = 1, so option at index 1 will be selected which - // is current tab size (4)! - int current = (getPrefManager().getEditorTabSize() / 2) - 1; - if (current < 0 || current >= sizes.length) { - current = 1; + 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(); } - builder.setTitle(R.string.title_tab_size); - builder.setSingleChoiceItems( - sizes, - current, - (d, i) -> { - d.dismiss(); - // Reversing the logic applied above... - 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; + private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) { + getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size); + binding.slider.setValue(size); + ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true; } - 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 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); + } - 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(); - } + ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true; + }); + builder.setPositiveButton(android.R.string.ok, null); + builder.setCancelable(false); + builder.show(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/GeneralPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/GeneralPreferences.java index fa60e9f30..f5e8f5d97 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/GeneralPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/GeneralPreferences.java @@ -20,70 +20,72 @@ package com.itsaky.androidide.fragments.preferences; import static com.itsaky.androidide.managers.PreferenceManager.*; import android.os.Bundle; + import androidx.annotation.NonNull; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; + import com.itsaky.androidide.R; public class GeneralPreferences extends BasePreferenceFragment - implements Preference.OnPreferenceChangeListener { + implements Preference.OnPreferenceChangeListener { - @Override - public void onCreatePreferences(Bundle p1, String p2) { - super.onCreatePreferences(p1, p2); - if (getContext() == null) return; + @Override + public void onCreatePreferences(Bundle p1, String p2) { + super.onCreatePreferences(p1, p2); + if (getContext() == null) return; - final PreferenceScreen screen = getPreferenceScreen(); - final SwitchPreference openProjects = new SwitchPreference(getContext()); - final SwitchPreference confirmProjectOpen = new SwitchPreference(getContext()); - final SwitchPreference shell = new SwitchPreference(getContext()); + final PreferenceScreen screen = getPreferenceScreen(); + final SwitchPreference openProjects = new SwitchPreference(getContext()); + final SwitchPreference confirmProjectOpen = new SwitchPreference(getContext()); + final SwitchPreference shell = new SwitchPreference(getContext()); - openProjects.setKey(KEY_OPEN_PROJECTS); - openProjects.setTitle(R.string.title_open_projects); - openProjects.setIcon(R.drawable.ic_open_project); - openProjects.setSummary(R.string.msg_open_projects); + openProjects.setKey(KEY_OPEN_PROJECTS); + openProjects.setTitle(R.string.title_open_projects); + openProjects.setIcon(R.drawable.ic_open_project); + openProjects.setSummary(R.string.msg_open_projects); - confirmProjectOpen.setKey(KEY_CONFIRM_PROJECT_OPEN); - confirmProjectOpen.setTitle(R.string.title_confirm_project_open); - confirmProjectOpen.setSummary(R.string.msg_confirm_project_open); - confirmProjectOpen.setIcon(R.drawable.ic_open_project); + confirmProjectOpen.setKey(KEY_CONFIRM_PROJECT_OPEN); + confirmProjectOpen.setTitle(R.string.title_confirm_project_open); + confirmProjectOpen.setSummary(R.string.msg_confirm_project_open); + confirmProjectOpen.setIcon(R.drawable.ic_open_project); - shell.setKey(KEY_TERMINAL_USE_SYSTEM_SHELL); - shell.setTitle(getString(R.string.title_default_shell)); - shell.setSummary(getString(R.string.msg_default_shell)); - shell.setIcon(R.drawable.ic_bash_commands); + shell.setKey(KEY_TERMINAL_USE_SYSTEM_SHELL); + shell.setTitle(getString(R.string.title_default_shell)); + shell.setSummary(getString(R.string.msg_default_shell)); + shell.setIcon(R.drawable.ic_bash_commands); - openProjects.setChecked(getPrefManager().autoOpenProject()); - confirmProjectOpen.setChecked(getPrefManager().confirmProjectOpen()); - shell.setChecked(getPrefManager().getBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL)); + openProjects.setChecked(getPrefManager().autoOpenProject()); + confirmProjectOpen.setChecked(getPrefManager().confirmProjectOpen()); + shell.setChecked(getPrefManager().getBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL)); - screen.addPreference(openProjects); - screen.addPreference(confirmProjectOpen); - screen.addPreference(shell); + screen.addPreference(openProjects); + screen.addPreference(confirmProjectOpen); + screen.addPreference(shell); - setPreferenceScreen(screen); + setPreferenceScreen(screen); - openProjects.setOnPreferenceChangeListener(this); - confirmProjectOpen.setOnPreferenceChangeListener(this); - shell.setOnPreferenceChangeListener(this); - } - - @Override - public boolean onPreferenceChange(@NonNull Preference p1, Object p2) { - boolean checked = (Boolean) p2; - final var key = p1.getKey(); - switch (key) { - case KEY_OPEN_PROJECTS: - getPrefManager().putBoolean(KEY_OPEN_PROJECTS, checked); - break; - case KEY_CONFIRM_PROJECT_OPEN: - getPrefManager().putBoolean(KEY_CONFIRM_PROJECT_OPEN, checked); - break; - case KEY_TERMINAL_USE_SYSTEM_SHELL: - getPrefManager().putBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL, checked); - break; + openProjects.setOnPreferenceChangeListener(this); + confirmProjectOpen.setOnPreferenceChangeListener(this); + shell.setOnPreferenceChangeListener(this); + } + + @Override + public boolean onPreferenceChange(@NonNull Preference p1, Object p2) { + boolean checked = (Boolean) p2; + final var key = p1.getKey(); + switch (key) { + case KEY_OPEN_PROJECTS: + getPrefManager().putBoolean(KEY_OPEN_PROJECTS, checked); + break; + case KEY_CONFIRM_PROJECT_OPEN: + getPrefManager().putBoolean(KEY_CONFIRM_PROJECT_OPEN, checked); + break; + case KEY_TERMINAL_USE_SYSTEM_SHELL: + getPrefManager().putBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL, checked); + break; + } + return true; } - return true; - } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java index 0cf26d891..83fb55afa 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java @@ -31,9 +31,11 @@ import android.view.ViewGroup; import android.widget.HorizontalScrollView; import android.widget.ScrollView; import android.widget.TextView; + import androidx.core.content.ContextCompat; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; + import com.blankj.utilcode.util.ResourceUtils; import com.blankj.utilcode.util.SizeUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -41,171 +43,173 @@ import com.itsaky.androidide.AboutActivity; import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.utils.DialogUtils; + import org.json.JSONArray; import org.json.JSONObject; public class IDEPreferences extends BasePreferenceFragment { - private GeneralPreferences mGeneralPrefs; - private BuildPreferences mBuildPreferences; - private EditorPreferences mEditorPreferences; + private GeneralPreferences mGeneralPrefs; + private BuildPreferences mBuildPreferences; + private EditorPreferences mEditorPreferences; - private String CHANGELOG = null; + private String CHANGELOG = null; - public static final String KEY_GENERAL = "idepref_general"; - public static final String KEY_APPEARANCE = "idepref_appearance"; - public static final String KEY_EDITOR = "idepref_editor"; - public static final String KEY_BUILD = "idepref_build"; - public static final String KEY_TELEGRAM = "idepref_telegram"; - public static final String KEY_ISSUES = "idepref_issues"; - public static final String KEY_CHANGELOG = "idepref_changelog"; - public static final String KEY_ABOUT = "idepref_about"; + public static final String KEY_GENERAL = "idepref_general"; + public static final String KEY_APPEARANCE = "idepref_appearance"; + public static final String KEY_EDITOR = "idepref_editor"; + public static final String KEY_BUILD = "idepref_build"; + public static final String KEY_TELEGRAM = "idepref_telegram"; + public static final String KEY_ISSUES = "idepref_issues"; + public static final String KEY_CHANGELOG = "idepref_changelog"; + public static final String KEY_ABOUT = "idepref_about"; - @Override - public void onCreatePreferences(Bundle savedState, String rootKey) { - super.onCreatePreferences(savedState, rootKey); - if (getContext() == null) return; + @Override + public void onCreatePreferences(Bundle savedState, String rootKey) { + super.onCreatePreferences(savedState, rootKey); + if (getContext() == null) return; - final PreferenceScreen screen = getPreferenceScreen(); - final Preference general = new Preference(getContext()); - final Preference editor = new Preference(getContext()); - final Preference build = new Preference(getContext()); - final Preference telegram = new Preference(getContext()); - final Preference github = new Preference(getContext()); - final Preference changelog = new Preference(getContext()); - final Preference about = new Preference(getContext()); + final PreferenceScreen screen = getPreferenceScreen(); + final Preference general = new Preference(getContext()); + final Preference editor = new Preference(getContext()); + final Preference build = new Preference(getContext()); + final Preference telegram = new Preference(getContext()); + final Preference github = new Preference(getContext()); + final Preference changelog = new Preference(getContext()); + final Preference about = new Preference(getContext()); - general.setKey(KEY_GENERAL); - general.setIconSpaceReserved(false); - general.setFragment(getGeneralFrag().getClass().getName()); - general.setTitle(R.string.title_general); + general.setKey(KEY_GENERAL); + general.setIconSpaceReserved(false); + general.setFragment(getGeneralFrag().getClass().getName()); + general.setTitle(R.string.title_general); - editor.setKey(KEY_EDITOR); - editor.setIconSpaceReserved(false); - editor.setFragment(getEditorFrag().getClass().getName()); - editor.setTitle(R.string.idepref_editor_title); + editor.setKey(KEY_EDITOR); + editor.setIconSpaceReserved(false); + editor.setFragment(getEditorFrag().getClass().getName()); + editor.setTitle(R.string.idepref_editor_title); - build.setKey(KEY_BUILD); - build.setIconSpaceReserved(false); - build.setFragment(getBuildFrag().getClass().getName()); - build.setTitle(R.string.idepref_build_title); + build.setKey(KEY_BUILD); + build.setIconSpaceReserved(false); + build.setFragment(getBuildFrag().getClass().getName()); + build.setTitle(R.string.idepref_build_title); - github.setKey(KEY_ISSUES); - github.setIconSpaceReserved(false); - github.setTitle(R.string.title_github); + github.setKey(KEY_ISSUES); + github.setIconSpaceReserved(false); + github.setTitle(R.string.title_github); - telegram.setKey(KEY_TELEGRAM); - telegram.setIconSpaceReserved(false); - telegram.setTitle(R.string.discussions_on_telegram); + telegram.setKey(KEY_TELEGRAM); + telegram.setIconSpaceReserved(false); + telegram.setTitle(R.string.discussions_on_telegram); - changelog.setKey(KEY_CHANGELOG); - changelog.setIconSpaceReserved(false); - changelog.setTitle(R.string.pref_changelog); + changelog.setKey(KEY_CHANGELOG); + changelog.setIconSpaceReserved(false); + changelog.setTitle(R.string.pref_changelog); - about.setKey(KEY_ABOUT); - about.setIconSpaceReserved(false); - about.setTitle(R.string.idepref_about_title); + about.setKey(KEY_ABOUT); + about.setIconSpaceReserved(false); + about.setTitle(R.string.idepref_about_title); - screen.addPreference(general); - screen.addPreference(editor); - screen.addPreference(build); - screen.addPreference(github); - screen.addPreference(telegram); - screen.addPreference(changelog); - screen.addPreference(about); + screen.addPreference(general); + screen.addPreference(editor); + screen.addPreference(build); + screen.addPreference(github); + screen.addPreference(telegram); + screen.addPreference(changelog); + screen.addPreference(about); - final Preference.OnPreferenceClickListener listener = getListener(); - github.setOnPreferenceClickListener(listener); - telegram.setOnPreferenceClickListener(listener); - changelog.setOnPreferenceClickListener(listener); - about.setOnPreferenceClickListener(listener); - } - - private GeneralPreferences getGeneralFrag() { - return mGeneralPrefs == null ? mGeneralPrefs = new GeneralPreferences() : mGeneralPrefs; - } - - private EditorPreferences getEditorFrag() { - return mEditorPreferences == null - ? mEditorPreferences = new EditorPreferences() - : mEditorPreferences; - } - - private BuildPreferences getBuildFrag() { - return mBuildPreferences == null - ? mBuildPreferences = new BuildPreferences() - : mBuildPreferences; - } - - private void showChangelog() { - final int dp8 = SizeUtils.dp2px(8); - final int dp16 = SizeUtils.dp2px(16); - final ScrollView vScroll = new ScrollView(getContext()); - vScroll.setFillViewport(true); - vScroll.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); - - final HorizontalScrollView scroll = new HorizontalScrollView(getContext()); - scroll.setLayoutParams(new ViewGroup.LayoutParams(-1, -2)); - scroll.setFillViewport(true); - scroll.setPadding(dp16, dp8, dp16, dp8); - scroll.setPaddingRelative(dp16, dp8, dp16, dp8); - - final TextView text = new TextView(getContext()); - text.setText(CHANGELOG == null ? CHANGELOG = changelogAsString() : CHANGELOG); - text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13); - text.setTextColor(ContextCompat.getColor(getContext(), R.color.primaryTextColor)); - scroll.removeAllViews(); - vScroll.removeAllViews(); - scroll.addView(text, new ViewGroup.LayoutParams(-1, -1)); - vScroll.addView(scroll); - - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); - builder.setTitle(R.string.pref_changelog); - builder.setView(vScroll); - builder.setPositiveButton(android.R.string.ok, null); - builder.create().show(); - } - - private String changelogAsString() { - try { - final SpannableStringBuilder sb = new SpannableStringBuilder(); - JSONArray arr = new JSONArray(ResourceUtils.readAssets2String("changelog.json")); - for (int i = 0; i < arr.length(); i++) { - JSONObject version = arr.getJSONObject(i); - sb.append( - "v".concat(version.getString("version")), - new StyleSpan(Typeface.BOLD), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - JSONArray changes = version.getJSONArray("changes"); - for (int j = 0; j < changes.length(); j++) { - sb.append("\n "); - sb.append("\u2022 ".concat(changes.getString(j))); - } - sb.append("\n\n"); - } - return sb.toString(); - } catch (Throwable th) { - return getString(R.string.msg_failed_get_changelog); + final Preference.OnPreferenceClickListener listener = getListener(); + github.setOnPreferenceClickListener(listener); + telegram.setOnPreferenceClickListener(listener); + changelog.setOnPreferenceClickListener(listener); + about.setOnPreferenceClickListener(listener); } - } - private Preference.OnPreferenceClickListener getListener() { - return new Preference.OnPreferenceClickListener() { + private GeneralPreferences getGeneralFrag() { + return mGeneralPrefs == null ? mGeneralPrefs = new GeneralPreferences() : mGeneralPrefs; + } - @Override - public boolean onPreferenceClick(Preference p1) { - final String key = p1.getKey(); - if (key.equals(KEY_CHANGELOG)) { - showChangelog(); - } else if (key.equals(KEY_TELEGRAM)) { - StudioApp.getInstance().openTelegramGroup(); - } else if (key.equals(KEY_ISSUES)) { - StudioApp.getInstance().openGitHub(); - } else if (key.equals(KEY_ABOUT)) { - startActivity(new Intent(getContext(), AboutActivity.class)); + private EditorPreferences getEditorFrag() { + return mEditorPreferences == null + ? mEditorPreferences = new EditorPreferences() + : mEditorPreferences; + } + + private BuildPreferences getBuildFrag() { + return mBuildPreferences == null + ? mBuildPreferences = new BuildPreferences() + : mBuildPreferences; + } + + private void showChangelog() { + final int dp8 = SizeUtils.dp2px(8); + final int dp16 = SizeUtils.dp2px(16); + final ScrollView vScroll = new ScrollView(getContext()); + vScroll.setFillViewport(true); + vScroll.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); + + final HorizontalScrollView scroll = new HorizontalScrollView(getContext()); + scroll.setLayoutParams(new ViewGroup.LayoutParams(-1, -2)); + scroll.setFillViewport(true); + scroll.setPadding(dp16, dp8, dp16, dp8); + scroll.setPaddingRelative(dp16, dp8, dp16, dp8); + + final TextView text = new TextView(getContext()); + text.setText(CHANGELOG == null ? CHANGELOG = changelogAsString() : CHANGELOG); + text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13); + text.setTextColor(ContextCompat.getColor(getContext(), R.color.primaryTextColor)); + scroll.removeAllViews(); + vScroll.removeAllViews(); + scroll.addView(text, new ViewGroup.LayoutParams(-1, -1)); + vScroll.addView(scroll); + + final MaterialAlertDialogBuilder builder = + DialogUtils.newMaterialDialogBuilder(getContext()); + builder.setTitle(R.string.pref_changelog); + builder.setView(vScroll); + builder.setPositiveButton(android.R.string.ok, null); + builder.create().show(); + } + + private String changelogAsString() { + try { + final SpannableStringBuilder sb = new SpannableStringBuilder(); + JSONArray arr = new JSONArray(ResourceUtils.readAssets2String("changelog.json")); + for (int i = 0; i < arr.length(); i++) { + JSONObject version = arr.getJSONObject(i); + sb.append( + "v".concat(version.getString("version")), + new StyleSpan(Typeface.BOLD), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + JSONArray changes = version.getJSONArray("changes"); + for (int j = 0; j < changes.length(); j++) { + sb.append("\n "); + sb.append("\u2022 ".concat(changes.getString(j))); + } + sb.append("\n\n"); + } + return sb.toString(); + } catch (Throwable th) { + return getString(R.string.msg_failed_get_changelog); } - return true; - } - }; - } + } + + private Preference.OnPreferenceClickListener getListener() { + return new Preference.OnPreferenceClickListener() { + + @Override + public boolean onPreferenceClick(Preference p1) { + final String key = p1.getKey(); + if (key.equals(KEY_CHANGELOG)) { + showChangelog(); + } else if (key.equals(KEY_TELEGRAM)) { + StudioApp.getInstance().openTelegramGroup(); + } else if (key.equals(KEY_ISSUES)) { + StudioApp.getInstance().openGitHub(); + } else if (key.equals(KEY_ABOUT)) { + startActivity(new Intent(getContext(), AboutActivity.class)); + } + return true; + } + }; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrEditorSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrEditorSheet.java index 0cdc94b81..3dbe4f4d3 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrEditorSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrEditorSheet.java @@ -33,10 +33,12 @@ 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 androidx.appcompat.app.AlertDialog; import androidx.transition.TransitionManager; + import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.itsaky.androidide.R; import com.itsaky.androidide.adapters.SimpleIconTextAdapter; @@ -55,6 +57,9 @@ import com.itsaky.inflater.IAttribute; import com.itsaky.inflater.IView; import com.itsaky.inflater.impl.UiAttribute; import com.itsaky.toaster.Toaster; + +import org.jetbrains.annotations.Contract; + import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -63,318 +68,332 @@ import java.util.List; import java.util.Objects; import java.util.TreeSet; import java.util.stream.Collectors; -import org.jetbrains.annotations.Contract; public class AttrEditorSheet extends BottomSheetDialogFragment - implements SimpleIconTextAdapter.OnBindListener { + implements SimpleIconTextAdapter.OnBindListener { - private static final List VIEW_ACTIONS = new ArrayList<>(); - private static final Logger LOG = Logger.instance("AttrBottomSheet"); - private AttributeListSheet mAttrListSheet; - private IView selectedView; - private File layout; - private LayoutAttrEditorSheetBinding binding; - private OnViewDeletionFailedListener mDeletionFailedListener; + private static final List VIEW_ACTIONS = new ArrayList<>(); + private static final Logger LOG = Logger.instance("AttrBottomSheet"); + private AttributeListSheet mAttrListSheet; + private IView selectedView; + private File layout; + private LayoutAttrEditorSheetBinding binding; + private OnViewDeletionFailedListener mDeletionFailedListener; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = - LayoutAttrEditorSheetBinding.inflate(LayoutInflater.from(getContext()), container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - if (VIEW_ACTIONS.isEmpty()) { - Collections.addAll( - VIEW_ACTIONS, - IconTextListItem.create(getString(R.string.msg_viewaction_add_attr), R.drawable.ic_add), - IconTextListItem.create( - getString(R.string.title_viewaction_delete), R.drawable.ic_delete), - IconTextListItem.create( - getString(R.string.msg_viewaction_select_parent), R.drawable.ic_view_select_parent)); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = + LayoutAttrEditorSheetBinding.inflate( + LayoutInflater.from(getContext()), container, false); + return binding.getRoot(); } - binding.valueEditorLayout.closeButton.setOnClickListener(v -> hideValueEditorLayout()); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); - setupViewData(); + if (VIEW_ACTIONS.isEmpty()) { + Collections.addAll( + VIEW_ACTIONS, + IconTextListItem.create( + getString(R.string.msg_viewaction_add_attr), R.drawable.ic_add), + IconTextListItem.create( + getString(R.string.title_viewaction_delete), R.drawable.ic_delete), + IconTextListItem.create( + getString(R.string.msg_viewaction_select_parent), + R.drawable.ic_view_select_parent)); + } - AttributeDialogs.init(requireActivity()); - } + binding.valueEditorLayout.closeButton.setOnClickListener(v -> hideValueEditorLayout()); - @Override - public void onDismiss(@NonNull DialogInterface dialog) { - AttributeDialogs.release(); - super.onDismiss(dialog); - } - - public AttrEditorSheet setDeletionFailedListener(OnViewDeletionFailedListener listener) { - this.mDeletionFailedListener = listener; - return this; - } - - public AttrEditorSheet setLayout(File layout) { - Objects.requireNonNull(layout); - - this.layout = layout; - return this; - } - - private void setupViewData() { - binding.actionsList.setAdapter(new SimpleIconTextAdapter(VIEW_ACTIONS).setOnBindListener(this)); - - if (this.selectedView == null) { - LOG.error("Cannot edit attributes of a null view."); - return; - } - - binding.widgetName.setText(this.selectedView.getXmlTag()); - binding.attrList.setAdapter( - new XMLAttributeListAdapter( - this.selectedView.getAttributes().stream() - .map(XMLAttribute::new) - .collect(Collectors.toList()), - this::onAttrClick)); - } - - public void setSelectedView(IView view) { - this.selectedView = view; - } - - private void onAttrClick( - LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) { - - if (this.selectedView == null) { - LOG.error("Cannot edit attributes of a null view."); - return; - } - - final var format = attribute.findFormat(); - if (format == -1) { - StudioApp.getInstance().toast(getString(R.string.msg_no_attr_format), Toaster.Type.ERROR); - LOG.error(getString(R.string.msg_no_attr_format), attribute); - return; - } - - showEditorDialog(attribute); - } - - private void hideValueEditorLayout() { - TransitionManager.beginDelayedTransition(binding.getRoot()); - binding.valueEditorLayout.getRoot().setVisibility(View.GONE); - } - - private void showEditorDialog(@NonNull XMLAttribute attribute) { - final var attr = attribute.getAttr(); - final var values = attr.possibleValues.toArray(new String[0]); - final AttributeDialogs.OnClickListener onDone = - (dialog, which, newValue) -> applyNewValue(attribute, newValue); - - final ColorPickerView.OnPickListener mPickListener = - (color, hexCode) -> applyNewValue(attribute, hexCode); - - AlertDialog dialog = null; - if (attribute.hasFormat(DIMENSION)) { - dialog = AttributeDialogs.dimensionEditor(attribute.getValue(), onDone); - } else if (attribute.hasFormat(STRING)) { - dialog = AttributeDialogs.stringEditor(onDone); - } else if (attribute.hasFormat(BOOLEAN)) { - dialog = booleanEditor(onDone); - } else if (attribute.hasFormat(ENUM)) { - dialog = enumEditor(values, attribute.getValue(), onDone); - } else if (attribute.hasFormat(FLAG)) { - dialog = flagEditor(values, attribute.getValue(), onDone); - } else if (attribute.hasFormat(COLOR)) { - // TODO Implement something to allow values from colors.xml or ColorStateLists - // Make this color picker optional - dialog = colorPicker(mPickListener); - } - - if (dialog != null) { - dialog.show(); - } - } - - private void applyNewValue(@NonNull IAttribute attribute, String newValue) { - if (this.selectedView.hasAttribute(attribute.getNamespace(), attribute.getAttributeName())) { - if (!this.selectedView.updateAttribute( - attribute.getNamespace(), attribute.getAttributeName(), newValue)) { - StudioApp.getInstance().toast(getString(R.string.msg_attr_not_updated), Toaster.Type.ERROR); - } else { - // Update the view data - // This will make sure that the attributes list has been updated setupViewData(); - } - } else { - addAttribute( - new UiAttribute(attribute.getNamespace(), attribute.getAttributeName(), newValue)); - } - } - private void addAttribute(@NonNull IAttribute attribute) { - this.selectedView.addAttribute(attribute); - setupViewData(); - StudioApp.getInstance().toast(getString(R.string.msg_attr_added), Toaster.Type.SUCCESS); - } - - @Override - public void postBind( - IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) { - final var binding = holder.binding; - binding.getRoot().setOnClickListener(v -> onViewActionClick(position)); - } - - private void onViewActionClick(int position) { - if (this.selectedView == null) { - return; + AttributeDialogs.init(requireActivity()); } - if (position == 0) { // Add attribute - var tag = this.selectedView.getXmlTag(); - if ("include".equals(tag) || "merge".equals(tag)) { - tag = "View"; - } + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + AttributeDialogs.release(); + super.onDismiss(dialog); + } - final var attrs = StudioApp.getInstance().attrInfo(); - final var style = attrs.getStyle(tag); - if (style == null) { - LOG.error("Unable to retrieve attributes for tag:", tag); - return; - } + public AttrEditorSheet setDeletionFailedListener(OnViewDeletionFailedListener listener) { + this.mDeletionFailedListener = listener; + return this; + } - final var attributes = new TreeSet(Comparator.comparing(attr -> attr.name)); - attributes.addAll(style.attributes); + public AttrEditorSheet setLayout(File layout) { + Objects.requireNonNull(layout); - final var widgetInfo = StudioApp.getInstance().widgetInfo(); - final var widget = widgetInfo.getWidgetBySimpleName(tag); - if (widget != null) { - for (var superclass : widget.superclasses) { - if ("java.lang.Object".equals(superclass)) { - break; - } + this.layout = layout; + return this; + } - final var simpleName = superclass.substring(superclass.lastIndexOf(".") + 1); - final var superStyle = attrs.getStyle(simpleName); - if (superStyle != null) { - attributes.addAll(superStyle.attributes); - } + private void setupViewData() { + binding.actionsList.setAdapter( + new SimpleIconTextAdapter(VIEW_ACTIONS).setOnBindListener(this)); + + if (this.selectedView == null) { + LOG.error("Cannot edit attributes of a null view."); + return; } - } - final var parent = this.selectedView.getParent(); - if (parent != null) { - final var parentTag = parent.getXmlTag(); - final var parentWidget = widgetInfo.getWidgetBySimpleName(parentTag); - if (parentWidget != null) { - final var parentLayoutParams = attrs.getStyle(parentTag + "_Layout"); - if (parentLayoutParams != null) { - attributes.addAll(parentLayoutParams.attributes); - } + binding.widgetName.setText(this.selectedView.getXmlTag()); + binding.attrList.setAdapter( + new XMLAttributeListAdapter( + this.selectedView.getAttributes().stream() + .map(XMLAttribute::new) + .collect(Collectors.toList()), + this::onAttrClick)); + } - final var paramSuperclasses = widgetInfo.getLayoutParamSuperClasses(parentWidget.name); - if (paramSuperclasses != null) { - for (var superclass : paramSuperclasses) { - if ("java.lang.Object".equals(superclass)) { - continue; - } + public void setSelectedView(IView view) { + this.selectedView = view; + } - final var split = superclass.split("\\."); - final var simpleClassName = split[split.length - 2]; - var paramName = split[split.length - 1]; - paramName = paramName.substring(0, paramName.length() - "Params".length()); - final var superParamEntry = attrs.getStyle(simpleClassName + "_" + paramName); - if (superParamEntry != null) { - attributes.addAll(superParamEntry.attributes); - } + private void onAttrClick( + LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) { + + if (this.selectedView == null) { + LOG.error("Cannot edit attributes of a null view."); + return; + } + + final var format = attribute.findFormat(); + if (format == -1) { + StudioApp.getInstance() + .toast(getString(R.string.msg_no_attr_format), Toaster.Type.ERROR); + LOG.error(getString(R.string.msg_no_attr_format), attribute); + return; + } + + showEditorDialog(attribute); + } + + private void hideValueEditorLayout() { + TransitionManager.beginDelayedTransition(binding.getRoot()); + binding.valueEditorLayout.getRoot().setVisibility(View.GONE); + } + + private void showEditorDialog(@NonNull XMLAttribute attribute) { + final var attr = attribute.getAttr(); + final var values = attr.possibleValues.toArray(new String[0]); + final AttributeDialogs.OnClickListener onDone = + (dialog, which, newValue) -> applyNewValue(attribute, newValue); + + final ColorPickerView.OnPickListener mPickListener = + (color, hexCode) -> applyNewValue(attribute, hexCode); + + AlertDialog dialog = null; + if (attribute.hasFormat(DIMENSION)) { + dialog = AttributeDialogs.dimensionEditor(attribute.getValue(), onDone); + } else if (attribute.hasFormat(STRING)) { + dialog = AttributeDialogs.stringEditor(onDone); + } else if (attribute.hasFormat(BOOLEAN)) { + dialog = booleanEditor(onDone); + } else if (attribute.hasFormat(ENUM)) { + dialog = enumEditor(values, attribute.getValue(), onDone); + } else if (attribute.hasFormat(FLAG)) { + dialog = flagEditor(values, attribute.getValue(), onDone); + } else if (attribute.hasFormat(COLOR)) { + // TODO Implement something to allow values from colors.xml or ColorStateLists + // Make this color picker optional + dialog = colorPicker(mPickListener); + } + + if (dialog != null) { + dialog.show(); + } + } + + private void applyNewValue(@NonNull IAttribute attribute, String newValue) { + if (this.selectedView.hasAttribute( + attribute.getNamespace(), attribute.getAttributeName())) { + if (!this.selectedView.updateAttribute( + attribute.getNamespace(), attribute.getAttributeName(), newValue)) { + StudioApp.getInstance() + .toast(getString(R.string.msg_attr_not_updated), Toaster.Type.ERROR); + } else { + // Update the view data + // This will make sure that the attributes list has been updated + setupViewData(); } - } + } else { + addAttribute( + new UiAttribute( + attribute.getNamespace(), attribute.getAttributeName(), newValue)); } - } - - final var sheet = getAttrListSheet(); - sheet.setItems(filterAppliedAttributes(attributes)); - sheet.show(getChildFragmentManager(), "attr_list_sheet"); - - } else if (position == 1) { // Delete - DialogUtils.newYesNoDialog( - getContext(), - (dialog, which) -> { - var handled = selectedView.removeFromParent(); - if (!handled) { - handled = - mDeletionFailedListener != null - && mDeletionFailedListener.onDeletionFailed(this.selectedView); - } - if (!handled) { - StudioApp.getInstance() - .toast(getString(R.string.msg_view_deletion_failed), Toaster.Type.ERROR); - } else { - dismiss(); - } - }, - (dialog, which) -> dialog.dismiss()) - .show(); - } else if (position == 2) { // Select parent - if (this.selectedView.getParent() == null) { - StudioApp.getInstance().toast(getString(R.string.msg_no_view_parent), Toaster.Type.ERROR); - return; - } - - this.selectedView = this.selectedView.getParent(); - - TransitionManager.beginDelayedTransition(binding.getRoot()); - setupViewData(); - } - } - - @NonNull - @Contract("_ -> new") - private List filterAppliedAttributes(@NonNull TreeSet attributes) { - attributes.removeIf(attr -> this.selectedView.hasAttribute(attr.namespace, attr.name)); - return new ArrayList<>(attributes); - } - - @NonNull - private AttributeListSheet getAttrListSheet() { - if (mAttrListSheet == null) { - mAttrListSheet = new AttributeListSheet(); - mAttrListSheet.onItemClick(this::addNewAttribute); - mAttrListSheet.setCancelable(true); } - return mAttrListSheet; - } + private void addAttribute(@NonNull IAttribute attribute) { + this.selectedView.addAttribute(attribute); + setupViewData(); + StudioApp.getInstance().toast(getString(R.string.msg_attr_added), Toaster.Type.SUCCESS); + } - private void addNewAttribute(@NonNull Attr attr) { - final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false); - attribute.setAttr(attr); - showEditorDialog(attribute); - } + @Override + public void postBind( + IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) { + final var binding = holder.binding; + binding.getRoot().setOnClickListener(v -> onViewActionClick(position)); + } - /** - * A listener can be used to get notified when we fail to remove a view from its parent. This is - * used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}. - * - *

When the user tries to remove the outermost view of the inflated layout, AttrEditorSheet - * fails to remove the view from its parent ({@link IView#getParent()} is null for root XML - * layout). In this case, DesignerActivity check if the view that we were trying to delete is the - * root layout or not. If it is the root layout, then it deletes the layout from the layout - * container. - * - * @author Akash Yadav - */ - public interface OnViewDeletionFailedListener { + private void onViewActionClick(int position) { + if (this.selectedView == null) { + return; + } + + if (position == 0) { // Add attribute + var tag = this.selectedView.getXmlTag(); + if ("include".equals(tag) || "merge".equals(tag)) { + tag = "View"; + } + + final var attrs = StudioApp.getInstance().attrInfo(); + final var style = attrs.getStyle(tag); + if (style == null) { + LOG.error("Unable to retrieve attributes for tag:", tag); + return; + } + + final var attributes = new TreeSet(Comparator.comparing(attr -> attr.name)); + attributes.addAll(style.attributes); + + final var widgetInfo = StudioApp.getInstance().widgetInfo(); + final var widget = widgetInfo.getWidgetBySimpleName(tag); + if (widget != null) { + for (var superclass : widget.superclasses) { + if ("java.lang.Object".equals(superclass)) { + break; + } + + final var simpleName = superclass.substring(superclass.lastIndexOf(".") + 1); + final var superStyle = attrs.getStyle(simpleName); + if (superStyle != null) { + attributes.addAll(superStyle.attributes); + } + } + } + + final var parent = this.selectedView.getParent(); + if (parent != null) { + final var parentTag = parent.getXmlTag(); + final var parentWidget = widgetInfo.getWidgetBySimpleName(parentTag); + if (parentWidget != null) { + final var parentLayoutParams = attrs.getStyle(parentTag + "_Layout"); + if (parentLayoutParams != null) { + attributes.addAll(parentLayoutParams.attributes); + } + + final var paramSuperclasses = + widgetInfo.getLayoutParamSuperClasses(parentWidget.name); + if (paramSuperclasses != null) { + for (var superclass : paramSuperclasses) { + if ("java.lang.Object".equals(superclass)) { + continue; + } + + final var split = superclass.split("\\."); + final var simpleClassName = split[split.length - 2]; + var paramName = split[split.length - 1]; + paramName = + paramName.substring(0, paramName.length() - "Params".length()); + final var superParamEntry = + attrs.getStyle(simpleClassName + "_" + paramName); + if (superParamEntry != null) { + attributes.addAll(superParamEntry.attributes); + } + } + } + } + } + + final var sheet = getAttrListSheet(); + sheet.setItems(filterAppliedAttributes(attributes)); + sheet.show(getChildFragmentManager(), "attr_list_sheet"); + + } else if (position == 1) { // Delete + DialogUtils.newYesNoDialog( + getContext(), + (dialog, which) -> { + var handled = selectedView.removeFromParent(); + if (!handled) { + handled = + mDeletionFailedListener != null + && mDeletionFailedListener.onDeletionFailed( + this.selectedView); + } + if (!handled) { + StudioApp.getInstance() + .toast( + getString(R.string.msg_view_deletion_failed), + Toaster.Type.ERROR); + } else { + dismiss(); + } + }, + (dialog, which) -> dialog.dismiss()) + .show(); + } else if (position == 2) { // Select parent + if (this.selectedView.getParent() == null) { + StudioApp.getInstance() + .toast(getString(R.string.msg_no_view_parent), Toaster.Type.ERROR); + return; + } + + this.selectedView = this.selectedView.getParent(); + + TransitionManager.beginDelayedTransition(binding.getRoot()); + setupViewData(); + } + } + + @NonNull + @Contract("_ -> new") + private List filterAppliedAttributes(@NonNull TreeSet attributes) { + attributes.removeIf(attr -> this.selectedView.hasAttribute(attr.namespace, attr.name)); + return new ArrayList<>(attributes); + } + + @NonNull + private AttributeListSheet getAttrListSheet() { + if (mAttrListSheet == null) { + mAttrListSheet = new AttributeListSheet(); + mAttrListSheet.onItemClick(this::addNewAttribute); + mAttrListSheet.setCancelable(true); + } + + return mAttrListSheet; + } + + private void addNewAttribute(@NonNull Attr attr) { + final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false); + attribute.setAttr(attr); + showEditorDialog(attribute); + } /** - * @param view The view that was not removed from its parent. - * @return {@code true} if the listener handled the error. {@code false} otherwise. + * A listener can be used to get notified when we fail to remove a view from its parent. This is + * used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}. + * + *

When the user tries to remove the outermost view of the inflated layout, AttrEditorSheet + * fails to remove the view from its parent ({@link IView#getParent()} is null for root XML + * layout). In this case, DesignerActivity check if the view that we were trying to delete is + * the root layout or not. If it is the root layout, then it deletes the layout from the layout + * container. + * + * @author Akash Yadav */ - boolean onDeletionFailed(IView view); - } + public interface OnViewDeletionFailedListener { + + /** + * @param view The view that was not removed from its parent. + * @return {@code true} if the listener handled the error. {@code false} otherwise. + */ + boolean onDeletionFailed(IView view); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttributeListSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttributeListSheet.java index 4f2d4cfec..1cbe749ea 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttributeListSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttributeListSheet.java @@ -22,15 +22,18 @@ 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 androidx.appcompat.widget.SearchView; import androidx.fragment.app.DialogFragment; + import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.itsaky.androidide.R; import com.itsaky.androidide.adapters.AttrListAdapter; import com.itsaky.androidide.databinding.LayoutAddAttrSheetBinding; import com.itsaky.attrinfo.models.Attr; + import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -45,81 +48,84 @@ import java.util.stream.Collectors; */ public class AttributeListSheet extends BottomSheetDialogFragment { - private LayoutAddAttrSheetBinding binding; - private Consumer clickConsumer; - private List mItems; + private LayoutAddAttrSheetBinding binding; + private Consumer clickConsumer; + private List mItems; - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard); - } - - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = LayoutAddAttrSheetBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - binding.searchBar.setOnQueryTextListener( - new SearchView.OnQueryTextListener() { - - @Override - public boolean onQueryTextSubmit(String query) { - return filterAttributes(query.trim()); - } - - @Override - public boolean onQueryTextChange(String newText) { - return filterAttributes(newText.trim()); - } - }); - binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, mItems)); - } - - private boolean filterAttributes(@NonNull String query) { - if (mItems == null || TextUtils.isEmpty(query)) { - update(); - return true; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard); } - final var filtered = - this.mItems.stream() - .filter( - attr -> attr.name.toLowerCase(Locale.ROOT).contains(query.toLowerCase(Locale.ROOT))) - .collect(Collectors.toList()); - - update(filtered); - return true; - } - - public void setItems(@Nullable List items) { - if (items == null) { - items = new ArrayList<>(); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = LayoutAddAttrSheetBinding.inflate(inflater, container, false); + return binding.getRoot(); } - mItems = items; - update(); - } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.searchBar.setOnQueryTextListener( + new SearchView.OnQueryTextListener() { - public void onItemClick(Consumer onClick) { - this.clickConsumer = onClick; - } + @Override + public boolean onQueryTextSubmit(String query) { + return filterAttributes(query.trim()); + } - public void update() { - update(mItems); - } - - public void update(List items) { - if (binding != null) { - binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, items)); + @Override + public boolean onQueryTextChange(String newText) { + return filterAttributes(newText.trim()); + } + }); + binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, mItems)); + } + + private boolean filterAttributes(@NonNull String query) { + if (mItems == null || TextUtils.isEmpty(query)) { + update(); + return true; + } + + final var filtered = + this.mItems.stream() + .filter( + attr -> + attr.name + .toLowerCase(Locale.ROOT) + .contains(query.toLowerCase(Locale.ROOT))) + .collect(Collectors.toList()); + + update(filtered); + return true; + } + + public void setItems(@Nullable List items) { + if (items == null) { + items = new ArrayList<>(); + } + + mItems = items; + update(); + } + + public void onItemClick(Consumer onClick) { + this.clickConsumer = onClick; + } + + public void update() { + update(mItems); + } + + public void update(List items) { + if (binding != null) { + binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, items)); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java index 83fd82c13..ad19dc04a 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java @@ -23,89 +23,91 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; + import androidx.annotation.NonNull; + import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.itsaky.androidide.databinding.LayoutSheetBinding; public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment { - protected Dialog mDialog; - protected boolean shadowEnabled = true; - protected boolean titleEnabled = true; - private LayoutSheetBinding binding; + protected Dialog mDialog; + protected boolean shadowEnabled = true; + protected boolean titleEnabled = true; + private LayoutSheetBinding binding; - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - mDialog = super.onCreateDialog(savedInstanceState); - mDialog.setOnShowListener(p1 -> onShow()); - return mDialog; - } - - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = LayoutSheetBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - bind(binding.container); - binding.title.setText(getTitle()); - binding.title.setOnClickListener(v -> handleTitleClick()); - - if (shouldHideTitle()) { - binding.getRoot().removeView(binding.title); + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + mDialog = super.onCreateDialog(savedInstanceState); + mDialog.setOnShowListener(p1 -> onShow()); + return mDialog; } - if (!shadowEnabled) { - binding.shadow.setVisibility(View.GONE); + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = LayoutSheetBinding.inflate(inflater, container, false); + return binding.getRoot(); } - } - private void handleTitleClick() { - if (isCancelable()) { - dismiss(); + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + bind(binding.container); + binding.title.setText(getTitle()); + binding.title.setOnClickListener(v -> handleTitleClick()); + + if (shouldHideTitle()) { + binding.getRoot().removeView(binding.title); + } + + if (!shadowEnabled) { + binding.shadow.setVisibility(View.GONE); + } } - } - public boolean isShowing() { - return mDialog != null && mDialog.isShowing(); - } - - protected boolean shouldHideTitle() { - return !titleEnabled; - } - - protected String getTitle() { - return ""; - } - - public BaseBottomSheetFragment setTitle(int title) { - if (binding != null) { - binding.title.setText(title); + private void handleTitleClick() { + if (isCancelable()) { + dismiss(); + } } - return this; - } - public BaseBottomSheetFragment setTitle(String title) { - if (binding != null) { - binding.title.setText(title); + public boolean isShowing() { + return mDialog != null && mDialog.isShowing(); } - return this; - } - public void setShowShadow(boolean enabled) { - this.shadowEnabled = enabled; - } + protected boolean shouldHideTitle() { + return !titleEnabled; + } - public void setShowTitle(boolean enabled) { - this.titleEnabled = enabled; - } + protected String getTitle() { + return ""; + } - protected void onShow() {} + public BaseBottomSheetFragment setTitle(int title) { + if (binding != null) { + binding.title.setText(title); + } + return this; + } - protected abstract void bind(LinearLayout container); + public BaseBottomSheetFragment setTitle(String title) { + if (binding != null) { + binding.title.setText(title); + } + return this; + } + + public void setShowShadow(boolean enabled) { + this.shadowEnabled = enabled; + } + + public void setShowTitle(boolean enabled) { + this.titleEnabled = enabled; + } + + protected void onShow() {} + + protected abstract void bind(LinearLayout container); } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java index 9d96e907b..a889bef26 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java @@ -23,75 +23,78 @@ package com.itsaky.androidide.fragments.sheets; import android.os.Bundle; import android.view.View; import android.widget.LinearLayout; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.adapters.OptionsSheetAdapter; import com.itsaky.androidide.models.SheetOption; + import java.util.ArrayList; import java.util.List; public class OptionsListFragment extends BaseBottomSheetFragment { - private RecyclerView mList; - private OnOptionsClickListener listener; - private final List mOptions = new ArrayList<>(); + private RecyclerView mList; + private OnOptionsClickListener listener; + private final List mOptions = new ArrayList<>(); - protected boolean dismissOnItemClick = true; + protected boolean dismissOnItemClick = true; - @Override - protected void bind(LinearLayout container) { - mList = new RecyclerView(getContext()); - container.removeAllViews(); - container.addView(mList, new LinearLayout.LayoutParams(-1, -1)); - } - - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mList.setLayoutManager(new LinearLayoutManager(getContext())); - mList.setAdapter( - new OptionsSheetAdapter( - mOptions, - __ -> { - if (dismissOnItemClick) dismiss(); - if (listener != null) listener.onOptionsClick(__); - })); - } - - public OptionsListFragment setOnOptionsClickListener(OnOptionsClickListener listener) { - this.listener = listener; - return this; - } - - public OptionsListFragment addOption(SheetOption option) { - if (!mOptions.contains(option)) { - mOptions.add(option); + @Override + protected void bind(LinearLayout container) { + mList = new RecyclerView(getContext()); + container.removeAllViews(); + container.addView(mList, new LinearLayout.LayoutParams(-1, -1)); } - return this; - } + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mList.setLayoutManager(new LinearLayoutManager(getContext())); + mList.setAdapter( + new OptionsSheetAdapter( + mOptions, + __ -> { + if (dismissOnItemClick) dismiss(); + if (listener != null) listener.onOptionsClick(__); + })); + } - public OptionsListFragment removeOption(int optionIndex) { - return removeOption(mOptions.get(optionIndex)); - } + public OptionsListFragment setOnOptionsClickListener(OnOptionsClickListener listener) { + this.listener = listener; + return this; + } - public OptionsListFragment removeOption(SheetOption option) { - mOptions.remove(option); - return this; - } + public OptionsListFragment addOption(SheetOption option) { + if (!mOptions.contains(option)) { + mOptions.add(option); + } - public OptionsListFragment setDismissOnItemClick(boolean dissmiss) { - this.dismissOnItemClick = dissmiss; - return this; - } + return this; + } - @Override - protected String getTitle() { - return getString(com.itsaky.androidide.R.string.file_options); - } + public OptionsListFragment removeOption(int optionIndex) { + return removeOption(mOptions.get(optionIndex)); + } - public static interface OnOptionsClickListener { - void onOptionsClick(SheetOption option); - } + public OptionsListFragment removeOption(SheetOption option) { + mOptions.remove(option); + return this; + } + + public OptionsListFragment setDismissOnItemClick(boolean dissmiss) { + this.dismissOnItemClick = dissmiss; + return this; + } + + @Override + protected String getTitle() { + return getString(com.itsaky.androidide.R.string.file_options); + } + + public static interface OnOptionsClickListener { + void onOptionsClick(SheetOption option); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java index 61a823614..96adebc21 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java @@ -26,103 +26,105 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.RelativeLayout; + import androidx.annotation.NonNull; + import com.itsaky.androidide.databinding.LayoutProgressSheetBinding; import com.itsaky.androidide.utils.Logger; public class ProgressSheet extends BaseBottomSheetFragment { - private LayoutProgressSheetBinding binding; - private Drawable drawable = null; - private String message = ""; - private String subMessage = ""; - private boolean subMessageEnabled = false; - private boolean welcomTextEnabled = false; + private LayoutProgressSheetBinding binding; + private Drawable drawable = null; + private String message = ""; + private String subMessage = ""; + private boolean subMessageEnabled = false; + private boolean welcomTextEnabled = false; - private final Logger LOG = Logger.instance("ProgressSheet"); + private final Logger LOG = Logger.instance("ProgressSheet"); - @Override - protected void bind(LinearLayout container) { - binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext())); - container.addView(binding.getRoot()); - } - - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - binding.message.setText(message); - if (subMessageEnabled) { - binding.subMessage.setText(subMessage); - binding.subMessage.setVisibility(View.VISIBLE); - RelativeLayout.LayoutParams p = - (RelativeLayout.LayoutParams) binding.message.getLayoutParams(); - try { - p.removeRule(RelativeLayout.CENTER_VERTICAL); - } catch (Throwable th) { - LOG.error("Unable to remove center_vertical rule.", th); - } - binding.message.setLayoutParams(p); - } else { - binding.subMessage.setVisibility(View.GONE); - RelativeLayout.LayoutParams p = - (RelativeLayout.LayoutParams) binding.message.getLayoutParams(); - try { - p.addRule(RelativeLayout.CENTER_VERTICAL); - } catch (Throwable th) { - LOG.error("Unable to remove center_vertical rule.", th); - } - binding.message.setLayoutParams(p); + @Override + protected void bind(LinearLayout container) { + binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext())); + container.addView(binding.getRoot()); } - if (!welcomTextEnabled) { - binding.welcomeText.setVisibility(View.GONE); - } - } + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); - @Override - protected boolean shouldHideTitle() { - return true; - } + binding.message.setText(message); + if (subMessageEnabled) { + binding.subMessage.setText(subMessage); + binding.subMessage.setVisibility(View.VISIBLE); + RelativeLayout.LayoutParams p = + (RelativeLayout.LayoutParams) binding.message.getLayoutParams(); + try { + p.removeRule(RelativeLayout.CENTER_VERTICAL); + } catch (Throwable th) { + LOG.error("Unable to remove center_vertical rule.", th); + } + binding.message.setLayoutParams(p); + } else { + binding.subMessage.setVisibility(View.GONE); + RelativeLayout.LayoutParams p = + (RelativeLayout.LayoutParams) binding.message.getLayoutParams(); + try { + p.addRule(RelativeLayout.CENTER_VERTICAL); + } catch (Throwable th) { + LOG.error("Unable to remove center_vertical rule.", th); + } + binding.message.setLayoutParams(p); + } - public ProgressSheet setWelcomeTextEnabled(boolean enabled) { - this.welcomTextEnabled = enabled; - return this; - } - - public ProgressSheet setSubMessageEnabled(boolean enabled) { - this.subMessageEnabled = enabled; - return this; - } - - public ProgressSheet setSubMessage(String msg) { - this.subMessage = msg; - if (isShowing()) { - binding.subMessage.setText(msg); - } - return this; - } - - public ProgressSheet setMessage(String message) { - this.message = message; - if (isShowing()) { - binding.message.setText(message); + if (!welcomTextEnabled) { + binding.welcomeText.setVisibility(View.GONE); + } } - return this; - } - - public ProgressSheet setProgressDrawable(Drawable drawable) { - this.drawable = drawable; - if (isShowing()) { - binding.progress.setIndeterminateDrawable(drawable); + @Override + protected boolean shouldHideTitle() { + return true; } - return this; - } + public ProgressSheet setWelcomeTextEnabled(boolean enabled) { + this.welcomTextEnabled = enabled; + return this; + } - @Override - public void dismiss() { - if (isShowing()) super.dismiss(); - } + public ProgressSheet setSubMessageEnabled(boolean enabled) { + this.subMessageEnabled = enabled; + return this; + } + + public ProgressSheet setSubMessage(String msg) { + this.subMessage = msg; + if (isShowing()) { + binding.subMessage.setText(msg); + } + return this; + } + + public ProgressSheet setMessage(String message) { + this.message = message; + if (isShowing()) { + binding.message.setText(message); + } + + return this; + } + + public ProgressSheet setProgressDrawable(Drawable drawable) { + this.drawable = drawable; + if (isShowing()) { + binding.progress.setIndeterminateDrawable(drawable); + } + + return this; + } + + @Override + public void dismiss() { + if (isShowing()) super.dismiss(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java index b7e6a2da9..fcd82e62f 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java @@ -22,112 +22,116 @@ import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; import android.view.ViewGroup; import android.widget.LinearLayout; + import androidx.annotation.NonNull; + import com.itsaky.androidide.R; import com.itsaky.androidide.utils.TypefaceUtils; import com.itsaky.androidide.views.editor.IDEEditor; + import io.github.rosemoe.sora.lang.EmptyLanguage; public class TextSheetFragment extends BaseBottomSheetFragment { - private SpannableStringBuilder outputBuilder; - private IDEEditor editor; - private boolean textSelectable; + private SpannableStringBuilder outputBuilder; + private IDEEditor editor; + private boolean textSelectable; - public TextSheetFragment setTextSelectable(boolean textSelectable) { - this.textSelectable = textSelectable; - return this; - } - - @Override - protected void bind(@NonNull LinearLayout container) { - final IDEEditor e = getEditor(); - container.setPadding(0, 0, 0, 0); - container.setPaddingRelative(0, 0, 0, 0); - LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(-1, 0); - p.weight = 1.0f; - if (e.getParent() != null && e.getParent() instanceof ViewGroup) { - ((ViewGroup) e.getParent()).removeView(e); + public TextSheetFragment setTextSelectable(boolean textSelectable) { + this.textSelectable = textSelectable; + return this; } - container.addView(getEditor(), p); - } - @Override - protected String getTitle() { - return getString(R.string.build_output); - } - - @Override - protected void onShow() { - super.onShow(); - editor.setText(getOutputBuilder()); - } - - private void scrollToBottom() { - if (getActivity() != null) getActivity().runOnUiThread(() -> editor.goToEnd()); - } - - public TextSheetFragment setTitleText(int res) { - super.setTitle(res); - return this; - } - - public TextSheetFragment setTitleText(String res) { - super.setTitle(res); - return this; - } - - public void append(String text) { - append(text, -1, true); - } - - public void append(String text, int spanColor, boolean appendLine) { - if (text != null && text.trim().length() > 0) { - appendInternal(appendLine && !text.endsWith("\n") ? text.concat("\n") : text, spanColor); + @Override + protected void bind(@NonNull LinearLayout container) { + final IDEEditor e = getEditor(); + container.setPadding(0, 0, 0, 0); + container.setPaddingRelative(0, 0, 0, 0); + LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(-1, 0); + p.weight = 1.0f; + if (e.getParent() != null && e.getParent() instanceof ViewGroup) { + ((ViewGroup) e.getParent()).removeView(e); + } + container.addView(getEditor(), p); } - } - private void appendInternal(String text, int spanColor) { - SpannableString str = new SpannableString(text); - str.setSpan( - new ForegroundColorSpan(spanColor == -1 ? Color.WHITE : spanColor), - 0, - text.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - getOutputBuilder().append(str); - if (mDialog != null && mDialog.isShowing()) { - setToEditor(str); + @Override + protected String getTitle() { + return getString(R.string.build_output); } - } - private void setToEditor(SpannableString line) { - if (getActivity() != null) - getActivity() - .runOnUiThread( - () -> { - editor.append(line); - scrollToBottom(); - }); - } + @Override + protected void onShow() { + super.onShow(); + editor.setText(getOutputBuilder()); + } - private IDEEditor getEditor() { - return editor == null ? createEditor() : editor; - } + private void scrollToBottom() { + if (getActivity() != null) getActivity().runOnUiThread(() -> editor.goToEnd()); + } - private IDEEditor createEditor() { - editor = new IDEEditor(getContext()); - editor.setEditable(false); - editor.setDividerWidth(0); - editor.setEditorLanguage(new EmptyLanguage()); - editor.setWordwrap(false); - editor.setUndoEnabled(false); - editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono()); - editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); - editor.setTextSize(12); - return editor; - } + public TextSheetFragment setTitleText(int res) { + super.setTitle(res); + return this; + } - private SpannableStringBuilder getOutputBuilder() { - return outputBuilder == null ? outputBuilder = new SpannableStringBuilder() : outputBuilder; - } + public TextSheetFragment setTitleText(String res) { + super.setTitle(res); + return this; + } + + public void append(String text) { + append(text, -1, true); + } + + public void append(String text, int spanColor, boolean appendLine) { + if (text != null && text.trim().length() > 0) { + appendInternal( + appendLine && !text.endsWith("\n") ? text.concat("\n") : text, spanColor); + } + } + + private void appendInternal(String text, int spanColor) { + SpannableString str = new SpannableString(text); + str.setSpan( + new ForegroundColorSpan(spanColor == -1 ? Color.WHITE : spanColor), + 0, + text.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + getOutputBuilder().append(str); + if (mDialog != null && mDialog.isShowing()) { + setToEditor(str); + } + } + + private void setToEditor(SpannableString line) { + if (getActivity() != null) + getActivity() + .runOnUiThread( + () -> { + editor.append(line); + scrollToBottom(); + }); + } + + private IDEEditor getEditor() { + return editor == null ? createEditor() : editor; + } + + private IDEEditor createEditor() { + editor = new IDEEditor(getContext()); + editor.setEditable(false); + editor.setDividerWidth(0); + editor.setEditorLanguage(new EmptyLanguage()); + editor.setWordwrap(false); + editor.setUndoEnabled(false); + editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono()); + editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); + editor.setTextSize(12); + return editor; + } + + private SpannableStringBuilder getOutputBuilder() { + return outputBuilder == null ? outputBuilder = new SpannableStringBuilder() : outputBuilder; + } } diff --git a/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java b/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java index c537b5c9b..ac8ee107a 100755 --- a/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java +++ b/app/src/main/java/com/itsaky/androidide/handlers/BuildServiceHandler.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.handlers; import android.view.View; + import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.itsaky.androidide.R; import com.itsaky.androidide.managers.PreferenceManager; @@ -30,6 +31,7 @@ import com.itsaky.androidide.tasks.GradleTask; import com.itsaky.androidide.tasks.gradle.build.ApkGeneratingTask; import com.itsaky.androidide.utils.DialogUtils; import com.itsaky.androidide.utils.Environment; + import java.io.File; import java.util.ArrayList; import java.util.Collection; @@ -40,210 +42,217 @@ import java.util.stream.Collectors; public class BuildServiceHandler extends IDEHandler implements BuildListener { - private IDEService service; - private boolean shouldInstallApk = false; + private IDEService service; + private boolean shouldInstallApk = false; - public BuildServiceHandler(Provider provider) { - super(provider); - } - - public IDEService getService() { - return service; - } - - @Override - public void start() { - if (this.activity() == null) { - throwNPE(); + public BuildServiceHandler(Provider provider) { + super(provider); } - AndroidProject project = provider.provideAndroidProject(); - if (project == null) { - throwNPE(); + public IDEService getService() { + return service; } - if (project.getProjectPath() == null) { - throwNPE(); - } - - this.service = new IDEService(new File(project.getProjectPath())); - this.service.setListener(this); - } - - @Override - public void stop() { - this.service.stopAllDaemons(); - this.service.exit(); - } - - @Override - public void onBuildModified() { - // Does nothing currently - } - - @Override - public void onProjectLoaded(IDEProject project, Optional appModule) { - project.iconPath = provider.provideAndroidProject().getIconPath(); - activity().setIDEProject(project); - - if (appModule.isPresent()) { - IDEModule app = appModule.get(); - activity().setStatus(activity().getString(R.string.msg_starting_completion)); - - final List paths = - app.dependencies.stream().filter(this::isClasspathValid).collect(Collectors.toList()); - - provider.provideAndroidProject().setClassPaths(paths); - - File androidJar = - new File( - Environment.ANDROID_HOME, - String.format("platforms/%s/android.jar", app.compileSdkVersion)); - if (androidJar.exists()) { - Environment.setBootClasspath(androidJar); - provider.provideAndroidProject().addClasspath(androidJar.getAbsolutePath()); - activity().updateServices(); - } else activity().setStatus("android.jar not found!"); - } else activity().setStatus("Cannot get :app module..."); - } - - @Override - public void onStartingGradleDaemon(GradleTask task) { - activity().setStatus(activity().getString(R.string.msg_starting_daemon)); - } - - @Override - public void onRunTask(GradleTask task, String name) { - if (task != null && task.canOutput()) { - activity().setStatus(service.typeString(task.getTaskID()) + " " + name); - } - } - - @Override - public void onBuildSuccessful(GradleTask task, String msg) { - appendOutput(task, msg); - if (task == null || activity() == null) return; - if (task.canOutput()) { - activity().setStatus(msg); - } - - // If this task generates APK, ask user if he/she wants to install them - if (shouldInstallApk && task instanceof ApkGeneratingTask) { - final IDEProject project = activity().provideIDEProject(); - final Optional app = - project.getModuleByPath(":app"); // TODO Handle multiple application modules - if (app.isPresent()) { - final String path = app.get().projectDir; - if (path != null) { - final File buildDir = new File(path, "build"); - installApks(((ApkGeneratingTask) task).getApks(buildDir)); + @Override + public void start() { + if (this.activity() == null) { + throwNPE(); } - } + + AndroidProject project = provider.provideAndroidProject(); + if (project == null) { + throwNPE(); + } + + if (project.getProjectPath() == null) { + throwNPE(); + } + + this.service = new IDEService(new File(project.getProjectPath())); + this.service.setListener(this); } - activity() - .getApp() - .getPrefManager() - .putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false); - activity().invalidateOptionsMenu(); - activity().getBinding().buildProgressIndicator.setVisibility(View.GONE); - } - - @Override - public void onBuildFailed(GradleTask task, String msg) { - appendOutput(task, msg); - if (task == null) return; - if (task.canOutput()) { - activity().setStatus(msg); + @Override + public void stop() { + this.service.stopAllDaemons(); + this.service.exit(); } - activity() - .getApp() - .getPrefManager() - .putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false); - activity().invalidateOptionsMenu(); - activity().getBinding().buildProgressIndicator.setVisibility(View.GONE); - } - - @Override - public void saveFiles() { - activity().saveAll(false, false); - } - - @Override - public void appendOutput(GradleTask task, CharSequence text) { - if (text == null) return; - if (task != null && !task.canOutput()) { - return; + @Override + public void onBuildModified() { + // Does nothing currently } - activity().appendBuildOut(text.toString()); - } + @Override + public void onProjectLoaded(IDEProject project, Optional appModule) { + project.iconPath = provider.provideAndroidProject().getIconPath(); + activity().setIDEProject(project); + + if (appModule.isPresent()) { + IDEModule app = appModule.get(); + activity().setStatus(activity().getString(R.string.msg_starting_completion)); + + final List paths = + app.dependencies.stream() + .filter(this::isClasspathValid) + .collect(Collectors.toList()); + + provider.provideAndroidProject().setClassPaths(paths); + + File androidJar = + new File( + Environment.ANDROID_HOME, + String.format("platforms/%s/android.jar", app.compileSdkVersion)); + if (androidJar.exists()) { + Environment.setBootClasspath(androidJar); + provider.provideAndroidProject().addClasspath(androidJar.getAbsolutePath()); + activity().updateServices(); + } else activity().setStatus("android.jar not found!"); + } else activity().setStatus("Cannot get :app module..."); + } + + @Override + public void onStartingGradleDaemon(GradleTask task) { + activity().setStatus(activity().getString(R.string.msg_starting_daemon)); + } + + @Override + public void onRunTask(GradleTask task, String name) { + if (task != null && task.canOutput()) { + activity().setStatus(service.typeString(task.getTaskID()) + " " + name); + } + } + + @Override + public void onBuildSuccessful(GradleTask task, String msg) { + appendOutput(task, msg); + if (task == null || activity() == null) return; + if (task.canOutput()) { + activity().setStatus(msg); + } + + // If this task generates APK, ask user if he/she wants to install them + if (shouldInstallApk && task instanceof ApkGeneratingTask) { + final IDEProject project = activity().provideIDEProject(); + final Optional app = + project.getModuleByPath(":app"); // TODO Handle multiple application modules + if (app.isPresent()) { + final String path = app.get().projectDir; + if (path != null) { + final File buildDir = new File(path, "build"); + installApks(((ApkGeneratingTask) task).getApks(buildDir)); + } + } + } - @Override - public void prepareBuild() { - boolean isFirstBuild = activity() - .getApp() - .getPrefManager() - .getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true); - activity() - .setStatus( - activity().getString(isFirstBuild ? R.string.preparing_first : R.string.preparing)); - if (isFirstBuild) { - activity().showFirstBuildNotice(); - } - activity().getBinding().buildProgressIndicator.setVisibility(View.VISIBLE); - } - - public void setShouldInstallApk(boolean install) { - this.shouldInstallApk = install; - } - - public void assembleDebug(boolean shouldInstallApk) { - setShouldInstallApk(shouldInstallApk); - getService().assembleDebug(shouldInstallApk); - } - - private boolean isClasspathValid(String path) { - if (path == null || path.trim().length() <= 0 || path.trim().equals("/")) return false; - File file = new File(path); - return file.isFile() - && file.getName().endsWith(".jar") - /** Release R.jar shouldn't be included in classpath */ - && !file.getAbsolutePath().endsWith("/release/R.jar"); - } - - private void installApks(final Set apks) { - if (activity() == null || apks == null || apks.isEmpty()) { - return; + .getApp() + .getPrefManager() + .putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false); + activity().invalidateOptionsMenu(); + activity().getBinding().buildProgressIndicator.setVisibility(View.GONE); } - if (apks.size() == 1) { - activity().install(apks.iterator().next()); - } else { - final List files = new ArrayList(apks); - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - builder.setTitle(activity().getString(R.string.title_install_apks)); - builder.setItems( - getNames(files), - (d, w) -> { - d.dismiss(); - activity().install(files.get(w)); - }); - builder.show(); - } - } + @Override + public void onBuildFailed(GradleTask task, String msg) { + appendOutput(task, msg); + if (task == null) return; + if (task.canOutput()) { + activity().setStatus(msg); + } - private CharSequence[] getNames(final Collection apks) { - final String[] names = new String[apks.size()]; - - int i = 0; - for (File apk : apks) { - names[i] = apk.getName(); - ++i; + activity() + .getApp() + .getPrefManager() + .putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false); + activity().invalidateOptionsMenu(); + activity().getBinding().buildProgressIndicator.setVisibility(View.GONE); } - return names; - } + @Override + public void saveFiles() { + activity().saveAll(false, false); + } + + @Override + public void appendOutput(GradleTask task, CharSequence text) { + if (text == null) return; + if (task != null && !task.canOutput()) { + return; + } + + activity().appendBuildOut(text.toString()); + } + + @Override + public void prepareBuild() { + boolean isFirstBuild = + activity() + .getApp() + .getPrefManager() + .getBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, true); + activity() + .setStatus( + activity() + .getString( + isFirstBuild + ? R.string.preparing_first + : R.string.preparing)); + if (isFirstBuild) { + activity().showFirstBuildNotice(); + } + activity().getBinding().buildProgressIndicator.setVisibility(View.VISIBLE); + } + + public void setShouldInstallApk(boolean install) { + this.shouldInstallApk = install; + } + + public void assembleDebug(boolean shouldInstallApk) { + setShouldInstallApk(shouldInstallApk); + getService().assembleDebug(shouldInstallApk); + } + + private boolean isClasspathValid(String path) { + if (path == null || path.trim().length() <= 0 || path.trim().equals("/")) return false; + File file = new File(path); + return file.isFile() + && file.getName().endsWith(".jar") + /** Release R.jar shouldn't be included in classpath */ + && !file.getAbsolutePath().endsWith("/release/R.jar"); + } + + private void installApks(final Set apks) { + if (activity() == null || apks == null || apks.isEmpty()) { + return; + } + + if (apks.size() == 1) { + activity().install(apks.iterator().next()); + } else { + final List files = new ArrayList(apks); + final MaterialAlertDialogBuilder builder = + DialogUtils.newMaterialDialogBuilder(activity()); + builder.setTitle(activity().getString(R.string.title_install_apks)); + builder.setItems( + getNames(files), + (d, w) -> { + d.dismiss(); + activity().install(files.get(w)); + }); + builder.show(); + } + } + + private CharSequence[] getNames(final Collection apks) { + final String[] names = new String[apks.size()]; + + int i = 0; + for (File apk : apks) { + names[i] = apk.getName(); + ++i; + } + + return names; + } } diff --git a/app/src/main/java/com/itsaky/androidide/handlers/FileOptionsHandler.java b/app/src/main/java/com/itsaky/androidide/handlers/FileOptionsHandler.java index 2090c0b9c..64b685ab6 100755 --- a/app/src/main/java/com/itsaky/androidide/handlers/FileOptionsHandler.java +++ b/app/src/main/java/com/itsaky/androidide/handlers/FileOptionsHandler.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.handlers; import android.view.LayoutInflater; + import com.blankj.utilcode.util.ClipboardUtils; import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; @@ -33,364 +34,411 @@ import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.ProjectWriter; import com.itsaky.toaster.Toaster; import com.unnamed.b.atv.model.TreeNode; + import java.io.File; import java.util.Objects; import java.util.regex.Pattern; public class FileOptionsHandler extends IDEHandler - implements OptionsListFragment.OnOptionsClickListener { + implements OptionsListFragment.OnOptionsClickListener { - public static final String RES_PATH_REGEX = "/.*/src/.*/res"; - public static final String LAYOUT_RES_PATH_REGEX = "/.*/src/.*/res/layout"; - public static final String MENU_RES_PATH_REGEX = "/.*/src/.*/res/menu"; - public static final String DRAWABLE_RES_PATH_REGEX = "/.*/src/.*/res/drawable"; - public static final String JAVA_PATH_REGEX = "/.*/src/.*/java"; + public static final String RES_PATH_REGEX = "/.*/src/.*/res"; + public static final String LAYOUT_RES_PATH_REGEX = "/.*/src/.*/res/layout"; + public static final String MENU_RES_PATH_REGEX = "/.*/src/.*/res/menu"; + public static final String DRAWABLE_RES_PATH_REGEX = "/.*/src/.*/res/drawable"; + public static final String JAVA_PATH_REGEX = "/.*/src/.*/java"; - public FileOptionsHandler(Provider provider) { - super(provider); - } - - @Override - public void start() { - if (activity() == null) { - throwNPE(); + public FileOptionsHandler(Provider provider) { + super(provider); } - } - @Override - public void stop() {} - - @Override - public void onOptionsClick(SheetOption option) { - if (option.extra instanceof File) { - final File f = (File) option.extra; - switch (option.id) { - case 0: - ClipboardUtils.copyText("[AndroidIDE] Copied File Path", f.getAbsolutePath()); - activity().getApp().toast(R.string.copied, Toaster.Type.SUCCESS); - break; - case 1: - renameFile(f); - break; - case 2: - delete(f); - break; - case 3: - createNewFile(f); - break; - case 4: - createNewFolder(f); - break; - } - } - } - - private void createNewFile(File f) { - createNewFile(f, false); - } - - private void createNewFile(final File f, boolean forceUnknownType) { - if (forceUnknownType) { - createNewEmptyFile(f); - } else { - final boolean isJava = - Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + JAVA_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - final boolean isRes = - Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + RES_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - final boolean isLayoutRes = - Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + LAYOUT_RES_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - final boolean isMenuRes = - Pattern.compile(Pattern.quote(androidProject().getProjectPath()) + MENU_RES_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - final boolean isDrawableRes = - Pattern.compile( - Pattern.quote(androidProject().getProjectPath()) + DRAWABLE_RES_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - if (isJava) { - createJavaClass(f); - } else if (isLayoutRes && f.getName().equals("layout")) { - createLayoutRes(f); - } else if (isMenuRes && f.getName().equals("menu")) { - createMenuRes(f); - } else if (isDrawableRes && f.getName().equals("drawable")) { - createDrawableRes(f); - } else if (isRes && f.getName().equals("res")) { - createNewResource(f); - } else { - createNewEmptyFile(f); - } - } - } - - private void createJavaClass(final File f) { - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - final LayoutCreateFileJavaBinding binding = - LayoutCreateFileJavaBinding.inflate(activity().getLayoutInflater()); - builder.setView(binding.getRoot()); - builder.setTitle(R.string.new_java_class); - builder.setPositiveButton( - R.string.text_create, - (p1, p2) -> { - p1.dismiss(); - final String name = - Objects.requireNonNull(binding.name.getEditText()).getText().toString().trim(); - final String pkgName = ProjectWriter.getPackageName(f); - if (pkgName == null || pkgName.trim().length() <= 0) { - activity().getApp().toast(R.string.msg_get_package_failed, Toaster.Type.ERROR); - } else { - final int id = binding.typeGroup.getCheckedButtonId(); - final var javaName = name.endsWith(".java") ? name : name.concat(".java"); - final var className = - !name.contains(".") ? name : name.substring(0, name.lastIndexOf(".")); - if (id == binding.typeClass.getId()) { - createFile(f, javaName, ProjectWriter.createJavaClass(pkgName, className)); - } else if (id == binding.typeInterface.getId()) { - createFile(f, javaName, ProjectWriter.createJavaInterface(pkgName, className)); - } else if (id == binding.typeEnum.getId()) { - createFile(f, javaName, ProjectWriter.createJavaEnum(pkgName, className)); - } else if (id == binding.typeActivity.getId()) { - createFile(f, javaName, ProjectWriter.createActivity(pkgName, className)); - } else { - createFile(f, name, ""); - } - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.setCancelable(false); - builder.create().show(); - } - - private void createLayoutRes(File f) { - createNewFileWithContent(Environment.mkdirIfNotExits(f), ProjectWriter.createLayout(), ".xml"); - } - - private void createMenuRes(File f) { - createNewFileWithContent(Environment.mkdirIfNotExits(f), ProjectWriter.createMenu(), ".xml"); - } - - private void createDrawableRes(File f) { - createNewFileWithContent( - Environment.mkdirIfNotExits(f), ProjectWriter.createDrawable(), ".xml"); - } - - private void createNewResource(File f) { - final String[] labels = { - activity().getString(R.string.restype_drawable), - activity().getString(R.string.restype_layout), - activity().getString(R.string.restype_menu), - activity().getString(R.string.restype_other) - }; - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - builder.setTitle(R.string.new_xml_resource); - builder.setItems( - labels, - (dialogInterface, position) -> { - if (position == 0) { - createDrawableRes(new File(f, "drawable")); - } else if (position == 1) { - createLayoutRes(new File(f, "layout")); - } else if (position == 2) { - createMenuRes(new File(f, "menu")); - } else if (position == 3) { - createNewFile(f, true); - } - }); - builder.create().show(); - } - - private void createNewEmptyFile(File f) { - createNewFileWithContent(f, ""); - } - - private void createNewFileWithContent(File f, String content) { - createNewFileWithContent(f, content, null); - } - - private void createNewFileWithContent(File folder, String content, String extension) { - final LayoutDialogTextInputBinding binding = - LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater()); - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - - Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.file_name); - - builder.setTitle(R.string.new_file); - builder.setMessage( - activity() - .getString(R.string.msg_can_contain_slashes) - .concat("\n\n") - .concat(activity().getString(R.string.msg_newfile_dest, folder.getAbsolutePath()))); - builder.setView(binding.getRoot()); - builder.setCancelable(false); - builder.setPositiveButton( - R.string.text_create, - (p1, p2) -> { - p1.dismiss(); - String name = binding.name.getEditText().getText().toString().trim(); - if (extension != null && extension.trim().length() > 0) { - name = name.endsWith(extension) ? name : name.concat(extension); - } - createFile(folder, name, content); - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.create().show(); - } - - private void createFile(File f, String name, String content) { - if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) { - final File file = new File(f, name); - if (file.exists()) { - activity().getApp().toast(R.string.msg_file_exists, Toaster.Type.ERROR); - } else { - if (FileIOUtils.writeFileFromString(file, content)) { - // TODO Notify language servers about file created event - activity().getApp().toast(R.string.msg_file_created, Toaster.Type.SUCCESS); - if (activity().getLastHoldTreeNode() != null) { - TreeNode node = new TreeNode(file); - node.setViewHolder(new FileTreeViewHolder(activity())); - activity().getLastHoldTreeNode().addChild(node); - activity().getFileTreeFragment().expandNode(activity().getLastHoldTreeNode()); - } else { - activity().getFileTreeFragment().listProjectFiles(); - } - } else { - activity().getApp().toast(R.string.msg_file_creation_failed, Toaster.Type.ERROR); + @Override + public void start() { + if (activity() == null) { + throwNPE(); } - } - } else { - activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR); } - } - private void createNewFolder(File f) { - final LayoutDialogTextInputBinding binding = - LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater()); - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + @Override + public void stop() {} - Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.folder_name); + @Override + public void onOptionsClick(SheetOption option) { + if (option.extra instanceof File) { + final File f = (File) option.extra; + switch (option.id) { + case 0: + ClipboardUtils.copyText("[AndroidIDE] Copied File Path", f.getAbsolutePath()); + activity().getApp().toast(R.string.copied, Toaster.Type.SUCCESS); + break; + case 1: + renameFile(f); + break; + case 2: + delete(f); + break; + case 3: + createNewFile(f); + break; + case 4: + createNewFolder(f); + break; + } + } + } - builder.setTitle(R.string.new_folder); - builder.setMessage(R.string.msg_can_contain_slashes); - builder.setView(binding.getRoot()); - builder.setCancelable(false); - builder.setPositiveButton( - R.string.text_create, - (p1, p2) -> { - p1.dismiss(); - final String name = binding.name.getEditText().getText().toString().trim(); - if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) { + private void createNewFile(File f) { + createNewFile(f, false); + } + + private void createNewFile(final File f, boolean forceUnknownType) { + if (forceUnknownType) { + createNewEmptyFile(f); + } else { + final boolean isJava = + Pattern.compile( + Pattern.quote(androidProject().getProjectPath()) + + JAVA_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + final boolean isRes = + Pattern.compile( + Pattern.quote(androidProject().getProjectPath()) + + RES_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + final boolean isLayoutRes = + Pattern.compile( + Pattern.quote(androidProject().getProjectPath()) + + LAYOUT_RES_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + final boolean isMenuRes = + Pattern.compile( + Pattern.quote(androidProject().getProjectPath()) + + MENU_RES_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + final boolean isDrawableRes = + Pattern.compile( + Pattern.quote(androidProject().getProjectPath()) + + DRAWABLE_RES_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + if (isJava) { + createJavaClass(f); + } else if (isLayoutRes && f.getName().equals("layout")) { + createLayoutRes(f); + } else if (isMenuRes && f.getName().equals("menu")) { + createMenuRes(f); + } else if (isDrawableRes && f.getName().equals("drawable")) { + createDrawableRes(f); + } else if (isRes && f.getName().equals("res")) { + createNewResource(f); + } else { + createNewEmptyFile(f); + } + } + } + + private void createJavaClass(final File f) { + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + final LayoutCreateFileJavaBinding binding = + LayoutCreateFileJavaBinding.inflate(activity().getLayoutInflater()); + builder.setView(binding.getRoot()); + builder.setTitle(R.string.new_java_class); + builder.setPositiveButton( + R.string.text_create, + (p1, p2) -> { + p1.dismiss(); + final String name = + Objects.requireNonNull(binding.name.getEditText()) + .getText() + .toString() + .trim(); + final String pkgName = ProjectWriter.getPackageName(f); + if (pkgName == null || pkgName.trim().length() <= 0) { + activity() + .getApp() + .toast(R.string.msg_get_package_failed, Toaster.Type.ERROR); + } else { + final int id = binding.typeGroup.getCheckedButtonId(); + final var javaName = name.endsWith(".java") ? name : name.concat(".java"); + final var className = + !name.contains(".") + ? name + : name.substring(0, name.lastIndexOf(".")); + if (id == binding.typeClass.getId()) { + createFile( + f, javaName, ProjectWriter.createJavaClass(pkgName, className)); + } else if (id == binding.typeInterface.getId()) { + createFile( + f, + javaName, + ProjectWriter.createJavaInterface(pkgName, className)); + } else if (id == binding.typeEnum.getId()) { + createFile( + f, javaName, ProjectWriter.createJavaEnum(pkgName, className)); + } else if (id == binding.typeActivity.getId()) { + createFile( + f, javaName, ProjectWriter.createActivity(pkgName, className)); + } else { + createFile(f, name, ""); + } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setCancelable(false); + builder.create().show(); + } + + private void createLayoutRes(File f) { + createNewFileWithContent( + Environment.mkdirIfNotExits(f), ProjectWriter.createLayout(), ".xml"); + } + + private void createMenuRes(File f) { + createNewFileWithContent( + Environment.mkdirIfNotExits(f), ProjectWriter.createMenu(), ".xml"); + } + + private void createDrawableRes(File f) { + createNewFileWithContent( + Environment.mkdirIfNotExits(f), ProjectWriter.createDrawable(), ".xml"); + } + + private void createNewResource(File f) { + final String[] labels = { + activity().getString(R.string.restype_drawable), + activity().getString(R.string.restype_layout), + activity().getString(R.string.restype_menu), + activity().getString(R.string.restype_other) + }; + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + builder.setTitle(R.string.new_xml_resource); + builder.setItems( + labels, + (dialogInterface, position) -> { + if (position == 0) { + createDrawableRes(new File(f, "drawable")); + } else if (position == 1) { + createLayoutRes(new File(f, "layout")); + } else if (position == 2) { + createMenuRes(new File(f, "menu")); + } else if (position == 3) { + createNewFile(f, true); + } + }); + builder.create().show(); + } + + private void createNewEmptyFile(File f) { + createNewFileWithContent(f, ""); + } + + private void createNewFileWithContent(File f, String content) { + createNewFileWithContent(f, content, null); + } + + private void createNewFileWithContent(File folder, String content, String extension) { + final LayoutDialogTextInputBinding binding = + LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater()); + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + + Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.file_name); + + builder.setTitle(R.string.new_file); + builder.setMessage( + activity() + .getString(R.string.msg_can_contain_slashes) + .concat("\n\n") + .concat( + activity() + .getString( + R.string.msg_newfile_dest, + folder.getAbsolutePath()))); + builder.setView(binding.getRoot()); + builder.setCancelable(false); + builder.setPositiveButton( + R.string.text_create, + (p1, p2) -> { + p1.dismiss(); + String name = binding.name.getEditText().getText().toString().trim(); + if (extension != null && extension.trim().length() > 0) { + name = name.endsWith(extension) ? name : name.concat(extension); + } + createFile(folder, name, content); + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.create().show(); + } + + private void createFile(File f, String name, String content) { + if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) { final File file = new File(f, name); if (file.exists()) { - activity().getApp().toast(R.string.msg_folder_exists, Toaster.Type.ERROR); + activity().getApp().toast(R.string.msg_file_exists, Toaster.Type.ERROR); } else { - if (file.mkdirs()) { - activity().getApp().toast(R.string.msg_folder_created, Toaster.Type.SUCCESS); - if (activity().getLastHoldTreeNode() != null) { - TreeNode node = new TreeNode(file); - node.setViewHolder(new FileTreeViewHolder(activity())); - activity().getLastHoldTreeNode().addChild(node); - activity().getFileTreeFragment().expandNode(activity().getLastHoldTreeNode()); + if (FileIOUtils.writeFileFromString(file, content)) { + // TODO Notify language servers about file created event + activity().getApp().toast(R.string.msg_file_created, Toaster.Type.SUCCESS); + if (activity().getLastHoldTreeNode() != null) { + TreeNode node = new TreeNode(file); + node.setViewHolder(new FileTreeViewHolder(activity())); + activity().getLastHoldTreeNode().addChild(node); + activity() + .getFileTreeFragment() + .expandNode(activity().getLastHoldTreeNode()); + } else { + activity().getFileTreeFragment().listProjectFiles(); + } } else { - activity().getFileTreeFragment().listProjectFiles(); + activity() + .getApp() + .toast(R.string.msg_file_creation_failed, Toaster.Type.ERROR); } - } else { - activity().getApp().toast(R.string.msg_folder_creation_failed, Toaster.Type.ERROR); - } } - } else { + } else { activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.create().show(); - } + } + } - private void delete(final File f) { - MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - builder - .setNegativeButton(R.string.no, null) - .setPositiveButton( - R.string.yes, - (p1, p2) -> { - p1.dismiss(); - final boolean deleted = FileUtils.delete(f); - activity() - .getApp() - .toast( - deleted ? R.string.deleted : R.string.delete_failed, - deleted ? Toaster.Type.SUCCESS : Toaster.Type.ERROR); - if (deleted) { - // TODO Notify language servers about file delete event - if (activity().getLastHoldTreeNode() != null) { - TreeNode parent = activity().getLastHoldTreeNode().getParent(); - parent.deleteChild(activity().getLastHoldTreeNode()); - activity().getFileTreeFragment().expandNode(parent); - } else { - activity().getFileTreeFragment().listProjectFiles(); - } + private void createNewFolder(File f) { + final LayoutDialogTextInputBinding binding = + LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater()); + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - final var frag = activity().getEditorForFile(f); - if (frag != null) { - activity().closeFile(activity().findIndexOfEditorByFile(frag.getFile())); - } - } - }) - .setTitle(R.string.title_confirm_delete) - .setMessage( - activity() - .getString( - R.string.msg_confirm_delete, - String.format("%s [%s]", f.getName(), f.getAbsolutePath()))) - .setCancelable(false) - .create() - .show(); - } + Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.folder_name); - private void renameFile(File f) { - final LayoutDialogTextInputBinding binding = - LayoutDialogTextInputBinding.inflate(LayoutInflater.from(activity())); - MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + builder.setTitle(R.string.new_folder); + builder.setMessage(R.string.msg_can_contain_slashes); + builder.setView(binding.getRoot()); + builder.setCancelable(false); + builder.setPositiveButton( + R.string.text_create, + (p1, p2) -> { + p1.dismiss(); + final String name = binding.name.getEditText().getText().toString().trim(); + if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) { + final File file = new File(f, name); + if (file.exists()) { + activity() + .getApp() + .toast(R.string.msg_folder_exists, Toaster.Type.ERROR); + } else { + if (file.mkdirs()) { + activity() + .getApp() + .toast(R.string.msg_folder_created, Toaster.Type.SUCCESS); + if (activity().getLastHoldTreeNode() != null) { + TreeNode node = new TreeNode(file); + node.setViewHolder(new FileTreeViewHolder(activity())); + activity().getLastHoldTreeNode().addChild(node); + activity() + .getFileTreeFragment() + .expandNode(activity().getLastHoldTreeNode()); + } else { + activity().getFileTreeFragment().listProjectFiles(); + } + } else { + activity() + .getApp() + .toast( + R.string.msg_folder_creation_failed, + Toaster.Type.ERROR); + } + } + } else { + activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.create().show(); + } - Objects.requireNonNull(binding.name.getEditText()) - .setHint(activity().getString(R.string.new_name)); - binding.name.getEditText().setText(f.getName()); + private void delete(final File f) { + MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + builder.setNegativeButton(R.string.no, null) + .setPositiveButton( + R.string.yes, + (p1, p2) -> { + p1.dismiss(); + final boolean deleted = FileUtils.delete(f); + activity() + .getApp() + .toast( + deleted ? R.string.deleted : R.string.delete_failed, + deleted ? Toaster.Type.SUCCESS : Toaster.Type.ERROR); + if (deleted) { + // TODO Notify language servers about file delete event + if (activity().getLastHoldTreeNode() != null) { + TreeNode parent = activity().getLastHoldTreeNode().getParent(); + parent.deleteChild(activity().getLastHoldTreeNode()); + activity().getFileTreeFragment().expandNode(parent); + } else { + activity().getFileTreeFragment().listProjectFiles(); + } - builder.setTitle(R.string.rename_file); - builder.setMessage(R.string.msg_rename_file); - builder.setView(binding.getRoot()); - builder.setNegativeButton(android.R.string.cancel, null); - builder.setPositiveButton( - R.string.rename_file, - (p1, p2) -> { - p1.dismiss(); - String name = binding.name.getEditText().getText().toString().trim(); - boolean renamed = name.length() > 0 && name.length() <= 40 && FileUtils.rename(f, name); - activity() - .getApp() - .toast( - renamed ? R.string.renamed : R.string.rename_failed, - renamed ? Toaster.Type.SUCCESS : Toaster.Type.ERROR); - if (renamed) { - // TODO Notify language servers about file rename event - if (activity().getLastHoldTreeNode() != null) { - TreeNode parent = activity().getLastHoldTreeNode().getParent(); - parent.deleteChild(activity().getLastHoldTreeNode()); - TreeNode node = new TreeNode(new File(f.getParentFile(), name)); - node.setViewHolder(new FileTreeViewHolder(activity())); - parent.addChild(node); - activity().getFileTreeFragment().expandNode(parent); - } else { - activity().getFileTreeFragment().listProjectFiles(); - } - } - }); - builder.create().show(); - } + final var frag = activity().getEditorForFile(f); + if (frag != null) { + activity() + .closeFile( + activity() + .findIndexOfEditorByFile( + frag.getFile())); + } + } + }) + .setTitle(R.string.title_confirm_delete) + .setMessage( + activity() + .getString( + R.string.msg_confirm_delete, + String.format("%s [%s]", f.getName(), f.getAbsolutePath()))) + .setCancelable(false) + .create() + .show(); + } + + private void renameFile(File f) { + final LayoutDialogTextInputBinding binding = + LayoutDialogTextInputBinding.inflate(LayoutInflater.from(activity())); + MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + + Objects.requireNonNull(binding.name.getEditText()) + .setHint(activity().getString(R.string.new_name)); + binding.name.getEditText().setText(f.getName()); + + builder.setTitle(R.string.rename_file); + builder.setMessage(R.string.msg_rename_file); + builder.setView(binding.getRoot()); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setPositiveButton( + R.string.rename_file, + (p1, p2) -> { + p1.dismiss(); + String name = binding.name.getEditText().getText().toString().trim(); + boolean renamed = + name.length() > 0 && name.length() <= 40 && FileUtils.rename(f, name); + activity() + .getApp() + .toast( + renamed ? R.string.renamed : R.string.rename_failed, + renamed ? Toaster.Type.SUCCESS : Toaster.Type.ERROR); + if (renamed) { + // TODO Notify language servers about file rename event + if (activity().getLastHoldTreeNode() != null) { + TreeNode parent = activity().getLastHoldTreeNode().getParent(); + parent.deleteChild(activity().getLastHoldTreeNode()); + TreeNode node = new TreeNode(new File(f.getParentFile(), name)); + node.setViewHolder(new FileTreeViewHolder(activity())); + parent.addChild(node); + activity().getFileTreeFragment().expandNode(parent); + } else { + activity().getFileTreeFragment().listProjectFiles(); + } + } + }); + builder.create().show(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java b/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java index 5b2589cf6..2f1bb80f2 100755 --- a/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java +++ b/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java @@ -28,56 +28,56 @@ import com.itsaky.androidide.utils.Logger; */ public abstract class IDEHandler { - protected static final Logger LOG = Logger.instance(); + protected static final Logger LOG = Logger.instance(); - protected Provider provider; + protected Provider provider; - public IDEHandler(Provider provider) { - this.provider = provider; - } + public IDEHandler(Provider provider) { + this.provider = provider; + } - protected void throwNPE() { - throw new NullPointerException(); - } + protected void throwNPE() { + throw new NullPointerException(); + } - protected EditorActivity activity() { - return provider.provideEditorActivity(); - } + protected EditorActivity activity() { + return provider.provideEditorActivity(); + } - protected AndroidProject androidProject() { - return provider.provideAndroidProject(); - } + protected AndroidProject androidProject() { + return provider.provideAndroidProject(); + } - protected IDEProject ideProject() { - return provider.provideIDEProject(); - } + protected IDEProject ideProject() { + return provider.provideIDEProject(); + } - public abstract void start(); + public abstract void start(); - public abstract void stop(); + public abstract void stop(); - /** An interface to communicate between a handler and its client */ - public interface Provider { + /** 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(); + /** + * Called by handler to get a reference to {@link EditorActivity} + * + * @throws NullPointerException is this is required + */ + EditorActivity provideEditorActivity(); - /** - * Called by handler to get a reference of the current {@link AndroidProject} - * - * @throws NullPointerException is this is required - */ - AndroidProject provideAndroidProject(); + /** + * Called by handler to get a reference of the current {@link AndroidProject} + * + * @throws NullPointerException is this is required + */ + AndroidProject provideAndroidProject(); - /** - * Called by handler to get a reference of the current {@link IDEProject} - * - * @throws NullPointerException is this is required - */ - IDEProject provideIDEProject(); - } + /** + * Called by handler to get a reference of the current {@link IDEProject} + * + * @throws NullPointerException is this is required + */ + IDEProject provideIDEProject(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java index 629f27b53..6d347055d 100755 --- a/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java @@ -19,10 +19,11 @@ package com.itsaky.androidide.interfaces; import com.itsaky.androidide.models.DiagnosticGroup; import com.itsaky.lsp.models.DiagnosticItem; + import java.io.File; public interface DiagnosticClickListener { - void onGroupClick(DiagnosticGroup group); + void onGroupClick(DiagnosticGroup group); - void onDiagnosticClick(File file, DiagnosticItem diagnostic); + void onDiagnosticClick(File file, DiagnosticItem diagnostic); } diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/EditorActivityProvider.java b/app/src/main/java/com/itsaky/androidide/interfaces/EditorActivityProvider.java index fa4ef4a48..e39ffa78d 100755 --- a/app/src/main/java/com/itsaky/androidide/interfaces/EditorActivityProvider.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/EditorActivityProvider.java @@ -21,9 +21,10 @@ import com.itsaky.androidide.EditorActivity; public interface EditorActivityProvider { - /** - * Provides a reference to {@link com.itsaky.androidide.EditorActivity EditorActivity}. This is - * used by LanguageClients to get reference to EditorActivity without keeping a strong reference. - */ - EditorActivity provide(); + /** + * Provides a reference to {@link com.itsaky.androidide.EditorActivity EditorActivity}. This is + * used by LanguageClients to get reference to EditorActivity without keeping a strong + * reference. + */ + EditorActivity provide(); } diff --git a/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java b/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java index e574687db..ab2869fba 100644 --- a/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.language; import androidx.annotation.NonNull; + import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.api.ICompletionProvider; import com.itsaky.lsp.api.ILanguageServer; @@ -25,10 +26,14 @@ 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 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; + +import org.jetbrains.annotations.Contract; + import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -37,7 +42,6 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; -import org.jetbrains.annotations.Contract; /** * Common implementation of completion provider which requests completions to provided language @@ -47,92 +51,95 @@ import org.jetbrains.annotations.Contract; */ public class CommonCompletionProvider { - private CompletableFuture future; + private CompletableFuture future; - private final ILanguageServer server; + private final ILanguageServer server; - public CommonCompletionProvider(ILanguageServer server) { - this.server = server; - } - - /** - * 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 complete( - ContentReference content, - Path file, - CharPosition position, - Predicate prefixMatcher) { - if (this.future != null && !this.future.isDone()) { - try { - this.future.cancel(true); - } catch (CancellationException e) { - return new ArrayList<>(); - } + public CommonCompletionProvider(ILanguageServer server) { + this.server = server; } - this.future = - CompletableFuture.supplyAsync( - () -> { - final var prefix = - CompletionHelper.computePrefix(content, position, prefixMatcher::test); - final var completer = server.getCompletionProvider(); - - if (!completer.canComplete(file)) { - return ICompletionProvider.EMPTY; - } - - final var params = - new CompletionParams( - new Position(position.line, position.column, position.index), file); - params.setContent(content); - params.setPrefix(prefix); - return completer.complete(params); - }); - - try { - return finalizeResults(future.get().getItems()); - } catch (Throwable e) { - LOG.error("Unable to compute completions", e); - return Collections.emptyList(); - } - } - - public static boolean checkJavaCompletionChar(char c) { - return MyCharacter.isJavaIdentifierPart(c) || c == '.'; - } - - public static boolean checkXMLCompletionChar(char c) { - return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/'; - } - - @NonNull - @Contract("_ -> param1") - private List finalizeResults(@NonNull List items) { - items.sort(RESULT_SORTER); - return items; - } - - private static final Comparator RESULT_SORTER = - (p1, p2) -> { - if (p1 == null && p2 == null) { - return 0; - } else if (p1 == null) { - return -1; - } else if (p2 == null) { - return 1; + /** + * 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 complete( + ContentReference content, + Path file, + CharPosition position, + Predicate prefixMatcher) { + if (this.future != null && !this.future.isDone()) { + try { + this.future.cancel(true); + } catch (CancellationException e) { + return new ArrayList<>(); + } } - String s1 = p1.getSortText() == null ? p1.getLabel() : p1.getSortText(); - String s2 = p2.getSortText() == null ? p2.getLabel() : p2.getSortText(); - return s1.compareTo(s2); - }; + this.future = + CompletableFuture.supplyAsync( + () -> { + final var prefix = + CompletionHelper.computePrefix( + content, position, prefixMatcher::test); + final var completer = server.getCompletionProvider(); - private static final Logger LOG = Logger.instance("CommonCompletionProvider"); + if (!completer.canComplete(file)) { + return ICompletionProvider.EMPTY; + } + + final var params = + new CompletionParams( + new Position( + position.line, position.column, position.index), + file); + params.setContent(content); + params.setPrefix(prefix); + return completer.complete(params); + }); + + try { + return finalizeResults(future.get().getItems()); + } catch (Throwable e) { + LOG.error("Unable to compute completions", e); + return Collections.emptyList(); + } + } + + public static boolean checkJavaCompletionChar(char c) { + return MyCharacter.isJavaIdentifierPart(c) || c == '.'; + } + + public static boolean checkXMLCompletionChar(char c) { + return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/'; + } + + @NonNull + @Contract("_ -> param1") + private List finalizeResults(@NonNull List items) { + items.sort(RESULT_SORTER); + return items; + } + + private static final Comparator RESULT_SORTER = + (p1, p2) -> { + if (p1 == null && p2 == null) { + return 0; + } else if (p1 == null) { + return -1; + } else if (p2 == null) { + return 1; + } + + String s1 = p1.getSortText() == null ? p1.getLabel() : p1.getSortText(); + String s2 = p2.getSortText() == null ? p2.getLabel() : p2.getSortText(); + return s1.compareTo(s2); + }; + + private static final Logger LOG = Logger.instance("CommonCompletionProvider"); } diff --git a/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java b/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java index 23e9b2b6b..99460286a 100644 --- a/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java @@ -17,9 +17,12 @@ package com.itsaky.androidide.language; import androidx.annotation.NonNull; + import com.itsaky.androidide.app.StudioApp; import com.itsaky.lsp.models.DiagnosticItem; + import io.github.rosemoe.sora.lang.Language; + import java.util.Collections; import java.util.List; @@ -30,22 +33,22 @@ import java.util.List; */ public abstract class IDELanguage implements Language { - @Override - public boolean useTab() { - return false; - } + @Override + public boolean useTab() { + return false; + } - public int getTabSize() { - return StudioApp.getInstance().getPrefManager().getEditorTabSize(); - } + public int getTabSize() { + return StudioApp.getInstance().getPrefManager().getEditorTabSize(); + } - /** - * Get the diagnostics from the last analyze. - * - * @return The diagnostics. Must not be null. - */ - @NonNull - public List getDiagnostics() { - return Collections.emptyList(); - } + /** + * Get the diagnostics from the last analyze. + * + * @return The diagnostics. Must not be null. + */ + @NonNull + public List getDiagnostics() { + return Collections.emptyList(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java index eb301fdb7..46a46fc56 100755 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java @@ -31,231 +31,234 @@ import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.get; import com.itsaky.androidide.lexers.groovy.GroovyLexer; import com.itsaky.androidide.utils.CharSequenceReader; import com.itsaky.androidide.utils.Logger; + 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; -import java.io.IOException; -import java.util.Stack; + 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; + public class GroovyAnalyzer extends SimpleAnalyzeManager { - private static final Logger LOG = Logger.instance("GroovyAnalyzer"); + private static final Logger LOG = Logger.instance("GroovyAnalyzer"); - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - final var styles = new Styles(); - final var colors = new MappedSpans.Builder(); - final CodePointCharStream stream; - try { - stream = CharStreams.fromReader(new CharSequenceReader(text)); - } catch (IOException e) { - LOG.error("Unable to read text to analyze", e); - return styles; + @Override + protected Styles analyze(StringBuilder text, Delegate delegate) { + final var styles = new Styles(); + final var colors = new MappedSpans.Builder(); + final CodePointCharStream stream; + try { + stream = CharStreams.fromReader(new CharSequenceReader(text)); + } catch (IOException e) { + LOG.error("Unable to read text to analyze", e); + return styles; + } + + final var lexer = new GroovyLexer(stream); + final var stack = new Stack(); + + Token token; + int line, column, lastLine = 1, currSwitch = 0, maxSwitch = 0, type, previous = 0; + boolean isFirst = true; + + while (!delegate.isCancelled()) { + token = lexer.nextToken(); + + if (token == null) { + break; + } + + line = token.getLine() - 1; + column = token.getCharPositionInLine(); + type = token.getType(); + + if (type == GroovyLexer.EOF) { + lastLine = line; + break; + } + + switch (type) { + case GroovyLexer.WS: + if (isFirst) { + colors.addNormalIfNull(); + } + break; + case GroovyLexer.ABSTRACT: + case GroovyLexer.ASSERT: + case GroovyLexer.BREAK: + case GroovyLexer.CASE: + case GroovyLexer.CATCH: + case GroovyLexer.CLASS: + case GroovyLexer.CONST: + case GroovyLexer.CONTINUE: + case GroovyLexer.DEFAULT: + case GroovyLexer.DO: + case GroovyLexer.ELSE: + case GroovyLexer.EXTENDS: + case GroovyLexer.FINAL: + case GroovyLexer.FINALLY: + case GroovyLexer.FOR: + case GroovyLexer.IF: + case GroovyLexer.GOTO: + case GroovyLexer.IMPLEMENTS: + case GroovyLexer.IMPORT: + case GroovyLexer.INSTANCEOF: + case GroovyLexer.INTERFACE: + case GroovyLexer.NATIVE: + case GroovyLexer.NEW: + case GroovyLexer.PACKAGE: + case GroovyLexer.PRIVATE: + case GroovyLexer.PROTECTED: + case GroovyLexer.PUBLIC: + case GroovyLexer.RETURN: + case GroovyLexer.STATIC: + case GroovyLexer.STRICTFP: + case GroovyLexer.SUPER: + case GroovyLexer.SWITCH: + case GroovyLexer.SYNCHRONIZED: + case GroovyLexer.THIS: + case GroovyLexer.THROW: + case GroovyLexer.THROWS: + case GroovyLexer.TRANSIENT: + case GroovyLexer.TRY: + case GroovyLexer.VOID: + case GroovyLexer.VOLATILE: + case GroovyLexer.WHILE: + colors.addIfNeeded(line, column, forKeyword()); + break; + case GroovyLexer.DECIMAL_LITERAL: + case GroovyLexer.HEX_LITERAL: + case GroovyLexer.OCT_LITERAL: + case GroovyLexer.BINARY_LITERAL: + case GroovyLexer.FLOAT_LITERAL: + case GroovyLexer.HEX_FLOAT_LITERAL: + case GroovyLexer.BOOL_LITERAL: + case GroovyLexer.CHAR_LITERAL: + case GroovyLexer.NULL_LITERAL: + colors.addIfNeeded(line, column, get(LITERAL)); + break; + case GroovyLexer.STRING_LITERAL: + case GroovyLexer.SINGLE_QUOTE_STRING: + colors.addIfNeeded(line, column, forString()); + break; + case GroovyLexer.LPAREN: + case GroovyLexer.RPAREN: + case GroovyLexer.LBRACK: + case GroovyLexer.RBRACK: + case GroovyLexer.SEMI: + case GroovyLexer.COMMA: + case GroovyLexer.ASSIGN: + case GroovyLexer.GT: + case GroovyLexer.LT: + case GroovyLexer.BANG: + case GroovyLexer.TILDE: + case GroovyLexer.QUESTION: + case GroovyLexer.COLON: + case GroovyLexer.EQUAL: + case GroovyLexer.GE: + case GroovyLexer.LE: + case GroovyLexer.NOTEQUAL: + case GroovyLexer.AND: + case GroovyLexer.OR: + case GroovyLexer.INC: + case GroovyLexer.DEC: + case GroovyLexer.ADD: + case GroovyLexer.SUB: + case GroovyLexer.MUL: + case GroovyLexer.DIV: + case GroovyLexer.BITAND: + case GroovyLexer.BITOR: + case GroovyLexer.CARET: + case GroovyLexer.MOD: + case GroovyLexer.ADD_ASSIGN: + case GroovyLexer.SUB_ASSIGN: + case GroovyLexer.MUL_ASSIGN: + case GroovyLexer.DIV_ASSIGN: + case GroovyLexer.AND_ASSIGN: + case GroovyLexer.OR_ASSIGN: + case GroovyLexer.XOR_ASSIGN: + case GroovyLexer.MOD_ASSIGN: + case GroovyLexer.LSHIFT_ASSIGN: + case GroovyLexer.RSHIFT_ASSIGN: + case GroovyLexer.URSHIFT_ASSIGN: + case GroovyLexer.ARROW: + case GroovyLexer.COLONCOLON: + case GroovyLexer.ELLIPSIS: + case GroovyLexer.DOT: + colors.addIfNeeded(line, column, get(OPERATOR)); + break; + case GroovyLexer.BOOLEAN: + case GroovyLexer.BYTE: + case GroovyLexer.CHAR: + case GroovyLexer.DOUBLE: + case GroovyLexer.ENUM: + case GroovyLexer.FLOAT: + case GroovyLexer.INT: + case GroovyLexer.LONG: + case GroovyLexer.SHORT: + colors.addIfNeeded(line, column, get(TYPE_NAME)); + break; + case GroovyLexer.COMMENT: + case GroovyLexer.LINE_COMMENT: + colors.addIfNeeded(line, column, forComment()); + break; + case GroovyLexer.AT: + colors.addIfNeeded(line, column, get(ANNOTATION)); + break; + case GroovyLexer.IDENTIFIER: + if (previous == GroovyLexer.AT) { + colors.addIfNeeded(line, column, get(ANNOTATION)); + break; + } + + colors.addIfNeeded(line, column, get(TEXT_NORMAL)); + break; + case GroovyLexer.LBRACE: + colors.addIfNeeded(line, column, get(OPERATOR)); + if (stack.isEmpty()) { + if (currSwitch > maxSwitch) maxSwitch = currSwitch; + currSwitch = 0; + } + currSwitch++; + var block = styles.obtainNewBlock(); + block.startLine = line; + block.startColumn = column; + stack.push(block); + break; + case GroovyLexer.RBRACE: + colors.addIfNeeded(line, column, get(OPERATOR)); + if (!stack.isEmpty()) { + var block2 = stack.pop(); + block2.endLine = line; + block2.endColumn = column; + if (block2.startLine != block2.endLine) styles.addCodeBlock(block2); + } + break; + default: + colors.addIfNeeded(line, column, get(TEXT_NORMAL)); + break; + } + + isFirst = false; + if (type != GroovyLexer.WS) { + previous = type; + } + } + + if (stack.isEmpty() && currSwitch > maxSwitch) { + maxSwitch = currSwitch; + } + + colors.determine(lastLine); + styles.setSuppressSwitch(maxSwitch + 10); + styles.spans = colors.build(); + + return styles; } - - final var lexer = new GroovyLexer(stream); - final var stack = new Stack(); - - Token token; - int line, column, lastLine = 1, currSwitch = 0, maxSwitch = 0, type, previous = 0; - boolean isFirst = true; - - while (!delegate.isCancelled()) { - token = lexer.nextToken(); - - if (token == null) { - break; - } - - line = token.getLine() - 1; - column = token.getCharPositionInLine(); - type = token.getType(); - - if (type == GroovyLexer.EOF) { - lastLine = line; - break; - } - - switch (type) { - case GroovyLexer.WS: - if (isFirst) { - colors.addNormalIfNull(); - } - break; - case GroovyLexer.ABSTRACT: - case GroovyLexer.ASSERT: - case GroovyLexer.BREAK: - case GroovyLexer.CASE: - case GroovyLexer.CATCH: - case GroovyLexer.CLASS: - case GroovyLexer.CONST: - case GroovyLexer.CONTINUE: - case GroovyLexer.DEFAULT: - case GroovyLexer.DO: - case GroovyLexer.ELSE: - case GroovyLexer.EXTENDS: - case GroovyLexer.FINAL: - case GroovyLexer.FINALLY: - case GroovyLexer.FOR: - case GroovyLexer.IF: - case GroovyLexer.GOTO: - case GroovyLexer.IMPLEMENTS: - case GroovyLexer.IMPORT: - case GroovyLexer.INSTANCEOF: - case GroovyLexer.INTERFACE: - case GroovyLexer.NATIVE: - case GroovyLexer.NEW: - case GroovyLexer.PACKAGE: - case GroovyLexer.PRIVATE: - case GroovyLexer.PROTECTED: - case GroovyLexer.PUBLIC: - case GroovyLexer.RETURN: - case GroovyLexer.STATIC: - case GroovyLexer.STRICTFP: - case GroovyLexer.SUPER: - case GroovyLexer.SWITCH: - case GroovyLexer.SYNCHRONIZED: - case GroovyLexer.THIS: - case GroovyLexer.THROW: - case GroovyLexer.THROWS: - case GroovyLexer.TRANSIENT: - case GroovyLexer.TRY: - case GroovyLexer.VOID: - case GroovyLexer.VOLATILE: - case GroovyLexer.WHILE: - colors.addIfNeeded(line, column, forKeyword()); - break; - case GroovyLexer.DECIMAL_LITERAL: - case GroovyLexer.HEX_LITERAL: - case GroovyLexer.OCT_LITERAL: - case GroovyLexer.BINARY_LITERAL: - case GroovyLexer.FLOAT_LITERAL: - case GroovyLexer.HEX_FLOAT_LITERAL: - case GroovyLexer.BOOL_LITERAL: - case GroovyLexer.CHAR_LITERAL: - case GroovyLexer.NULL_LITERAL: - colors.addIfNeeded(line, column, get(LITERAL)); - break; - case GroovyLexer.STRING_LITERAL: - case GroovyLexer.SINGLE_QUOTE_STRING: - colors.addIfNeeded(line, column, forString()); - break; - case GroovyLexer.LPAREN: - case GroovyLexer.RPAREN: - case GroovyLexer.LBRACK: - case GroovyLexer.RBRACK: - case GroovyLexer.SEMI: - case GroovyLexer.COMMA: - case GroovyLexer.ASSIGN: - case GroovyLexer.GT: - case GroovyLexer.LT: - case GroovyLexer.BANG: - case GroovyLexer.TILDE: - case GroovyLexer.QUESTION: - case GroovyLexer.COLON: - case GroovyLexer.EQUAL: - case GroovyLexer.GE: - case GroovyLexer.LE: - case GroovyLexer.NOTEQUAL: - case GroovyLexer.AND: - case GroovyLexer.OR: - case GroovyLexer.INC: - case GroovyLexer.DEC: - case GroovyLexer.ADD: - case GroovyLexer.SUB: - case GroovyLexer.MUL: - case GroovyLexer.DIV: - case GroovyLexer.BITAND: - case GroovyLexer.BITOR: - case GroovyLexer.CARET: - case GroovyLexer.MOD: - case GroovyLexer.ADD_ASSIGN: - case GroovyLexer.SUB_ASSIGN: - case GroovyLexer.MUL_ASSIGN: - case GroovyLexer.DIV_ASSIGN: - case GroovyLexer.AND_ASSIGN: - case GroovyLexer.OR_ASSIGN: - case GroovyLexer.XOR_ASSIGN: - case GroovyLexer.MOD_ASSIGN: - case GroovyLexer.LSHIFT_ASSIGN: - case GroovyLexer.RSHIFT_ASSIGN: - case GroovyLexer.URSHIFT_ASSIGN: - case GroovyLexer.ARROW: - case GroovyLexer.COLONCOLON: - case GroovyLexer.ELLIPSIS: - case GroovyLexer.DOT: - colors.addIfNeeded(line, column, get(OPERATOR)); - break; - case GroovyLexer.BOOLEAN: - case GroovyLexer.BYTE: - case GroovyLexer.CHAR: - case GroovyLexer.DOUBLE: - case GroovyLexer.ENUM: - case GroovyLexer.FLOAT: - case GroovyLexer.INT: - case GroovyLexer.LONG: - case GroovyLexer.SHORT: - colors.addIfNeeded(line, column, get(TYPE_NAME)); - break; - case GroovyLexer.COMMENT: - case GroovyLexer.LINE_COMMENT: - colors.addIfNeeded(line, column, forComment()); - break; - case GroovyLexer.AT: - colors.addIfNeeded(line, column, get(ANNOTATION)); - break; - case GroovyLexer.IDENTIFIER: - if (previous == GroovyLexer.AT) { - colors.addIfNeeded(line, column, get(ANNOTATION)); - break; - } - - colors.addIfNeeded(line, column, get(TEXT_NORMAL)); - break; - case GroovyLexer.LBRACE: - colors.addIfNeeded(line, column, get(OPERATOR)); - if (stack.isEmpty()) { - if (currSwitch > maxSwitch) maxSwitch = currSwitch; - currSwitch = 0; - } - currSwitch++; - var block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - stack.push(block); - break; - case GroovyLexer.RBRACE: - colors.addIfNeeded(line, column, get(OPERATOR)); - if (!stack.isEmpty()) { - var block2 = stack.pop(); - block2.endLine = line; - block2.endColumn = column; - if (block2.startLine != block2.endLine) styles.addCodeBlock(block2); - } - break; - default: - colors.addIfNeeded(line, column, get(TEXT_NORMAL)); - break; - } - - isFirst = false; - if (type != GroovyLexer.WS) { - previous = type; - } - } - - if (stack.isEmpty() && currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - - colors.determine(lastLine); - styles.setSuppressSwitch(maxSwitch + 10); - styles.spans = colors.build(); - - return styles; - } } diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java index 95f65b84e..c150281c0 100755 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java @@ -20,361 +20,368 @@ package com.itsaky.androidide.language.groovy; import android.os.Bundle; + import com.itsaky.lsp.models.CompletionItem; import com.itsaky.lsp.models.CompletionItemKind; import com.itsaky.lsp.models.InsertTextFormat; + import io.github.rosemoe.sora.lang.completion.CompletionHelper; import io.github.rosemoe.sora.lang.completion.CompletionPublisher; import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.ContentReference; import io.github.rosemoe.sora.util.MyCharacter; + import java.util.ArrayList; import java.util.List; import java.util.Locale; public class GroovyAutoComplete { - private static final List ANDROIDX_ARTIFACTS = createArtifacts(); - private static final List CONFIGURATIONS = createConfigs(); - private static final List OTHERS = createOtherCompletions(); + private static final List ANDROIDX_ARTIFACTS = createArtifacts(); + private static final List CONFIGURATIONS = createConfigs(); + private static final List OTHERS = createOtherCompletions(); - public void complete( - ContentReference content, - CharPosition position, - CompletionPublisher publisher, - Bundle extraArguments) { - publisher.setUpdateThreshold(0); - final var prefix = - CompletionHelper.computePrefix(content, position, MyCharacter::isJavaIdentifierPart); - for (String artifact : ANDROIDX_ARTIFACTS) { - if (!artifact.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue; - publisher.addItem(createCompletionItem(artifact)); + public void complete( + ContentReference content, + CharPosition position, + CompletionPublisher publisher, + Bundle extraArguments) { + publisher.setUpdateThreshold(0); + final var prefix = + CompletionHelper.computePrefix( + content, position, MyCharacter::isJavaIdentifierPart); + for (String artifact : ANDROIDX_ARTIFACTS) { + if (!artifact.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) + continue; + publisher.addItem(createCompletionItem(artifact)); + } + + for (String config : CONFIGURATIONS) { + if (!config.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) + continue; + publisher.addItem(createCompletionItem(config)); + } + + for (String other : OTHERS) { + if (!other.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) + continue; + publisher.addItem(createCompletionItem(other)); + } } - for (String config : CONFIGURATIONS) { - if (!config.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue; - publisher.addItem(createCompletionItem(config)); + private CompletionItem createCompletionItem(String itemLabel) { + CompletionItem item = new CompletionItem(); + item.setLabel(itemLabel); + item.setDetail(""); + item.setInsertText(itemLabel); + item.setSortText("0" + itemLabel); + item.setInsertTextFormat(InsertTextFormat.PLAIN_TEXT); + item.setKind(CompletionItemKind.SNIPPET); + return item; } - for (String other : OTHERS) { - if (!other.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue; - publisher.addItem(createCompletionItem(other)); + private static List createArtifacts() { + var artifacts = new ArrayList(); + artifacts.add("androidx.arch.core:core-common"); + artifacts.add("androidx.arch.core:core"); + artifacts.add("androidx.arch.core:core-testing"); + artifacts.add("androidx.arch.core:core-runtime"); + artifacts.add("androidx.lifecycle:lifecycle-common"); + artifacts.add("androidx.lifecycle:lifecycle-common-java8"); + artifacts.add("androidx.lifecycle:lifecycle-compiler"); + artifacts.add("androidx.lifecycle:lifecycle-extensions"); + artifacts.add("androidx.lifecycle:lifecycle-livedata"); + artifacts.add("androidx.lifecycle:lifecycle-livedata-core"); + artifacts.add("androidx.lifecycle:lifecycle-reactivestreams"); + artifacts.add("androidx.lifecycle:lifecycle-runtime"); + artifacts.add("androidx.lifecycle:lifecycle-viewmodel"); + artifacts.add("androidx.paging:paging-common"); + artifacts.add("androidx.paging:paging-runtime"); + artifacts.add("androidx.paging:paging-rxjava2"); + artifacts.add("androidx.room:room-common"); + artifacts.add("androidx.room:room-compiler"); + artifacts.add("androidx.room:room-guava"); + artifacts.add("androidx.room:room-migration"); + artifacts.add("androidx.room:room-runtime"); + artifacts.add("androidx.room:room-rxjava2"); + artifacts.add("androidx.room:room-testing"); + artifacts.add("androidx.sqlite:sqlite"); + artifacts.add("androidx.sqlite:sqlite-framework"); + artifacts.add("androidx.constraintlayout:constraintlayout"); + artifacts.add("androidx.constraintlayout:constraintlayout-solver"); + artifacts.add("androidx.test.espresso.idling:idling-concurrent"); + artifacts.add("androidx.test.espresso.idling:idling-net"); + artifacts.add("androidx.test.espresso:espresso-accessibility"); + artifacts.add("androidx.test.espresso:espresso-contrib"); + artifacts.add("androidx.test.espresso:espresso-core"); + artifacts.add("androidx.test.espresso:espresso-idling-resource"); + artifacts.add("androidx.test.espresso:espresso-intents"); + artifacts.add("androidx.test.espresso:espresso-remote"); + artifacts.add("androidx.test.espresso:espresso-web"); + artifacts.add("androidx.test.jank:janktesthelper"); + artifacts.add("androidx.test:test-services"); + artifacts.add("androidx.test.uiautomator:uiautomator"); + artifacts.add("androidx.test:monitor"); + artifacts.add("androidx.test:orchestrator"); + artifacts.add("androidx.test:rules"); + artifacts.add("androidx.test:runner"); + artifacts.add("androidx.vectordrawable:vectordrawable-animated"); + artifacts.add("androidx.appcompat:appcompat"); + artifacts.add("androidx.asynclayoutinflater:asynclayoutinflater"); + artifacts.add("androidx.car:car"); + artifacts.add("androidx.cardview:cardview"); + artifacts.add("androidx.collection:collection"); + artifacts.add("androidx.coordinatorlayout:coordinatorlayout"); + artifacts.add("androidx.cursoradapter:cursoradapter"); + artifacts.add("androidx.browser:browser"); + artifacts.add("androidx.customview:customview"); + artifacts.add("com.google.android.material:material"); + artifacts.add("androidx.documentfile:documentfile"); + artifacts.add("androidx.drawerlayout:drawerlayout"); + artifacts.add("androidx.exifinterface:exifinterface"); + artifacts.add("androidx.gridlayout:gridlayout"); + artifacts.add("androidx.heifwriter:heifwriter"); + artifacts.add("androidx.interpolator:interpolator"); + artifacts.add("androidx.leanback:leanback"); + artifacts.add("androidx.loader:loader"); + artifacts.add("androidx.localbroadcastmanager:localbroadcastmanager"); + artifacts.add("androidx.media2:media2"); + artifacts.add("androidx.media2:media2-exoplayer"); + artifacts.add("androidx.mediarouter:mediarouter"); + artifacts.add("androidx.multidex:multidex"); + artifacts.add("androidx.multidex:multidex-instrumentation"); + artifacts.add("androidx.palette:palette"); + artifacts.add("androidx.percentlayout:percentlayout"); + artifacts.add("androidx.leanback:leanback-preference"); + artifacts.add("androidx.legacy:legacy-preference-v14"); + artifacts.add("androidx.preference:preference"); + artifacts.add("androidx.print:print"); + artifacts.add("androidx.recommendation:recommendation"); + artifacts.add("androidx.recyclerview:recyclerview-selection"); + artifacts.add("androidx.recyclerview:recyclerview"); + artifacts.add("androidx.slice:slice-builders"); + artifacts.add("androidx.slice:slice-core"); + artifacts.add("androidx.slice:slice-view"); + artifacts.add("androidx.slidingpanelayout:slidingpanelayout"); + artifacts.add("androidx.annotation:annotation"); + artifacts.add("androidx.core:core"); + artifacts.add("androidx.contentpager:contentpager"); + artifacts.add("androidx.legacy:legacy-support-core-ui"); + artifacts.add("androidx.legacy:legacy-support-core-utils"); + artifacts.add("androidx.dynamicanimation:dynamicanimation"); + artifacts.add("androidx.emoji:emoji"); + artifacts.add("androidx.emoji:emoji-appcompat"); + artifacts.add("androidx.emoji:emoji-bundled"); + artifacts.add("androidx.fragment:fragment"); + artifacts.add("androidx.media:media"); + artifacts.add("androidx.tvprovider:tvprovider"); + artifacts.add("androidx.legacy:legacy-support-v13"); + artifacts.add("androidx.legacy:legacy-support-v4"); + artifacts.add("androidx.vectordrawable:vectordrawable"); + artifacts.add("androidx.swiperefreshlayout:swiperefreshlayout"); + artifacts.add("androidx.textclassifier:textclassifier"); + artifacts.add("androidx.transition:transition"); + artifacts.add("androidx.versionedparcelable:versionedparcelable"); + artifacts.add("androidx.viewpager:viewpager"); + artifacts.add("androidx.wear:wear"); + artifacts.add("androidx.webkit:webkit"); + + artifacts.add("fileTree(dir: 'libs', include: ['*.jar'])"); + return artifacts; } - } - private CompletionItem createCompletionItem(String itemLabel) { - CompletionItem item = new CompletionItem(); - item.setLabel(itemLabel); - item.setDetail(""); - item.setInsertText(itemLabel); - item.setSortText("0" + itemLabel); - item.setInsertTextFormat(InsertTextFormat.PLAIN_TEXT); - item.setKind(CompletionItemKind.SNIPPET); - return item; - } + private static List createConfigs() { + var configs = new ArrayList(); + configs.add("androidApis"); + configs.add("androidTestAnnotationProcessor"); + configs.add("androidTestApi"); + configs.add("androidTestApk"); + configs.add("androidTestCompile"); + configs.add("androidTestCompileOnly"); + configs.add("androidTestDebugAnnotationProcessor"); + configs.add("androidTestDebugApi"); + configs.add("androidTestDebugApk"); + configs.add("androidTestDebugCompile"); + configs.add("androidTestDebugCompileOnly"); + configs.add("androidTestDebugImplementation"); + configs.add("androidTestDebugProvided"); + configs.add("androidTestDebugRuntimeOnly"); + configs.add("androidTestDebugWearApp"); + configs.add("androidTestImplementation"); + configs.add("androidTestProvided"); + configs.add("androidTestRuntimeOnly"); + configs.add("androidTestUtil"); + configs.add("androidTestWearApp"); + configs.add("annotationProcessor"); + configs.add("api"); + configs.add("apk"); + configs.add("archives"); + configs.add("compile"); + configs.add("compileOnly"); + configs.add("coreLibraryDesugaring"); + configs.add("debugAnnotationProcessor"); + configs.add("debugApi"); + configs.add("debugApk"); + configs.add("debugCompile"); + configs.add("debugCompileOnly"); + configs.add("debugImplementation"); + configs.add("debugProvided"); + configs.add("debugRuntimeOnly"); + configs.add("debugWearApp"); + configs.add("default"); + configs.add("implementation"); + configs.add("implementation platform()"); + configs.add("lintChecks"); + configs.add("lintClassPath"); + configs.add("lintPublish"); + configs.add("provided"); + configs.add("releaseAnnotationProcessor"); + configs.add("releaseApi"); + configs.add("releaseApk"); + configs.add("releaseCompile"); + configs.add("releaseCompileOnly"); + configs.add("releaseImplementation"); + configs.add("releaseProvided"); + configs.add("releaseRuntimeOnly"); + configs.add("releaseWearApp"); + configs.add("runtimeOnly"); + configs.add("testAnnotationProcessor"); + configs.add("testApi"); + configs.add("testApk"); + configs.add("testCompile"); + configs.add("testCompileOnly"); + configs.add("testDebugAnnotationProcessor"); + configs.add("testDebugApi"); + configs.add("testDebugApk"); + configs.add("testDebugCompile"); + configs.add("testDebugCompileOnly"); + configs.add("testDebugImplementation"); + configs.add("testDebugProvided"); + configs.add("testDebugRuntimeOnly"); + configs.add("testDebugWearApp"); + configs.add("testImplementation"); + configs.add("testProvided"); + configs.add("testReleaseAnnotationProcessor"); + configs.add("testReleaseApi"); + configs.add("testReleaseApk"); + configs.add("testReleaseCompile"); + configs.add("testReleaseCompileOnly"); + configs.add("testReleaseImplementation"); + configs.add("testReleaseProvided"); + configs.add("testReleaseRuntimeOnly"); + configs.add("testReleaseWearApp"); + configs.add("testRuntimeOnly"); + configs.add("testWearApp"); + configs.add("wearApp"); + return configs; + } - private static List createArtifacts() { - var artifacts = new ArrayList(); - artifacts.add("androidx.arch.core:core-common"); - artifacts.add("androidx.arch.core:core"); - artifacts.add("androidx.arch.core:core-testing"); - artifacts.add("androidx.arch.core:core-runtime"); - artifacts.add("androidx.lifecycle:lifecycle-common"); - artifacts.add("androidx.lifecycle:lifecycle-common-java8"); - artifacts.add("androidx.lifecycle:lifecycle-compiler"); - artifacts.add("androidx.lifecycle:lifecycle-extensions"); - artifacts.add("androidx.lifecycle:lifecycle-livedata"); - artifacts.add("androidx.lifecycle:lifecycle-livedata-core"); - artifacts.add("androidx.lifecycle:lifecycle-reactivestreams"); - artifacts.add("androidx.lifecycle:lifecycle-runtime"); - artifacts.add("androidx.lifecycle:lifecycle-viewmodel"); - artifacts.add("androidx.paging:paging-common"); - artifacts.add("androidx.paging:paging-runtime"); - artifacts.add("androidx.paging:paging-rxjava2"); - artifacts.add("androidx.room:room-common"); - artifacts.add("androidx.room:room-compiler"); - artifacts.add("androidx.room:room-guava"); - artifacts.add("androidx.room:room-migration"); - artifacts.add("androidx.room:room-runtime"); - artifacts.add("androidx.room:room-rxjava2"); - artifacts.add("androidx.room:room-testing"); - artifacts.add("androidx.sqlite:sqlite"); - artifacts.add("androidx.sqlite:sqlite-framework"); - artifacts.add("androidx.constraintlayout:constraintlayout"); - artifacts.add("androidx.constraintlayout:constraintlayout-solver"); - artifacts.add("androidx.test.espresso.idling:idling-concurrent"); - artifacts.add("androidx.test.espresso.idling:idling-net"); - artifacts.add("androidx.test.espresso:espresso-accessibility"); - artifacts.add("androidx.test.espresso:espresso-contrib"); - artifacts.add("androidx.test.espresso:espresso-core"); - artifacts.add("androidx.test.espresso:espresso-idling-resource"); - artifacts.add("androidx.test.espresso:espresso-intents"); - artifacts.add("androidx.test.espresso:espresso-remote"); - artifacts.add("androidx.test.espresso:espresso-web"); - artifacts.add("androidx.test.jank:janktesthelper"); - artifacts.add("androidx.test:test-services"); - artifacts.add("androidx.test.uiautomator:uiautomator"); - artifacts.add("androidx.test:monitor"); - artifacts.add("androidx.test:orchestrator"); - artifacts.add("androidx.test:rules"); - artifacts.add("androidx.test:runner"); - artifacts.add("androidx.vectordrawable:vectordrawable-animated"); - artifacts.add("androidx.appcompat:appcompat"); - artifacts.add("androidx.asynclayoutinflater:asynclayoutinflater"); - artifacts.add("androidx.car:car"); - artifacts.add("androidx.cardview:cardview"); - artifacts.add("androidx.collection:collection"); - artifacts.add("androidx.coordinatorlayout:coordinatorlayout"); - artifacts.add("androidx.cursoradapter:cursoradapter"); - artifacts.add("androidx.browser:browser"); - artifacts.add("androidx.customview:customview"); - artifacts.add("com.google.android.material:material"); - artifacts.add("androidx.documentfile:documentfile"); - artifacts.add("androidx.drawerlayout:drawerlayout"); - artifacts.add("androidx.exifinterface:exifinterface"); - artifacts.add("androidx.gridlayout:gridlayout"); - artifacts.add("androidx.heifwriter:heifwriter"); - artifacts.add("androidx.interpolator:interpolator"); - artifacts.add("androidx.leanback:leanback"); - artifacts.add("androidx.loader:loader"); - artifacts.add("androidx.localbroadcastmanager:localbroadcastmanager"); - artifacts.add("androidx.media2:media2"); - artifacts.add("androidx.media2:media2-exoplayer"); - artifacts.add("androidx.mediarouter:mediarouter"); - artifacts.add("androidx.multidex:multidex"); - artifacts.add("androidx.multidex:multidex-instrumentation"); - artifacts.add("androidx.palette:palette"); - artifacts.add("androidx.percentlayout:percentlayout"); - artifacts.add("androidx.leanback:leanback-preference"); - artifacts.add("androidx.legacy:legacy-preference-v14"); - artifacts.add("androidx.preference:preference"); - artifacts.add("androidx.print:print"); - artifacts.add("androidx.recommendation:recommendation"); - artifacts.add("androidx.recyclerview:recyclerview-selection"); - artifacts.add("androidx.recyclerview:recyclerview"); - artifacts.add("androidx.slice:slice-builders"); - artifacts.add("androidx.slice:slice-core"); - artifacts.add("androidx.slice:slice-view"); - artifacts.add("androidx.slidingpanelayout:slidingpanelayout"); - artifacts.add("androidx.annotation:annotation"); - artifacts.add("androidx.core:core"); - artifacts.add("androidx.contentpager:contentpager"); - artifacts.add("androidx.legacy:legacy-support-core-ui"); - artifacts.add("androidx.legacy:legacy-support-core-utils"); - artifacts.add("androidx.dynamicanimation:dynamicanimation"); - artifacts.add("androidx.emoji:emoji"); - artifacts.add("androidx.emoji:emoji-appcompat"); - artifacts.add("androidx.emoji:emoji-bundled"); - artifacts.add("androidx.fragment:fragment"); - artifacts.add("androidx.media:media"); - artifacts.add("androidx.tvprovider:tvprovider"); - artifacts.add("androidx.legacy:legacy-support-v13"); - artifacts.add("androidx.legacy:legacy-support-v4"); - artifacts.add("androidx.vectordrawable:vectordrawable"); - artifacts.add("androidx.swiperefreshlayout:swiperefreshlayout"); - artifacts.add("androidx.textclassifier:textclassifier"); - artifacts.add("androidx.transition:transition"); - artifacts.add("androidx.versionedparcelable:versionedparcelable"); - artifacts.add("androidx.viewpager:viewpager"); - artifacts.add("androidx.wear:wear"); - artifacts.add("androidx.webkit:webkit"); + private static List createOtherCompletions() { + var others = new ArrayList(); - artifacts.add("fileTree(dir: 'libs', include: ['*.jar'])"); - return artifacts; - } + // Common words that user may use + others.add("android"); + others.add("applicationVariants"); + others.add("compileSdkVersion"); + others.add("buildToolsVersion"); + others.add("defaultConfig"); + others.add("applicationId"); + others.add("minSdkVersion"); + others.add("targetSdkVersion"); + others.add("versionCode"); + others.add("versionName"); + others.add("multiDexEnabled"); + others.add("vectorDrawables.useSupportLibrary"); + others.add("compileOptions"); + others.add("coreLibraryDesugaringEnabled"); + others.add("sourceCompatibility"); + others.add("targetCompatibility"); + others.add("JavaVersion.VERSION_1_8"); + others.add("JavaVersion.VERSION_1_7"); - private static List createConfigs() { - var configs = new ArrayList(); - configs.add("androidApis"); - configs.add("androidTestAnnotationProcessor"); - configs.add("androidTestApi"); - configs.add("androidTestApk"); - configs.add("androidTestCompile"); - configs.add("androidTestCompileOnly"); - configs.add("androidTestDebugAnnotationProcessor"); - configs.add("androidTestDebugApi"); - configs.add("androidTestDebugApk"); - configs.add("androidTestDebugCompile"); - configs.add("androidTestDebugCompileOnly"); - configs.add("androidTestDebugImplementation"); - configs.add("androidTestDebugProvided"); - configs.add("androidTestDebugRuntimeOnly"); - configs.add("androidTestDebugWearApp"); - configs.add("androidTestImplementation"); - configs.add("androidTestProvided"); - configs.add("androidTestRuntimeOnly"); - configs.add("androidTestUtil"); - configs.add("androidTestWearApp"); - configs.add("annotationProcessor"); - configs.add("api"); - configs.add("apk"); - configs.add("archives"); - configs.add("compile"); - configs.add("compileOnly"); - configs.add("coreLibraryDesugaring"); - configs.add("debugAnnotationProcessor"); - configs.add("debugApi"); - configs.add("debugApk"); - configs.add("debugCompile"); - configs.add("debugCompileOnly"); - configs.add("debugImplementation"); - configs.add("debugProvided"); - configs.add("debugRuntimeOnly"); - configs.add("debugWearApp"); - configs.add("default"); - configs.add("implementation"); - configs.add("implementation platform()"); - configs.add("lintChecks"); - configs.add("lintClassPath"); - configs.add("lintPublish"); - configs.add("provided"); - configs.add("releaseAnnotationProcessor"); - configs.add("releaseApi"); - configs.add("releaseApk"); - configs.add("releaseCompile"); - configs.add("releaseCompileOnly"); - configs.add("releaseImplementation"); - configs.add("releaseProvided"); - configs.add("releaseRuntimeOnly"); - configs.add("releaseWearApp"); - configs.add("runtimeOnly"); - configs.add("testAnnotationProcessor"); - configs.add("testApi"); - configs.add("testApk"); - configs.add("testCompile"); - configs.add("testCompileOnly"); - configs.add("testDebugAnnotationProcessor"); - configs.add("testDebugApi"); - configs.add("testDebugApk"); - configs.add("testDebugCompile"); - configs.add("testDebugCompileOnly"); - configs.add("testDebugImplementation"); - configs.add("testDebugProvided"); - configs.add("testDebugRuntimeOnly"); - configs.add("testDebugWearApp"); - configs.add("testImplementation"); - configs.add("testProvided"); - configs.add("testReleaseAnnotationProcessor"); - configs.add("testReleaseApi"); - configs.add("testReleaseApk"); - configs.add("testReleaseCompile"); - configs.add("testReleaseCompileOnly"); - configs.add("testReleaseImplementation"); - configs.add("testReleaseProvided"); - configs.add("testReleaseRuntimeOnly"); - configs.add("testReleaseWearApp"); - configs.add("testRuntimeOnly"); - configs.add("testWearApp"); - configs.add("wearApp"); - return configs; - } + // Lint options + others.add("lintOptions"); + others.add("quiet"); + others.add("abortOnError"); + others.add("checkReleaseBuilds"); + others.add("ignoreWarnings"); + others.add("checkAllWarnings"); + others.add("warningsAsErrors"); + others.add("disable"); + others.add("enable"); + others.add("check"); + others.add("noLines"); + others.add("showAll"); + others.add("explainIssues"); + others.add("lintConfig"); + others.add("textReport"); + others.add("textOutput"); + others.add("xmlReport"); + others.add("xmlOutput"); + others.add("htmlReport"); + others.add("htmlOutput"); + others.add("fatal"); + others.add("error"); + others.add("warning"); + others.add("ignore"); + others.add("informational"); + others.add("baseline"); + others.add("checkTestSources"); + others.add("ignoreTestSources"); + others.add("checkGeneratedSources"); + others.add("checkDependencies"); - private static List createOtherCompletions() { - var others = new ArrayList(); + // buildFeatures options + others.add("buildFeatures"); + others.add("aidl"); + others.add("buildConfig"); + others.add("compose"); + others.add("prefab"); + others.add("renderScript"); + others.add("resValues"); + others.add("shaders"); + others.add("viewBinding"); - // Common words that user may use - others.add("android"); - others.add("applicationVariants"); - others.add("compileSdkVersion"); - others.add("buildToolsVersion"); - others.add("defaultConfig"); - others.add("applicationId"); - others.add("minSdkVersion"); - others.add("targetSdkVersion"); - others.add("versionCode"); - others.add("versionName"); - others.add("multiDexEnabled"); - others.add("vectorDrawables.useSupportLibrary"); - others.add("compileOptions"); - others.add("coreLibraryDesugaringEnabled"); - others.add("sourceCompatibility"); - others.add("targetCompatibility"); - others.add("JavaVersion.VERSION_1_8"); - others.add("JavaVersion.VERSION_1_7"); + // Signing configs and all + others.add("signingConfigs"); + others.add("debug"); + others.add("storeFile"); + others.add("storePassword"); + others.add("keyAlias"); + others.add("keyPassword"); + others.add("release"); + others.add("storeFile"); + others.add("storePassword"); + others.add("keyAlias"); + others.add("keyPassword"); + others.add("buildTypes"); + others.add("debug"); + others.add("minifyEnabled"); + others.add("proguardFiles"); + others.add("signingConfig"); + others.add("stringfog.enable"); + others.add("release"); + others.add("minifyEnabled"); + others.add("proguardFiles"); + others.add("signingConfig"); + others.add("stringfog.enable"); + others.add("packagingOptions"); + others.add("exclude"); - // Lint options - others.add("lintOptions"); - others.add("quiet"); - others.add("abortOnError"); - others.add("checkReleaseBuilds"); - others.add("ignoreWarnings"); - others.add("checkAllWarnings"); - others.add("warningsAsErrors"); - others.add("disable"); - others.add("enable"); - others.add("check"); - others.add("noLines"); - others.add("showAll"); - others.add("explainIssues"); - others.add("lintConfig"); - others.add("textReport"); - others.add("textOutput"); - others.add("xmlReport"); - others.add("xmlOutput"); - others.add("htmlReport"); - others.add("htmlOutput"); - others.add("fatal"); - others.add("error"); - others.add("warning"); - others.add("ignore"); - others.add("informational"); - others.add("baseline"); - others.add("checkTestSources"); - others.add("ignoreTestSources"); - others.add("checkGeneratedSources"); - others.add("checkDependencies"); - - // buildFeatures options - others.add("buildFeatures"); - others.add("aidl"); - others.add("buildConfig"); - others.add("compose"); - others.add("prefab"); - others.add("renderScript"); - others.add("resValues"); - others.add("shaders"); - others.add("viewBinding"); - - // Signing configs and all - others.add("signingConfigs"); - others.add("debug"); - others.add("storeFile"); - others.add("storePassword"); - others.add("keyAlias"); - others.add("keyPassword"); - others.add("release"); - others.add("storeFile"); - others.add("storePassword"); - others.add("keyAlias"); - others.add("keyPassword"); - others.add("buildTypes"); - others.add("debug"); - others.add("minifyEnabled"); - others.add("proguardFiles"); - others.add("signingConfig"); - others.add("stringfog.enable"); - others.add("release"); - others.add("minifyEnabled"); - others.add("proguardFiles"); - others.add("signingConfig"); - others.add("stringfog.enable"); - others.add("packagingOptions"); - others.add("exclude"); - - // Maybe used in root project's build.gradle - others.add("buildscript"); - others.add("ext"); - others.add("project.ext"); - others.add("repositories"); - others.add("maven"); - others.add("url"); - others.add("google()"); - others.add("mavenLocal()"); - others.add("mavenCentral()"); - others.add("jcenter()"); - others.add("classpath"); - others.add("allprojects"); - others.add("subprojects"); - return others; - } + // Maybe used in root project's build.gradle + others.add("buildscript"); + others.add("ext"); + others.add("project.ext"); + others.add("repositories"); + others.add("maven"); + others.add("url"); + others.add("google()"); + others.add("mavenLocal()"); + others.add("mavenCentral()"); + others.add("jcenter()"); + others.add("classpath"); + others.add("allprojects"); + others.add("subprojects"); + return others; + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java index 7aa08713c..94ed4e613 100755 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java @@ -18,11 +18,14 @@ package com.itsaky.androidide.language.groovy; import android.os.Bundle; + import androidx.annotation.NonNull; + import com.itsaky.androidide.language.IDELanguage; import com.itsaky.androidide.lexers.groovy.GroovyLexer; import com.itsaky.androidide.utils.CharSequenceReader; import com.itsaky.androidide.utils.Logger; + import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; import io.github.rosemoe.sora.lang.completion.CompletionPublisher; @@ -32,133 +35,141 @@ import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.ContentReference; import io.github.rosemoe.sora.text.TextUtils; import io.github.rosemoe.sora.widget.SymbolPairMatch; -import java.io.StringReader; + import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.Token; +import java.io.StringReader; + public class GroovyLanguage extends IDELanguage { - private final GroovyAnalyzer analyzer; - private final GroovyAutoComplete completer; + private final GroovyAnalyzer analyzer; + private final GroovyAutoComplete completer; - private static final Logger LOG = Logger.instance("GroovyLanguage"); + private static final Logger LOG = Logger.instance("GroovyLanguage"); - public GroovyLanguage() { - analyzer = new GroovyAnalyzer(); - completer = new GroovyAutoComplete(); - } - - public int getIndentAdvance(String p1) { - try { - GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new StringReader(p1))); - Token token = null; - int advance = 0; - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - switch (token.getType()) { - case GroovyLexer.LBRACE: - advance++; - break; - case GroovyLexer.RBRACE: - advance--; - break; - } - } - advance = Math.max(0, advance); - return advance * getTabSize(); - } catch (Throwable e) { - LOG.error("Failed to calculate indent advance", e); + public GroovyLanguage() { + analyzer = new GroovyAnalyzer(); + completer = new GroovyAutoComplete(); } - 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 { - - completer.complete(content, position, publisher, extraArguments); - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - try { - GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new CharSequenceReader(content))); - Token token; - int advance = 0; - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - switch (token.getType()) { - case GroovyLexer.LBRACE: - advance++; - break; - case GroovyLexer.RBRACE: - advance--; - break; + public int getIndentAdvance(String p1) { + try { + GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new StringReader(p1))); + Token token = null; + int advance = 0; + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + switch (token.getType()) { + case GroovyLexer.LBRACE: + advance++; + break; + case GroovyLexer.RBRACE: + advance--; + break; + } + } + advance = Math.max(0, advance); + return advance * getTabSize(); + } catch (Throwable e) { + LOG.error("Failed to calculate indent advance", e); } - } - advance = Math.max(0, advance); - return advance * getTabSize(); - } catch (Throwable e) { - LOG.error("Failed to calculate indent advance", e); - } - return 0; - } - - @Override - public CharSequence format(CharSequence content) { - return content; - } - - private final NewlineHandler[] newlineHandlers = new NewlineHandler[] {new BraceHandler()}; - - @Override - public NewlineHandler[] getNewlineHandlers() { - return newlineHandlers; - } - - @Override - public void destroy() {} - - class BraceHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - beforeText = beforeText.trim(); - afterText = afterText.trim(); - return beforeText.endsWith("{") && afterText.startsWith("}"); + return 0; } @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText); - int advanceAfter = getIndentAdvance(afterText); - String text; - StringBuilder sb = - new StringBuilder("\n") - .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) - .append('\n') - .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); + 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 { + + completer.complete(content, position, publisher, extraArguments); + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + try { + GroovyLexer lexer = + new GroovyLexer(CharStreams.fromReader(new CharSequenceReader(content))); + Token token; + int advance = 0; + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + switch (token.getType()) { + case GroovyLexer.LBRACE: + advance++; + break; + case GroovyLexer.RBRACE: + advance--; + break; + } + } + advance = Math.max(0, advance); + return advance * getTabSize(); + } catch (Throwable e) { + LOG.error("Failed to calculate indent advance", e); + } + return 0; + } + + @Override + public CharSequence format(CharSequence content) { + return content; + } + + private final NewlineHandler[] newlineHandlers = new NewlineHandler[] {new BraceHandler()}; + + @Override + public NewlineHandler[] getNewlineHandlers() { + return newlineHandlers; + } + + @Override + public void destroy() {} + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + beforeText = beforeText.trim(); + afterText = afterText.trim(); + return beforeText.endsWith("{") && afterText.startsWith("}"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = + new StringBuilder("\n") + .append( + TextUtils.createIndent( + count + advanceBefore, tabSize, useTab())) + .append('\n') + .append( + text = + TextUtils.createIndent( + count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java index 60ad2ef8a..4b7899002 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java @@ -28,6 +28,7 @@ import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.get; import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.withoutCompletion; import androidx.annotation.NonNull; + import com.itsaky.androidide.lexers.java.JavaLexer; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; import com.itsaky.androidide.utils.CharSequenceReader; @@ -39,21 +40,24 @@ import com.itsaky.lsp.models.DiagnosticResult; import com.itsaky.lsp.models.DiagnosticSeverity; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; + 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.Span; import io.github.rosemoe.sora.lang.styling.Styles; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.Token; +import org.jetbrains.annotations.Contract; + import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Stack; import java.util.concurrent.CompletableFuture; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CodePointCharStream; -import org.antlr.v4.runtime.Token; -import org.jetbrains.annotations.Contract; /** * Code analyzer for the Java Language @@ -62,335 +66,338 @@ import org.jetbrains.annotations.Contract; */ public class JavaAnalyzer extends SimpleAnalyzeManager { - private List diagnostics = new ArrayList<>(); - private final List ideDiagnostics = new ArrayList<>(); - private final ILanguageServer languageServer; + private List diagnostics = new ArrayList<>(); + private final List ideDiagnostics = new ArrayList<>(); + private final ILanguageServer languageServer; - public JavaAnalyzer(ILanguageServer languageServer) { - this.languageServer = languageServer; - } - - @NonNull - public List getDiagnostics() { - final var result = new ArrayList<>(ideDiagnostics); - if (diagnostics != null && !diagnostics.isEmpty()) { - result.addAll(diagnostics); - } - result.sort(DiagnosticItem.START_COMPARATOR); - return result; - } - - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - final var args = getExtraArguments(); - if (args.containsKey(IDEEditor.KEY_FILE)) { - final var file = new File(args.getString(IDEEditor.KEY_FILE)); - if (languageServer != null && file.exists()) { - CompletableFuture.runAsync( - () -> { - final var analyzer = languageServer.getCodeAnalyzer(); - - diagnostics = analyzer.analyze(file.toPath()); - - if (languageServer.getClient() != null) { - languageServer - .getClient() - .publishDiagnostics(new DiagnosticResult(file.toPath(), diagnostics)); - } - }); - } + public JavaAnalyzer(ILanguageServer languageServer) { + this.languageServer = languageServer; } - final CodePointCharStream stream; - final var styles = new Styles(); - final var colors = new MappedSpans.Builder(); - try { - stream = CharStreams.fromReader(new CharSequenceReader(text)); - } catch (IOException e) { - LOG.error("Unable to create char stream for text", e); - return styles; + @NonNull + public List getDiagnostics() { + final var result = new ArrayList<>(ideDiagnostics); + if (diagnostics != null && !diagnostics.isEmpty()) { + result.addAll(diagnostics); + } + result.sort(DiagnosticItem.START_COMPARATOR); + return result; } - final var lexer = new JavaLexer(stream); - final var stack = new Stack(); - Token token; - int line, column, lastLine = 1, type, currSwitch = 0, maxSwitch = 0, previous = -1; - boolean isFirst = true; + @Override + protected Styles analyze(StringBuilder text, Delegate delegate) { + final var args = getExtraArguments(); + if (args.containsKey(IDEEditor.KEY_FILE)) { + final var file = new File(args.getString(IDEEditor.KEY_FILE)); + if (languageServer != null && file.exists()) { + CompletableFuture.runAsync( + () -> { + final var analyzer = languageServer.getCodeAnalyzer(); - ideDiagnostics.clear(); + diagnostics = analyzer.analyze(file.toPath()); - while (!delegate.isCancelled()) { - token = lexer.nextToken(); + if (languageServer.getClient() != null) { + languageServer + .getClient() + .publishDiagnostics( + new DiagnosticResult(file.toPath(), diagnostics)); + } + }); + } + } - if (token == null) { - break; - } + final CodePointCharStream stream; + final var styles = new Styles(); + final var colors = new MappedSpans.Builder(); + try { + stream = CharStreams.fromReader(new CharSequenceReader(text)); + } catch (IOException e) { + LOG.error("Unable to create char stream for text", e); + return styles; + } + final var lexer = new JavaLexer(stream); + final var stack = new Stack(); - type = token.getType(); - line = token.getLine() - 1; - column = token.getCharPositionInLine(); + Token token; + int line, column, lastLine = 1, type, currSwitch = 0, maxSwitch = 0, previous = -1; + boolean isFirst = true; - if (type == JavaLexer.EOF) { - lastLine = line; - break; - } + ideDiagnostics.clear(); - final var tokenLength = token.getText().length(); - switch (type) { - case JavaLexer.WS: - if (isFirst) { - colors.addNormalIfNull(); - } - break; - case JavaLexer.ABSTRACT: - case JavaLexer.ASSERT: - case JavaLexer.BREAK: - case JavaLexer.CASE: - case JavaLexer.CATCH: - case JavaLexer.CLASS: - case JavaLexer.CONST: - case JavaLexer.CONTINUE: - case JavaLexer.DEFAULT: - case JavaLexer.DO: - case JavaLexer.ELSE: - case JavaLexer.EXTENDS: - case JavaLexer.FINAL: - case JavaLexer.FINALLY: - case JavaLexer.FOR: - case JavaLexer.IF: - case JavaLexer.GOTO: - case JavaLexer.IMPLEMENTS: - case JavaLexer.IMPORT: - case JavaLexer.INSTANCEOF: - case JavaLexer.INTERFACE: - case JavaLexer.NATIVE: - case JavaLexer.NEW: - case JavaLexer.PACKAGE: - case JavaLexer.PRIVATE: - case JavaLexer.PROTECTED: - case JavaLexer.PUBLIC: - case JavaLexer.RETURN: - case JavaLexer.STATIC: - case JavaLexer.STRICTFP: - case JavaLexer.SUPER: - case JavaLexer.SWITCH: - case JavaLexer.SYNCHRONIZED: - case JavaLexer.THIS: - case JavaLexer.THROW: - case JavaLexer.THROWS: - case JavaLexer.TRANSIENT: - case JavaLexer.TRY: - case JavaLexer.VOID: - case JavaLexer.VOLATILE: - case JavaLexer.WHILE: - case JavaLexer.VAR: - colors.addIfNeeded(line, column, forKeyword()); - break; - case JavaLexer.DECIMAL_LITERAL: - case JavaLexer.HEX_LITERAL: - case JavaLexer.OCT_LITERAL: - case JavaLexer.BINARY_LITERAL: - case JavaLexer.FLOAT_LITERAL: - case JavaLexer.HEX_FLOAT_LITERAL: - case JavaLexer.BOOL_LITERAL: - case JavaLexer.CHAR_LITERAL: - case JavaLexer.NULL_LITERAL: - colors.addIfNeeded(line, column, get(LITERAL)); - break; - case JavaLexer.STRING_LITERAL: - colors.addIfNeeded(line, column, forString()); - break; - case JavaLexer.LPAREN: - case JavaLexer.RPAREN: - case JavaLexer.LBRACK: - case JavaLexer.RBRACK: - case JavaLexer.SEMI: - case JavaLexer.COMMA: - case JavaLexer.ASSIGN: - case JavaLexer.GT: - case JavaLexer.LT: - case JavaLexer.BANG: - case JavaLexer.TILDE: - case JavaLexer.QUESTION: - case JavaLexer.COLON: - case JavaLexer.EQUAL: - case JavaLexer.GE: - case JavaLexer.LE: - case JavaLexer.NOTEQUAL: - case JavaLexer.AND: - case JavaLexer.OR: - case JavaLexer.INC: - case JavaLexer.DEC: - case JavaLexer.ADD: - case JavaLexer.SUB: - case JavaLexer.MUL: - case JavaLexer.DIV: - case JavaLexer.BITAND: - case JavaLexer.BITOR: - case JavaLexer.CARET: - case JavaLexer.MOD: - case JavaLexer.ADD_ASSIGN: - case JavaLexer.SUB_ASSIGN: - case JavaLexer.MUL_ASSIGN: - case JavaLexer.DIV_ASSIGN: - case JavaLexer.AND_ASSIGN: - case JavaLexer.OR_ASSIGN: - case JavaLexer.XOR_ASSIGN: - case JavaLexer.MOD_ASSIGN: - case JavaLexer.LSHIFT_ASSIGN: - case JavaLexer.RSHIFT_ASSIGN: - case JavaLexer.URSHIFT_ASSIGN: - case JavaLexer.ARROW: - case JavaLexer.COLONCOLON: - case JavaLexer.ELLIPSIS: - case JavaLexer.DOT: - colors.addIfNeeded(line, column, get(OPERATOR)); - break; - case JavaLexer.BOOLEAN: - case JavaLexer.BYTE: - case JavaLexer.CHAR: - case JavaLexer.DOUBLE: - case JavaLexer.ENUM: - case JavaLexer.FLOAT: - case JavaLexer.INT: - case JavaLexer.LONG: - case JavaLexer.SHORT: - colors.addIfNeeded(line, column, get(TYPE_NAME)); - break; - case JavaLexer.BLOCK_COMMENT: - colors.addIfNeeded(line, column, forComment()); - break; - case JavaLexer.LINE_COMMENT: - var commentType = SchemeAndroidIDE.COMMENT; + while (!delegate.isCancelled()) { + token = lexer.nextToken(); - // highlight special line comments - var commentText = token.getText(); - if (commentText.length() > 2) { - commentText = commentText.substring(2); - commentText = commentText.trim(); - - var mark = true; - if ("todo".equalsIgnoreCase(commentText.substring(0, 4))) { - commentType = SchemeAndroidIDE.TODO_COMMENT; - } else if ("fixme".equalsIgnoreCase(commentText.substring(0, 5))) { - commentType = SchemeAndroidIDE.FIXME_COMMENT; - } else { - mark = false; + if (token == null) { + break; } - if (mark) { - if (diagnostics == null) { - diagnostics = new ArrayList<>(); - } - final var diagnostic = new DiagnosticItem(); - diagnostic.setSeverity(DiagnosticSeverity.WARNING); - diagnostic.setMessage(commentText); - diagnostic.setCode("special.comment"); - diagnostic.setRange( - new Range(new Position(line, column), new Position(line, column + tokenLength))); - diagnostic.setSource(commentText); - ideDiagnostics.add(diagnostic); + type = token.getType(); + line = token.getLine() - 1; + column = token.getCharPositionInLine(); + + if (type == JavaLexer.EOF) { + lastLine = line; + break; } - } - colors.addIfNeeded(line, column, withoutCompletion(commentType)); + final var tokenLength = token.getText().length(); + switch (type) { + case JavaLexer.WS: + if (isFirst) { + colors.addNormalIfNull(); + } + break; + case JavaLexer.ABSTRACT: + case JavaLexer.ASSERT: + case JavaLexer.BREAK: + case JavaLexer.CASE: + case JavaLexer.CATCH: + case JavaLexer.CLASS: + case JavaLexer.CONST: + case JavaLexer.CONTINUE: + case JavaLexer.DEFAULT: + case JavaLexer.DO: + case JavaLexer.ELSE: + case JavaLexer.EXTENDS: + case JavaLexer.FINAL: + case JavaLexer.FINALLY: + case JavaLexer.FOR: + case JavaLexer.IF: + case JavaLexer.GOTO: + case JavaLexer.IMPLEMENTS: + case JavaLexer.IMPORT: + case JavaLexer.INSTANCEOF: + case JavaLexer.INTERFACE: + case JavaLexer.NATIVE: + case JavaLexer.NEW: + case JavaLexer.PACKAGE: + case JavaLexer.PRIVATE: + case JavaLexer.PROTECTED: + case JavaLexer.PUBLIC: + case JavaLexer.RETURN: + case JavaLexer.STATIC: + case JavaLexer.STRICTFP: + case JavaLexer.SUPER: + case JavaLexer.SWITCH: + case JavaLexer.SYNCHRONIZED: + case JavaLexer.THIS: + case JavaLexer.THROW: + case JavaLexer.THROWS: + case JavaLexer.TRANSIENT: + case JavaLexer.TRY: + case JavaLexer.VOID: + case JavaLexer.VOLATILE: + case JavaLexer.WHILE: + case JavaLexer.VAR: + colors.addIfNeeded(line, column, forKeyword()); + break; + case JavaLexer.DECIMAL_LITERAL: + case JavaLexer.HEX_LITERAL: + case JavaLexer.OCT_LITERAL: + case JavaLexer.BINARY_LITERAL: + case JavaLexer.FLOAT_LITERAL: + case JavaLexer.HEX_FLOAT_LITERAL: + case JavaLexer.BOOL_LITERAL: + case JavaLexer.CHAR_LITERAL: + case JavaLexer.NULL_LITERAL: + colors.addIfNeeded(line, column, get(LITERAL)); + break; + case JavaLexer.STRING_LITERAL: + colors.addIfNeeded(line, column, forString()); + break; + case JavaLexer.LPAREN: + case JavaLexer.RPAREN: + case JavaLexer.LBRACK: + case JavaLexer.RBRACK: + case JavaLexer.SEMI: + case JavaLexer.COMMA: + case JavaLexer.ASSIGN: + case JavaLexer.GT: + case JavaLexer.LT: + case JavaLexer.BANG: + case JavaLexer.TILDE: + case JavaLexer.QUESTION: + case JavaLexer.COLON: + case JavaLexer.EQUAL: + case JavaLexer.GE: + case JavaLexer.LE: + case JavaLexer.NOTEQUAL: + case JavaLexer.AND: + case JavaLexer.OR: + case JavaLexer.INC: + case JavaLexer.DEC: + case JavaLexer.ADD: + case JavaLexer.SUB: + case JavaLexer.MUL: + case JavaLexer.DIV: + case JavaLexer.BITAND: + case JavaLexer.BITOR: + case JavaLexer.CARET: + case JavaLexer.MOD: + case JavaLexer.ADD_ASSIGN: + case JavaLexer.SUB_ASSIGN: + case JavaLexer.MUL_ASSIGN: + case JavaLexer.DIV_ASSIGN: + case JavaLexer.AND_ASSIGN: + case JavaLexer.OR_ASSIGN: + case JavaLexer.XOR_ASSIGN: + case JavaLexer.MOD_ASSIGN: + case JavaLexer.LSHIFT_ASSIGN: + case JavaLexer.RSHIFT_ASSIGN: + case JavaLexer.URSHIFT_ASSIGN: + case JavaLexer.ARROW: + case JavaLexer.COLONCOLON: + case JavaLexer.ELLIPSIS: + case JavaLexer.DOT: + colors.addIfNeeded(line, column, get(OPERATOR)); + break; + case JavaLexer.BOOLEAN: + case JavaLexer.BYTE: + case JavaLexer.CHAR: + case JavaLexer.DOUBLE: + case JavaLexer.ENUM: + case JavaLexer.FLOAT: + case JavaLexer.INT: + case JavaLexer.LONG: + case JavaLexer.SHORT: + colors.addIfNeeded(line, column, get(TYPE_NAME)); + break; + case JavaLexer.BLOCK_COMMENT: + colors.addIfNeeded(line, column, forComment()); + break; + case JavaLexer.LINE_COMMENT: + var commentType = SchemeAndroidIDE.COMMENT; - break; - case JavaLexer.AT: - colors.addIfNeeded(line, column, get(ANNOTATION)); - break; - case JavaLexer.IDENTIFIER: - int colorId = SchemeAndroidIDE.TEXT_NORMAL; - if (previous == JavaLexer.AT) { - colorId = SchemeAndroidIDE.ANNOTATION; - } - colors.addIfNeeded(line, column, get(colorId)); - break; - case JavaLexer.LBRACE: - colors.addIfNeeded(line, column, get(OPERATOR)); - if (stack.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; + // highlight special line comments + var commentText = token.getText(); + if (commentText.length() > 2) { + commentText = commentText.substring(2); + commentText = commentText.trim(); + + var mark = true; + if ("todo".equalsIgnoreCase(commentText.substring(0, 4))) { + commentType = SchemeAndroidIDE.TODO_COMMENT; + } else if ("fixme".equalsIgnoreCase(commentText.substring(0, 5))) { + commentType = SchemeAndroidIDE.FIXME_COMMENT; + } else { + mark = false; + } + + if (mark) { + if (diagnostics == null) { + diagnostics = new ArrayList<>(); + } + final var diagnostic = new DiagnosticItem(); + diagnostic.setSeverity(DiagnosticSeverity.WARNING); + diagnostic.setMessage(commentText); + diagnostic.setCode("special.comment"); + diagnostic.setRange( + new Range( + new Position(line, column), + new Position(line, column + tokenLength))); + diagnostic.setSource(commentText); + ideDiagnostics.add(diagnostic); + } + } + + colors.addIfNeeded(line, column, withoutCompletion(commentType)); + + break; + case JavaLexer.AT: + colors.addIfNeeded(line, column, get(ANNOTATION)); + break; + case JavaLexer.IDENTIFIER: + int colorId = SchemeAndroidIDE.TEXT_NORMAL; + if (previous == JavaLexer.AT) { + colorId = SchemeAndroidIDE.ANNOTATION; + } + colors.addIfNeeded(line, column, get(colorId)); + break; + case JavaLexer.LBRACE: + colors.addIfNeeded(line, column, get(OPERATOR)); + if (stack.isEmpty()) { + if (currSwitch > maxSwitch) { + maxSwitch = currSwitch; + } + currSwitch = 0; + } + currSwitch++; + var block = styles.obtainNewBlock(); + block.startLine = line; + block.startColumn = column; + stack.push(block); + break; + case JavaLexer.RBRACE: + colors.addIfNeeded(line, column, get(OPERATOR)); + if (!stack.isEmpty()) { + var block2 = stack.pop(); + block2.endLine = line; + block2.endColumn = column; + if (block2.startLine != block2.endLine) { + styles.addCodeBlock(block2); + } + } + break; + default: + colors.addIfNeeded(line, column, get(TEXT_NORMAL)); + break; } - currSwitch = 0; - } - currSwitch++; - var block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - stack.push(block); - break; - case JavaLexer.RBRACE: - colors.addIfNeeded(line, column, get(OPERATOR)); - if (!stack.isEmpty()) { - var block2 = stack.pop(); - block2.endLine = line; - block2.endColumn = column; - if (block2.startLine != block2.endLine) { - styles.addCodeBlock(block2); + + isFirst = false; + + if (type != JavaLexer.WS) { + previous = type; } - } - break; - default: - colors.addIfNeeded(line, column, get(TEXT_NORMAL)); - break; - } + } - isFirst = false; + if (stack.isEmpty() && currSwitch > maxSwitch) { + maxSwitch = currSwitch; + } - if (type != JavaLexer.WS) { - previous = type; - } + colors.determine(lastLine); + styles.setSuppressSwitch(maxSwitch + 10); + + if (diagnostics != null && !diagnostics.isEmpty()) { + markDiagnostics(colors, diagnostics); + } + + if (!ideDiagnostics.isEmpty()) { + markDiagnostics(colors, ideDiagnostics); + } + + styles.spans = colors.build(); + + return styles; } - if (stack.isEmpty() && currSwitch > maxSwitch) { - maxSwitch = currSwitch; + private void markDiagnostics(MappedSpans.Builder colors, List diagnostics) { + diagnostics.sort(DiagnosticItem.START_COMPARATOR); + for (var d : diagnostics) { + var start = d.getRange().getStart(); + var end = d.getRange().getEnd(); + + colors.markProblemRegion( + convertToSpanFlag(d.getSeverity()), + start.getLine(), + start.getColumn(), + end.getLine(), + end.getColumn()); + } } - colors.determine(lastLine); - styles.setSuppressSwitch(maxSwitch + 10); - - if (diagnostics != null && !diagnostics.isEmpty()) { - markDiagnostics(colors, diagnostics); + @Contract(pure = true) + private int convertToSpanFlag(@NonNull DiagnosticSeverity severity) { + switch (severity) { + case WARNING: + return Span.FLAG_WARNING; + case ERROR: + return Span.FLAG_ERROR; + case HINT: + case INFO: + default: + return Span.FLAG_TYPO; + } } - if (!ideDiagnostics.isEmpty()) { - markDiagnostics(colors, ideDiagnostics); - } - - styles.spans = colors.build(); - - return styles; - } - - private void markDiagnostics(MappedSpans.Builder colors, List diagnostics) { - diagnostics.sort(DiagnosticItem.START_COMPARATOR); - for (var d : diagnostics) { - var start = d.getRange().getStart(); - var end = d.getRange().getEnd(); - - colors.markProblemRegion( - convertToSpanFlag(d.getSeverity()), - start.getLine(), - start.getColumn(), - end.getLine(), - end.getColumn()); - } - } - - @Contract(pure = true) - private int convertToSpanFlag(@NonNull DiagnosticSeverity severity) { - switch (severity) { - case WARNING: - return Span.FLAG_WARNING; - case ERROR: - return Span.FLAG_ERROR; - case HINT: - case INFO: - default: - return Span.FLAG_TYPO; - } - } - - private static final Logger LOG = Logger.instance("JavaAnalyzer"); + private static final Logger LOG = Logger.instance("JavaAnalyzer"); } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java index e293efcd6..ca8e019eb 100755 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java @@ -18,7 +18,9 @@ package com.itsaky.androidide.language.java; 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; @@ -27,6 +29,7 @@ import com.itsaky.androidide.lexers.java.JavaParser; import com.itsaky.androidide.utils.Logger; import com.itsaky.androidide.views.editor.IDEEditor; import com.itsaky.lsp.models.DiagnosticItem; + import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; import io.github.rosemoe.sora.lang.completion.CompletionPublisher; @@ -36,153 +39,163 @@ import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.ContentReference; import io.github.rosemoe.sora.text.TextUtils; import io.github.rosemoe.sora.widget.SymbolPairMatch; + +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 java.util.Collections; import java.util.List; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; public class JavaLanguage extends IDELanguage { - private JavaAnalyzer analyzer; - private CommonCompletionProvider completer; + private JavaAnalyzer analyzer; + private CommonCompletionProvider completer; - private final NewlineHandler[] newlineHandlers; + private final NewlineHandler[] newlineHandlers; - private static final Logger LOG = Logger.instance("JavaLanguage"); + private static final Logger LOG = Logger.instance("JavaLanguage"); - public JavaLanguage() { - final var server = StudioApp.getInstance().getJavaLanguageServer(); - this.analyzer = new JavaAnalyzer(server); - this.completer = new CommonCompletionProvider(server); + public JavaLanguage() { + final var server = StudioApp.getInstance().getJavaLanguageServer(); + this.analyzer = new JavaAnalyzer(server); + this.completer = new CommonCompletionProvider(server); - this.newlineHandlers = new NewlineHandler[1]; - this.newlineHandlers[0] = new BraceHandler(); - } + this.newlineHandlers = new NewlineHandler[1]; + this.newlineHandlers[0] = new BraceHandler(); + } - @NonNull - @Override - public List getDiagnostics() { - return analyzer == null ? Collections.emptyList() : analyzer.getDiagnostics(); - } + @NonNull + @Override + public List getDiagnostics() { + return analyzer == null ? Collections.emptyList() : analyzer.getDiagnostics(); + } - public int getIndentAdvance(String p1) { - try { - JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(p1))); - Token token; - int advance = 0; - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - switch (token.getType()) { - case JavaLexer.LBRACE: - advance++; - break; - case JavaParser.RBRACE: - advance--; - break; + public int getIndentAdvance(String p1) { + try { + JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(p1))); + Token token; + int advance = 0; + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + switch (token.getType()) { + case JavaLexer.LBRACE: + advance++; + break; + case JavaParser.RBRACE: + advance--; + break; + } + } + advance = Math.max(0, advance); + return advance * getTabSize(); + } catch (Throwable e) { + LOG.error("Error calculating indent advance", e); } - } - advance = Math.max(0, advance); - return advance * getTabSize(); - } catch (Throwable e) { - LOG.error("Error calculating indent advance", e); - } - return 0; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new JavaSymbolPairs(); - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return analyzer; - } - - @Override - public int getInterruptionLevel() { - return 0; - } - - @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::checkJavaCompletionChar))); - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return getIndentAdvance(content.getLine(line).substring(0, column)); - } - - @Override - public boolean useTab() { - return false; - } - - @Override - public CharSequence format(CharSequence content) { - return content; - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return newlineHandlers; - } - - @Override - public void destroy() { - analyzer = null; - completer = null; - } - - class BraceHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.endsWith("{") && afterText.startsWith("}"); + return 0; } @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText); - int advanceAfter = getIndentAdvance(afterText); - String text; - StringBuilder sb = - new StringBuilder("\n") - .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) - .append('\n') - .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); + public SymbolPairMatch getSymbolPairs() { + return new JavaSymbolPairs(); } - } - private static class JavaSymbolPairs extends SymbolPairMatch { - public JavaSymbolPairs() { - super.putPair('{', new Replacement("{}", 1)); - super.putPair('(', new Replacement("()", 1)); - super.putPair('[', new Replacement("[]", 1)); - super.putPair('"', new Replacement("\"\"", 1)); - super.putPair('\'', new Replacement("''", 1)); - super.putPair('<', new Replacement("<>", 1)); + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return analyzer; + } + + @Override + public int getInterruptionLevel() { + return 0; + } + + @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::checkJavaCompletionChar))); + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return getIndentAdvance(content.getLine(line).substring(0, column)); + } + + @Override + public boolean useTab() { + return false; + } + + @Override + public CharSequence format(CharSequence content) { + return content; + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return newlineHandlers; + } + + @Override + public void destroy() { + analyzer = null; + completer = null; + } + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith("{") && afterText.startsWith("}"); + } + + @Override + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = + new StringBuilder("\n") + .append( + TextUtils.createIndent( + count + advanceBefore, tabSize, useTab())) + .append('\n') + .append( + text = + TextUtils.createIndent( + count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); + } + } + + private static class JavaSymbolPairs extends SymbolPairMatch { + public JavaSymbolPairs() { + super.putPair('{', new Replacement("{}", 1)); + super.putPair('(', new Replacement("()", 1)); + super.putPair('[', new Replacement("[]", 1)); + super.putPair('"', new Replacement("\"\"", 1)); + super.putPair('\'', new Replacement("''", 1)); + super.putPair('<', new Replacement("<>", 1)); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java index 9cf5d2c1a..3067cc108 100755 --- a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java +++ b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java @@ -18,10 +18,13 @@ package com.itsaky.androidide.language.logs; import android.os.Bundle; + import androidx.annotation.NonNull; + import com.itsaky.androidide.language.IDELanguage; import com.itsaky.androidide.models.LogLine; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; + import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; import io.github.rosemoe.sora.lang.analysis.SimpleAnalyzeManager; import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; @@ -32,90 +35,91 @@ import io.github.rosemoe.sora.lang.styling.Styles; import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.ContentReference; import io.github.rosemoe.sora.widget.SymbolPairMatch; + import java.util.ArrayList; import java.util.List; public class LogLanguageImpl extends IDELanguage { - private static final LogAnalyzer analyzer = new LogAnalyzer(); + private static final LogAnalyzer analyzer = new LogAnalyzer(); - public void addLine(LogLine line) { - analyzer.addLine(line); - } + public void addLine(LogLine line) { + analyzer.addLine(line); + } - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return analyzer; - } - - @Override - public int getInterruptionLevel() { - return 0; - } - - @Override - public void requireAutoComplete( - @NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) - throws CompletionCancelledException {} - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return 0; - } - - @Override - public boolean useTab() { - return false; - } - - @Override - public CharSequence format(CharSequence text) { - return text; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return new NewlineHandler[0]; - } - - @Override - public void destroy() {} - - private static class LogAnalyzer extends SimpleAnalyzeManager { - - private static final List lines = new ArrayList<>(); - - public LogAnalyzer addLine(LogLine line) { - if (line != null) lines.add(line); - - return this; + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return analyzer; } @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - final var styles = new Styles(); - final var colors = new MappedSpans.Builder(); - int lastLine = 0; - for (int i = 0; i < lines.size() && !delegate.isCancelled(); i++) { - if (i == 0) colors.addNormalIfNull(); - LogLine line = lines.get(i); - colors.addIfNeeded(i, 0, SchemeAndroidIDE.get(line.priority)); - lastLine = i; - } - colors.determine(lastLine); - - styles.spans = colors.build(); - - return styles; + public int getInterruptionLevel() { + return 0; + } + + @Override + public void requireAutoComplete( + @NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) + throws CompletionCancelledException {} + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return 0; + } + + @Override + public boolean useTab() { + return false; + } + + @Override + public CharSequence format(CharSequence text) { + return text; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[0]; + } + + @Override + public void destroy() {} + + private static class LogAnalyzer extends SimpleAnalyzeManager { + + private static final List lines = new ArrayList<>(); + + public LogAnalyzer addLine(LogLine line) { + if (line != null) lines.add(line); + + return this; + } + + @Override + protected Styles analyze(StringBuilder text, Delegate delegate) { + final var styles = new Styles(); + final var colors = new MappedSpans.Builder(); + int lastLine = 0; + for (int i = 0; i < lines.size() && !delegate.isCancelled(); i++) { + if (i == 0) colors.addNormalIfNull(); + LogLine line = lines.get(i); + colors.addIfNeeded(i, 0, SchemeAndroidIDE.get(line.priority)); + lastLine = i; + } + colors.determine(lastLine); + + styles.spans = colors.build(); + + return styles; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java index 00f21573f..2ca58de72 100755 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java @@ -21,22 +21,26 @@ import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.OPERATO import static com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.TEXT_SELECTED; 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.Logger; + 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; -import java.io.IOException; -import java.util.Stack; + 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; + /** * Simple analyzer implementation for the XML files. * @@ -44,109 +48,109 @@ import org.antlr.v4.runtime.Token; */ public class XMLAnalyzer extends SimpleAnalyzeManager { - private static final Logger LOG = Logger.instance("XMLAnalyzer"); + private static final Logger LOG = Logger.instance("XMLAnalyzer"); - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - final var styles = new Styles(); - final var colors = new MappedSpans.Builder(); + @Override + protected Styles analyze(StringBuilder text, Delegate 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; + 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(); + + 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(TEXT_SELECTED)); + break; + } + first = false; + if (token.getType() != XMLLexer.SEA_WS) { + previous = token.getType(); + } + } + colors.determine(lastLine); + + styles.spans = colors.build(); + + 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(); - - 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(TEXT_SELECTED)); - break; - } - first = false; - if (token.getType() != XMLLexer.SEA_WS) { - previous = token.getType(); - } - } - colors.determine(lastLine); - - styles.spans = colors.build(); - - return styles; - } } diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java index d8e12e325..a5872ccba 100755 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java @@ -18,7 +18,9 @@ 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; @@ -26,6 +28,7 @@ import com.itsaky.androidide.lexers.xml.XMLLexer; import com.itsaky.androidide.utils.JavaCharacter; import com.itsaky.androidide.utils.Logger; import com.itsaky.androidide.views.editor.IDEEditor; + import io.github.rosemoe.sora.lang.analysis.AnalyzeManager; import io.github.rosemoe.sora.lang.completion.CompletionCancelledException; import io.github.rosemoe.sora.lang.completion.CompletionPublisher; @@ -33,117 +36,123 @@ 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; -import java.io.StringReader; -import java.nio.file.Paths; -import java.util.ArrayList; + 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; + public class XMLLanguage extends IDELanguage { - private XMLAnalyzer analyzer; - private final CommonCompletionProvider completer; - private final NewlineHandler[] newlineHandlers; + private XMLAnalyzer analyzer; + private final CommonCompletionProvider completer; + private final NewlineHandler[] newlineHandlers; - private static final Logger LOG = Logger.instance("XMLLanguage"); + private static final Logger LOG = Logger.instance("XMLLanguage"); - public XMLLanguage() { - this.completer = new CommonCompletionProvider(StudioApp.getInstance().getXMLLanguageServer()); - this.analyzer = new XMLAnalyzer(); - this.newlineHandlers = new NewlineHandler[0]; - } + public XMLLanguage() { + this.completer = + new CommonCompletionProvider(StudioApp.getInstance().getXMLLanguageServer()); + this.analyzer = new XMLAnalyzer(); + this.newlineHandlers = new NewlineHandler[0]; + } - public boolean isAutoCompleteChar(char ch) { - return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/'; - } + public boolean isAutoCompleteChar(char ch) { + return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/'; + } - public int getIndentAdvance(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: - case XMLLexer.OPEN_SLASH: - case XMLLexer.XMLDeclOpen: - advance++; - break; - case XMLLexer.CLOSE: - case XMLLexer.SLASH_CLOSE: - case XMLLexer.SPECIAL_CLOSE: - advance--; - break; - default: - break; + public int getIndentAdvance(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: + case XMLLexer.OPEN_SLASH: + case XMLLexer.XMLDeclOpen: + advance++; + break; + case XMLLexer.CLOSE: + case XMLLexer.SLASH_CLOSE: + case XMLLexer.SPECIAL_CLOSE: + advance--; + break; + default: + break; + } + } + advance = Math.max(0, advance); + return advance * getTabSize(); + } catch (Throwable e) { + LOG.error("Failed to compute indent advance", e); } - } - 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; + return 0; } - 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 SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - final var text = content.getLine(line).substring(0, column); - return getIndentAdvance(text); - } + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return analyzer; + } - @Override - public boolean useTab() { - return false; - } + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_STRONG; + } - @Override - public CharSequence format(CharSequence content) { - return content; - } + @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; + } - @Override - public NewlineHandler[] getNewlineHandlers() { - return newlineHandlers; - } + 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 void destroy() { - analyzer = null; - } + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + final var text = content.getLine(line).substring(0, column); + return getIndentAdvance(text); + } + + @Override + public boolean useTab() { + return false; + } + + @Override + public CharSequence format(CharSequence content) { + return content; + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return newlineHandlers; + } + + @Override + public void destroy() { + analyzer = null; + } } diff --git a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java index 4659007f1..a42955e76 100755 --- a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java @@ -20,6 +20,7 @@ package com.itsaky.androidide.lsp; import android.view.View; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -47,7 +48,9 @@ import com.itsaky.lsp.models.Location; import com.itsaky.lsp.models.Range; import com.itsaky.lsp.models.TextEdit; import com.itsaky.toaster.Toaster; + import io.github.rosemoe.sora.text.Content; + import java.io.File; import java.util.ArrayList; import java.util.HashMap; @@ -58,318 +61,329 @@ import java.util.Objects; /** AndroidIDE specific implementation of the LanguageClient */ public class IDELanguageClientImpl implements ILanguageClient { - protected static final Gson gson = new Gson(); - protected static final Logger LOG = Logger.instance("AbstractLanguageClient"); + protected static final Gson gson = new Gson(); + protected static final Logger LOG = Logger.instance("AbstractLanguageClient"); - private final Map> diagnostics = new HashMap<>(); - private static IDELanguageClientImpl mInstance; + private final Map> diagnostics = new HashMap<>(); + private static IDELanguageClientImpl mInstance; - protected EditorActivityProvider activityProvider; + protected EditorActivityProvider activityProvider; - private IDELanguageClientImpl(EditorActivityProvider provider) { - setActivityProvider(provider); - } - - public static IDELanguageClientImpl getInstance() { - if (mInstance == null) { - throw new IllegalStateException("Client not initialized"); + private IDELanguageClientImpl(EditorActivityProvider provider) { + setActivityProvider(provider); } - 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; - - diagnostics.put(file, result.getDiagnostics()); - activity().setDiagnosticsAdapter(newDiagnosticsAdapter()); - } - - /** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */ - public void showLocations(List 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> results = new HashMap<>(); - for (int i = 0; i < locations.size(); i++) { - try { - final Location loc = locations.get(i); - if (loc == null) { - continue; + public static IDELanguageClientImpl getInstance() { + if (mInstance == null) { + throw new IllegalStateException("Client not initialized"); } - 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 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); - } + return mInstance; } - activity().handleSearchResults(results); - } + public static IDELanguageClientImpl initialize(EditorActivityProvider provider) { + if (mInstance != null) { + throw new IllegalStateException("Client is already initialized"); + } - /** - * 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) { - StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); - return; + mInstance = new IDELanguageClientImpl(provider); + + return getInstance(); } - final ProgressSheet progress = new ProgressSheet(); - progress.setSubMessageEnabled(false); - progress.setWelcomeTextEnabled(false); - progress.setCancelable(false); - progress.setMessage(activity().getString(R.string.msg_performing_fixes)); - progress.show(activity().getSupportFragmentManager(), "quick_fix_progress"); - - new TaskExecutor() - .executeAsyncProvideError( - () -> performCodeActionAsync(editor, action), - (a, b) -> { - progress.dismiss(); - if (a == null || b != null || !a) { - StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); - } - }); - } - - private List mapAsGroup(Map> diags) { - List 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 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 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) { - final var changes = action.getChanges(); - if (changes.isEmpty()) { - return Boolean.FALSE; + public static void shutdown() { + mInstance = null; } - for (var change : changes) { - final var path = change.getFile(); - if (path == null) { - continue; - } + public static boolean isInitialized() { + return mInstance != null; + } - final File file = path.toFile(); - if (!file.exists()) { - continue; - } + public void setActivityProvider(EditorActivityProvider provider) { + this.activityProvider = provider; + } - 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); + protected EditorActivity activity() { + if (activityProvider == null) return null; + return activityProvider.provide(); + } - 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); + 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; + + diagnostics.put(file, result.getDiagnostics()); + activity().setDiagnosticsAdapter(newDiagnosticsAdapter()); + } + + /** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */ + public void showLocations(List 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> 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 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); } - return Boolean.TRUE; - } - - 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() - .setSelectionRegion( - range.getStart().getLine(), - range.getStart().getColumn(), - range.getEnd().getLine(), - range.getEnd().getColumn()); - } - } else { - activity().openFileAndSelect(file, range); + /** + * 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) { + StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); + return; } - success = true; - } + + final ProgressSheet progress = new ProgressSheet(); + progress.setSubMessageEnabled(false); + progress.setWelcomeTextEnabled(false); + progress.setCancelable(false); + progress.setMessage(activity().getString(R.string.msg_performing_fixes)); + progress.show(activity().getSupportFragmentManager(), "quick_fix_progress"); + + new TaskExecutor() + .executeAsyncProvideError( + () -> performCodeActionAsync(editor, action), + (a, b) -> { + progress.dismiss(); + if (a == null || b != null || !a) { + StudioApp.getInstance() + .toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); + } + }); } - result.setSuccess(success); - return result; - } + private List mapAsGroup(Map> diags) { + List 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 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 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) { + 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 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() + .setSelectionRegion( + range.getStart().getLine(), + range.getStart().getColumn(), + range.getEnd().getLine(), + range.getEnd().getColumn()); + } + } else { + activity().openFileAndSelect(file, range); + } + success = true; + } + } + + result.setSuccess(success); + return result; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/ApkMetadata.java b/app/src/main/java/com/itsaky/androidide/models/ApkMetadata.java index 9d37bd260..caf96cb42 100755 --- a/app/src/main/java/com/itsaky/androidide/models/ApkMetadata.java +++ b/app/src/main/java/com/itsaky/androidide/models/ApkMetadata.java @@ -18,212 +18,213 @@ package com.itsaky.androidide.models; import com.google.gson.annotations.SerializedName; + import java.util.List; public class ApkMetadata { - @SerializedName("version") - private int version; + @SerializedName("version") + private int version; - @SerializedName("artifactType") - private ArtifactType artifactType; + @SerializedName("artifactType") + private ArtifactType artifactType; - @SerializedName("applicationId") - private String applicationId; + @SerializedName("applicationId") + private String applicationId; - @SerializedName("variantName") - private String variantName; + @SerializedName("variantName") + private String variantName; - @SerializedName("elements") - private List elements; + @SerializedName("elements") + private List elements; - @SerializedName("elementType") - private String elementType; + @SerializedName("elementType") + private String elementType; - public void setVersion(int version) { - this.version = version; - } - - public void setArtifactType(ArtifactType artifactType) { - this.artifactType = artifactType; - } - - public void setApplicationId(String applicationId) { - this.applicationId = applicationId; - } - - public void setVariantName(String variantName) { - this.variantName = variantName; - } - - public void setElements(List elements) { - this.elements = elements; - } - - public void setElementType(String elementType) { - this.elementType = elementType; - } - - public int getVersion() { - return version; - } - - public ArtifactType getArtifactType() { - return artifactType; - } - - public String getApplicationId() { - return applicationId; - } - - public String getVariantName() { - return variantName; - } - - public List getElements() { - return elements; - } - - public String getElementType() { - return elementType; - } - - public static class ArtifactType { - - public static String TYPE_APK = "APK"; - - @SerializedName("type") - private String type; - - @SerializedName("kind") - private String kind; - - public void setType(String type) { - this.type = type; + public void setVersion(int version) { + this.version = version; } - public void setKind(String kind) { - this.kind = kind; + public void setArtifactType(ArtifactType artifactType) { + this.artifactType = artifactType; } - public String getType() { - return type; + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; } - public String getKind() { - return kind; + public void setVariantName(String variantName) { + this.variantName = variantName; + } + + public void setElements(List elements) { + this.elements = elements; + } + + public void setElementType(String elementType) { + this.elementType = elementType; + } + + public int getVersion() { + return version; + } + + public ArtifactType getArtifactType() { + return artifactType; + } + + public String getApplicationId() { + return applicationId; + } + + public String getVariantName() { + return variantName; + } + + public List getElements() { + return elements; + } + + public String getElementType() { + return elementType; + } + + public static class ArtifactType { + + public static String TYPE_APK = "APK"; + + @SerializedName("type") + private String type; + + @SerializedName("kind") + private String kind; + + public void setType(String type) { + this.type = type; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public String getType() { + return type; + } + + public String getKind() { + return kind; + } + + @Override + public String toString() { + return "[" + getClass().getCanonicalName() + "]" + "\ntype=" + type + ",\nkind=" + kind; + } + } + + public static class Element { + + @SerializedName("type") + private String type; + + @SerializedName("filters") + private List filters; + + @SerializedName("attributes") + private List attributes; + + @SerializedName("versionCode") + private int versionCode; + + @SerializedName("versionName") + private String versionName; + + @SerializedName("outputFile") + private String outputFile; + + public void setType(String type) { + this.type = type; + } + + public void setFilters(List filters) { + this.filters = filters; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + public void setVersionCode(int versionCode) { + this.versionCode = versionCode; + } + + public void setVersionName(String versionName) { + this.versionName = versionName; + } + + public void setOutputFile(String outputFile) { + this.outputFile = outputFile; + } + + public String getType() { + return type; + } + + public List getFilters() { + return filters; + } + + public List getAttributes() { + return attributes; + } + + public int getVersionCode() { + return versionCode; + } + + public String getVersionName() { + return versionName; + } + + public String getOutputFile() { + return outputFile; + } + + @Override + public String toString() { + return "[" + + getClass().getCanonicalName() + + "]" + + "\ntype=" + + type + + ",\nfilters=" + + filters + + ",\nattributes=" + + attributes + + ",\nversionCode=" + + versionCode + + ",\nversionName=" + + versionName + + ",\noutputFile=" + + outputFile; + } } @Override public String toString() { - return "[" + getClass().getCanonicalName() + "]" + "\ntype=" + type + ",\nkind=" + kind; + return "[" + + getClass().getCanonicalName() + + "]" + + "\nversion=" + + version + + ",\nartifactType=" + + artifactType + + ",\napplicationId=" + + applicationId + + ",\nvariantName=" + + variantName + + ",\nelements=" + + elements + + ",\nelementType=" + + elementType; } - } - - public static class Element { - - @SerializedName("type") - private String type; - - @SerializedName("filters") - private List filters; - - @SerializedName("attributes") - private List attributes; - - @SerializedName("versionCode") - private int versionCode; - - @SerializedName("versionName") - private String versionName; - - @SerializedName("outputFile") - private String outputFile; - - public void setType(String type) { - this.type = type; - } - - public void setFilters(List filters) { - this.filters = filters; - } - - public void setAttributes(List attributes) { - this.attributes = attributes; - } - - public void setVersionCode(int versionCode) { - this.versionCode = versionCode; - } - - public void setVersionName(String versionName) { - this.versionName = versionName; - } - - public void setOutputFile(String outputFile) { - this.outputFile = outputFile; - } - - public String getType() { - return type; - } - - public List getFilters() { - return filters; - } - - public List getAttributes() { - return attributes; - } - - public int getVersionCode() { - return versionCode; - } - - public String getVersionName() { - return versionName; - } - - public String getOutputFile() { - return outputFile; - } - - @Override - public String toString() { - return "[" - + getClass().getCanonicalName() - + "]" - + "\ntype=" - + type - + ",\nfilters=" - + filters - + ",\nattributes=" - + attributes - + ",\nversionCode=" - + versionCode - + ",\nversionName=" - + versionName - + ",\noutputFile=" - + outputFile; - } - } - - @Override - public String toString() { - return "[" - + getClass().getCanonicalName() - + "]" - + "\nversion=" - + version - + ",\nartifactType=" - + artifactType - + ",\napplicationId=" - + applicationId - + ",\nvariantName=" - + variantName - + ",\nelements=" - + elements - + ",\nelementType=" - + elementType; - } } diff --git a/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java b/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java index 6c2230d78..da8095cf0 100755 --- a/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java +++ b/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java @@ -18,19 +18,20 @@ package com.itsaky.androidide.models; import com.itsaky.lsp.models.DiagnosticItem; + import java.io.File; import java.util.List; public class DiagnosticGroup { - public int icon; - public String text; - public File file; - public List diagnostics; + public int icon; + public String text; + public File file; + public List diagnostics; - public DiagnosticGroup(int icon, File file, List diagnostics) { - this.icon = icon; - this.file = file; - this.text = file.getName(); - this.diagnostics = diagnostics; - } + public DiagnosticGroup(int icon, File file, List diagnostics) { + this.icon = icon; + this.file = file; + this.text = file.getName(); + this.diagnostics = diagnostics; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/IconTextListItem.java b/app/src/main/java/com/itsaky/androidide/models/IconTextListItem.java index 13df30bf4..16d8427de 100755 --- a/app/src/main/java/com/itsaky/androidide/models/IconTextListItem.java +++ b/app/src/main/java/com/itsaky/androidide/models/IconTextListItem.java @@ -21,21 +21,21 @@ import androidx.annotation.DrawableRes; public interface IconTextListItem { - String getText(); + String getText(); - int getIconResource(); + int getIconResource(); - public static IconTextListItem create(String name, @DrawableRes int icon) { - return new IconTextListItem() { - @Override - public String getText() { - return name; - } + public static IconTextListItem create(String name, @DrawableRes int icon) { + return new IconTextListItem() { + @Override + public String getText() { + return name; + } - @Override - public int getIconResource() { - return icon; - } - }; - } + @Override + public int getIconResource() { + return icon; + } + }; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/License.java b/app/src/main/java/com/itsaky/androidide/models/License.java index 1c3095e6b..55e377a08 100755 --- a/app/src/main/java/com/itsaky/androidide/models/License.java +++ b/app/src/main/java/com/itsaky/androidide/models/License.java @@ -20,18 +20,18 @@ package com.itsaky.androidide.models; import com.google.gson.annotations.SerializedName; public class License { - @SerializedName("name") - public String name; + @SerializedName("name") + public String name; - @SerializedName("license") - public String license; + @SerializedName("license") + public String license; - @SerializedName("url") - public String url; + @SerializedName("url") + public String url; - public License(String name, String license, String url) { - this.name = name; - this.license = license; - this.url = url; - } + public License(String name, String license, String url) { + this.name = name; + this.license = license; + this.url = url; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/LogLine.java b/app/src/main/java/com/itsaky/androidide/models/LogLine.java index 3abb3ebcb..0186c1445 100755 --- a/app/src/main/java/com/itsaky/androidide/models/LogLine.java +++ b/app/src/main/java/com/itsaky/androidide/models/LogLine.java @@ -18,126 +18,130 @@ package com.itsaky.androidide.models; import android.content.Context; + import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; + import com.itsaky.androidide.R; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; + import java.util.Locale; public class LogLine { - public String unformatted; + public String unformatted; - public String date; - public String time; - public String pid; - public String tid; - public String priorityChar; - public String tag; - public String message; + public String date; + public String time; + public String pid; + public String tid; + public String priorityChar; + public String tag; + public String message; - public int priority; + public int priority; - public boolean formatted; + public boolean formatted; - // It makes things easier in LogLanguageImpl - public static final int INFO = SchemeAndroidIDE.LOG_INFO; - public static final int DEBUG = SchemeAndroidIDE.LOG_DEBUG; - public static final int ERROR = SchemeAndroidIDE.LOG_ERROR; - public static final int WARNING = SchemeAndroidIDE.LOG_WARNING; + // It makes things easier in LogLanguageImpl + public static final int INFO = SchemeAndroidIDE.LOG_INFO; + public static final int DEBUG = SchemeAndroidIDE.LOG_DEBUG; + public static final int ERROR = SchemeAndroidIDE.LOG_ERROR; + public static final int WARNING = SchemeAndroidIDE.LOG_WARNING; - public LogLine( - String date, - String time, - String pid, - String tid, - String priorityChar, - String tag, - String message) { - this(date, time, pid, tid, priorityChar, tag, message, true); - } - - public LogLine(String unformatted) { - this.unformatted = unformatted; - this.formatted = false; - } - - public LogLine( - String date, - String time, - String pid, - String tid, - String priorityChar, - @NonNull String tag, - String message, - boolean formatted) { - this.date = date; - this.time = time; - this.pid = pid; - this.tid = tid; - this.priorityChar = priorityChar; - this.tag = tag; - this.message = message; - this.priority = parsePriority(priorityChar); - this.formatted = formatted; - - if (tag.length() > 25) { - this.tag = "...".concat(tag.substring(tag.length() - 25)); + public LogLine( + String date, + String time, + String pid, + String tid, + String priorityChar, + String tag, + String message) { + this(date, time, pid, tid, priorityChar, tag, message, true); } - } - @NonNull - public static LogLine forLogString(@NonNull final String log) { - try { - final var split = log.split("\\s", 7); - return new LogLine( - split[0], // date - split[1], // time - split[2], // process id - split[3], // thread id - split[4], // priority - split[5], // tag - split[6] // message - ); - } catch (Throwable th) { - return new LogLine(log); + public LogLine(String unformatted) { + this.unformatted = unformatted; + this.formatted = false; } - } - private int parsePriority(String s) { - if (s == null || s.trim().length() > 1) { - return DEBUG; - } - char c = s.toLowerCase(Locale.US).charAt(0); - if (c == 'w') { - return WARNING; - } else if (c == 'e') { - return ERROR; - } else if (c == 'i') { - return INFO; - } else { - return DEBUG; - } - } + public LogLine( + String date, + String time, + String pid, + String tid, + String priorityChar, + @NonNull String tag, + String message, + boolean formatted) { + this.date = date; + this.time = time; + this.pid = pid; + this.tid = tid; + this.priorityChar = priorityChar; + this.tag = tag; + this.message = message; + this.priority = parsePriority(priorityChar); + this.formatted = formatted; - public int getColor(Context ctx) { - int id = R.color.log_normal; - if (priority == WARNING) { - id = R.color.log_warning; - } else if (priority == ERROR) { - id = R.color.log_error; - } else if (priority == INFO) { - id = R.color.log_info; + if (tag.length() > 25) { + this.tag = "...".concat(tag.substring(tag.length() - 25)); + } } - return ContextCompat.getColor(ctx, id); - } - @NonNull - @Override - public String toString() { - return this.formatted - ? String.format( - "%-6s %-13s %-6s %-6s %-2s %-40s %s", date, time, pid, tid, priorityChar, tag, message) - : this.unformatted; - } + @NonNull + public static LogLine forLogString(@NonNull final String log) { + try { + final var split = log.split("\\s", 7); + return new LogLine( + split[0], // date + split[1], // time + split[2], // process id + split[3], // thread id + split[4], // priority + split[5], // tag + split[6] // message + ); + } catch (Throwable th) { + return new LogLine(log); + } + } + + private int parsePriority(String s) { + if (s == null || s.trim().length() > 1) { + return DEBUG; + } + char c = s.toLowerCase(Locale.US).charAt(0); + if (c == 'w') { + return WARNING; + } else if (c == 'e') { + return ERROR; + } else if (c == 'i') { + return INFO; + } else { + return DEBUG; + } + } + + public int getColor(Context ctx) { + int id = R.color.log_normal; + if (priority == WARNING) { + id = R.color.log_warning; + } else if (priority == ERROR) { + id = R.color.log_error; + } else if (priority == INFO) { + id = R.color.log_info; + } + return ContextCompat.getColor(ctx, id); + } + + @NonNull + @Override + public String toString() { + return this.formatted + ? String.format( + "%-6s %-13s %-6s %-6s %-2s %-40s %s", + date, time, pid, tid, priorityChar, tag, message) + : this.unformatted; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/NewProjectDetails.java b/app/src/main/java/com/itsaky/androidide/models/NewProjectDetails.java index a1360c44b..2201edaff 100755 --- a/app/src/main/java/com/itsaky/androidide/models/NewProjectDetails.java +++ b/app/src/main/java/com/itsaky/androidide/models/NewProjectDetails.java @@ -19,53 +19,53 @@ package com.itsaky.androidide.models; public class NewProjectDetails { - public String name; - public String packageName; - public int minSdk; - public int targetSdk; + public String name; + public String packageName; + public int minSdk; + public int targetSdk; - public NewProjectDetails() {} + public NewProjectDetails() {} - public NewProjectDetails(String name, String packageName, int minSdk, int targetSdk) { - this.name = name; - this.packageName = packageName; - this.minSdk = minSdk; - this.targetSdk = targetSdk; - } + public NewProjectDetails(String name, String packageName, int minSdk, int targetSdk) { + this.name = name; + this.packageName = packageName; + this.minSdk = minSdk; + this.targetSdk = targetSdk; + } - public NewProjectDetails setName(String name) { - this.name = name; - return this; - } + public NewProjectDetails setName(String name) { + this.name = name; + return this; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public NewProjectDetails setPackageName(String packageName) { - this.packageName = packageName; - return this; - } + public NewProjectDetails setPackageName(String packageName) { + this.packageName = packageName; + return this; + } - public String getPackageName() { - return packageName; - } + public String getPackageName() { + return packageName; + } - public NewProjectDetails setMinSdk(int minSdk) { - this.minSdk = minSdk; - return this; - } + public NewProjectDetails setMinSdk(int minSdk) { + this.minSdk = minSdk; + return this; + } - public int getMinSdk() { - return minSdk; - } + public int getMinSdk() { + return minSdk; + } - public NewProjectDetails setTargetSdk(int targetSdk) { - this.targetSdk = targetSdk; - return this; - } + public NewProjectDetails setTargetSdk(int targetSdk) { + this.targetSdk = targetSdk; + return this; + } - public int getTargetSdk() { - return targetSdk; - } + public int getTargetSdk() { + return targetSdk; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/PrefBasedJavaServerSettings.java b/app/src/main/java/com/itsaky/androidide/models/PrefBasedJavaServerSettings.java index a969bddf3..a0a60bcef 100644 --- a/app/src/main/java/com/itsaky/androidide/models/PrefBasedJavaServerSettings.java +++ b/app/src/main/java/com/itsaky/androidide/models/PrefBasedJavaServerSettings.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.models; import androidx.annotation.NonNull; + import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.managers.PreferenceManager; import com.itsaky.lsp.java.models.DefaultJavaServerSettings; @@ -29,27 +30,27 @@ import com.itsaky.lsp.java.models.DefaultJavaServerSettings; */ public class PrefBasedJavaServerSettings extends DefaultJavaServerSettings { - public static final String KEY_JAVA_PREF_MATCH_LOWER = "idepref_editor_java_matchLower"; + public static final String KEY_JAVA_PREF_MATCH_LOWER = "idepref_editor_java_matchLower"; - private static PrefBasedJavaServerSettings instance; + private static PrefBasedJavaServerSettings instance; - private final PreferenceManager prefs; + private final PreferenceManager prefs; - public PrefBasedJavaServerSettings() { - this.prefs = StudioApp.getInstance().getPrefManager(); - } - - @NonNull - public static PrefBasedJavaServerSettings getInstance() { - if (instance == null) { - instance = new PrefBasedJavaServerSettings(); + public PrefBasedJavaServerSettings() { + this.prefs = StudioApp.getInstance().getPrefManager(); } - return instance; - } + @NonNull + public static PrefBasedJavaServerSettings getInstance() { + if (instance == null) { + instance = new PrefBasedJavaServerSettings(); + } - @Override - public boolean shouldMatchAllLowerCase() { - return prefs.getBoolean(KEY_JAVA_PREF_MATCH_LOWER, false); - } + return instance; + } + + @Override + public boolean shouldMatchAllLowerCase() { + return prefs.getBoolean(KEY_JAVA_PREF_MATCH_LOWER, false); + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/ProjectTemplate.java b/app/src/main/java/com/itsaky/androidide/models/ProjectTemplate.java index fb2f3c962..cefeeb978 100755 --- a/app/src/main/java/com/itsaky/androidide/models/ProjectTemplate.java +++ b/app/src/main/java/com/itsaky/androidide/models/ProjectTemplate.java @@ -18,67 +18,68 @@ package com.itsaky.androidide.models; import android.content.Context; + import androidx.annotation.DrawableRes; import androidx.annotation.StringRes; public class ProjectTemplate { - private int id; - private String name; - private String description; + private int id; + private String name; + private String description; - @DrawableRes private int imageId; + @DrawableRes private int imageId; - public ProjectTemplate() {} + public ProjectTemplate() {} - public ProjectTemplate(int id, String name, String description, int imageId) { - this.id = id; - this.name = name; - this.description = description; - this.imageId = imageId; - } + public ProjectTemplate(int id, String name, String description, int imageId) { + this.id = id; + this.name = name; + this.description = description; + this.imageId = imageId; + } - public ProjectTemplate setId(int id) { - this.id = id; - return this; - } + public ProjectTemplate setId(int id) { + this.id = id; + return this; + } - public int getId() { - return id; - } + public int getId() { + return id; + } - public ProjectTemplate setName(Context ctx, @StringRes int name) { - return setName(ctx.getString(name)); - } + public ProjectTemplate setName(Context ctx, @StringRes int name) { + return setName(ctx.getString(name)); + } - public ProjectTemplate setName(String name) { - this.name = name; - return this; - } + public ProjectTemplate setName(String name) { + this.name = name; + return this; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public ProjectTemplate setDescription(Context ctx, @StringRes int desc) { - return setDescription(ctx.getString(desc)); - } + public ProjectTemplate setDescription(Context ctx, @StringRes int desc) { + return setDescription(ctx.getString(desc)); + } - public ProjectTemplate setDescription(String description) { - this.description = description; - return this; - } + public ProjectTemplate setDescription(String description) { + this.description = description; + return this; + } - public String getDescription() { - return description; - } + public String getDescription() { + return description; + } - public ProjectTemplate setImageId(@DrawableRes int imageId) { - this.imageId = imageId; - return this; - } + public ProjectTemplate setImageId(@DrawableRes int imageId) { + this.imageId = imageId; + return this; + } - public int getImageId() { - return imageId; - } + public int getImageId() { + return imageId; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/SearchResult.java b/app/src/main/java/com/itsaky/androidide/models/SearchResult.java index 96b86214a..4ee6e7484 100755 --- a/app/src/main/java/com/itsaky/androidide/models/SearchResult.java +++ b/app/src/main/java/com/itsaky/androidide/models/SearchResult.java @@ -18,17 +18,18 @@ package com.itsaky.androidide.models; import com.itsaky.lsp.models.Range; + import java.io.File; public class SearchResult extends Range { - public File file; - public String line; - public String match; + public File file; + public String line; + public String match; - public SearchResult(Range src, File file, String line, String match) { - super(src.getStart(), src.getEnd()); - this.file = file; - this.line = line; - this.match = match; - } + public SearchResult(Range src, File file, String line, String match) { + super(src.getStart(), src.getEnd()); + this.file = file; + this.line = line; + this.match = match; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/UIWidget.java b/app/src/main/java/com/itsaky/androidide/models/UIWidget.java index e62e1cad9..390d2de43 100755 --- a/app/src/main/java/com/itsaky/androidide/models/UIWidget.java +++ b/app/src/main/java/com/itsaky/androidide/models/UIWidget.java @@ -20,7 +20,9 @@ package com.itsaky.androidide.models; import android.os.Parcel; import android.os.Parcelable; import android.view.View; + import androidx.annotation.DrawableRes; + import java.util.Objects; /** @@ -30,93 +32,93 @@ import java.util.Objects; */ public class UIWidget implements IconTextListItem, Parcelable { - private final String name; - private final int icon; + private final String name; + private final int icon; - private final String clazz; + private final String clazz; - private UIWidget(Parcel in) { - this.name = in.readString(); - this.icon = in.readInt(); - this.clazz = in.readString(); - } + private UIWidget(Parcel in) { + this.name = in.readString(); + this.icon = in.readInt(); + this.clazz = in.readString(); + } - public UIWidget(String name, @DrawableRes int icon, Class clazz) { - this.name = name; - this.icon = icon; - this.clazz = clazz.getName(); - } + public UIWidget(String name, @DrawableRes int icon, Class clazz) { + this.name = name; + this.icon = icon; + this.clazz = clazz.getName(); + } - public static final Creator CREATOR = - new Creator() { - @Override - public UIWidget createFromParcel(Parcel in) { - return new UIWidget(in); - } + public static final Creator CREATOR = + new Creator() { + @Override + public UIWidget createFromParcel(Parcel in) { + return new UIWidget(in); + } - @Override - public UIWidget[] newArray(int size) { - return new UIWidget[size]; - } - }; + @Override + public UIWidget[] newArray(int size) { + return new UIWidget[size]; + } + }; - public String getName() { - return name; - } + public String getName() { + return name; + } - public int getIcon() { - return icon; - } + public int getIcon() { + return icon; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UIWidget widget = (UIWidget) o; - return getIcon() == widget.getIcon() - && getName().equals(widget.getName()) - && clazz.equals(widget.clazz); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UIWidget widget = (UIWidget) o; + return getIcon() == widget.getIcon() + && getName().equals(widget.getName()) + && clazz.equals(widget.clazz); + } - @Override - public int hashCode() { - return Objects.hash(getName(), getIcon(), clazz); - } + @Override + public int hashCode() { + return Objects.hash(getName(), getIcon(), clazz); + } - /** - * Get the class of this widget. - * - * @return The class that this widget represents - * @throws ClassNotFoundException If there is no class with this widget's class name. However, - * this will never be thrown. - */ - public Class asClass() throws ClassNotFoundException { - return getClass().getClassLoader().loadClass(this.clazz).asSubclass(View.class); - } + /** + * Get the class of this widget. + * + * @return The class that this widget represents + * @throws ClassNotFoundException If there is no class with this widget's class name. However, + * this will never be thrown. + */ + public Class asClass() throws ClassNotFoundException { + return getClass().getClassLoader().loadClass(this.clazz).asSubclass(View.class); + } - public String getWidgetClassName() { - return this.clazz; - } + public String getWidgetClassName() { + return this.clazz; + } - @Override - public String getText() { - return getName(); - } + @Override + public String getText() { + return getName(); + } - @Override - public int getIconResource() { - return getIcon(); - } + @Override + public int getIconResource() { + return getIcon(); + } - @Override - public int describeContents() { - return 0; - } + @Override + public int describeContents() { + return 0; + } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(name); - dest.writeInt(icon); - dest.writeString(clazz); - } + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(name); + dest.writeInt(icon); + dest.writeString(clazz); + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/UIWidgetGroup.java b/app/src/main/java/com/itsaky/androidide/models/UIWidgetGroup.java index 82020bbeb..622a3e3f0 100755 --- a/app/src/main/java/com/itsaky/androidide/models/UIWidgetGroup.java +++ b/app/src/main/java/com/itsaky/androidide/models/UIWidgetGroup.java @@ -19,11 +19,14 @@ package com.itsaky.androidide.models; import android.os.Parcel; import android.os.Parcelable; + import androidx.annotation.NonNull; + +import org.jetbrains.annotations.Contract; + import java.util.ArrayList; import java.util.List; import java.util.Objects; -import org.jetbrains.annotations.Contract; /** * Model class for holding data about a group that will be displayed in Widgets list in UI Designer @@ -35,131 +38,131 @@ import org.jetbrains.annotations.Contract; */ public class UIWidgetGroup implements IconTextListItem, Parcelable { - /** Name of this group. */ - private final String name; + /** Name of this group. */ + private final String name; - /** Children of this group. */ - private final List children; + /** Children of this group. */ + private final List children; - /** Is this group currently selected in the group list? */ - private boolean selected; + /** Is this group currently selected in the group list? */ + private boolean selected; - public UIWidgetGroup(String name) { - this.name = name; - this.selected = false; - this.children = new ArrayList<>(); - } - - protected UIWidgetGroup(@NonNull Parcel in) { - this.name = in.readString(); - this.children = in.createTypedArrayList(UIWidget.CREATOR); - this.selected = in.readByte() != 0; - } - - public static final Creator CREATOR = - new Creator() { - - @NonNull - @Contract("_ -> new") - @Override - public UIWidgetGroup createFromParcel(Parcel in) { - return new UIWidgetGroup(in); - } - - @NonNull - @Contract(value = "_ -> new", pure = true) - @Override - public UIWidgetGroup[] newArray(int size) { - return new UIWidgetGroup[size]; - } - }; - - /** - * Get the name of this group. - * - * @return The name of this group. - */ - public String getName() { - return name; - } - - /** - * Get the children of this group. - * - * @return The children. - */ - @NonNull - public List getChildren() { - return children; - } - - public void setSelected(boolean selected) { - this.selected = selected; - } - - public boolean isSelected() { - return selected; - } - - /** - * Add this widget to this group. Does nothing if the child is already present. - * - * @param child The child to add. - * @return This instance for chained method calls. - */ - public UIWidgetGroup addChild(UIWidget child) { - if (children.contains(child)) { - return this; + public UIWidgetGroup(String name) { + this.name = name; + this.selected = false; + this.children = new ArrayList<>(); } - this.children.add(child); - return this; - } - /** - * Get the child at the given index. - * - * @param index The index of the child. - * @return The child at the index. - * @throws IndexOutOfBoundsException If the given index is not valid. - */ - public UIWidget getChild(int index) { - return children.get(index); - } + protected UIWidgetGroup(@NonNull Parcel in) { + this.name = in.readString(); + this.children = in.createTypedArrayList(UIWidget.CREATOR); + this.selected = in.readByte() != 0; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UIWidgetGroup group = (UIWidgetGroup) o; - return isSelected() == group.isSelected() - && getName().equals(group.getName()) - && getChildren().equals(group.getChildren()); - } + public static final Creator CREATOR = + new Creator() { - @Override - public int hashCode() { - return Objects.hash(getName(), getChildren(), isSelected()); - } + @NonNull + @Contract("_ -> new") + @Override + public UIWidgetGroup createFromParcel(Parcel in) { + return new UIWidgetGroup(in); + } - @Override - public String getText() { - return getName(); - } + @NonNull + @Contract(value = "_ -> new", pure = true) + @Override + public UIWidgetGroup[] newArray(int size) { + return new UIWidgetGroup[size]; + } + }; - @Override - public int getIconResource() { - return -1; - } + /** + * Get the name of this group. + * + * @return The name of this group. + */ + public String getName() { + return name; + } - @Override - public int describeContents() { - return 0; - } + /** + * Get the children of this group. + * + * @return The children. + */ + @NonNull + public List getChildren() { + return children; + } - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(name); - dest.writeTypedList(children); - dest.writeByte((byte) (selected ? 1 : 0)); - } + public void setSelected(boolean selected) { + this.selected = selected; + } + + public boolean isSelected() { + return selected; + } + + /** + * Add this widget to this group. Does nothing if the child is already present. + * + * @param child The child to add. + * @return This instance for chained method calls. + */ + public UIWidgetGroup addChild(UIWidget child) { + if (children.contains(child)) { + return this; + } + this.children.add(child); + return this; + } + + /** + * Get the child at the given index. + * + * @param index The index of the child. + * @return The child at the index. + * @throws IndexOutOfBoundsException If the given index is not valid. + */ + public UIWidget getChild(int index) { + return children.get(index); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UIWidgetGroup group = (UIWidgetGroup) o; + return isSelected() == group.isSelected() + && getName().equals(group.getName()) + && getChildren().equals(group.getChildren()); + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getChildren(), isSelected()); + } + + @Override + public String getText() { + return getName(); + } + + @Override + public int getIconResource() { + return -1; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(name); + dest.writeTypedList(children); + dest.writeByte((byte) (selected ? 1 : 0)); + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/XMLAttribute.java b/app/src/main/java/com/itsaky/androidide/models/XMLAttribute.java index 1382c4683..1aecf1098 100644 --- a/app/src/main/java/com/itsaky/androidide/models/XMLAttribute.java +++ b/app/src/main/java/com/itsaky/androidide/models/XMLAttribute.java @@ -19,15 +19,19 @@ package com.itsaky.androidide.models; import android.os.Parcel; import android.os.Parcelable; + import androidx.annotation.NonNull; + import com.itsaky.androidide.app.StudioApp; import com.itsaky.attrinfo.models.Attr; import com.itsaky.inflater.IAttribute; import com.itsaky.inflater.impl.UiAttribute; import com.itsaky.xml.INamespace; -import java.util.Objects; + import org.jetbrains.annotations.Contract; +import java.util.Objects; + /** * A model that is used to show attributes list of a view. * @@ -35,136 +39,140 @@ import org.jetbrains.annotations.Contract; */ public class XMLAttribute extends UiAttribute implements Parcelable { - public static final Creator CREATOR = - new Creator<>() { - @NonNull - @Contract("_ -> new") - @Override - public XMLAttribute createFromParcel(Parcel in) { - return new XMLAttribute(in); - } + public static final Creator CREATOR = + new Creator<>() { + @NonNull + @Contract("_ -> new") + @Override + public XMLAttribute createFromParcel(Parcel in) { + return new XMLAttribute(in); + } - @NonNull - @Contract(value = "_ -> new", pure = true) - @Override - public XMLAttribute[] newArray(int size) { - return new XMLAttribute[size]; - } - }; - public Attr attr; - /** Is this attribute applied to any view? */ - private boolean isApplied; - /** - * Format of this attribute. May be stored here so we don't have to look in {@link - * com.itsaky.attrinfo.AttrInfo AttrInfo} again and again. - */ - private int format = -1; + @NonNull + @Contract(value = "_ -> new", pure = true) + @Override + public XMLAttribute[] newArray(int size) { + return new XMLAttribute[size]; + } + }; + public Attr attr; + /** Is this attribute applied to any view? */ + private boolean isApplied; + /** + * Format of this attribute. May be stored here so we don't have to look in {@link + * com.itsaky.attrinfo.AttrInfo AttrInfo} again and again. + */ + private int format = -1; - public XMLAttribute(IAttribute attribute) { - this(attribute, true); - } - - public XMLAttribute(@NonNull IAttribute attribute, boolean isApplied) { - this(attribute.getNamespace(), attribute.getAttributeName(), attribute.getValue(), isApplied); - } - - public XMLAttribute(INamespace namespace, String name, String value, boolean isApplied) { - super(namespace, name, value); - this.isApplied = isApplied; - } - - private XMLAttribute(@NonNull Parcel in) { - this( - in.readParcelable(INamespace.class.getClassLoader()), - in.readString(), - in.readString(), - in.readByte() != 0); - } - - /** - * Create a new attribute instance with the android namespace. {@link #isApplied()} will return - * true by default. - * - * @param name The name of the attribute. - * @param value The value of the attribute. - * @return A new instance of this class with the provided data. - */ - @NonNull - @Contract("_, _ -> new") - public static XMLAttribute newAndroidAttribute(String name, String value) { - return new XMLAttribute(INamespace.ANDROID, name, value, true); - } - - /** - * Create a new attribute instance with the android namespace. The {@link #isApplied()} will - * return {@code false} by default. - * - * @param name The name of the attribute. - * @return The new attribute instance. - */ - @NonNull - @Contract("_ -> new") - public static XMLAttribute newAndroidAttribute(String name) { - return new XMLAttribute(INamespace.ANDROID, name, "", false); - } - - public int getFormat() { - return format; - } - - public void setFormat(int format) { - this.format = format; - } - - public int findFormat() { - - if (getFormat() > 0) { - return getFormat(); + public XMLAttribute(IAttribute attribute) { + this(attribute, true); } - final var attrs = StudioApp.getInstance().attrInfo(); - this.attr = attrs.getAttribute(getAttributeName()); - if (this.attr != null) { - this.format = this.attr.format; + public XMLAttribute(@NonNull IAttribute attribute, boolean isApplied) { + this( + attribute.getNamespace(), + attribute.getAttributeName(), + attribute.getValue(), + isApplied); } - return getFormat(); - } + public XMLAttribute(INamespace namespace, String name, String value, boolean isApplied) { + super(namespace, name, value); + this.isApplied = isApplied; + } - public boolean hasFormat(int format) { - return (this.format & format) != 0; - } + private XMLAttribute(@NonNull Parcel in) { + this( + in.readParcelable(INamespace.class.getClassLoader()), + in.readString(), + in.readString(), + in.readByte() != 0); + } - public Attr getAttr() { - return attr; - } + /** + * Create a new attribute instance with the android namespace. {@link #isApplied()} will return + * true by default. + * + * @param name The name of the attribute. + * @param value The value of the attribute. + * @return A new instance of this class with the provided data. + */ + @NonNull + @Contract("_, _ -> new") + public static XMLAttribute newAndroidAttribute(String name, String value) { + return new XMLAttribute(INamespace.ANDROID, name, value, true); + } - public void setAttr(Attr attr) { - Objects.requireNonNull(attr); - this.attr = attr; - setFormat(attr.format); - } + /** + * Create a new attribute instance with the android namespace. The {@link #isApplied()} will + * return {@code false} by default. + * + * @param name The name of the attribute. + * @return The new attribute instance. + */ + @NonNull + @Contract("_ -> new") + public static XMLAttribute newAndroidAttribute(String name) { + return new XMLAttribute(INamespace.ANDROID, name, "", false); + } - @Override - public void apply(String value) { - super.apply(value); - isApplied = true; - } + public int getFormat() { + return format; + } - public boolean isApplied() { - return isApplied; - } + public void setFormat(int format) { + this.format = format; + } - @Override - public int describeContents() { - return 0; - } + public int findFormat() { - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeParcelable(getNamespace(), flags); - dest.writeString(getAttributeName()); - dest.writeString(getValue()); - dest.writeByte((byte) (isApplied ? 1 : 0)); - } + if (getFormat() > 0) { + return getFormat(); + } + + final var attrs = StudioApp.getInstance().attrInfo(); + this.attr = attrs.getAttribute(getAttributeName()); + if (this.attr != null) { + this.format = this.attr.format; + } + + return getFormat(); + } + + public boolean hasFormat(int format) { + return (this.format & format) != 0; + } + + public Attr getAttr() { + return attr; + } + + public void setAttr(Attr attr) { + Objects.requireNonNull(attr); + this.attr = attr; + setFormat(attr.format); + } + + @Override + public void apply(String value) { + super.apply(value); + isApplied = true; + } + + public boolean isApplied() { + return isApplied; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(getNamespace(), flags); + dest.writeString(getAttributeName()); + dest.writeString(getValue()); + dest.writeByte((byte) (isApplied ? 1 : 0)); + } } diff --git a/app/src/main/java/com/itsaky/androidide/project/AndroidProject.java b/app/src/main/java/com/itsaky/androidide/project/AndroidProject.java index 66b1c69d6..1cc0f2876 100755 --- a/app/src/main/java/com/itsaky/androidide/project/AndroidProject.java +++ b/app/src/main/java/com/itsaky/androidide/project/AndroidProject.java @@ -22,202 +22,204 @@ package com.itsaky.androidide.project; import android.os.Parcel; import android.os.Parcelable; + import com.itsaky.androidide.app.StudioApp; + import java.io.File; import java.util.ArrayList; import java.util.List; public class AndroidProject implements Parcelable { - private String appName; - private String packageName; - private String iconPath; - private String projectPath; - private String mainModule; - private String mainSourcePath; + private String appName; + private String packageName; + private String iconPath; + private String projectPath; + private String mainModule; + private String mainSourcePath; - private List modulePaths = new ArrayList<>(); - private List sourcePaths = new ArrayList<>(); - private List classPaths = new ArrayList<>(); + private List modulePaths = new ArrayList<>(); + private List sourcePaths = new ArrayList<>(); + private List classPaths = new ArrayList<>(); - public AndroidProject() { - addSource(StudioApp.getInstance().getLogSenderDir().getAbsolutePath()); - } + public AndroidProject() { + addSource(StudioApp.getInstance().getLogSenderDir().getAbsolutePath()); + } - protected AndroidProject(Parcel in) { - appName = in.readString(); - packageName = in.readString(); - iconPath = in.readString(); - projectPath = in.readString(); - mainModule = in.readString(); - mainSourcePath = in.readString(); - in.readList(modulePaths, String.class.getClassLoader()); - in.readList(sourcePaths, String.class.getClassLoader()); - in.readList(classPaths, String.class.getClassLoader()); - } + protected AndroidProject(Parcel in) { + appName = in.readString(); + packageName = in.readString(); + iconPath = in.readString(); + projectPath = in.readString(); + mainModule = in.readString(); + mainSourcePath = in.readString(); + in.readList(modulePaths, String.class.getClassLoader()); + in.readList(sourcePaths, String.class.getClassLoader()); + in.readList(classPaths, String.class.getClassLoader()); + } - public File getBuildDir() { - return new File(getMainModulePath(), "build"); - } + public File getBuildDir() { + return new File(getMainModulePath(), "build"); + } - public AndroidProject setMainModule(String mainModule) { - this.mainModule = mainModule; - return this; - } + public AndroidProject setMainModule(String mainModule) { + this.mainModule = mainModule; + return this; + } - public String getMainModule() { - return mainModule; - } + public String getMainModule() { + return mainModule; + } - public String getMainModulePath() { - return new File(getProjectPath(), getMainModule()).getAbsolutePath(); - } + public String getMainModulePath() { + return new File(getProjectPath(), getMainModule()).getAbsolutePath(); + } - public AndroidProject setMainSourcePath(String mainModulePath) { - this.mainSourcePath = mainModulePath; - return this; - } + public AndroidProject setMainSourcePath(String mainModulePath) { + this.mainSourcePath = mainModulePath; + return this; + } - public String getMainSourcePath() { - return mainSourcePath; - } + public String getMainSourcePath() { + return mainSourcePath; + } - public AndroidProject setModulePaths(List modulePaths) { - this.modulePaths = modulePaths; - return this; - } + public AndroidProject setModulePaths(List modulePaths) { + this.modulePaths = modulePaths; + return this; + } - public List getModulePaths() { - if (modulePaths == null) modulePaths = new ArrayList<>(); - return modulePaths; - } + public List getModulePaths() { + if (modulePaths == null) modulePaths = new ArrayList<>(); + return modulePaths; + } - public AndroidProject addModule(String path) { - if (!getModulePaths().contains(path)) getModulePaths().add(path); - return this; - } + public AndroidProject addModule(String path) { + if (!getModulePaths().contains(path)) getModulePaths().add(path); + return this; + } - public AndroidProject setSourcePaths(List sourcePaths) { - this.sourcePaths = sourcePaths; - return this; - } + public AndroidProject setSourcePaths(List sourcePaths) { + this.sourcePaths = sourcePaths; + return this; + } - public List getSourcePaths() { - if (sourcePaths == null) sourcePaths = new ArrayList<>(); - return sourcePaths; - } + public List getSourcePaths() { + if (sourcePaths == null) sourcePaths = new ArrayList<>(); + return sourcePaths; + } - public AndroidProject addSource(String path) { - if (!getSourcePaths().contains(path)) getSourcePaths().add(path); - return this; - } + public AndroidProject addSource(String path) { + if (!getSourcePaths().contains(path)) getSourcePaths().add(path); + return this; + } - public AndroidProject setClassPaths(List classPaths) { - this.classPaths = classPaths; - return this; - } + public AndroidProject setClassPaths(List classPaths) { + this.classPaths = classPaths; + return this; + } - public List getClassPaths() { - if (classPaths == null) classPaths = new ArrayList<>(); - return classPaths; - } + public List getClassPaths() { + if (classPaths == null) classPaths = new ArrayList<>(); + return classPaths; + } - public AndroidProject addClasspath(String path) { - if (!getClassPaths().contains(path)) getClassPaths().add(path); - return this; - } + public AndroidProject addClasspath(String path) { + if (!getClassPaths().contains(path)) getClassPaths().add(path); + return this; + } - public AndroidProject setAppName(String appName) { - this.appName = appName; - return this; - } + public AndroidProject setAppName(String appName) { + this.appName = appName; + return this; + } - public String getAppName() { - return appName; - } + public String getAppName() { + return appName; + } - public AndroidProject setPackageName(String packageName) { - this.packageName = packageName; - return this; - } + public AndroidProject setPackageName(String packageName) { + this.packageName = packageName; + return this; + } - public String getPackageName() { - return packageName; - } + public String getPackageName() { + return packageName; + } - public AndroidProject setIconPath(String iconPath) { - this.iconPath = iconPath; - return this; - } + public AndroidProject setIconPath(String iconPath) { + this.iconPath = iconPath; + return this; + } - public String getIconPath() { - return iconPath; - } + public String getIconPath() { + return iconPath; + } - public AndroidProject setProjectPath(String projectPath) { - this.projectPath = projectPath; - return this; - } + public AndroidProject setProjectPath(String projectPath) { + this.projectPath = projectPath; + return this; + } - public String getProjectPath() { - return projectPath; - } + public String getProjectPath() { + return projectPath; + } - @Override - public int describeContents() { - return 0; - } + @Override + public int describeContents() { + return 0; + } - @Override - public void writeToParcel(Parcel p1, int p2) { - p1.writeString(appName); - p1.writeString(packageName); - p1.writeString(iconPath); - p1.writeString(projectPath); - p1.writeString(mainModule); - p1.writeString(mainSourcePath); - p1.writeList(modulePaths); - p1.writeList(sourcePaths); - p1.writeList(classPaths); - } + @Override + public void writeToParcel(Parcel p1, int p2) { + p1.writeString(appName); + p1.writeString(packageName); + p1.writeString(iconPath); + p1.writeString(projectPath); + p1.writeString(mainModule); + p1.writeString(mainSourcePath); + p1.writeList(modulePaths); + p1.writeList(sourcePaths); + p1.writeList(classPaths); + } - @Override - public String toString() { - return "[" - + "\nappName=" - + getAppName() - + "\npackageName=" - + getPackageName() - + "\niconPath=" - + getIconPath() - + "\nprojectPath=" - + getProjectPath() - + "\nmainModule=" - + getMainModule() - + "\nmainModulePath=" - + getMainModulePath() - + "\nmainModuleSourcePath=" - + getMainSourcePath() - + "\nmodules=" - + getModulePaths() - + "\nsources=" - + getSourcePaths() - + "\nclasspaths=" - + getClassPaths() - + "\n]"; - } + @Override + public String toString() { + return "[" + + "\nappName=" + + getAppName() + + "\npackageName=" + + getPackageName() + + "\niconPath=" + + getIconPath() + + "\nprojectPath=" + + getProjectPath() + + "\nmainModule=" + + getMainModule() + + "\nmainModulePath=" + + getMainModulePath() + + "\nmainModuleSourcePath=" + + getMainSourcePath() + + "\nmodules=" + + getModulePaths() + + "\nsources=" + + getSourcePaths() + + "\nclasspaths=" + + getClassPaths() + + "\n]"; + } - public static final Creator CREATOR = - new Creator() { + public static final Creator CREATOR = + new Creator() { - @Override - public AndroidProject createFromParcel(Parcel p1) { - return new AndroidProject(p1); - } + @Override + public AndroidProject createFromParcel(Parcel p1) { + return new AndroidProject(p1); + } - @Override - public AndroidProject[] newArray(int p1) { - return new AndroidProject[p1]; - } - }; + @Override + public AndroidProject[] newArray(int p1) { + return new AndroidProject[p1]; + } + }; } diff --git a/app/src/main/java/com/itsaky/androidide/project/IDEModule.java b/app/src/main/java/com/itsaky/androidide/project/IDEModule.java index 86a27d7cb..0eabfd116 100755 --- a/app/src/main/java/com/itsaky/androidide/project/IDEModule.java +++ b/app/src/main/java/com/itsaky/androidide/project/IDEModule.java @@ -26,43 +26,43 @@ import com.google.gson.annotations.SerializedName; */ public class IDEModule extends IDEProject { - /** Build tools version of this project */ - @SerializedName("buildToolsVersion") - public String buildToolsVersion; + /** Build tools version of this project */ + @SerializedName("buildToolsVersion") + public String buildToolsVersion; - /** - * Compile SDK version of this project - * - *

Representation: 'android-31', 'android-30', 'android-29', etc - */ - @SerializedName("compileSdkVersion") - public String compileSdkVersion; + /** + * Compile SDK version of this project + * + *

Representation: 'android-31', 'android-30', 'android-29', etc + */ + @SerializedName("compileSdkVersion") + public String compileSdkVersion; - /** Minimum SDK version of this project */ - @SerializedName("minSdk") - public SDK minSdk; + /** Minimum SDK version of this project */ + @SerializedName("minSdk") + public SDK minSdk; - /** Target SDK version of this project */ - @SerializedName("targetSdk") - public SDK targetSdk; + /** Target SDK version of this project */ + @SerializedName("targetSdk") + public SDK targetSdk; - /** Version code of this project */ - @SerializedName("versionCode") - public int versionCode = 0; + /** Version code of this project */ + @SerializedName("versionCode") + public int versionCode = 0; - /** Version name of this project */ - @SerializedName("versionName") - public String versionName = "Not defined"; + /** Version name of this project */ + @SerializedName("versionName") + public String versionName = "Not defined"; - /** Is this project an Android library project? */ - @SerializedName("isLibrary") - public boolean isLibrary = true; + /** Is this project an Android library project? */ + @SerializedName("isLibrary") + public boolean isLibrary = true; - /** Does this module has viewBinding enabled? */ - @SerializedName("viewBindingEnabled") - public boolean viewBindingEnabled; + /** Does this module has viewBinding enabled? */ + @SerializedName("viewBindingEnabled") + public boolean viewBindingEnabled; - /** Library projects don't have applicationId, application projects do. */ - @SerializedName("applicationId") - public String applicationId; + /** Library projects don't have applicationId, application projects do. */ + @SerializedName("applicationId") + public String applicationId; } diff --git a/app/src/main/java/com/itsaky/androidide/project/IDEProject.java b/app/src/main/java/com/itsaky/androidide/project/IDEProject.java index a3c1942df..05aef2b9a 100755 --- a/app/src/main/java/com/itsaky/androidide/project/IDEProject.java +++ b/app/src/main/java/com/itsaky/androidide/project/IDEProject.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.project; import com.google.gson.annotations.SerializedName; + import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -25,71 +26,71 @@ import java.util.Optional; /** A model that contains data of a project currently opened in AndroidIDE */ public class IDEProject { - /** Name of this project */ - @SerializedName("name") - public String name = ""; + /** Name of this project */ + @SerializedName("name") + public String name = ""; - /** Name of this project which should be displayed to user */ - @SerializedName("displayName") - public String displayName = ""; + /** Name of this project which should be displayed to user */ + @SerializedName("displayName") + public String displayName = ""; - /** Description of this project. Maybe null. */ - @SerializedName("description") - public String description = ""; + /** Description of this project. Maybe null. */ + @SerializedName("description") + public String description = ""; - /** - * Path of this project. This is NOT the directory path of this project. - * - *

For example, ':app or ':module:subModule' - */ - @SerializedName("path") - public String path = ""; + /** + * Path of this project. This is NOT the directory path of this project. + * + *

For example, ':app or ':module:subModule' + */ + @SerializedName("path") + public String path = ""; - /** Path of the directory containing this project */ - @SerializedName("projectDir") - public String projectDir; + /** Path of the directory containing this project */ + @SerializedName("projectDir") + public String projectDir; - /** File path of the application's icon We get this manually by parsing AndroidManifest.xml */ - public String iconPath; + /** File path of the application's icon We get this manually by parsing AndroidManifest.xml */ + public String iconPath; - /** Subprojects of this project */ - @SerializedName("modules") - public List modules = new ArrayList<>(); + /** Subprojects of this project */ + @SerializedName("modules") + public List modules = new ArrayList<>(); - /** Tasks included in this project */ - @SerializedName("tasks") - public List tasks = new ArrayList<>(); + /** Tasks included in this project */ + @SerializedName("tasks") + public List tasks = new ArrayList<>(); - /** Path of jar file of dependencies of this project */ - @SerializedName("dependencies") - public List dependencies = new ArrayList<>(); + /** Path of jar file of dependencies of this project */ + @SerializedName("dependencies") + public List dependencies = new ArrayList<>(); - /** - * Get the module by its path - * - * @param path Path of the module - * @return An Optional containing the IDEModule or Optional.empty() - */ - public Optional getModuleByPath(String path) { - if (modules != null && modules.size() > 0) { - for (IDEModule module : modules) { - if (module == null) continue; - if (module.path.trim().equals(path.trim())) return Optional.of(module); - } + /** + * Get the module by its path + * + * @param path Path of the module + * @return An Optional containing the IDEModule or Optional.empty() + */ + public Optional getModuleByPath(String path) { + if (modules != null && modules.size() > 0) { + for (IDEModule module : modules) { + if (module == null) continue; + if (module.path.trim().equals(path.trim())) return Optional.of(module); + } + } + return Optional.empty(); } - return Optional.empty(); - } - /** Stores information about a SDK. Like, • Codename • API string • API level */ - public static class SDK { + /** Stores information about a SDK. Like, • Codename • API string • API level */ + public static class SDK { - @SerializedName("codename") - public String codename; + @SerializedName("codename") + public String codename; - @SerializedName("apiString") - public String apiString; + @SerializedName("apiString") + public String apiString; - @SerializedName("apiLevel") - public int apiLevel; - } + @SerializedName("apiLevel") + public int apiLevel; + } } diff --git a/app/src/main/java/com/itsaky/androidide/project/IDETask.java b/app/src/main/java/com/itsaky/androidide/project/IDETask.java index 23fc13d30..c66e80dbb 100755 --- a/app/src/main/java/com/itsaky/androidide/project/IDETask.java +++ b/app/src/main/java/com/itsaky/androidide/project/IDETask.java @@ -22,23 +22,23 @@ import com.google.gson.annotations.SerializedName; /** Represents a Gradle task */ public class IDETask { - /** Name of this task */ - @SerializedName("name") - public String name; + /** Name of this task */ + @SerializedName("name") + public String name; - /** Description of this task */ - @SerializedName("description") - public String description; + /** Description of this task */ + @SerializedName("description") + public String description; - /** Name of the group that this task belongs to */ - @SerializedName("group") - public String group; + /** Name of the group that this task belongs to */ + @SerializedName("group") + public String group; - /** - * Path of this task - * - *

For example, ':app:assembleDebug', ':app:build' - */ - @SerializedName("path") - public String path; + /** + * Path of this task + * + *

For example, ':app:assembleDebug', ':app:build' + */ + @SerializedName("path") + public String path; } diff --git a/app/src/main/java/com/itsaky/androidide/project/ProjectFileObserver.java b/app/src/main/java/com/itsaky/androidide/project/ProjectFileObserver.java index 69a0ea6b2..8739f9469 100644 --- a/app/src/main/java/com/itsaky/androidide/project/ProjectFileObserver.java +++ b/app/src/main/java/com/itsaky/androidide/project/ProjectFileObserver.java @@ -26,13 +26,13 @@ import android.os.FileObserver; * @author Akash Yadav */ public abstract class ProjectFileObserver extends FileObserver { - // Cannot use constructors that accept java.io.File - // They were added in API 29 - public ProjectFileObserver(String path) { - this(path, ALL_EVENTS); - } + // Cannot use constructors that accept java.io.File + // They were added in API 29 + public ProjectFileObserver(String path) { + this(path, ALL_EVENTS); + } - public ProjectFileObserver(String path, int mask) { - super(path, mask); - } + public ProjectFileObserver(String path, int mask) { + super(path, mask); + } } diff --git a/app/src/main/java/com/itsaky/androidide/project/ProjectResourceTable.java b/app/src/main/java/com/itsaky/androidide/project/ProjectResourceTable.java index 433703e95..0f1a846a9 100644 --- a/app/src/main/java/com/itsaky/androidide/project/ProjectResourceTable.java +++ b/app/src/main/java/com/itsaky/androidide/project/ProjectResourceTable.java @@ -18,234 +18,236 @@ package com.itsaky.androidide.project; import androidx.annotation.NonNull; + import com.itsaky.androidide.utils.Logger; import com.itsaky.inflater.IResourceTable; import com.itsaky.inflater.values.ValuesTableFactory; + import java.io.File; import java.io.FileFilter; public class ProjectResourceTable implements IResourceTable { - private File resDir; - private File color; - private File layout; + private File resDir; + private File color; + private File layout; - private File[] drawables; - private File[] mipmaps; + private File[] drawables; + private File[] mipmaps; - private static final Logger LOG = Logger.instance("ProjectResourceFinder"); + private static final Logger LOG = Logger.instance("ProjectResourceFinder"); - @Override - public File findDrawable(@NonNull String name) { - for (File drawable : drawables) { - final File file = findFileWithName(drawable.listFiles(), name); - if (file != null) { - return file; - } - } + @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; - } - - @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; + return null; } @Override - public boolean accept(@NonNull File file) { - return file.getName().startsWith(name); + 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; + } + + @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); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java b/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java index 021e4d835..21a5e6592 100755 --- a/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java +++ b/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java @@ -23,44 +23,45 @@ package com.itsaky.androidide.services; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; + import com.itsaky.androidide.models.LogLine; import com.itsaky.androidide.utils.Logger; public class LogReceiver extends BroadcastReceiver { - private LogListener listener; + private LogListener listener; - public static final String APPEND_LOG = "com.itsaky.androidide.logs.APPEND_LOG"; - public static final String EXTRA_LINE = "log_line"; + public static final String APPEND_LOG = "com.itsaky.androidide.logs.APPEND_LOG"; + public static final String EXTRA_LINE = "log_line"; - private final Logger LOG = Logger.instance("LogReceiver"); + private final Logger LOG = Logger.instance("LogReceiver"); - public LogReceiver setLogListener(LogListener listener) { - this.listener = listener; - return this; - } - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(APPEND_LOG) && intent.hasExtra(EXTRA_LINE)) { - String line = intent.getStringExtra(EXTRA_LINE); - if (line == null) return; - try { - sendLogLine(line); - } catch (Throwable th) { - LOG.error("Unable to parse log line from app.", th); - } + public LogReceiver setLogListener(LogListener listener) { + this.listener = listener; + return this; } - } - private void sendLogLine(String line) { - final var log = LogLine.forLogString(line); - if (listener != null) { - listener.appendLogLine(log); + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(APPEND_LOG) && intent.hasExtra(EXTRA_LINE)) { + String line = intent.getStringExtra(EXTRA_LINE); + if (line == null) return; + try { + sendLogLine(line); + } catch (Throwable th) { + LOG.error("Unable to parse log line from app.", th); + } + } } - } - public interface LogListener { - void appendLogLine(LogLine line); - } + private void sendLogLine(String line) { + final var log = LogLine.forLogString(line); + if (listener != null) { + listener.appendLogLine(log); + } + } + + public interface LogListener { + void appendLogLine(LogLine line); + } } diff --git a/app/src/main/java/com/itsaky/androidide/services/MessagingService.java b/app/src/main/java/com/itsaky/androidide/services/MessagingService.java index 2dd23b8c0..39e481cf4 100755 --- a/app/src/main/java/com/itsaky/androidide/services/MessagingService.java +++ b/app/src/main/java/com/itsaky/androidide/services/MessagingService.java @@ -23,115 +23,118 @@ package com.itsaky.androidide.services; import android.app.PendingIntent; import android.content.Intent; import android.provider.Settings; + import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; + import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; import com.itsaky.androidide.BuildConfig; import com.itsaky.androidide.SplashActivity; import com.itsaky.androidide.app.StudioApp; + import java.util.Map; public class MessagingService extends FirebaseMessagingService { - public static final String TOPIC_UPDATE = "app_update"; - public static final String TOPIC_DEV_MSGS = "developer_notifications"; + public static final String TOPIC_UPDATE = "app_update"; + public static final String TOPIC_DEV_MSGS = "developer_notifications"; - private final String KEY_TITLE = "title"; - private final String KEY_MESSAGE = "message"; - private final String KEY_VERSION = "version"; - private final String KEY_CLICK_ACTION = "click_action"; + private final String KEY_TITLE = "title"; + private final String KEY_MESSAGE = "message"; + private final String KEY_VERSION = "version"; + private final String KEY_CLICK_ACTION = "click_action"; - private final String ACTION_OPEN_APP = "open_app"; - private final String ACTION_OPEN_URL = "open_url:"; + private final String ACTION_OPEN_APP = "open_app"; + private final String ACTION_OPEN_URL = "open_url:"; - @Override - public void onMessageReceived(RemoteMessage message) { - if (message != null && message.getData() != null) - processNotification(message.getFrom(), message.getData()); - } + @Override + public void onMessageReceived(RemoteMessage message) { + if (message != null && message.getData() != null) + processNotification(message.getFrom(), message.getData()); + } - @Override - public void onNewToken(String token) { - super.onNewToken(token); - } + @Override + public void onNewToken(String token) { + super.onNewToken(token); + } - private void processNotification(String topic, Map data) { - if (topic != null && topic.contains("/")) { - topic = topic.substring(topic.lastIndexOf("/") + 1); - if (topic.equals(TOPIC_UPDATE) && checkHasUpdateData(data)) { - final String title = data.get(KEY_TITLE); - final String msg = data.get(KEY_MESSAGE); - final String version = data.get(KEY_VERSION); - final String action = data.get(KEY_CLICK_ACTION); - final int l = stringAsInt(version); - if (l > BuildConfig.VERSION_CODE) { - showNotificationWithAction(action, title, msg); + private void processNotification(String topic, Map data) { + if (topic != null && topic.contains("/")) { + topic = topic.substring(topic.lastIndexOf("/") + 1); + if (topic.equals(TOPIC_UPDATE) && checkHasUpdateData(data)) { + final String title = data.get(KEY_TITLE); + final String msg = data.get(KEY_MESSAGE); + final String version = data.get(KEY_VERSION); + final String action = data.get(KEY_CLICK_ACTION); + final int l = stringAsInt(version); + if (l > BuildConfig.VERSION_CODE) { + showNotificationWithAction(action, title, msg); + } + } else if (topic.equals(TOPIC_DEV_MSGS) && checkHasTitleAndMessage(data)) { + final String title = data.get(KEY_TITLE); + final String msg = data.get(KEY_MESSAGE); + final String action = data.get(KEY_CLICK_ACTION); + showNotificationWithAction(action, title, msg); + } } - } else if (topic.equals(TOPIC_DEV_MSGS) && checkHasTitleAndMessage(data)) { - final String title = data.get(KEY_TITLE); - final String msg = data.get(KEY_MESSAGE); - final String action = data.get(KEY_CLICK_ACTION); - showNotificationWithAction(action, title, msg); - } - } - } - - private void showNotificationWithAction(String action, String title, String msg) { - Intent i = null; - int flag = PendingIntent.FLAG_IMMUTABLE; - if (action.equals(ACTION_OPEN_APP)) { - i = - StudioApp.getInstance() - .getPackageManager() - .getLaunchIntentForPackage(StudioApp.getInstance().getPackageName()); - } else if (action.startsWith(ACTION_OPEN_URL)) { - i = createOpenUrlIntent(action.substring(ACTION_OPEN_URL.length())); - flag |= Intent.FLAG_ACTIVITY_NEW_TASK; } - if (i != null) { - PendingIntent intent = PendingIntent.getActivity(this, 0, i, flag); - showNotification(title, msg, intent, StudioApp.NOTIFICATION_ID_UPDATE); + private void showNotificationWithAction(String action, String title, String msg) { + Intent i = null; + int flag = PendingIntent.FLAG_IMMUTABLE; + if (action.equals(ACTION_OPEN_APP)) { + i = + StudioApp.getInstance() + .getPackageManager() + .getLaunchIntentForPackage(StudioApp.getInstance().getPackageName()); + } else if (action.startsWith(ACTION_OPEN_URL)) { + i = createOpenUrlIntent(action.substring(ACTION_OPEN_URL.length())); + flag |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + + if (i != null) { + PendingIntent intent = PendingIntent.getActivity(this, 0, i, flag); + showNotification(title, msg, intent, StudioApp.NOTIFICATION_ID_UPDATE); + } } - } - private void showNotification(String title, String msg, PendingIntent i, String channelId) { - NotificationCompat.Builder b = new NotificationCompat.Builder(this, channelId); - b.setContentTitle(title) - .setContentText(msg) - .setAutoCancel(true) - .setSound(Settings.System.DEFAULT_NOTIFICATION_URI) - .setContentIntent(i); + private void showNotification(String title, String msg, PendingIntent i, String channelId) { + NotificationCompat.Builder b = new NotificationCompat.Builder(this, channelId); + b.setContentTitle(title) + .setContentText(msg) + .setAutoCancel(true) + .setSound(Settings.System.DEFAULT_NOTIFICATION_URI) + .setContentIntent(i); - NotificationManagerCompat.from(StudioApp.getInstance()).notify(0, b.build()); - } - - private Intent createOpenAppIntent() { - Intent i = new Intent(this, SplashActivity.class); - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - return i; - } - - private Intent createOpenUrlIntent(String url) { - return new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url)); - } - - private int stringAsInt(String str) { - try { - return Integer.valueOf(str); - } catch (Throwable th) { - return -1; + NotificationManagerCompat.from(StudioApp.getInstance()).notify(0, b.build()); } - } - private boolean checkHasUpdateData(Map data) { - return checkHasTitleAndMessage(data) - && data.containsKey(KEY_VERSION) - && data.containsKey(KEY_CLICK_ACTION); - } + private Intent createOpenAppIntent() { + Intent i = new Intent(this, SplashActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return i; + } - private boolean checkHasTitleAndMessage(Map data) { - return data.containsKey(KEY_TITLE) && data.containsKey(KEY_MESSAGE); - } + private Intent createOpenUrlIntent(String url) { + return new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url)); + } + + private int stringAsInt(String str) { + try { + return Integer.valueOf(str); + } catch (Throwable th) { + return -1; + } + } + + private boolean checkHasUpdateData(Map data) { + return checkHasTitleAndMessage(data) + && data.containsKey(KEY_VERSION) + && data.containsKey(KEY_CLICK_ACTION); + } + + private boolean checkHasTitleAndMessage(Map data) { + return data.containsKey(KEY_TITLE) && data.containsKey(KEY_MESSAGE); + } } diff --git a/app/src/main/java/com/itsaky/androidide/services/builder/BuildListener.java b/app/src/main/java/com/itsaky/androidide/services/builder/BuildListener.java index 94ea6f518..739c8c702 100755 --- a/app/src/main/java/com/itsaky/androidide/services/builder/BuildListener.java +++ b/app/src/main/java/com/itsaky/androidide/services/builder/BuildListener.java @@ -20,63 +20,64 @@ package com.itsaky.androidide.services.builder; import com.itsaky.androidide.project.IDEModule; import com.itsaky.androidide.project.IDEProject; import com.itsaky.androidide.tasks.GradleTask; + import java.util.Optional; /** A client should implement this interface to get callbacks about a build */ public interface BuildListener { - /** Called to notify the client about the modification of build/generated files */ - void onBuildModified(); + /** Called to notify the client about the modification of build/generated files */ + void onBuildModified(); - /** - * Called when the project has been loaded and its data has been read - * - * @param project The loaded project - * @param appModule The main application module - */ - void onProjectLoaded(IDEProject project, Optional appModule); + /** + * Called when the project has been loaded and its data has been read + * + * @param project The loaded project + * @param appModule The main application module + */ + void onProjectLoaded(IDEProject project, Optional appModule); - /** - * Called when a Gradle Daemon is being started - * - * @param task Task that is currently running - */ - void onStartingGradleDaemon(GradleTask task); + /** + * Called when a Gradle Daemon is being started + * + * @param task Task that is currently running + */ + void onStartingGradleDaemon(GradleTask task); - /** - * Called when a task is started - * - * @param task Task that is currently running - * @param name Name of this task that should be shown to user - */ - void onRunTask(GradleTask task, String name); + /** + * Called when a task is started + * + * @param task Task that is currently running + * @param name Name of this task that should be shown to user + */ + void onRunTask(GradleTask task, String name); - /** - * Notifys about a successful build - * - * @param task Task that succeeded - * @param msg Successful message - */ - void onBuildSuccessful(GradleTask task, String msg); + /** + * Notifys about a successful build + * + * @param task Task that succeeded + * @param msg Successful message + */ + void onBuildSuccessful(GradleTask task, String msg); - /** - * Notifys about a failed build - * - * @param task Task that failed - * @param msg Failure message - */ - void onBuildFailed(GradleTask task, String msg); + /** + * Notifys about a failed build + * + * @param task Task that failed + * @param msg Failure message + */ + void onBuildFailed(GradleTask task, String msg); - /** Called to notify client to save any opened files */ - void saveFiles(); + /** Called to notify client to save any opened files */ + void saveFiles(); - /** - * Notifys the client to append the build output that should be shown to user - * - * @param task Task that is currently running - * @param text Output to append - */ - void appendOutput(GradleTask task, CharSequence text); + /** + * Notifys the client to append the build output that should be shown to user + * + * @param task Task that is currently running + * @param text Output to append + */ + void appendOutput(GradleTask task, CharSequence text); - /** Called to notify the client to prepare for a build */ - void prepareBuild(); + /** Called to notify the client to prepare for a build */ + void prepareBuild(); } diff --git a/app/src/main/java/com/itsaky/androidide/services/builder/IDEService.java b/app/src/main/java/com/itsaky/androidide/services/builder/IDEService.java index 58e11461f..c1c9e420f 100755 --- a/app/src/main/java/com/itsaky/androidide/services/builder/IDEService.java +++ b/app/src/main/java/com/itsaky/androidide/services/builder/IDEService.java @@ -21,6 +21,7 @@ import static com.itsaky.androidide.managers.ToolsManager.getCommonAsset; import androidx.annotation.NonNull; import androidx.annotation.StringRes; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.ResourceUtils; @@ -43,6 +44,9 @@ import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.InputStreamLineReader; import com.itsaky.androidide.utils.Logger; import com.itsaky.toaster.Toaster; + +import org.jetbrains.annotations.Contract; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -58,420 +62,433 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import org.jetbrains.annotations.Contract; public class IDEService { - private BuildListener listener; - private GradleTask currentTask; + private BuildListener listener; + private GradleTask currentTask; - private IDEProject mIDEProject; - private IDEModule mAppModule; + private IDEProject mIDEProject; + private IDEModule mAppModule; - private final File projectRoot; - private final IProcessExecutor processExecutor; + private final File projectRoot; + private final IProcessExecutor processExecutor; - private boolean isBuilding = false; + private boolean isBuilding = false; - public static final int TASK_SHOW_DEPENDENCIES = GradleTask.TASK_SHOW_DEPENDENCIES; - public static final int TASK_ASSEMBLE_DEBUG = GradleTask.TASK_ASSEMBLE_DEBUG; - public static final int TASK_ASSEMBLE_RELEASE = GradleTask.TASK_ASSEMBLE_RELEASE; - public static final int TASK_BUILD = GradleTask.TASK_BUILD; - public static final int TASK_BUNDLE = GradleTask.TASK_BUNDLE; - public static final int TASK_CLEAN = GradleTask.TASK_CLEAN; - public static final int TASK_CLEAN_BUILD = GradleTask.TASK_CLEAN_BUILD; - public static final int TASK_DEX = GradleTask.TASK_DEX; - public static final int TASK_LINT = GradleTask.TASK_LINT; - public static final int TASK_LINT_DEBUG = GradleTask.TASK_LINT_DEBUG; - public static final int TASK_LINT_RELEASE = GradleTask.TASK_LINT_RELEASE; + public static final int TASK_SHOW_DEPENDENCIES = GradleTask.TASK_SHOW_DEPENDENCIES; + public static final int TASK_ASSEMBLE_DEBUG = GradleTask.TASK_ASSEMBLE_DEBUG; + public static final int TASK_ASSEMBLE_RELEASE = GradleTask.TASK_ASSEMBLE_RELEASE; + public static final int TASK_BUILD = GradleTask.TASK_BUILD; + public static final int TASK_BUNDLE = GradleTask.TASK_BUNDLE; + public static final int TASK_CLEAN = GradleTask.TASK_CLEAN; + public static final int TASK_CLEAN_BUILD = GradleTask.TASK_CLEAN_BUILD; + public static final int TASK_DEX = GradleTask.TASK_DEX; + public static final int TASK_LINT = GradleTask.TASK_LINT; + public static final int TASK_LINT_DEBUG = GradleTask.TASK_LINT_DEBUG; + public static final int TASK_LINT_RELEASE = GradleTask.TASK_LINT_RELEASE; - public static final Logger LOG = Logger.instance("IDEService"); + public static final Logger LOG = Logger.instance("IDEService"); - private final InputStreamLineReader.OnReadListener mOutputReadListener = this::onBuildOutput; + private final InputStreamLineReader.OnReadListener mOutputReadListener = this::onBuildOutput; - private final IProcessExitListener mProcessExitListener = - new IProcessExitListener() { - @Override - public void onExit(int code) { - isBuilding = false; - if (listener != null) { - final String msg = getString(R.string.msg_gradle_terminated, code); - if (code == 0) { - onBuildSuccessful(currentTask, msg); - } else { - onBuildFailed(currentTask, msg); - } - } + private final IProcessExitListener mProcessExitListener = + new IProcessExitListener() { + @Override + public void onExit(int code) { + isBuilding = false; + if (listener != null) { + final String msg = getString(R.string.msg_gradle_terminated, code); + if (code == 0) { + onBuildSuccessful(currentTask, msg); + } else { + onBuildFailed(currentTask, msg); + } + } + } + }; + + public IDEService(File projectRoot) { + this.projectRoot = projectRoot; + this.processExecutor = ProcessExecutorFactory.commonExecutor(); + } + + public IDEService setListener(final BuildListener listener) { + this.listener = listener == null ? null : new MainThreadBuildListener(listener); + return this; + } + + protected void onBuildOutput(String line) { + if (listener == null || line == null) return; + line = line.trim(); + + String PROJECT_INITIALIZED = ">>> PROJECT INITIALIZED <<<"; + if (line.contains(PROJECT_INITIALIZED)) { + readIdeProject(); + return; } - }; - public IDEService(File projectRoot) { - this.projectRoot = projectRoot; - this.processExecutor = ProcessExecutorFactory.commonExecutor(); - } + String text = line; - public IDEService setListener(final BuildListener listener) { - this.listener = listener == null ? null : new MainThreadBuildListener(listener); - return this; - } + final var app = StudioApp.getInstance(); + // Try to shorten common file paths + if (line.contains(app.getRootDir().getAbsolutePath())) { + text = line.replace(app.getRootDir().getAbsolutePath(), "HOME"); + } + if (line.contains( + Objects.requireNonNull(app.getRootDir().getParentFile()).getAbsolutePath())) { + text = line.replace(app.getRootDir().getParentFile().getAbsolutePath(), "ROOT"); + } - protected void onBuildOutput(String line) { - if (listener == null || line == null) return; - line = line.trim(); + listener.appendOutput(currentTask, text); - String PROJECT_INITIALIZED = ">>> PROJECT INITIALIZED <<<"; - if (line.contains(PROJECT_INITIALIZED)) { - readIdeProject(); - return; + String RUN_TASK = "> Task"; + String STARTING_DAEMON = "Starting a "; + if (line.startsWith(RUN_TASK)) { + listener.onRunTask(currentTask, line.trim()); + } else if (line.startsWith(STARTING_DAEMON)) { + listener.onStartingGradleDaemon(currentTask); + } } - String text = line; + protected void onBuildSuccessful(GradleTask task, String msg) { + if (currentTask != null && currentTask.getTaskID() != TASK_SHOW_DEPENDENCIES) { + listener.onBuildSuccessful(task, msg.trim()); + } + appendOutputSeparator(); - final var app = StudioApp.getInstance(); - // Try to shorten common file paths - if (line.contains(app.getRootDir().getAbsolutePath())) { - text = line.replace(app.getRootDir().getAbsolutePath(), "HOME"); - } - if (line.contains(Objects.requireNonNull(app.getRootDir().getParentFile()).getAbsolutePath())) { - text = line.replace(app.getRootDir().getParentFile().getAbsolutePath(), "ROOT"); + if (currentTask != null && currentTask.affectsGeneratedSources()) { + notifyExternalSourceChange(); + } } - listener.appendOutput(currentTask, text); - - String RUN_TASK = "> Task"; - String STARTING_DAEMON = "Starting a "; - if (line.startsWith(RUN_TASK)) { - listener.onRunTask(currentTask, line.trim()); - } else if (line.startsWith(STARTING_DAEMON)) { - listener.onStartingGradleDaemon(currentTask); - } - } - - protected void onBuildSuccessful(GradleTask task, String msg) { - if (currentTask != null && currentTask.getTaskID() != TASK_SHOW_DEPENDENCIES) { - listener.onBuildSuccessful(task, msg.trim()); - } - appendOutputSeparator(); - - if (currentTask != null && currentTask.affectsGeneratedSources()) { - notifyExternalSourceChange(); - } - } - - protected void onBuildFailed(GradleTask task, @NonNull String msg) { - isBuilding = false; - listener.onBuildFailed(task, msg.trim()); - appendOutputSeparator(); - } - - private void readIdeProject() { - // Parsing project data is meaningless if there is no one listening... - if (listener == null) { - return; + protected void onBuildFailed(GradleTask task, @NonNull String msg) { + isBuilding = false; + listener.onBuildFailed(task, msg.trim()); + appendOutputSeparator(); } - final BuildListener local = listener; - new Thread( - () -> { - mIDEProject = - new Gson() - .fromJson( - FileIOUtils.readFile2String(Environment.PROJECT_DATA_FILE), - IDEProject.class); - if (mIDEProject != null) { - Optional appModule = mIDEProject.getModuleByPath(":app"); - appModule.ifPresent(ideModule -> mAppModule = ideModule); - local.onProjectLoaded(mIDEProject, appModule); - } else { - StudioApp.getInstance() - .toast( - com.itsaky.androidide.R.string.msg_init_project_failed, Toaster.Type.ERROR); - } - }) - .start(); - } + private void readIdeProject() { + // Parsing project data is meaningless if there is no one listening... + if (listener == null) { + return; + } - private void appendOutputSeparator() { - listener.appendOutput(currentTask, "\n\n"); - } - - public void exit() { - // Delete the tmp directory. It is not needed anymore... - FileUtils.delete(Environment.TMP_DIR); - } - - @NonNull - private String currentTime() { - String pattern = "HH:mm:ss"; - DateFormat df = new SimpleDateFormat(pattern, Locale.US); - Date today = Calendar.getInstance().getTime(); - return df.format(today); - } - - private String getString(@StringRes int id) { - return StudioApp.getInstance().getString(id); - } - - private String getString(@StringRes int id, Object... format) { - return StudioApp.getInstance().getString(id, format); - } - - private void notifyExternalSourceChange() { - // TODO Watch for file changes using a file observer - // instead of depending on the build - if (listener != null) { - listener.onBuildModified(); - } - } - - @NonNull - private String[] getArguments(List tasks) { - final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); - final List args = new ArrayList<>(); - - // 'bash' shell is always preferred - if (Environment.SHELL.exists()) { - args.add(Environment.SHELL.getAbsolutePath()); - } else { - args.add(Environment.BUSYBOX.getAbsolutePath()); - args.add("sh"); + final BuildListener local = listener; + new Thread( + () -> { + mIDEProject = + new Gson() + .fromJson( + FileIOUtils.readFile2String( + Environment.PROJECT_DATA_FILE), + IDEProject.class); + if (mIDEProject != null) { + Optional appModule = mIDEProject.getModuleByPath(":app"); + appModule.ifPresent(ideModule -> mAppModule = ideModule); + local.onProjectLoaded(mIDEProject, appModule); + } else { + StudioApp.getInstance() + .toast( + com.itsaky.androidide.R.string + .msg_init_project_failed, + Toaster.Type.ERROR); + } + }) + .start(); } - args.add(new File(projectRoot, "gradlew").getAbsolutePath()); - args.addAll(asAppTasks(tasks)); - args.add("--init-script"); - args.add(Environment.INIT_SCRIPT.getAbsolutePath()); - - if (prefs.isStackTraceEnabled()) { - args.add("--stacktrace"); - } - if (prefs.isGradleInfoEnabled()) { - args.add("--info"); - } - if (prefs.isGradleDebugEnabled()) { - args.add("--debug"); - } - if (prefs.isGradleScanEnabled()) { - args.add("--scan"); - } - if (prefs.isGradleWarningEnabled()) { - args.add("--warning-mode"); - args.add("all"); + private void appendOutputSeparator() { + listener.appendOutput(currentTask, "\n\n"); } - return args.toArray(new String[0]); - } - - private Collection asAppTasks(@NonNull List tasks) { - return tasks.stream() - .filter(Objects::nonNull) - .map(this::asAppModuleTask) - .collect(Collectors.toList()); - } - - @Contract(pure = true) - private String asAppModuleTask(@NonNull String name) { - return name.startsWith(":app:") ? name : ":app:" + name; - } - - public void execTask(GradleTask task) { - execTask(task, false); - } - - public void execTask(GradleTask task, boolean stopIfRunning) { - if (stopIfRunning && isBuilding()) { - stopAllDaemons(); - } else if (isBuilding()) { - return; + public void exit() { + // Delete the tmp directory. It is not needed anymore... + FileUtils.delete(Environment.TMP_DIR); } - execTask( - task, - isGradleWrapperAvailable() - ? CompletableFuture.completedFuture(Boolean.TRUE) - : installWrapper()); - } - - private void execTask(GradleTask task, CompletableFuture installing) { - - final Runnable taskRunner = - () -> { - if (installing != null) { - Boolean result; - String failure = ""; - try { - result = installing.get(); - } catch (Throwable th) { - result = Boolean.FALSE; - failure = th.getMessage(); - } - - if (result.equals(Boolean.FALSE)) { - if (listener != null) { - listener.appendOutput(task, "-------------------- ERROR --------------------"); - listener.appendOutput( - task, getString(R.string.msg_gradlew_installation_failed, failure)); - listener.appendOutput(task, "-----------------------------------------------"); - } - return; - } - } - - if (listener != null) { - - final ProcessStreamsHolder holder = new ProcessStreamsHolder(); - - listener.saveFiles(); - - currentTask = task; - listener.appendOutput( - task, getString(R.string.msg_task_begin, currentTime(), task.getName())); - try { - - Environment.mkdirIfNotExits(Environment.TMP_DIR); - - processExecutor.execAsync( - holder, - mProcessExitListener, - projectRoot.getAbsolutePath(), - true, - getArguments(task.getTasks())); - isBuilding = true; - - startReading(holder.in); - - listener.prepareBuild(); - } catch (Throwable e) { - listener.onBuildFailed(task, e.getMessage()); - } - } - }; - - final Thread thread = new Thread(taskRunner, "GradleTaskExecutor"); - thread.setDaemon(true); - thread.start(); - } - - private void startReading(InputStream in) { - final Thread reader = - new Thread(new InputStreamLineReader(in, mOutputReadListener), "GradleProcessOutputReader"); - reader.setDaemon(true); - reader.start(); - } - - private CompletableFuture installWrapper() { - if (listener != null) { - listener.appendOutput(null, "-------------------- NOTE --------------------"); - listener.appendOutput(null, getString(R.string.msg_installing_gradlew)); - listener.appendOutput(null, "----------------------------------------------"); + @NonNull + private String currentTime() { + String pattern = "HH:mm:ss"; + DateFormat df = new SimpleDateFormat(pattern, Locale.US); + Date today = Calendar.getInstance().getTime(); + return df.format(today); } - return CompletableFuture.supplyAsync( - () -> { - final File extracted = new File(Environment.TMP_DIR, "gradle-wrapper.zip"); - if (ResourceUtils.copyFileFromAssets( - getCommonAsset("gradle-wrapper.zip"), extracted.getAbsolutePath())) { - try { - final List files = ZipUtils.unzipFile(extracted, projectRoot); - if (files != null && !files.isEmpty()) { - return Boolean.TRUE; - } - } catch (IOException e) { - return Boolean.FALSE; - } - } - - return Boolean.FALSE; - }); - } - - public boolean isGradleWrapperAvailable() { - if (projectRoot == null || !projectRoot.exists()) { - return false; + private String getString(@StringRes int id) { + return StudioApp.getInstance().getString(id); } - final File gradlew = new File(projectRoot, "gradlew"); - final File gradleWrapperJar = new File(projectRoot, "gradle/wrapper/gradle-wrapper.jar"); - final File gradleWrapperProps = - new File(projectRoot, "gradle/wrapper/gradle-wrapper.properties"); - return gradlew.exists() && gradleWrapperJar.exists() && gradleWrapperProps.exists(); - } - - public void assembleDebug(boolean installApk) { - execTask(((AssembleDebug) BaseGradleTasks.ASSEMBLE_DEBUG).setInstallApk(installApk)); - } - - public void assembleRelease() { - execTask(BaseGradleTasks.ASSEMBLE_RELEASE); - } - - public void build() { - execTask(BaseGradleTasks.BUILD); - } - - public void bundle() { - execTask(BaseGradleTasks.BUNDLE); - } - - public void clean() { - execTask(BaseGradleTasks.CLEAN); - } - - public void cleanAndRebuild() { - execTask(BaseGradleTasks.CLEAN_BUILD); - } - - public void stopAllDaemons() { - StudioApp.getInstance() - .newShell(null) - .bgAppend( - String.format( - Locale.US, "cd '%s' && sh gradlew --stop", projectRoot.getAbsolutePath())); - if (isBuilding()) { - if (listener != null) - listener.onBuildFailed(currentTask, getString(R.string.msg_daemons_stopped)); - isBuilding = false; + private String getString(@StringRes int id, Object... format) { + return StudioApp.getInstance().getString(id, format); } - } - public void lint() { - execTask(BaseGradleTasks.LINT); - } - - public void lintDebug() { - execTask(BaseGradleTasks.LINT_DEBUG); - } - - public void lintRelease() { - execTask(BaseGradleTasks.LINT_RELEASE); - } - - public void updateResourceClasses() { - execTask(new UpdateResourceClassesTask(mAppModule != null && mAppModule.viewBindingEnabled)); - } - - public boolean isBuilding() { - return isBuilding; - } - - public String typeString(int type) { - switch (type) { - case TASK_ASSEMBLE_DEBUG: - return getString(R.string.build_debug); - case TASK_ASSEMBLE_RELEASE: - return getString(R.string.build_release); - case TASK_BUILD: - return getString(R.string.build); - case TASK_CLEAN_BUILD: - return getString(R.string.clean_amp_build); - case TASK_CLEAN: - return getString(R.string.clean_project); - case TASK_BUNDLE: - return getString(R.string.create_aab); - case TASK_DEX: - return getString(R.string.compiling); - default: - return ""; + private void notifyExternalSourceChange() { + // TODO Watch for file changes using a file observer + // instead of depending on the build + if (listener != null) { + listener.onBuildModified(); + } + } + + @NonNull + private String[] getArguments(List tasks) { + final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); + final List args = new ArrayList<>(); + + // 'bash' shell is always preferred + if (Environment.SHELL.exists()) { + args.add(Environment.SHELL.getAbsolutePath()); + } else { + args.add(Environment.BUSYBOX.getAbsolutePath()); + args.add("sh"); + } + + args.add(new File(projectRoot, "gradlew").getAbsolutePath()); + args.addAll(asAppTasks(tasks)); + args.add("--init-script"); + args.add(Environment.INIT_SCRIPT.getAbsolutePath()); + + if (prefs.isStackTraceEnabled()) { + args.add("--stacktrace"); + } + if (prefs.isGradleInfoEnabled()) { + args.add("--info"); + } + if (prefs.isGradleDebugEnabled()) { + args.add("--debug"); + } + if (prefs.isGradleScanEnabled()) { + args.add("--scan"); + } + if (prefs.isGradleWarningEnabled()) { + args.add("--warning-mode"); + args.add("all"); + } + + return args.toArray(new String[0]); + } + + private Collection asAppTasks(@NonNull List tasks) { + return tasks.stream() + .filter(Objects::nonNull) + .map(this::asAppModuleTask) + .collect(Collectors.toList()); + } + + @Contract(pure = true) + private String asAppModuleTask(@NonNull String name) { + return name.startsWith(":app:") ? name : ":app:" + name; + } + + public void execTask(GradleTask task) { + execTask(task, false); + } + + public void execTask(GradleTask task, boolean stopIfRunning) { + if (stopIfRunning && isBuilding()) { + stopAllDaemons(); + } else if (isBuilding()) { + return; + } + + execTask( + task, + isGradleWrapperAvailable() + ? CompletableFuture.completedFuture(Boolean.TRUE) + : installWrapper()); + } + + private void execTask(GradleTask task, CompletableFuture installing) { + + final Runnable taskRunner = + () -> { + if (installing != null) { + Boolean result; + String failure = ""; + try { + result = installing.get(); + } catch (Throwable th) { + result = Boolean.FALSE; + failure = th.getMessage(); + } + + if (result.equals(Boolean.FALSE)) { + if (listener != null) { + listener.appendOutput( + task, "-------------------- ERROR --------------------"); + listener.appendOutput( + task, + getString( + R.string.msg_gradlew_installation_failed, failure)); + listener.appendOutput( + task, "-----------------------------------------------"); + } + return; + } + } + + if (listener != null) { + + final ProcessStreamsHolder holder = new ProcessStreamsHolder(); + + listener.saveFiles(); + + currentTask = task; + listener.appendOutput( + task, + getString(R.string.msg_task_begin, currentTime(), task.getName())); + try { + + Environment.mkdirIfNotExits(Environment.TMP_DIR); + + processExecutor.execAsync( + holder, + mProcessExitListener, + projectRoot.getAbsolutePath(), + true, + getArguments(task.getTasks())); + isBuilding = true; + + startReading(holder.in); + + listener.prepareBuild(); + } catch (Throwable e) { + listener.onBuildFailed(task, e.getMessage()); + } + } + }; + + final Thread thread = new Thread(taskRunner, "GradleTaskExecutor"); + thread.setDaemon(true); + thread.start(); + } + + private void startReading(InputStream in) { + final Thread reader = + new Thread( + new InputStreamLineReader(in, mOutputReadListener), + "GradleProcessOutputReader"); + reader.setDaemon(true); + reader.start(); + } + + private CompletableFuture installWrapper() { + if (listener != null) { + listener.appendOutput(null, "-------------------- NOTE --------------------"); + listener.appendOutput(null, getString(R.string.msg_installing_gradlew)); + listener.appendOutput(null, "----------------------------------------------"); + } + + return CompletableFuture.supplyAsync( + () -> { + final File extracted = new File(Environment.TMP_DIR, "gradle-wrapper.zip"); + if (ResourceUtils.copyFileFromAssets( + getCommonAsset("gradle-wrapper.zip"), extracted.getAbsolutePath())) { + try { + final List files = ZipUtils.unzipFile(extracted, projectRoot); + if (files != null && !files.isEmpty()) { + return Boolean.TRUE; + } + } catch (IOException e) { + return Boolean.FALSE; + } + } + + return Boolean.FALSE; + }); + } + + public boolean isGradleWrapperAvailable() { + if (projectRoot == null || !projectRoot.exists()) { + return false; + } + final File gradlew = new File(projectRoot, "gradlew"); + final File gradleWrapperJar = new File(projectRoot, "gradle/wrapper/gradle-wrapper.jar"); + final File gradleWrapperProps = + new File(projectRoot, "gradle/wrapper/gradle-wrapper.properties"); + + return gradlew.exists() && gradleWrapperJar.exists() && gradleWrapperProps.exists(); + } + + public void assembleDebug(boolean installApk) { + execTask(((AssembleDebug) BaseGradleTasks.ASSEMBLE_DEBUG).setInstallApk(installApk)); + } + + public void assembleRelease() { + execTask(BaseGradleTasks.ASSEMBLE_RELEASE); + } + + public void build() { + execTask(BaseGradleTasks.BUILD); + } + + public void bundle() { + execTask(BaseGradleTasks.BUNDLE); + } + + public void clean() { + execTask(BaseGradleTasks.CLEAN); + } + + public void cleanAndRebuild() { + execTask(BaseGradleTasks.CLEAN_BUILD); + } + + public void stopAllDaemons() { + StudioApp.getInstance() + .newShell(null) + .bgAppend( + String.format( + Locale.US, + "cd '%s' && sh gradlew --stop", + projectRoot.getAbsolutePath())); + if (isBuilding()) { + if (listener != null) + listener.onBuildFailed(currentTask, getString(R.string.msg_daemons_stopped)); + isBuilding = false; + } + } + + public void lint() { + execTask(BaseGradleTasks.LINT); + } + + public void lintDebug() { + execTask(BaseGradleTasks.LINT_DEBUG); + } + + public void lintRelease() { + execTask(BaseGradleTasks.LINT_RELEASE); + } + + public void updateResourceClasses() { + execTask( + new UpdateResourceClassesTask(mAppModule != null && mAppModule.viewBindingEnabled)); + } + + public boolean isBuilding() { + return isBuilding; + } + + public String typeString(int type) { + switch (type) { + case TASK_ASSEMBLE_DEBUG: + return getString(R.string.build_debug); + case TASK_ASSEMBLE_RELEASE: + return getString(R.string.build_release); + case TASK_BUILD: + return getString(R.string.build); + case TASK_CLEAN_BUILD: + return getString(R.string.clean_amp_build); + case TASK_CLEAN: + return getString(R.string.clean_project); + case TASK_BUNDLE: + return getString(R.string.create_aab); + case TASK_DEX: + return getString(R.string.compiling); + default: + return ""; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/services/builder/MainThreadBuildListener.java b/app/src/main/java/com/itsaky/androidide/services/builder/MainThreadBuildListener.java index 8a8b37617..ebbc9aeca 100755 --- a/app/src/main/java/com/itsaky/androidide/services/builder/MainThreadBuildListener.java +++ b/app/src/main/java/com/itsaky/androidide/services/builder/MainThreadBuildListener.java @@ -18,64 +18,66 @@ package com.itsaky.androidide.services.builder; import androidx.annotation.NonNull; + import com.blankj.utilcode.util.ThreadUtils; import com.itsaky.androidide.project.IDEModule; import com.itsaky.androidide.project.IDEProject; import com.itsaky.androidide.tasks.GradleTask; + import java.util.Objects; import java.util.Optional; class MainThreadBuildListener implements BuildListener { - private final BuildListener listener; + private final BuildListener listener; - public MainThreadBuildListener(@NonNull final BuildListener listener) { - Objects.requireNonNull(listener, "Build listener cannot be null"); - this.listener = listener; - } + public MainThreadBuildListener(@NonNull final BuildListener listener) { + Objects.requireNonNull(listener, "Build listener cannot be null"); + this.listener = listener; + } - @Override - public void onBuildModified() { - ThreadUtils.runOnUiThread(listener::onBuildModified); - } + @Override + public void onBuildModified() { + ThreadUtils.runOnUiThread(listener::onBuildModified); + } - @Override - public void onProjectLoaded(IDEProject project, Optional appModule) { - ThreadUtils.runOnUiThread(() -> listener.onProjectLoaded(project, appModule)); - } + @Override + public void onProjectLoaded(IDEProject project, Optional appModule) { + ThreadUtils.runOnUiThread(() -> listener.onProjectLoaded(project, appModule)); + } - @Override - public void onStartingGradleDaemon(GradleTask task) { - ThreadUtils.runOnUiThread(() -> listener.onStartingGradleDaemon(task)); - } + @Override + public void onStartingGradleDaemon(GradleTask task) { + ThreadUtils.runOnUiThread(() -> listener.onStartingGradleDaemon(task)); + } - @Override - public void onRunTask(GradleTask task, String name) { - ThreadUtils.runOnUiThread(() -> listener.onRunTask(task, name)); - } + @Override + public void onRunTask(GradleTask task, String name) { + ThreadUtils.runOnUiThread(() -> listener.onRunTask(task, name)); + } - @Override - public void onBuildSuccessful(GradleTask task, String msg) { - ThreadUtils.runOnUiThread(() -> listener.onBuildSuccessful(task, msg)); - } + @Override + public void onBuildSuccessful(GradleTask task, String msg) { + ThreadUtils.runOnUiThread(() -> listener.onBuildSuccessful(task, msg)); + } - @Override - public void onBuildFailed(GradleTask task, String msg) { - ThreadUtils.runOnUiThread(() -> listener.onBuildFailed(task, msg)); - } + @Override + public void onBuildFailed(GradleTask task, String msg) { + ThreadUtils.runOnUiThread(() -> listener.onBuildFailed(task, msg)); + } - @Override - public void saveFiles() { - ThreadUtils.runOnUiThread(listener::saveFiles); - } + @Override + public void saveFiles() { + ThreadUtils.runOnUiThread(listener::saveFiles); + } - @Override - public void appendOutput(GradleTask task, CharSequence text) { - ThreadUtils.runOnUiThread(() -> listener.appendOutput(task, text)); - } + @Override + public void appendOutput(GradleTask task, CharSequence text) { + ThreadUtils.runOnUiThread(() -> listener.appendOutput(task, text)); + } - @Override - public void prepareBuild() { - ThreadUtils.runOnUiThread(listener::prepareBuild); - } + @Override + public void prepareBuild() { + ThreadUtils.runOnUiThread(listener::prepareBuild); + } } diff --git a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java index e793ce7dc..b09f5953f 100755 --- a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.syntax.colorschemes; import android.graphics.Color; + import io.github.rosemoe.sora.lang.styling.TextStyle; import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; @@ -29,149 +30,149 @@ import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; */ public class SchemeAndroidIDE extends EditorColorScheme { - private static int current = EditorColorScheme.END_COLOR_ID; + private static int current = EditorColorScheme.END_COLOR_ID; - public static final int LOG_INFO = current++; - public static final int LOG_DEBUG = current++; - public static final int LOG_ERROR = current++; - public static final int LOG_WARNING = current++; - public static final int DIAGNOSTIC_ERROR = current++; - public static final int DIAGNOSTIC_WARNING = current++; - public static final int DIAGNOSTIC_HINT = current++; - public static final int DIAGNOSTIC_INFO = current++; - public static final int STDERR = current++; - public static final int STDOUT = current++; - public static final int XML_TAG = current++; - public static final int FIELD = current++; - public static final int STATIC_FIELD = current++; - public static final int PACKAGE_NAME = current++; - public static final int ENUM_TYPE = current++; - public static final int INTERFACE = current++; - public static final int ENUM = current++; - public static final int PARAMETER = current++; - public static final int CONSTRUCTOR = current++; - public static final int STATIC_INIT = current++; - public static final int INSTANCE_INIT = current++; - public static final int TYPE_PARAM = current++; - public static final int RESOURCE_VARIABLE = current++; - public static final int EXCEPTION_PARAM = current++; - public static final int METHOD_DECLARATION = current++; - public static final int METHOD_INVOCATION = current++; - public static final int TYPE_NAME = current++; - public static final int LOCAL_VARIABLE = current++; - public static final int TODO_COMMENT = current++; - public static final int FIXME_COMMENT = current++; + public static final int LOG_INFO = current++; + public static final int LOG_DEBUG = current++; + public static final int LOG_ERROR = current++; + public static final int LOG_WARNING = current++; + public static final int DIAGNOSTIC_ERROR = current++; + public static final int DIAGNOSTIC_WARNING = current++; + public static final int DIAGNOSTIC_HINT = current++; + public static final int DIAGNOSTIC_INFO = current++; + public static final int STDERR = current++; + public static final int STDOUT = current++; + public static final int XML_TAG = current++; + public static final int FIELD = current++; + public static final int STATIC_FIELD = current++; + public static final int PACKAGE_NAME = current++; + public static final int ENUM_TYPE = current++; + public static final int INTERFACE = current++; + public static final int ENUM = current++; + public static final int PARAMETER = current++; + public static final int CONSTRUCTOR = current++; + public static final int STATIC_INIT = current++; + public static final int INSTANCE_INIT = current++; + public static final int TYPE_PARAM = current++; + public static final int RESOURCE_VARIABLE = current++; + public static final int EXCEPTION_PARAM = current++; + public static final int METHOD_DECLARATION = current++; + public static final int METHOD_INVOCATION = current++; + public static final int TYPE_NAME = current++; + public static final int LOCAL_VARIABLE = current++; + public static final int TODO_COMMENT = current++; + public static final int FIXME_COMMENT = current++; - @Override - public void applyDefault() { - // Apply default colors - super.applyDefault(); + @Override + public void applyDefault() { + // Apply default colors + super.applyDefault(); - // Apply customized colors - setColor(WHOLE_BACKGROUND, 0xff212121); - setColor(LINE_NUMBER_BACKGROUND, 0xff212121); - setColor(LINE_DIVIDER, Color.TRANSPARENT); - setColor(LINE_NUMBER, 0xffaaaaaa); - setColor(LINE_NUMBER_PANEL, 0xff000000); - setColor(LINE_NUMBER_PANEL_TEXT, 0xffffffff); - setColor(TEXT_NORMAL, 0xfff5f5f5); - setColor(SELECTED_TEXT_BACKGROUND, 0xff757575); - setColor(TEXT_SELECTED, 0xff424242); - setColor(SELECTION_INSERT, 0xffff5252); - setColor(SELECTION_HANDLE, 0xffff5252); - setColor(CURRENT_LINE, 0xff313131); - setColor(UNDERLINE, 0xffffffff); - setColor(SCROLL_BAR_THUMB, 0xff757575); - setColor(SCROLL_BAR_THUMB_PRESSED, 0xffff5252); - setColor(SCROLL_BAR_TRACK, 0xffbdbdbd); - setColor(BLOCK_LINE, 0xff424242); - setColor(BLOCK_LINE_CURRENT, 0xffffffff); - setColor(AUTO_COMP_PANEL_BG, 0xff757575); - setColor(AUTO_COMP_PANEL_CORNER, 0xff9e9e9e); - setColor(MATCHED_TEXT_BACKGROUND, 0xffFF8F00); - setColor(NON_PRINTABLE_CHAR, 0xffdddddd); - setColor(KEYWORD, 0xffff6060); - setColor(OPERATOR, 0xff4fc3f7); - setColor(LITERAL, 0xff8bc34a); - setColor(TYPE_NAME, 0xff4fc3f7); - setColor(LOCAL_VARIABLE, 0xfff5f5f5); - setColor(RESOURCE_VARIABLE, 0xfff5f5f5); - setColor(PARAMETER, 0xfff5f5f5); - setColor(EXCEPTION_PARAM, 0xfff5f5f5); - setColor(METHOD_DECLARATION, 0xfff5f5f5); - setColor(METHOD_INVOCATION, 0xfff5f5f5); - setColor(CONSTRUCTOR, 0xfff5f5f5); - setColor(ANNOTATION, 0xff4fc3f7); - setColor(PACKAGE_NAME, 0xfff0be4b); - setColor(FIELD, 0xfff0be4b); - setColor(STATIC_INIT, 0xfff0be4b); - setColor(INSTANCE_INIT, 0xfff0be4b); - setColor(TYPE_PARAM, 0xfff0be4b); - setColor(STATIC_FIELD, 0xffffb300); - setColor(ENUM_TYPE, 0xff4fc3f7); - setColor(INTERFACE, 0xff4fc3f7); - setColor(ENUM, 0xffffb300); - setColor(XML_TAG, 0xffff6060); + // Apply customized colors + setColor(WHOLE_BACKGROUND, 0xff212121); + setColor(LINE_NUMBER_BACKGROUND, 0xff212121); + setColor(LINE_DIVIDER, Color.TRANSPARENT); + setColor(LINE_NUMBER, 0xffaaaaaa); + setColor(LINE_NUMBER_PANEL, 0xff000000); + setColor(LINE_NUMBER_PANEL_TEXT, 0xffffffff); + setColor(TEXT_NORMAL, 0xfff5f5f5); + setColor(SELECTED_TEXT_BACKGROUND, 0xff757575); + setColor(TEXT_SELECTED, 0xff424242); + setColor(SELECTION_INSERT, 0xffff5252); + setColor(SELECTION_HANDLE, 0xffff5252); + setColor(CURRENT_LINE, 0xff313131); + setColor(UNDERLINE, 0xffffffff); + setColor(SCROLL_BAR_THUMB, 0xff757575); + setColor(SCROLL_BAR_THUMB_PRESSED, 0xffff5252); + setColor(SCROLL_BAR_TRACK, 0xffbdbdbd); + setColor(BLOCK_LINE, 0xff424242); + setColor(BLOCK_LINE_CURRENT, 0xffffffff); + setColor(AUTO_COMP_PANEL_BG, 0xff757575); + setColor(AUTO_COMP_PANEL_CORNER, 0xff9e9e9e); + setColor(MATCHED_TEXT_BACKGROUND, 0xffFF8F00); + setColor(NON_PRINTABLE_CHAR, 0xffdddddd); + setColor(KEYWORD, 0xffff6060); + setColor(OPERATOR, 0xff4fc3f7); + setColor(LITERAL, 0xff8bc34a); + setColor(TYPE_NAME, 0xff4fc3f7); + setColor(LOCAL_VARIABLE, 0xfff5f5f5); + setColor(RESOURCE_VARIABLE, 0xfff5f5f5); + setColor(PARAMETER, 0xfff5f5f5); + setColor(EXCEPTION_PARAM, 0xfff5f5f5); + setColor(METHOD_DECLARATION, 0xfff5f5f5); + setColor(METHOD_INVOCATION, 0xfff5f5f5); + setColor(CONSTRUCTOR, 0xfff5f5f5); + setColor(ANNOTATION, 0xff4fc3f7); + setColor(PACKAGE_NAME, 0xfff0be4b); + setColor(FIELD, 0xfff0be4b); + setColor(STATIC_INIT, 0xfff0be4b); + setColor(INSTANCE_INIT, 0xfff0be4b); + setColor(TYPE_PARAM, 0xfff0be4b); + setColor(STATIC_FIELD, 0xffffb300); + setColor(ENUM_TYPE, 0xff4fc3f7); + setColor(INTERFACE, 0xff4fc3f7); + setColor(ENUM, 0xffffb300); + setColor(XML_TAG, 0xffff6060); - setColor(DIAGNOSTIC_ERROR, 0xfff44336); - setColor(DIAGNOSTIC_WARNING, 0xffFFEB3B); - setColor(DIAGNOSTIC_INFO, 0xff4CAF50); - setColor(DIAGNOSTIC_HINT, 0xffffffff); + setColor(DIAGNOSTIC_ERROR, 0xfff44336); + setColor(DIAGNOSTIC_WARNING, 0xffFFEB3B); + setColor(DIAGNOSTIC_INFO, 0xff4CAF50); + setColor(DIAGNOSTIC_HINT, 0xffffffff); - setColor(STDERR, 0xfff44336); - setColor(STDOUT, 0xff4CAF50); + setColor(STDERR, 0xfff44336); + setColor(STDOUT, 0xff4CAF50); - setColor(TODO_COMMENT, 0xffffc400); - setColor(FIXME_COMMENT, 0xffffab00); - setColor(COMMENT, 0xffbdbdbd); - } + setColor(TODO_COMMENT, 0xffffc400); + setColor(FIXME_COMMENT, 0xffffab00); + setColor(COMMENT, 0xffbdbdbd); + } - /** - * Delegates to {@link TextStyle#makeStyle(int)} - * - * @param id The color id. - * @return The style flags. - */ - public static long get(int id) { - return TextStyle.makeStyle(id); - } + /** + * Delegates to {@link TextStyle#makeStyle(int)} + * + * @param id The color id. + * @return The style flags. + */ + public static long get(int id) { + return TextStyle.makeStyle(id); + } - /** - * Create style for keywords. Convenient method to avoid calling {@link TextStyle#makeStyle(int, - * int, boolean, boolean, boolean)} - * - * @return The default style for keywords. - */ - public static long forKeyword() { - return TextStyle.makeStyle(KEYWORD, 0, true, false, false); - } + /** + * Create style for keywords. Convenient method to avoid calling {@link TextStyle#makeStyle(int, + * int, boolean, boolean, boolean)} + * + * @return The default style for keywords. + */ + public static long forKeyword() { + return TextStyle.makeStyle(KEYWORD, 0, true, false, false); + } - /** - * Create style for string literals. The returned style sets the {@link - * TextStyle#NO_COMPLETION_BIT}. - * - * @return The style for string literals. - */ - public static long forString() { - return TextStyle.makeStyle(LITERAL, true); - } + /** + * Create style for string literals. The returned style sets the {@link + * TextStyle#NO_COMPLETION_BIT}. + * + * @return The style for string literals. + */ + public static long forString() { + return TextStyle.makeStyle(LITERAL, true); + } - /** - * Create color style for default comments. - * - * @return The style for {@link #COMMENT}. - */ - public static long forComment() { - return TextStyle.makeStyle(COMMENT, true); - } + /** + * Create color style for default comments. + * + * @return The style for {@link #COMMENT}. + */ + public static long forComment() { + return TextStyle.makeStyle(COMMENT, true); + } - /** - * Create style for the given id without completions. - * - * @param id The id to create style for. - * @return The style for the id. - */ - public static long withoutCompletion(int id) { - return TextStyle.makeStyle(id, true); - } + /** + * Create style for the given id without completions. + * + * @param id The id to create style for. + * @return The style for the id. + */ + public static long withoutCompletion(int id) { + return TextStyle.makeStyle(id, true); + } } diff --git a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/Highlighter.java b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/Highlighter.java index 6aae2b4a0..659c2f9d5 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/Highlighter.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/Highlighter.java @@ -28,5 +28,5 @@ import android.text.SpannableStringBuilder; * an EditorLanguage instead */ public interface Highlighter { - SpannableStringBuilder highlight(String code, String match) throws Exception; + SpannableStringBuilder highlight(String code, String match) throws Exception; } diff --git a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java index 1ff564c76..36e05d2d7 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java @@ -19,183 +19,193 @@ package com.itsaky.androidide.syntax.highlighters; import android.text.SpannableStringBuilder; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; + import com.itsaky.androidide.lexers.java.JavaLexer; import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE; + import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; -import java.io.StringReader; -import java.util.regex.Matcher; -import java.util.regex.Pattern; + import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.Token; +import java.io.StringReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class JavaHighlighter implements Highlighter { - @Override - public SpannableStringBuilder highlight(String code, String match) throws Exception { - final JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(code))); - final SpannableStringBuilder sb = new SpannableStringBuilder(); - final EditorColorScheme scheme = new SchemeAndroidIDE(); - Token token; - while ((token = lexer.nextToken()) != null) { - if (token.getType() == JavaLexer.EOF) break; - switch (token.getType()) { - case JavaLexer.WS: - sb.append(token.getText()); - break; - case JavaLexer.ABSTRACT: - case JavaLexer.ASSERT: - case JavaLexer.BREAK: - case JavaLexer.CASE: - case JavaLexer.CATCH: - case JavaLexer.CLASS: - case JavaLexer.CONST: - case JavaLexer.CONTINUE: - case JavaLexer.DEFAULT: - case JavaLexer.DO: - case JavaLexer.ELSE: - case JavaLexer.EXTENDS: - case JavaLexer.FINAL: - case JavaLexer.FINALLY: - case JavaLexer.FOR: - case JavaLexer.IF: - case JavaLexer.GOTO: - case JavaLexer.IMPLEMENTS: - case JavaLexer.IMPORT: - case JavaLexer.INSTANCEOF: - case JavaLexer.INTERFACE: - case JavaLexer.NATIVE: - case JavaLexer.NEW: - case JavaLexer.PACKAGE: - case JavaLexer.PRIVATE: - case JavaLexer.PROTECTED: - case JavaLexer.PUBLIC: - case JavaLexer.RETURN: - case JavaLexer.STATIC: - case JavaLexer.STRICTFP: - case JavaLexer.SUPER: - case JavaLexer.SWITCH: - case JavaLexer.SYNCHRONIZED: - case JavaLexer.THIS: - case JavaLexer.THROW: - case JavaLexer.THROWS: - case JavaLexer.TRANSIENT: - case JavaLexer.TRY: - case JavaLexer.VOID: - case JavaLexer.VOLATILE: - case JavaLexer.WHILE: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.KEYWORD)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case JavaLexer.DECIMAL_LITERAL: - case JavaLexer.HEX_LITERAL: - case JavaLexer.OCT_LITERAL: - case JavaLexer.BINARY_LITERAL: - case JavaLexer.FLOAT_LITERAL: - case JavaLexer.HEX_FLOAT_LITERAL: - case JavaLexer.BOOL_LITERAL: - case JavaLexer.CHAR_LITERAL: - case JavaLexer.NULL_LITERAL: - case JavaLexer.STRING_LITERAL: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.LITERAL)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case JavaLexer.LPAREN: - case JavaLexer.RPAREN: - case JavaLexer.LBRACK: - case JavaLexer.RBRACK: - case JavaLexer.SEMI: - case JavaLexer.COMMA: - case JavaLexer.ASSIGN: - case JavaLexer.GT: - case JavaLexer.LT: - case JavaLexer.BANG: - case JavaLexer.TILDE: - case JavaLexer.QUESTION: - case JavaLexer.COLON: - case JavaLexer.EQUAL: - case JavaLexer.GE: - case JavaLexer.LE: - case JavaLexer.NOTEQUAL: - case JavaLexer.AND: - case JavaLexer.OR: - case JavaLexer.INC: - case JavaLexer.DEC: - case JavaLexer.ADD: - case JavaLexer.SUB: - case JavaLexer.MUL: - case JavaLexer.DIV: - case JavaLexer.BITAND: - case JavaLexer.BITOR: - case JavaLexer.CARET: - case JavaLexer.MOD: - case JavaLexer.ADD_ASSIGN: - case JavaLexer.SUB_ASSIGN: - case JavaLexer.MUL_ASSIGN: - case JavaLexer.DIV_ASSIGN: - case JavaLexer.AND_ASSIGN: - case JavaLexer.OR_ASSIGN: - case JavaLexer.XOR_ASSIGN: - case JavaLexer.MOD_ASSIGN: - case JavaLexer.LSHIFT_ASSIGN: - case JavaLexer.RSHIFT_ASSIGN: - case JavaLexer.URSHIFT_ASSIGN: - case JavaLexer.ARROW: - case JavaLexer.COLONCOLON: - case JavaLexer.ELLIPSIS: - case JavaLexer.DOT: - case JavaLexer.LBRACE: - case JavaLexer.RBRACE: - case JavaLexer.AT: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.OPERATOR)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case JavaLexer.BOOLEAN: - case JavaLexer.BYTE: - case JavaLexer.CHAR: - case JavaLexer.DOUBLE: - case JavaLexer.ENUM: - case JavaLexer.FLOAT: - case JavaLexer.INT: - case JavaLexer.LONG: - case JavaLexer.SHORT: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(SchemeAndroidIDE.TYPE_NAME)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case JavaLexer.BLOCK_COMMENT: - case JavaLexer.LINE_COMMENT: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.COMMENT)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - default: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.TEXT_NORMAL)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - } - } - final BackgroundColorSpan bg = new BackgroundColorSpan(0xffffff00); - final ForegroundColorSpan fg = new ForegroundColorSpan(0xff000000); + @Override + public SpannableStringBuilder highlight(String code, String match) throws Exception { + final JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(code))); + final SpannableStringBuilder sb = new SpannableStringBuilder(); + final EditorColorScheme scheme = new SchemeAndroidIDE(); + Token token; + while ((token = lexer.nextToken()) != null) { + if (token.getType() == JavaLexer.EOF) break; + switch (token.getType()) { + case JavaLexer.WS: + sb.append(token.getText()); + break; + case JavaLexer.ABSTRACT: + case JavaLexer.ASSERT: + case JavaLexer.BREAK: + case JavaLexer.CASE: + case JavaLexer.CATCH: + case JavaLexer.CLASS: + case JavaLexer.CONST: + case JavaLexer.CONTINUE: + case JavaLexer.DEFAULT: + case JavaLexer.DO: + case JavaLexer.ELSE: + case JavaLexer.EXTENDS: + case JavaLexer.FINAL: + case JavaLexer.FINALLY: + case JavaLexer.FOR: + case JavaLexer.IF: + case JavaLexer.GOTO: + case JavaLexer.IMPLEMENTS: + case JavaLexer.IMPORT: + case JavaLexer.INSTANCEOF: + case JavaLexer.INTERFACE: + case JavaLexer.NATIVE: + case JavaLexer.NEW: + case JavaLexer.PACKAGE: + case JavaLexer.PRIVATE: + case JavaLexer.PROTECTED: + case JavaLexer.PUBLIC: + case JavaLexer.RETURN: + case JavaLexer.STATIC: + case JavaLexer.STRICTFP: + case JavaLexer.SUPER: + case JavaLexer.SWITCH: + case JavaLexer.SYNCHRONIZED: + case JavaLexer.THIS: + case JavaLexer.THROW: + case JavaLexer.THROWS: + case JavaLexer.TRANSIENT: + case JavaLexer.TRY: + case JavaLexer.VOID: + case JavaLexer.VOLATILE: + case JavaLexer.WHILE: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.KEYWORD)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case JavaLexer.DECIMAL_LITERAL: + case JavaLexer.HEX_LITERAL: + case JavaLexer.OCT_LITERAL: + case JavaLexer.BINARY_LITERAL: + case JavaLexer.FLOAT_LITERAL: + case JavaLexer.HEX_FLOAT_LITERAL: + case JavaLexer.BOOL_LITERAL: + case JavaLexer.CHAR_LITERAL: + case JavaLexer.NULL_LITERAL: + case JavaLexer.STRING_LITERAL: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.LITERAL)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case JavaLexer.LPAREN: + case JavaLexer.RPAREN: + case JavaLexer.LBRACK: + case JavaLexer.RBRACK: + case JavaLexer.SEMI: + case JavaLexer.COMMA: + case JavaLexer.ASSIGN: + case JavaLexer.GT: + case JavaLexer.LT: + case JavaLexer.BANG: + case JavaLexer.TILDE: + case JavaLexer.QUESTION: + case JavaLexer.COLON: + case JavaLexer.EQUAL: + case JavaLexer.GE: + case JavaLexer.LE: + case JavaLexer.NOTEQUAL: + case JavaLexer.AND: + case JavaLexer.OR: + case JavaLexer.INC: + case JavaLexer.DEC: + case JavaLexer.ADD: + case JavaLexer.SUB: + case JavaLexer.MUL: + case JavaLexer.DIV: + case JavaLexer.BITAND: + case JavaLexer.BITOR: + case JavaLexer.CARET: + case JavaLexer.MOD: + case JavaLexer.ADD_ASSIGN: + case JavaLexer.SUB_ASSIGN: + case JavaLexer.MUL_ASSIGN: + case JavaLexer.DIV_ASSIGN: + case JavaLexer.AND_ASSIGN: + case JavaLexer.OR_ASSIGN: + case JavaLexer.XOR_ASSIGN: + case JavaLexer.MOD_ASSIGN: + case JavaLexer.LSHIFT_ASSIGN: + case JavaLexer.RSHIFT_ASSIGN: + case JavaLexer.URSHIFT_ASSIGN: + case JavaLexer.ARROW: + case JavaLexer.COLONCOLON: + case JavaLexer.ELLIPSIS: + case JavaLexer.DOT: + case JavaLexer.LBRACE: + case JavaLexer.RBRACE: + case JavaLexer.AT: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.OPERATOR)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case JavaLexer.BOOLEAN: + case JavaLexer.BYTE: + case JavaLexer.CHAR: + case JavaLexer.DOUBLE: + case JavaLexer.ENUM: + case JavaLexer.FLOAT: + case JavaLexer.INT: + case JavaLexer.LONG: + case JavaLexer.SHORT: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(SchemeAndroidIDE.TYPE_NAME)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case JavaLexer.BLOCK_COMMENT: + case JavaLexer.LINE_COMMENT: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.COMMENT)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + default: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.TEXT_NORMAL)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + } + } + final BackgroundColorSpan bg = new BackgroundColorSpan(0xffffff00); + final ForegroundColorSpan fg = new ForegroundColorSpan(0xff000000); - Pattern pattern = Pattern.compile(Pattern.quote(match)); - Matcher matcher = pattern.matcher(code); - while (matcher.find()) { - sb.setSpan( - bg, matcher.start(), matcher.end(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - sb.setSpan( - fg, matcher.start(), matcher.end(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } + Pattern pattern = Pattern.compile(Pattern.quote(match)); + Matcher matcher = pattern.matcher(code); + while (matcher.find()) { + sb.setSpan( + bg, + matcher.start(), + matcher.end(), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + sb.setSpan( + fg, + matcher.start(), + matcher.end(), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + } - return sb; - } + return sb; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/BaseGradleTask.java b/app/src/main/java/com/itsaky/androidide/tasks/BaseGradleTask.java index 02ab54ca6..143db5cd9 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/BaseGradleTask.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/BaseGradleTask.java @@ -24,21 +24,23 @@ import com.itsaky.androidide.services.builder.IDEService; public abstract class BaseGradleTask implements GradleTask { - @Override - public boolean affectsGeneratedSources() { - return false; - } + @Override + public boolean affectsGeneratedSources() { + return false; + } - @Override - public boolean shouldSaveFiles() { - return getType() == Type.BUILD || getType() == Type.LINT || getTaskID() == IDEService.TASK_DEX; - } + @Override + public boolean shouldSaveFiles() { + return getType() == Type.BUILD + || getType() == Type.LINT + || getTaskID() == IDEService.TASK_DEX; + } - @Override - public boolean equals(Object obj) { - return obj != null - && obj instanceof GradleTask - && getTaskID() == ((GradleTask) obj).getTaskID() - && getType() == ((GradleTask) obj).getType(); - } + @Override + public boolean equals(Object obj) { + return obj != null + && obj instanceof GradleTask + && getTaskID() == ((GradleTask) obj).getTaskID() + && getType() == ((GradleTask) obj).getType(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/GradleTask.java b/app/src/main/java/com/itsaky/androidide/tasks/GradleTask.java index 795e82f33..1ef74b7f1 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/GradleTask.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/GradleTask.java @@ -24,45 +24,45 @@ import java.util.List; public interface GradleTask { - public String getName(); + public String getName(); - public String getCommands(); + public String getCommands(); - public List getTasks(); + public List getTasks(); - public int getTaskID(); + public int getTaskID(); - public boolean affectsGeneratedSources(); + public boolean affectsGeneratedSources(); - public boolean shouldSaveFiles(); + public boolean shouldSaveFiles(); - public boolean canOutput(); + public boolean canOutput(); - public Type getType(); + public Type getType(); - public static enum Type { - ANDROIDIDE_PLUGIN, - BUILD, - LINT, - HELP, - OTHER - } + public static enum Type { + ANDROIDIDE_PLUGIN, + BUILD, + LINT, + HELP, + OTHER + } - public static final int TASK_SHOW_DEPENDENCIES = 0; - public static final int TASK_ASSEMBLE_DEBUG = 1; - public static final int TASK_ASSEMBLE_RELEASE = 2; - public static final int TASK_BUILD = 3; - public static final int TASK_BUNDLE = 4; - public static final int TASK_CLEAN = 5; - public static final int TASK_CLEAN_BUILD = 6; - public static final int TASK_COMPILE_JAVA = 7; - public static final int TASK_DEX = 8; - public static final int TASK_LINT = 9; - public static final int TASK_LINT_DEBUG = 10; - public static final int TASK_LINT_RELEASE = 11; - public static final int TASK_TASKS = 12; - public static final int TASK_INIT_PROJECT = 13; - public static final int TASK_PROCESS_DEBUG_RESOURCES = 14; - public static final int DATABINDING_GEN_BASE_CLASSES_DEBUG = 15; - public static final int TASK_UPDATE_RESOURCE_CLASSES = 16; + public static final int TASK_SHOW_DEPENDENCIES = 0; + public static final int TASK_ASSEMBLE_DEBUG = 1; + public static final int TASK_ASSEMBLE_RELEASE = 2; + public static final int TASK_BUILD = 3; + public static final int TASK_BUNDLE = 4; + public static final int TASK_CLEAN = 5; + public static final int TASK_CLEAN_BUILD = 6; + public static final int TASK_COMPILE_JAVA = 7; + public static final int TASK_DEX = 8; + public static final int TASK_LINT = 9; + public static final int TASK_LINT_DEBUG = 10; + public static final int TASK_LINT_RELEASE = 11; + public static final int TASK_TASKS = 12; + public static final int TASK_INIT_PROJECT = 13; + public static final int TASK_PROCESS_DEBUG_RESOURCES = 14; + public static final int DATABINDING_GEN_BASE_CLASSES_DEBUG = 15; + public static final int TASK_UPDATE_RESOURCE_CLASSES = 16; } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java b/app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java index 41ec5ea5a..aff4ca5d9 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java @@ -22,60 +22,62 @@ package com.itsaky.androidide.tasks; import android.os.Handler; import android.os.Looper; + import com.itsaky.androidide.utils.Logger; + import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class TaskExecutor { - private final Executor executor = Executors.newSingleThreadExecutor(); - private final Handler handler = new Handler(Looper.getMainLooper()); + private final Executor executor = Executors.newSingleThreadExecutor(); + private final Handler handler = new Handler(Looper.getMainLooper()); - private final Logger LOG = Logger.instance("TaskExecutor"); + private final Logger LOG = Logger.instance("TaskExecutor"); - public interface Callback { - void complete(R result); - } + public interface Callback { + void complete(R result); + } - public interface CallbackWithError { - void complete(R result, Throwable error); - } + public interface CallbackWithError { + void complete(R result, Throwable error); + } - public void executeAsync(Callable callable, Callback callback) { - executor.execute( - () -> { - try { - final R result = callable.call(); - handler.post( + public void executeAsync(Callable callable, Callback callback) { + executor.execute( () -> { - callback.complete(result); + try { + final R result = callable.call(); + handler.post( + () -> { + callback.complete(result); + }); + } catch (Throwable th) { + LOG.error("Callable task was not able to finish", th); + } }); - } catch (Throwable th) { - LOG.error("Callable task was not able to finish", th); - } - }); - } + } - public void executeAsyncProvideError(Callable callable, CallbackWithError callback) { - executor.execute( - () -> { - Throwable error = null; - R result = null; + public void executeAsyncProvideError(Callable callable, CallbackWithError callback) { + executor.execute( + () -> { + Throwable error = null; + R result = null; - try { - result = callable.call(); - } catch (Throwable th) { - LOG.error("Callable task was not able to finish", th); - error = th; - } - final R resultCopied = result; - final Throwable errorCopied = error; + try { + result = callable.call(); + } catch (Throwable th) { + LOG.error("Callable task was not able to finish", th); + error = th; + } + final R resultCopied = result; + final Throwable errorCopied = error; - handler.post( - () -> { - callback.complete(resultCopied, errorCopied); - }); - }); - } + handler.post( + () -> { + callback.complete(resultCopied, errorCopied); + }); + }); + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/FileTreeCallable.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/FileTreeCallable.java index ccb6618c2..49404db51 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/FileTreeCallable.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/FileTreeCallable.java @@ -21,8 +21,10 @@ package com.itsaky.androidide.tasks.callables; import android.content.Context; + import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder; import com.unnamed.b.atv.model.TreeNode; + import java.io.File; import java.io.FileFilter; import java.util.Arrays; @@ -30,53 +32,53 @@ import java.util.Comparator; import java.util.concurrent.Callable; public class FileTreeCallable implements Callable { - private final Context ctx; - private TreeNode parent; - private File file; + private final Context ctx; + private TreeNode parent; + private File file; - public FileTreeCallable(Context ctx, TreeNode parent, File file) { - this.ctx = ctx; - this.parent = parent; - this.file = file; - } - - @Override - public Boolean call() throws Exception { - getNodeFromArray(file.listFiles(/*new HiddenFilesFilter()*/ ), parent); - return true; - } - - private void getNodeFromArray(File[] files, TreeNode parent) { - Arrays.sort(files, new SortFileName()); - Arrays.sort(files, new SortFolder()); - for (File file : files) { - TreeNode node = new TreeNode(file); - node.setViewHolder(new FileTreeViewHolder(ctx)); - parent.addChild(node); + public FileTreeCallable(Context ctx, TreeNode parent, File file) { + this.ctx = ctx; + this.parent = parent; + this.file = file; } - } - - public static class HiddenFilesFilter implements FileFilter { @Override - public boolean accept(File p1) { - return !p1.getName().startsWith("."); + public Boolean call() throws Exception { + getNodeFromArray(file.listFiles(/*new HiddenFilesFilter()*/ ), parent); + return true; } - } - public static class SortFileName implements Comparator { - @Override - public int compare(File f1, File f2) { - return f1.getName().compareTo(f2.getName()); + private void getNodeFromArray(File[] files, TreeNode parent) { + Arrays.sort(files, new SortFileName()); + Arrays.sort(files, new SortFolder()); + for (File file : files) { + TreeNode node = new TreeNode(file); + node.setViewHolder(new FileTreeViewHolder(ctx)); + parent.addChild(node); + } } - } - public static class SortFolder implements Comparator { - @Override - public int compare(File f1, File f2) { - if (f1.isDirectory() == f2.isDirectory()) return 0; - else if (f1.isDirectory() && !f2.isDirectory()) return -1; - else return 1; + public static class HiddenFilesFilter implements FileFilter { + + @Override + public boolean accept(File p1) { + return !p1.getName().startsWith("."); + } + } + + public static class SortFileName implements Comparator { + @Override + public int compare(File f1, File f2) { + return f1.getName().compareTo(f2.getName()); + } + } + + public static class SortFolder implements Comparator { + @Override + public int compare(File f1, File f2) { + if (f1.isDirectory() == f2.isDirectory()) return 0; + else if (f1.isDirectory() && !f2.isDirectory()) return -1; + else return 1; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java index 333ecd32f..5be6c8f69 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java @@ -21,9 +21,11 @@ package com.itsaky.androidide.tasks.callables; import android.content.Context; + import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.itsaky.androidide.models.License; + import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.List; @@ -31,22 +33,22 @@ import java.util.concurrent.Callable; public class LicenseReader implements Callable> { - private final Context ctx; + private final Context ctx; - public LicenseReader(Context ctx) { - this.ctx = ctx; - } - - @Override - public List call() throws Exception { - BufferedReader reader = - new BufferedReader(new InputStreamReader(ctx.getAssets().open("licenses.json"))); - String line; - StringBuilder sb = new StringBuilder(); - while ((line = reader.readLine()) != null) { - sb.append(line); + public LicenseReader(Context ctx) { + this.ctx = ctx; + } + + @Override + public List call() throws Exception { + BufferedReader reader = + new BufferedReader(new InputStreamReader(ctx.getAssets().open("licenses.json"))); + String line; + StringBuilder sb = new StringBuilder(); + while ((line = reader.readLine()) != null) { + sb.append(line); + } + TypeToken> token = new TypeToken>() {}; + return new Gson().fromJson(sb.toString(), token.getType()); } - TypeToken> token = new TypeToken>() {}; - return new Gson().fromJson(sb.toString(), token.getType()); - } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/ListDirectoryCallable.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/ListDirectoryCallable.java index 56dd1aaab..99690adfa 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/ListDirectoryCallable.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/ListDirectoryCallable.java @@ -21,29 +21,33 @@ package com.itsaky.androidide.tasks.callables; import android.text.TextUtils; + import com.blankj.utilcode.util.FileUtils; + import java.io.File; import java.io.FileFilter; public class ListDirectoryCallable implements java.util.concurrent.Callable { - private File file; + private File file; - public ListDirectoryCallable(File file) { - this.file = file; - } + public ListDirectoryCallable(File file) { + this.file = file; + } - @Override - public String call() throws Exception { - return TextUtils.join("\n", FileUtils.listFilesInDirWithFilter(file, ARCHIVE_FILTER, false)); - } + @Override + public String call() throws Exception { + return TextUtils.join( + "\n", FileUtils.listFilesInDirWithFilter(file, ARCHIVE_FILTER, false)); + } - private final FileFilter ARCHIVE_FILTER = - new FileFilter() { + private final FileFilter ARCHIVE_FILTER = + new FileFilter() { - @Override - public boolean accept(File p1) { - return p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip")); - } - }; + @Override + public boolean accept(File p1) { + return p1.isFile() + && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip")); + } + }; } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/ProjectCreatorCallable.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/ProjectCreatorCallable.java index d17c79236..f63030b87 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/ProjectCreatorCallable.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/ProjectCreatorCallable.java @@ -24,24 +24,25 @@ import com.itsaky.androidide.interfaces.ProjectWriterCallback; import com.itsaky.androidide.models.NewProjectDetails; import com.itsaky.androidide.models.ProjectTemplate; import com.itsaky.androidide.utils.ProjectWriter; + import java.util.concurrent.Callable; public class ProjectCreatorCallable implements Callable { - private ProjectTemplate template; - private NewProjectDetails details; - private ProjectWriterCallback callback; + private ProjectTemplate template; + private NewProjectDetails details; + private ProjectWriterCallback callback; - public ProjectCreatorCallable( - ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback callback) { - this.template = template; - this.details = details; - this.callback = callback; - } + public ProjectCreatorCallable( + ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback callback) { + this.template = template; + this.details = details; + this.callback = callback; + } - @Override - public Void call() throws Exception { - ProjectWriter.write(template, details, callback); - return null; - } + @Override + public Void call() throws Exception { + ProjectWriter.write(template, details, callback); + return null; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/UnzipCallable.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/UnzipCallable.java index 915ebe482..0016e1223 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/UnzipCallable.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/UnzipCallable.java @@ -21,22 +21,23 @@ package com.itsaky.androidide.tasks.callables; import com.blankj.utilcode.util.ZipUtils; + import java.io.File; import java.util.List; import java.util.concurrent.Callable; public final class UnzipCallable implements Callable> { - private File src; - private File dest; + private File src; + private File dest; - public UnzipCallable(File src, File dest) { - this.src = src; - this.dest = dest; - } + public UnzipCallable(File src, File dest) { + this.src = src; + this.dest = dest; + } - @Override - public List call() throws Exception { - return ZipUtils.unzipFile(src, dest); - } + @Override + public List call() throws Exception { + return ZipUtils.unzipFile(src, dest); + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/BaseGradleTasks.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/BaseGradleTasks.java index d5db37398..1e59e9837 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/BaseGradleTasks.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/BaseGradleTasks.java @@ -35,15 +35,15 @@ import com.itsaky.androidide.tasks.gradle.resources.ProcessDebugResources; public class BaseGradleTasks { - public static final BaseGradleTask BUILD = new Build(), - ASSEMBLE_DEBUG = new AssembleDebug(), - ASSEMBLE_RELEASE = new AssembleRelease(), - BUNDLE = new Bundle(), - CLEAN = new Clean(), - CLEAN_BUILD = new CleanBuild(), - LINT = new Lint(), - LINT_DEBUG = new LintDebug(), - LINT_RELEASE = new LintRelease(), - PROCESS_DEBUG_RESOURCES = new ProcessDebugResources(), - INITIALIZE_IDE_PROJECT = new InitializeIDEProject(); + public static final BaseGradleTask BUILD = new Build(), + ASSEMBLE_DEBUG = new AssembleDebug(), + ASSEMBLE_RELEASE = new AssembleRelease(), + BUNDLE = new Bundle(), + CLEAN = new Clean(), + CLEAN_BUILD = new CleanBuild(), + LINT = new Lint(), + LINT_DEBUG = new LintDebug(), + LINT_RELEASE = new LintRelease(), + PROCESS_DEBUG_RESOURCES = new ProcessDebugResources(), + INITIALIZE_IDE_PROJECT = new InitializeIDEProject(); } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/ApkGeneratingTask.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/ApkGeneratingTask.java index 1079ba7c1..055ea4130 100644 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/ApkGeneratingTask.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/ApkGeneratingTask.java @@ -29,6 +29,7 @@ import com.itsaky.androidide.models.ApkMetadata; import com.itsaky.androidide.tasks.BaseGradleTask; import com.itsaky.androidide.utils.JSONUtility; import com.itsaky.androidide.utils.Logger; + import java.io.File; import java.util.Collections; import java.util.HashSet; @@ -37,107 +38,109 @@ import java.util.Set; public abstract class ApkGeneratingTask extends BaseGradleTask { - /** - * Get the directories where APKs are generated. These directories must contain an - * output-metadata.json file - */ - protected abstract Set provideApkDirectories(File buildDir); + /** + * Get the directories where APKs are generated. These directories must contain an + * output-metadata.json file + */ + protected abstract Set provideApkDirectories(File buildDir); - protected Set directories(File buildDir) { - if (buildDir == null || !buildDir.exists() || !buildDir.isDirectory()) { - return Collections.emptySet(); - } - - return provideApkDirectories(buildDir); - } - - /** Get generated APKs */ - public Set getApks(File buildDir) { - final Set result = new HashSet<>(); - final Set dirs = directories(buildDir); - if (dirs == null || dirs.isEmpty()) { - return result; - } - - for (File dir : dirs) { - final File jsonData = new File(dir, "output-metadata.json"); - if (!jsonData.exists() || !FileUtils.isUtf8(jsonData)) { - continue; - } - - final String contents = FileIOUtils.readFile2String(jsonData); - - try { - final ApkMetadata metadata = JSONUtility.gson.fromJson(contents, ApkMetadata.class); - if (!isValid(metadata)) { - continue; + protected Set directories(File buildDir) { + if (buildDir == null || !buildDir.exists() || !buildDir.isDirectory()) { + return Collections.emptySet(); } - element_finder: - for (Element element : metadata.getElements()) { - if (element == null || element.getOutputFile() == null) { - continue element_finder; - } + return provideApkDirectories(buildDir); + } - if (element.getOutputFile().endsWith(".apk")) { - final File apk = new File(dir, element.getOutputFile()); - if (apk.exists() && apk.isFile()) { - result.add(apk); + /** Get generated APKs */ + public Set getApks(File buildDir) { + final Set result = new HashSet<>(); + final Set dirs = directories(buildDir); + if (dirs == null || dirs.isEmpty()) { + return result; + } + + for (File dir : dirs) { + final File jsonData = new File(dir, "output-metadata.json"); + if (!jsonData.exists() || !FileUtils.isUtf8(jsonData)) { + continue; } - } + + final String contents = FileIOUtils.readFile2String(jsonData); + + try { + final ApkMetadata metadata = JSONUtility.gson.fromJson(contents, ApkMetadata.class); + if (!isValid(metadata)) { + continue; + } + + element_finder: + for (Element element : metadata.getElements()) { + if (element == null || element.getOutputFile() == null) { + continue element_finder; + } + + if (element.getOutputFile().endsWith(".apk")) { + final File apk = new File(dir, element.getOutputFile()); + if (apk.exists() && apk.isFile()) { + result.add(apk); + } + } + } + + } catch (Throwable th) { + LOG.error( + StudioApp.getInstance() + .getString( + com.itsaky.androidide.R.string + .err_cannot_parse_apk_metadata), + th); + } + } + return result; + } + + private boolean isValid(ApkMetadata metadata) { + + // Null checks + if (metadata == null + || metadata.getArtifactType() == null + || metadata.getArtifactType().getType() == null + || metadata.getElements() == null) { + return false; } - } catch (Throwable th) { - LOG.error( - StudioApp.getInstance() - .getString(com.itsaky.androidide.R.string.err_cannot_parse_apk_metadata), - th); - } - } - return result; - } + final ArtifactType type = metadata.getArtifactType(); + final List elements = metadata.getElements(); - private boolean isValid(ApkMetadata metadata) { + if (!type.getType().equals(ArtifactType.TYPE_APK)) { + return false; + } - // Null checks - if (metadata == null - || metadata.getArtifactType() == null - || metadata.getArtifactType().getType() == null - || metadata.getElements() == null) { - return false; + if (elements.isEmpty()) { + return false; + } + + boolean atLeastOneApk = false; + for (Element element : elements) { + if (element == null + || element.getOutputFile() == null + || !element.getOutputFile().endsWith(".apk")) { + continue; + } + + if (element.getOutputFile().endsWith(".apk")) { + atLeastOneApk = true; + break; + } + } + + if (atLeastOneApk) { + return true; + } + + return false; } - final ArtifactType type = metadata.getArtifactType(); - final List elements = metadata.getElements(); - - if (!type.getType().equals(ArtifactType.TYPE_APK)) { - return false; - } - - if (elements.isEmpty()) { - return false; - } - - boolean atLeastOneApk = false; - for (Element element : elements) { - if (element == null - || element.getOutputFile() == null - || !element.getOutputFile().endsWith(".apk")) { - continue; - } - - if (element.getOutputFile().endsWith(".apk")) { - atLeastOneApk = true; - break; - } - } - - if (atLeastOneApk) { - return true; - } - - return false; - } - - private static final Logger LOG = Logger.instance("ApkGeneratingTask"); + private static final Logger LOG = Logger.instance("ApkGeneratingTask"); } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/AssembleDebug.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/AssembleDebug.java index cfcb80390..cf054a536 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/AssembleDebug.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/AssembleDebug.java @@ -23,6 +23,7 @@ package com.itsaky.androidide.tasks.gradle.build; import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; + import java.io.File; import java.util.Arrays; import java.util.HashSet; @@ -31,47 +32,47 @@ import java.util.Set; public class AssembleDebug extends ApkGeneratingTask { - private boolean installApk = true; + private boolean installApk = true; - public AssembleDebug setInstallApk(boolean installApk) { - this.installApk = installApk; - return this; - } + public AssembleDebug setInstallApk(boolean installApk) { + this.installApk = installApk; + return this; + } - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.build_debug); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.build_debug); + } - @Override - protected Set provideApkDirectories(File buildDir) { - final Set dirs = new HashSet<>(); - dirs.add(new File(buildDir, "outputs/apk/debug")); - return dirs; - } + @Override + protected Set provideApkDirectories(File buildDir) { + final Set dirs = new HashSet<>(); + dirs.add(new File(buildDir, "outputs/apk/debug")); + return dirs; + } - @Override - public String getCommands() { - return "assembleDebug"; - } + @Override + public String getCommands() { + return "assembleDebug"; + } - @Override - public List getTasks() { - return Arrays.asList(getCommands()); - } + @Override + public List getTasks() { + return Arrays.asList(getCommands()); + } - @Override - public int getTaskID() { - return IDEService.TASK_ASSEMBLE_DEBUG; - } + @Override + public int getTaskID() { + return IDEService.TASK_ASSEMBLE_DEBUG; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public Type getType() { - return Type.BUILD; - } + @Override + public Type getType() { + return Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/AssembleRelease.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/AssembleRelease.java index fbc619ae0..76e43cc59 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/AssembleRelease.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/AssembleRelease.java @@ -24,6 +24,7 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; import com.itsaky.androidide.tasks.GradleTask; + import java.io.File; import java.util.Arrays; import java.util.HashSet; @@ -32,40 +33,40 @@ import java.util.Set; public class AssembleRelease extends ApkGeneratingTask { - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.build_release); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.build_release); + } - @Override - protected Set provideApkDirectories(File buildDir) { - final Set dirs = new HashSet<>(); - dirs.add(new File(buildDir, "outputs/apk/release")); - return dirs; - } + @Override + protected Set provideApkDirectories(File buildDir) { + final Set dirs = new HashSet<>(); + dirs.add(new File(buildDir, "outputs/apk/release")); + return dirs; + } - @Override - public String getCommands() { - return "assembleRelease"; - } + @Override + public String getCommands() { + return "assembleRelease"; + } - @Override - public List getTasks() { - return Arrays.asList(getCommands()); - } + @Override + public List getTasks() { + return Arrays.asList(getCommands()); + } - @Override - public int getTaskID() { - return IDEService.TASK_ASSEMBLE_RELEASE; - } + @Override + public int getTaskID() { + return IDEService.TASK_ASSEMBLE_RELEASE; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public GradleTask.Type getType() { - return GradleTask.Type.BUILD; - } + @Override + public GradleTask.Type getType() { + return GradleTask.Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Build.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Build.java index 3f7f5638c..57b605112 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Build.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Build.java @@ -24,6 +24,7 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; import com.itsaky.androidide.tasks.GradleTask; + import java.io.File; import java.util.Arrays; import java.util.HashSet; @@ -32,41 +33,41 @@ import java.util.Set; public class Build extends ApkGeneratingTask { - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.build); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.build); + } - @Override - protected Set provideApkDirectories(File buildDir) { - final Set dirs = new HashSet<>(); - dirs.add(new File(buildDir, "outputs/apk/debug")); - dirs.add(new File(buildDir, "outputs/apk/release")); - return dirs; - } + @Override + protected Set provideApkDirectories(File buildDir) { + final Set dirs = new HashSet<>(); + dirs.add(new File(buildDir, "outputs/apk/debug")); + dirs.add(new File(buildDir, "outputs/apk/release")); + return dirs; + } - @Override - public String getCommands() { - return "build"; - } + @Override + public String getCommands() { + return "build"; + } - @Override - public List getTasks() { - return Arrays.asList(getCommands()); - } + @Override + public List getTasks() { + return Arrays.asList(getCommands()); + } - @Override - public int getTaskID() { - return IDEService.TASK_BUILD; - } + @Override + public int getTaskID() { + return IDEService.TASK_BUILD; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public GradleTask.Type getType() { - return GradleTask.Type.BUILD; - } + @Override + public GradleTask.Type getType() { + return GradleTask.Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Bundle.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Bundle.java index a0263dfb6..0c8385b1f 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Bundle.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Bundle.java @@ -25,38 +25,39 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import com.itsaky.androidide.tasks.GradleTask; + import java.util.Arrays; import java.util.List; public class Bundle extends BaseGradleTask { - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.create_aab); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.create_aab); + } - @Override - public String getCommands() { - return "bundle"; - } + @Override + public String getCommands() { + return "bundle"; + } - @Override - public List getTasks() { - return Arrays.asList(getCommands()); - } + @Override + public List getTasks() { + return Arrays.asList(getCommands()); + } - @Override - public int getTaskID() { - return IDEService.TASK_BUNDLE; - } + @Override + public int getTaskID() { + return IDEService.TASK_BUNDLE; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public GradleTask.Type getType() { - return GradleTask.Type.BUILD; - } + @Override + public GradleTask.Type getType() { + return GradleTask.Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Clean.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Clean.java index 831646237..721671534 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Clean.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/Clean.java @@ -25,38 +25,39 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import com.itsaky.androidide.tasks.GradleTask; + import java.util.Arrays; import java.util.List; public class Clean extends BaseGradleTask { - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.clean_project); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.clean_project); + } - @Override - public String getCommands() { - return "clean"; - } + @Override + public String getCommands() { + return "clean"; + } - @Override - public List getTasks() { - return Arrays.asList(getCommands()); - } + @Override + public List getTasks() { + return Arrays.asList(getCommands()); + } - @Override - public int getTaskID() { - return IDEService.TASK_CLEAN; - } + @Override + public int getTaskID() { + return IDEService.TASK_CLEAN; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public GradleTask.Type getType() { - return GradleTask.Type.BUILD; - } + @Override + public GradleTask.Type getType() { + return GradleTask.Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/CleanBuild.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/CleanBuild.java index 218402b24..e3f912c69 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/CleanBuild.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/build/CleanBuild.java @@ -25,38 +25,39 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; import com.itsaky.androidide.tasks.GradleTask; + import java.util.Arrays; import java.util.List; public class CleanBuild extends BaseGradleTask { - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.clean_amp_build); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.clean_amp_build); + } - @Override - public String getCommands() { - return "clean build"; - } + @Override + public String getCommands() { + return "clean build"; + } - @Override - public List getTasks() { - return Arrays.asList("clean", "build"); - } + @Override + public List getTasks() { + return Arrays.asList("clean", "build"); + } - @Override - public int getTaskID() { - return IDEService.TASK_CLEAN_BUILD; - } + @Override + public int getTaskID() { + return IDEService.TASK_CLEAN_BUILD; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public GradleTask.Type getType() { - return GradleTask.Type.BUILD; - } + @Override + public GradleTask.Type getType() { + return GradleTask.Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/Lint.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/Lint.java index e727ce85c..bd83e83c1 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/Lint.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/Lint.java @@ -24,38 +24,39 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; + import java.util.Arrays; import java.util.List; public class Lint extends BaseGradleTask { - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.lint); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.lint); + } - @Override - public String getCommands() { - return "lint"; - } + @Override + public String getCommands() { + return "lint"; + } - @Override - public List getTasks() { - return Arrays.asList("lint"); - } + @Override + public List getTasks() { + return Arrays.asList("lint"); + } - @Override - public int getTaskID() { - return IDEService.TASK_LINT; - } + @Override + public int getTaskID() { + return IDEService.TASK_LINT; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public Type getType() { - return Type.LINT; - } + @Override + public Type getType() { + return Type.LINT; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintDebug.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintDebug.java index 99e6d2ec7..2730f6883 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintDebug.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintDebug.java @@ -24,38 +24,39 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; + import java.util.Collections; import java.util.List; public class LintDebug extends BaseGradleTask { - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.lint_debug); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.lint_debug); + } - @Override - public String getCommands() { - return "lintDebug"; - } + @Override + public String getCommands() { + return "lintDebug"; + } - @Override - public List getTasks() { - return Collections.singletonList("lintDebug"); - } + @Override + public List getTasks() { + return Collections.singletonList("lintDebug"); + } - @Override - public int getTaskID() { - return IDEService.TASK_LINT_DEBUG; - } + @Override + public int getTaskID() { + return IDEService.TASK_LINT_DEBUG; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public Type getType() { - return Type.LINT; - } + @Override + public Type getType() { + return Type.LINT; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintRelease.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintRelease.java index 9a0dcc5f9..929babd0c 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintRelease.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/lint/LintRelease.java @@ -24,38 +24,39 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.services.builder.IDEService; import com.itsaky.androidide.tasks.BaseGradleTask; + import java.util.Arrays; import java.util.List; public class LintRelease extends BaseGradleTask { - @Override - public String getName() { - return StudioApp.getInstance().getString(R.string.lint_release); - } + @Override + public String getName() { + return StudioApp.getInstance().getString(R.string.lint_release); + } - @Override - public String getCommands() { - return "lintRelease"; - } + @Override + public String getCommands() { + return "lintRelease"; + } - @Override - public List getTasks() { - return Arrays.asList("lintRelease"); - } + @Override + public List getTasks() { + return Arrays.asList("lintRelease"); + } - @Override - public int getTaskID() { - return IDEService.TASK_LINT_RELEASE; - } + @Override + public int getTaskID() { + return IDEService.TASK_LINT_RELEASE; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public Type getType() { - return Type.LINT; - } + @Override + public Type getType() { + return Type.LINT; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/plugin/InitializeIDEProject.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/plugin/InitializeIDEProject.java index 63e4024f1..1aa3603b9 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/plugin/InitializeIDEProject.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/plugin/InitializeIDEProject.java @@ -22,38 +22,39 @@ package com.itsaky.androidide.tasks.gradle.plugin; import com.itsaky.androidide.tasks.BaseGradleTask; import com.itsaky.androidide.tasks.GradleTask; + import java.util.Arrays; import java.util.List; public class InitializeIDEProject extends BaseGradleTask { - @Override - public String getName() { - return "initializeIDEProject"; - } + @Override + public String getName() { + return "initializeIDEProject"; + } - @Override - public String getCommands() { - return "initializeIDEProject"; - } + @Override + public String getCommands() { + return "initializeIDEProject"; + } - @Override - public List getTasks() { - return Arrays.asList(getCommands()); - } + @Override + public List getTasks() { + return Arrays.asList(getCommands()); + } - @Override - public int getTaskID() { - return TASK_INIT_PROJECT; - } + @Override + public int getTaskID() { + return TASK_INIT_PROJECT; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public GradleTask.Type getType() { - return Type.ANDROIDIDE_PLUGIN; - } + @Override + public GradleTask.Type getType() { + return Type.ANDROIDIDE_PLUGIN; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/DatabindingGenBaseClassesDebug.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/DatabindingGenBaseClassesDebug.java index e1b7589a2..beb8dd2d1 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/DatabindingGenBaseClassesDebug.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/DatabindingGenBaseClassesDebug.java @@ -21,43 +21,44 @@ package com.itsaky.androidide.tasks.gradle.resources; import com.itsaky.androidide.tasks.BaseGradleTask; + import java.util.Arrays; import java.util.List; public class DatabindingGenBaseClassesDebug extends BaseGradleTask { - @Override - public boolean affectsGeneratedSources() { - return true; - } + @Override + public boolean affectsGeneratedSources() { + return true; + } - @Override - public String getName() { - return "Generate databinding classes"; - } + @Override + public String getName() { + return "Generate databinding classes"; + } - @Override - public String getCommands() { - return "databindingGenBaseClassesDebug"; - } + @Override + public String getCommands() { + return "databindingGenBaseClassesDebug"; + } - @Override - public List getTasks() { - return Arrays.asList(getCommands()); - } + @Override + public List getTasks() { + return Arrays.asList(getCommands()); + } - @Override - public int getTaskID() { - return DATABINDING_GEN_BASE_CLASSES_DEBUG; - } + @Override + public int getTaskID() { + return DATABINDING_GEN_BASE_CLASSES_DEBUG; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public Type getType() { - return Type.BUILD; - } + @Override + public Type getType() { + return Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/ProcessDebugResources.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/ProcessDebugResources.java index 84ed7bd3e..f2c61ca84 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/ProcessDebugResources.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/ProcessDebugResources.java @@ -22,43 +22,44 @@ package com.itsaky.androidide.tasks.gradle.resources; import com.itsaky.androidide.tasks.BaseGradleTask; import com.itsaky.androidide.tasks.GradleTask; + import java.util.Arrays; import java.util.List; public class ProcessDebugResources extends BaseGradleTask { - @Override - public boolean affectsGeneratedSources() { - return true; - } + @Override + public boolean affectsGeneratedSources() { + return true; + } - @Override - public String getName() { - return "processDebugResources"; - } + @Override + public String getName() { + return "processDebugResources"; + } - @Override - public String getCommands() { - return "processDebugResources"; - } + @Override + public String getCommands() { + return "processDebugResources"; + } - @Override - public List getTasks() { - return Arrays.asList(getCommands()); - } + @Override + public List getTasks() { + return Arrays.asList(getCommands()); + } - @Override - public int getTaskID() { - return TASK_PROCESS_DEBUG_RESOURCES; - } + @Override + public int getTaskID() { + return TASK_PROCESS_DEBUG_RESOURCES; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public boolean canOutput() { + return true; + } - @Override - public GradleTask.Type getType() { - return Type.BUILD; - } + @Override + public GradleTask.Type getType() { + return Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/UpdateResourceClassesTask.java b/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/UpdateResourceClassesTask.java index 1c2c24bc7..1fd747239 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/UpdateResourceClassesTask.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/gradle/resources/UpdateResourceClassesTask.java @@ -22,60 +22,61 @@ package com.itsaky.androidide.tasks.gradle.resources; import com.itsaky.androidide.tasks.BaseGradleTask; import com.itsaky.androidide.tasks.GradleTask; + import java.util.ArrayList; import java.util.List; public class UpdateResourceClassesTask extends BaseGradleTask { - private final boolean viewBinding; + private final boolean viewBinding; - public UpdateResourceClassesTask(boolean viewBinding) { - this.viewBinding = viewBinding; - } - - @Override - public boolean affectsGeneratedSources() { - return true; - } - - @Override - public String getName() { - return "Update resource classes"; - } - - @Override - public String getCommands() { - String command = ""; - if (viewBinding) { - command = new DatabindingGenBaseClassesDebug().getCommands(); - command += " "; + public UpdateResourceClassesTask(boolean viewBinding) { + this.viewBinding = viewBinding; } - command += new ProcessDebugResources().getCommands(); - return command; - } - @Override - public List getTasks() { - List tasks = new ArrayList<>(); - if (viewBinding) { - tasks.addAll(new DatabindingGenBaseClassesDebug().getTasks()); + @Override + public boolean affectsGeneratedSources() { + return true; } - tasks.addAll(new ProcessDebugResources().getTasks()); - return tasks; - } - @Override - public int getTaskID() { - return TASK_UPDATE_RESOURCE_CLASSES; - } + @Override + public String getName() { + return "Update resource classes"; + } - @Override - public boolean canOutput() { - return true; - } + @Override + public String getCommands() { + String command = ""; + if (viewBinding) { + command = new DatabindingGenBaseClassesDebug().getCommands(); + command += " "; + } + command += new ProcessDebugResources().getCommands(); + return command; + } - @Override - public GradleTask.Type getType() { - return Type.BUILD; - } + @Override + public List getTasks() { + List tasks = new ArrayList<>(); + if (viewBinding) { + tasks.addAll(new DatabindingGenBaseClassesDebug().getTasks()); + } + tasks.addAll(new ProcessDebugResources().getTasks()); + return tasks; + } + + @Override + public int getTaskID() { + return TASK_UPDATE_RESOURCE_CLASSES; + } + + @Override + public boolean canOutput() { + return true; + } + + @Override + public GradleTask.Type getType() { + return Type.BUILD; + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragData.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragData.java index f61489793..ed237b4fc 100755 --- a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragData.java +++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragData.java @@ -27,29 +27,29 @@ import com.itsaky.inflater.IView; */ public class WidgetDragData { - /** - * Set this to true if the view that is being dragged was already inflated in the layout. If this - * is set to {@code true}, {@link #alreadyInflatedView} must NOT be {@code null}. - * - *

If this is {@code false}, then {@link #newDragData} must be not be null. - */ - public boolean isAlreadyInflated; + /** + * Set this to true if the view that is being dragged was already inflated in the layout. If + * this is set to {@code true}, {@link #alreadyInflatedView} must NOT be {@code null}. + * + *

If this is {@code false}, then {@link #newDragData} must be not be null. + */ + public boolean isAlreadyInflated; - /** - * The view that was already inflated and is being relocated. This must not be null if {@link - * #isAlreadyInflated} is {@code true}. - */ - public IView alreadyInflatedView; + /** + * The view that was already inflated and is being relocated. This must not be null if {@link + * #isAlreadyInflated} is {@code true}. + */ + public IView alreadyInflatedView; - /** The UIWidget that is being dragged. */ - public UIWidget newDragData; + /** The UIWidget that is being dragged. */ + public UIWidget newDragData; - private WidgetDragData() {} + private WidgetDragData() {} - public WidgetDragData( - boolean isAlreadyInflated, IView alreadyInflatedView, UIWidget newDragData) { - this.isAlreadyInflated = isAlreadyInflated; - this.alreadyInflatedView = alreadyInflatedView; - this.newDragData = newDragData; - } + public WidgetDragData( + boolean isAlreadyInflated, IView alreadyInflatedView, UIWidget newDragData) { + this.isAlreadyInflated = isAlreadyInflated; + this.alreadyInflatedView = alreadyInflatedView; + this.newDragData = newDragData; + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java index 721df92b2..ab7f67376 100755 --- a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java +++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java @@ -22,7 +22,9 @@ import android.graphics.RectF; import android.view.DragEvent; import android.view.View; import android.view.ViewGroup; + import androidx.annotation.NonNull; + import com.blankj.utilcode.util.SizeUtils; import com.itsaky.androidide.DesignerActivity; import com.itsaky.androidide.R; @@ -36,6 +38,7 @@ import com.itsaky.inflater.impl.UiAttribute; import com.itsaky.inflater.impl.UiView; import com.itsaky.inflater.impl.UiViewGroup; import com.itsaky.xml.INamespace; + import org.jetbrains.annotations.Contract; /** @@ -45,182 +48,185 @@ import org.jetbrains.annotations.Contract; */ public class WidgetDragListener implements View.OnDragListener { - private final IView placeholder; - private final IViewGroup viewGroup; - private final OnViewAddedListener addedListener; + private final IView placeholder; + private final IViewGroup viewGroup; + private final OnViewAddedListener addedListener; - private final int PLACEHOLDER_HEIGHT = 20; // in dp - private final int PLACEHOLDER_WIDTH = 40; // in dp + private final int PLACEHOLDER_HEIGHT = 20; // in dp + private final int PLACEHOLDER_WIDTH = 40; // in dp - public static final String ANDROID_NS = "android"; + public static final String ANDROID_NS = "android"; - private static final Logger LOG = Logger.instance("WidgetDragListener"); + private static final Logger LOG = Logger.instance("WidgetDragListener"); - public WidgetDragListener( - @NonNull Context context, IViewGroup viewGroup, OnViewAddedListener addedListener) { - this.placeholder = new UiView("android.view.View", new View(context), true); - this.viewGroup = viewGroup; - this.addedListener = addedListener; - this.placeholder.asView().setBackgroundResource(R.drawable.bg_widget_drag_placeholder); - this.placeholder - .asView() - .setLayoutParams( - new ViewGroup.LayoutParams( - SizeUtils.dp2px(PLACEHOLDER_WIDTH), SizeUtils.dp2px(PLACEHOLDER_HEIGHT))); - } - - @Override - public boolean onDrag(View v, @NonNull DragEvent event) { - - if (this.viewGroup instanceof ErrorUiView) { - return false; + public WidgetDragListener( + @NonNull Context context, IViewGroup viewGroup, OnViewAddedListener addedListener) { + this.placeholder = new UiView("android.view.View", new View(context), true); + this.viewGroup = viewGroup; + this.addedListener = addedListener; + this.placeholder.asView().setBackgroundResource(R.drawable.bg_widget_drag_placeholder); + this.placeholder + .asView() + .setLayoutParams( + new ViewGroup.LayoutParams( + SizeUtils.dp2px(PLACEHOLDER_WIDTH), + SizeUtils.dp2px(PLACEHOLDER_HEIGHT))); } - final var action = event.getAction(); - final var clipDesc = event.getClipDescription(); - final var dragData = (WidgetDragData) event.getLocalState(); + @Override + public boolean onDrag(View v, @NonNull DragEvent event) { - var handled = true; - switch (action) { - case DragEvent.ACTION_DRAG_STARTED: - handled = clipDesc.hasMimeType(DesignerActivity.DRAGGING_WIDGET_MIME); - break; - case DragEvent.ACTION_DRAG_ENTERED: - case DragEvent.ACTION_DRAG_LOCATION: - this.placeholder.removeFromParent(); // If placeholder is added in another group, remove it. - this.viewGroup.addView(this.placeholder, findPlaceHolderIndex(event.getX(), event.getY())); - break; - case DragEvent.ACTION_DRAG_EXITED: - this.placeholder.removeFromParent(); - break; - case DragEvent.ACTION_DROP: - final var index = this.viewGroup.indexOfChild(this.placeholder); - this.placeholder.removeFromParent(); - - var view = - dragData.isAlreadyInflated - ? dragData.alreadyInflatedView - : createView(dragData.newDragData); - if (!dragData.isAlreadyInflated) { - applyBasicAttributes(view); + if (this.viewGroup instanceof ErrorUiView) { + return false; } - // Remove from parent if it has a parent - view.removeFromParent(); - this.viewGroup.addView(view, index); + final var action = event.getAction(); + final var clipDesc = event.getClipDescription(); + final var dragData = (WidgetDragData) event.getLocalState(); - if (this.addedListener != null) { - this.addedListener.onViewAdded(view); + var handled = true; + switch (action) { + case DragEvent.ACTION_DRAG_STARTED: + handled = clipDesc.hasMimeType(DesignerActivity.DRAGGING_WIDGET_MIME); + break; + case DragEvent.ACTION_DRAG_ENTERED: + case DragEvent.ACTION_DRAG_LOCATION: + this.placeholder + .removeFromParent(); // If placeholder is added in another group, remove it. + this.viewGroup.addView( + this.placeholder, findPlaceHolderIndex(event.getX(), event.getY())); + break; + case DragEvent.ACTION_DRAG_EXITED: + this.placeholder.removeFromParent(); + break; + case DragEvent.ACTION_DROP: + final var index = this.viewGroup.indexOfChild(this.placeholder); + this.placeholder.removeFromParent(); + + var view = + dragData.isAlreadyInflated + ? dragData.alreadyInflatedView + : createView(dragData.newDragData); + if (!dragData.isAlreadyInflated) { + applyBasicAttributes(view); + } + + // Remove from parent if it has a parent + view.removeFromParent(); + this.viewGroup.addView(view, index); + + if (this.addedListener != null) { + this.addedListener.onViewAdded(view); + } + + break; } - break; + return handled; } - return handled; - } + private void applyBasicAttributes(@NonNull IView view) { + view.addAttribute(layoutWidthAttr()); + view.addAttribute(layoutHeightAttr()); + } - private void applyBasicAttributes(@NonNull IView view) { - view.addAttribute(layoutWidthAttr()); - view.addAttribute(layoutHeightAttr()); - } - - /** - * Finds the index at which the placeholder will be added in the current view group. - * - * @param x The x coordinate of the touch event - * @param y The y coordinate of the touch event - * @return The index at which the placeholder of the view should be added. The returned index is - * always valid index in the {@link #viewGroup}. - */ - private int findPlaceHolderIndex(final float x, final float y) { - final var count = this.viewGroup.getChildCount(); - for (int i = 0; i < this.viewGroup.getChildCount(); i++) { - final var child = this.viewGroup.getChildAt(i); - final var rect = getViewRect(child); - if (rect.contains(x, y)) { - final var top = topHalf(rect); - final var bottom = bottomHalf(rect); - if (top.contains(x, y)) { - return Math.max(0, i - 1); - } else if (bottom.contains(x, y)) { - return Math.min(count, i + 1); + /** + * Finds the index at which the placeholder will be added in the current view group. + * + * @param x The x coordinate of the touch event + * @param y The y coordinate of the touch event + * @return The index at which the placeholder of the view should be added. The returned index is + * always valid index in the {@link #viewGroup}. + */ + private int findPlaceHolderIndex(final float x, final float y) { + final var count = this.viewGroup.getChildCount(); + for (int i = 0; i < this.viewGroup.getChildCount(); i++) { + final var child = this.viewGroup.getChildAt(i); + final var rect = getViewRect(child); + if (rect.contains(x, y)) { + final var top = topHalf(rect); + final var bottom = bottomHalf(rect); + if (top.contains(x, y)) { + return Math.max(0, i - 1); + } else if (bottom.contains(x, y)) { + return Math.min(count, i + 1); + } + } } - } + + // If we don't find a suitable index, return the last index + return count; } - // If we don't find a suitable index, return the last index - return count; - } + @NonNull + private RectF getViewRect(@NonNull IView view) { + final var v = view.asView(); - @NonNull - private RectF getViewRect(@NonNull IView view) { - final var v = view.asView(); + final var rect = new RectF(); + rect.left = v.getLeft(); + rect.top = v.getTop(); + rect.right = rect.left + v.getWidth(); + rect.bottom = rect.top + v.getHeight(); - final var rect = new RectF(); - rect.left = v.getLeft(); - rect.top = v.getTop(); - rect.right = rect.left + v.getWidth(); - rect.bottom = rect.top + v.getHeight(); - - return rect; - } - - private RectF topHalf(RectF src) { - final RectF result = new RectF(src); - result.bottom -= result.height() / 2; - return src; - } - - private RectF bottomHalf(RectF src) { - final RectF result = new RectF(src); - result.top += result.height() / 2; - return src; - } - - private RectF leftHalf(RectF src) { - final RectF result = new RectF(src); - result.right -= result.width() / 2; - return src; - } - - private RectF rightHalf(RectF src) { - final RectF result = new RectF(src); - result.left += result.width() / 2; - return src; - } - - @NonNull - @Contract(" -> new") - private IAttribute layoutHeightAttr() { - return new UiAttribute(INamespace.ANDROID, "layout_height", "wrap_content"); - } - - @NonNull - @Contract(" -> new") - private IAttribute layoutWidthAttr() { - return new UiAttribute(INamespace.ANDROID, "layout_width", "wrap_content"); - } - - @NonNull - private IView createView(@NonNull UIWidget widget) { - try { - final var clazz = widget.asClass(); - final var constructor = clazz.getConstructor(Context.class); - constructor.setAccessible(true); - final var instance = constructor.newInstance(this.viewGroup.asView().getContext()); - - if (instance instanceof ViewGroup) { - return new UiViewGroup(clazz.getName(), (ViewGroup) instance); - } else { - return new UiView(clazz.getName(), instance); - } - } catch (Throwable e) { - LOG.error("Unable to add widget", e); - throw new RuntimeException("Unable to add widget", e); + return rect; } - } - public static interface OnViewAddedListener { - void onViewAdded(IView view); - } + private RectF topHalf(RectF src) { + final RectF result = new RectF(src); + result.bottom -= result.height() / 2; + return src; + } + + private RectF bottomHalf(RectF src) { + final RectF result = new RectF(src); + result.top += result.height() / 2; + return src; + } + + private RectF leftHalf(RectF src) { + final RectF result = new RectF(src); + result.right -= result.width() / 2; + return src; + } + + private RectF rightHalf(RectF src) { + final RectF result = new RectF(src); + result.left += result.width() / 2; + return src; + } + + @NonNull + @Contract(" -> new") + private IAttribute layoutHeightAttr() { + return new UiAttribute(INamespace.ANDROID, "layout_height", "wrap_content"); + } + + @NonNull + @Contract(" -> new") + private IAttribute layoutWidthAttr() { + return new UiAttribute(INamespace.ANDROID, "layout_width", "wrap_content"); + } + + @NonNull + private IView createView(@NonNull UIWidget widget) { + try { + final var clazz = widget.asClass(); + final var constructor = clazz.getConstructor(Context.class); + constructor.setAccessible(true); + final var instance = constructor.newInstance(this.viewGroup.asView().getContext()); + + if (instance instanceof ViewGroup) { + return new UiViewGroup(clazz.getName(), (ViewGroup) instance); + } else { + return new UiView(clazz.getName(), instance); + } + } catch (Throwable e) { + LOG.error("Unable to add widget", e); + throw new RuntimeException("Unable to add widget", e); + } + } + + public static interface OnViewAddedListener { + void onViewAdded(IView view); + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragShadowBuilder.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragShadowBuilder.java index cc87c0d85..30cfa5ca2 100755 --- a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragShadowBuilder.java +++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragShadowBuilder.java @@ -22,21 +22,21 @@ import android.view.View; public class WidgetDragShadowBuilder extends View.DragShadowBuilder { - private final double TOUCH_Y_OFFSET = 0.3; + private final double TOUCH_Y_OFFSET = 0.3; - public WidgetDragShadowBuilder(View view) { - super(view); - } - - @Override - public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { - if (getView() != null) { - final var width = getView().getWidth(); - final var height = getView().getHeight(); - outShadowSize.set(width, height); - outShadowTouchPoint.set(width / 2, height + (int) (height * TOUCH_Y_OFFSET)); - } else { - super.onProvideShadowMetrics(outShadowSize, outShadowTouchPoint); + public WidgetDragShadowBuilder(View view) { + super(view); + } + + @Override + public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { + if (getView() != null) { + final var width = getView().getWidth(); + final var height = getView().getHeight(); + outShadowSize.set(width, height); + outShadowTouchPoint.set(width / 2, height + (int) (height * TOUCH_Y_OFFSET)); + } else { + super.onProvideShadowMetrics(outShadowSize, outShadowTouchPoint); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetTouchListener.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetTouchListener.java index d9a4fa3af..296a89258 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/WidgetTouchListener.java +++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetTouchListener.java @@ -22,8 +22,10 @@ import android.view.GestureDetector; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.itsaky.inflater.IView; /** @@ -36,73 +38,73 @@ import com.itsaky.inflater.IView; * @author Akash Yadav */ public class WidgetTouchListener extends GestureDetector.SimpleOnGestureListener - implements View.OnTouchListener { + implements View.OnTouchListener { - private final GestureDetector mGestureDetector; - private final OnClickListener clickListener; - private final OnLongClickListener longClickListener; + private final GestureDetector mGestureDetector; + private final OnClickListener clickListener; + private final OnLongClickListener longClickListener; - private final IView mView; + private final IView mView; - public WidgetTouchListener( - @NonNull IView view, - @Nullable OnClickListener clickListener, - @Nullable OnLongClickListener longClickListener) { - this.mView = view; - this.mGestureDetector = new GestureDetector(view.asView().getContext(), this); - this.clickListener = clickListener; - this.longClickListener = longClickListener; - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View view, @NonNull MotionEvent motionEvent) { - return mGestureDetector.onTouchEvent(motionEvent); - } - - @Override - public boolean onDown(MotionEvent e) { - return true; // We would like to get notified about further events. - } - - // TODO Should we use onSingleTapConfirmed instead? - @Override - public boolean onSingleTapUp(MotionEvent e) { - if (this.clickListener != null) { - this.clickListener.onClick(this.mView); + public WidgetTouchListener( + @NonNull IView view, + @Nullable OnClickListener clickListener, + @Nullable OnLongClickListener longClickListener) { + this.mView = view; + this.mGestureDetector = new GestureDetector(view.asView().getContext(), this); + this.clickListener = clickListener; + this.longClickListener = longClickListener; } - return true; - } - - @Override - public void onLongPress(MotionEvent e) { - - if (this.mView.getParent() == null) { - // This is the root layout so this should not be draggable - return; + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View view, @NonNull MotionEvent motionEvent) { + return mGestureDetector.onTouchEvent(motionEvent); } - if (this.longClickListener != null && this.longClickListener.onLongClick(this.mView)) { - this.mView.asView().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + @Override + public boolean onDown(MotionEvent e) { + return true; // We would like to get notified about further events. } - } - /** - * A listener which is invoked when we detect a single click event. - * - * @author Akash Yadav - */ - public interface OnClickListener { - void onClick(IView view); - } + // TODO Should we use onSingleTapConfirmed instead? + @Override + public boolean onSingleTapUp(MotionEvent e) { + if (this.clickListener != null) { + this.clickListener.onClick(this.mView); + } - /** - * A listener which is invoked when we detect a long click event. - * - * @author Akash Yadav - */ - public interface OnLongClickListener { - boolean onLongClick(IView view); - } + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + + if (this.mView.getParent() == null) { + // This is the root layout so this should not be draggable + return; + } + + if (this.longClickListener != null && this.longClickListener.onLongClick(this.mView)) { + this.mView.asView().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } + } + + /** + * A listener which is invoked when we detect a single click event. + * + * @author Akash Yadav + */ + public interface OnClickListener { + void onClick(IView view); + } + + /** + * A listener which is invoked when we detect a long click event. + * + * @author Akash Yadav + */ + public interface OnLongClickListener { + boolean onLongClick(IView view); + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/edit/BaseSuggestionProvider.java b/app/src/main/java/com/itsaky/androidide/ui/edit/BaseSuggestionProvider.java index e543e8cff..e427c1255 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/edit/BaseSuggestionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/ui/edit/BaseSuggestionProvider.java @@ -20,6 +20,7 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.inflater.IResourceTable; import com.itsaky.inflater.values.ValuesTable; import com.itsaky.inflater.values.ValuesTableFactory; + import java.io.File; import java.util.Objects; import java.util.regex.Pattern; @@ -31,27 +32,27 @@ import java.util.regex.Pattern; */ public abstract class BaseSuggestionProvider implements IValueSuggestionProvider { - protected final File resDir; - protected final File layout; + protected final File resDir; + protected final File layout; - protected BaseSuggestionProvider(File layout) { - Objects.requireNonNull(layout); + protected BaseSuggestionProvider(File layout) { + Objects.requireNonNull(layout); - final var path = layout.getAbsolutePath(); - if (!Pattern.compile(".*/src/.*/res/layout/(\\w|_)+\\.xml").matcher(path).matches()) { - throw new IllegalArgumentException("Given layout file is not valid"); + final var path = layout.getAbsolutePath(); + if (!Pattern.compile(".*/src/.*/res/layout/(\\w|_)+\\.xml").matcher(path).matches()) { + throw new IllegalArgumentException("Given layout file is not valid"); + } + + this.layout = layout; + this.resDir = Objects.requireNonNull(layout.getParentFile()).getParentFile(); + Objects.requireNonNull(this.resDir); } - this.layout = layout; - this.resDir = Objects.requireNonNull(layout.getParentFile()).getParentFile(); - Objects.requireNonNull(this.resDir); - } + protected ValuesTable getValuesTable() { + return ValuesTableFactory.getTable(this.resDir); + } - protected ValuesTable getValuesTable() { - return ValuesTableFactory.getTable(this.resDir); - } - - protected IResourceTable getResourceTable() { - return StudioApp.getInstance().getResourceTable(); - } + protected IResourceTable getResourceTable() { + return StudioApp.getInstance().getResourceTable(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/edit/DimensionSuggestionProvider.java b/app/src/main/java/com/itsaky/androidide/ui/edit/DimensionSuggestionProvider.java index c74167592..0236f604d 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/edit/DimensionSuggestionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/ui/edit/DimensionSuggestionProvider.java @@ -17,9 +17,11 @@ 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; @@ -31,56 +33,56 @@ import java.util.List; */ public class DimensionSuggestionProvider extends BaseSuggestionProvider { - /** The dimension units that will be suggested. */ - private final List dimensions = new ArrayList<>(); + /** The dimension units that will be suggested. */ + private final List dimensions = new ArrayList<>(); - public DimensionSuggestionProvider(File layout) { - super(layout); + 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"); - } + 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 boolean checkFormat(int format) { + return (format & Attr.DIMENSION) != 0; + } - @Override - public @NonNull List suggest(IAttribute attribute, @NonNull String prefix) { - final var list = new ArrayList(); - if (prefix.length() > 0 && Character.isDigit(prefix.charAt(0))) { - // Fixed dimension values - // e.g. 10dp + @Override + public @NonNull List suggest(IAttribute attribute, @NonNull String prefix) { + final var list = new ArrayList(); + 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.matchesPartialName(dimension, prefix, true)) { - list.add(dimension); + var val = extractDimensionVal(prefix); + for (var unit : this.dimensions) { + final var dimension = val + unit; + if (StringUtils.matchesPartialName(dimension, prefix, true)) { + list.add(dimension); + } + } } - } + + return list; } - 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++; + } - 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()); } - - return Integer.parseInt(s.toString()); - } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/edit/IValueSuggestionProvider.java b/app/src/main/java/com/itsaky/androidide/ui/edit/IValueSuggestionProvider.java index edb3868aa..d145aad88 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/edit/IValueSuggestionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/ui/edit/IValueSuggestionProvider.java @@ -18,7 +18,9 @@ package com.itsaky.androidide.ui.edit; import androidx.annotation.NonNull; + import com.itsaky.inflater.IAttribute; + import java.util.List; /** @@ -28,22 +30,22 @@ import java.util.List; */ public interface IValueSuggestionProvider { - /** - * Check if the given format is supported by this provider. - * - * @param format The formats to check. - * @return true if the given formats contain a supported format, false - * otherwise. - */ - boolean checkFormat(int format); + /** + * Check if the given format is supported by this provider. + * + * @param format The formats to check. + * @return true if the given formats contain a supported format, false + * otherwise. + */ + boolean checkFormat(int format); - /** - * Provide suggestions for the given attribute. - * - * @param attribute The attribute to provide suggestions for. - * @param prefix The prefix (partial identifier) to filter the suggestions. - * @return The suggestion items. Must not be null. - */ - @NonNull - List suggest(IAttribute attribute, String prefix); + /** + * Provide suggestions for the given attribute. + * + * @param attribute The attribute to provide suggestions for. + * @param prefix The prefix (partial identifier) to filter the suggestions. + * @return The suggestion items. Must not be null. + */ + @NonNull + List suggest(IAttribute attribute, String prefix); } diff --git a/app/src/main/java/com/itsaky/androidide/utils/AttributeDialogs.java b/app/src/main/java/com/itsaky/androidide/utils/AttributeDialogs.java index d87ad96c8..20c039d80 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/AttributeDialogs.java +++ b/app/src/main/java/com/itsaky/androidide/utils/AttributeDialogs.java @@ -25,14 +25,17 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; + import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; + import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.colorpicker.ColorPickerView; import com.itsaky.androidide.databinding.LayoutDimensionAttrEditorBinding; import com.itsaky.androidide.databinding.LayoutTextAttrEditorBinding; import com.itsaky.toaster.Toaster; + import java.util.ArrayList; import java.util.regex.Pattern; @@ -46,274 +49,283 @@ import java.util.regex.Pattern; */ public class AttributeDialogs { - private static final String DIMENSION_MATCH = "match_parent"; - private static final String DIMENSION_WRAP = "wrap_content"; + private static final String DIMENSION_MATCH = "match_parent"; + private static final String DIMENSION_WRAP = "wrap_content"; - private static final Logger LOG = Logger.instance("AttributeDialogs"); + private static final Logger LOG = Logger.instance("AttributeDialogs"); - /** - * Context that will be used for creating dialogs. This must be initialized by calling {@link - * #init(Context)} and the reference must be released by calling {@link #release()} - */ - private static Context mContext; + /** + * Context that will be used for creating dialogs. This must be initialized by calling {@link + * #init(Context)} and the reference must be released by calling {@link #release()} + */ + private static Context mContext; - /** - * Initialize this class with the provided context. - * - * @param ctx The context that will be used for creating dialogs. - * @see #release() - */ - public static void init(Context ctx) { - mContext = ctx; - } - - /** - * Release the context instance stored in this class. - * - * @see #init(Context) - */ - public static void release() { - mContext = null; - } - - @NonNull - public static AlertDialog booleanEditor(OnClickListener listener) { - final var items = new String[] {"true", "false"}; - final var builder = newMaterialDialogBuilder(mContext); - builder.setTitle(mContext.getString(R.string.msg_choose_new_val)); - builder.setItems( - items, - (dialog, which) -> { - dialog.dismiss(); - final var val = items[which]; - if (listener != null) { - listener.onClick(dialog, which, val); - } - }); - return builder.create(); - } - - /** - * Create a new dialog for editing an attribute with format STRING. - * - * @param listener The listener that will be invoked when the user clicks the positive button. - * @return The newly created dialog instance. - */ - @NonNull - public static AlertDialog stringEditor(OnClickListener listener) { - final var binding = LayoutTextAttrEditorBinding.inflate(LayoutInflater.from(mContext)); - final var builder = newMaterialDialogBuilder(mContext); - builder.setTitle(mContext.getString(R.string.msg_new_str_value)); - builder.setView(binding.getRoot()); - builder.setPositiveButton( - android.R.string.ok, - (dialog, which) -> { - final var newValue = binding.valueField.getEditText().getText().toString().trim(); - if (newValue.length() <= 0) { - StudioApp.getInstance() - .toast(mContext.getString(R.string.msg_invalid_attr_value), Toaster.Type.ERROR); - return; - } - if (listener != null) { - listener.onClick(dialog, which, newValue); - } - }); - builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); - return builder.create(); - } - - /** - * Create a new dialog for editing an attribute which accepts dimension values. - * - * @param value The current value of the attribute. Used for determining which item must be - * selected by default. - * @param listener A listener that will be invoked when the user presses the positive button of - * the dialog. This listener is invoked only when the user passes a valid dimension value. - * @return The newly created dialog instance. Note that this dialog is not shown by default. - */ - @NonNull - public static AlertDialog dimensionEditor(CharSequence value, OnClickListener listener) { - final var binding = LayoutDimensionAttrEditorBinding.inflate(LayoutInflater.from(mContext)); - binding.choices.setOnCheckedChangeListener( - (group, checkedId) -> - binding.otherValue.setVisibility(binding.other.isChecked() ? View.VISIBLE : View.GONE)); - - if (DIMENSION_MATCH.contentEquals(value)) { - binding.choices.check(R.id.match); - } else if (DIMENSION_WRAP.contentEquals(value)) { - binding.choices.check(R.id.wrap); - } else { - binding.choices.check(R.id.other); + /** + * Initialize this class with the provided context. + * + * @param ctx The context that will be used for creating dialogs. + * @see #release() + */ + public static void init(Context ctx) { + mContext = ctx; } - final var builder = newMaterialDialogBuilder(mContext); - builder.setView(binding.getRoot()); - builder.setPositiveButton( - android.R.string.ok, - ((dialog, which) -> { - var val = ""; - if (binding.choices.getCheckedRadioButtonId() == R.id.match) { - val = DIMENSION_MATCH; - } else if (binding.choices.getCheckedRadioButtonId() == R.id.wrap) { - val = DIMENSION_WRAP; - } else { - if (binding.otherValue.getEditText() != null) { - val = binding.otherValue.getEditText().getText().toString().trim(); - } else { - val = ""; - } - } - - if (val.length() <= 0) { - StudioApp.getInstance() - .toast(mContext.getString(R.string.msg_invalid_attr_value), Toaster.Type.ERROR); - return; - } - - dialog.dismiss(); - - if (listener != null) { - listener.onClick(dialog, which, val); - } - })); - builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); - return builder.create(); - } - - /** - * @see #enumEditor(CharSequence[], int, OnClickListener) - */ - public static AlertDialog enumEditor( - CharSequence[] values, CharSequence value, OnClickListener listener) { - return enumEditor(values, findIndexOf(values, value), listener); - } - - private static int findIndexOf(@NonNull CharSequence[] values, CharSequence value) { - for (int i = 0; i < values.length; i++) { - if (value.equals(values[i])) { - return i; - } + /** + * Release the context instance stored in this class. + * + * @see #init(Context) + */ + public static void release() { + mContext = null; } - return -1; - } + @NonNull + public static AlertDialog booleanEditor(OnClickListener listener) { + final var items = new String[] {"true", "false"}; + final var builder = newMaterialDialogBuilder(mContext); + builder.setTitle(mContext.getString(R.string.msg_choose_new_val)); + builder.setItems( + items, + (dialog, which) -> { + dialog.dismiss(); + final var val = items[which]; + if (listener != null) { + listener.onClick(dialog, which, val); + } + }); + return builder.create(); + } - /** - * Create a new single choice dialog for selecting enum values. - * - * @param values The value entries in the enum declaration. - * @param selected The item that will be selected when the dialog is shown. - * @param listener The click listener that will be invoked when an item is selected from the list. - * @return The newly created dialog instance. - */ - public static AlertDialog enumEditor( - CharSequence[] values, int selected, OnClickListener listener) { - final var builder = newMaterialDialogBuilder(mContext); - builder.setSingleChoiceItems( - values, - selected, - (dialog, which) -> { - dialog.dismiss(); - final var val = values[which].toString(); - if (listener != null) { - listener.onClick(dialog, which, val); - } - }); - return builder.show(); - } + /** + * Create a new dialog for editing an attribute with format STRING. + * + * @param listener The listener that will be invoked when the user clicks the positive button. + * @return The newly created dialog instance. + */ + @NonNull + public static AlertDialog stringEditor(OnClickListener listener) { + final var binding = LayoutTextAttrEditorBinding.inflate(LayoutInflater.from(mContext)); + final var builder = newMaterialDialogBuilder(mContext); + builder.setTitle(mContext.getString(R.string.msg_new_str_value)); + builder.setView(binding.getRoot()); + builder.setPositiveButton( + android.R.string.ok, + (dialog, which) -> { + final var newValue = + binding.valueField.getEditText().getText().toString().trim(); + if (newValue.length() <= 0) { + StudioApp.getInstance() + .toast( + mContext.getString(R.string.msg_invalid_attr_value), + Toaster.Type.ERROR); + return; + } + if (listener != null) { + listener.onClick(dialog, which, newValue); + } + }); + builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); + return builder.create(); + } - /** - * Creates a new attribute editor dialog to edit flag attributes. - * - * @param values The possible values. - * @param value The current value of the attribute. - * @param listener The listener that will be invoked when the user presses the positive button. - * @return The newly created dialog instance. - */ - @NonNull - public static AlertDialog flagEditor( - @NonNull CharSequence[] values, @NonNull CharSequence value, OnClickListener listener) { - final var checked = new boolean[values.length]; - final var vals = value.toString().split(Pattern.quote("|")); - for (var i = 0; i < values.length; i++) { - for (var val : vals) { - if (val.contentEquals(values[i])) { - checked[i] = true; + /** + * Create a new dialog for editing an attribute which accepts dimension values. + * + * @param value The current value of the attribute. Used for determining which item must be + * selected by default. + * @param listener A listener that will be invoked when the user presses the positive button of + * the dialog. This listener is invoked only when the user passes a valid dimension value. + * @return The newly created dialog instance. Note that this dialog is not shown by default. + */ + @NonNull + public static AlertDialog dimensionEditor(CharSequence value, OnClickListener listener) { + final var binding = LayoutDimensionAttrEditorBinding.inflate(LayoutInflater.from(mContext)); + binding.choices.setOnCheckedChangeListener( + (group, checkedId) -> + binding.otherValue.setVisibility( + binding.other.isChecked() ? View.VISIBLE : View.GONE)); + + if (DIMENSION_MATCH.contentEquals(value)) { + binding.choices.check(R.id.match); + } else if (DIMENSION_WRAP.contentEquals(value)) { + binding.choices.check(R.id.wrap); + } else { + binding.choices.check(R.id.other); } - } + + final var builder = newMaterialDialogBuilder(mContext); + builder.setView(binding.getRoot()); + builder.setPositiveButton( + android.R.string.ok, + ((dialog, which) -> { + var val = ""; + if (binding.choices.getCheckedRadioButtonId() == R.id.match) { + val = DIMENSION_MATCH; + } else if (binding.choices.getCheckedRadioButtonId() == R.id.wrap) { + val = DIMENSION_WRAP; + } else { + if (binding.otherValue.getEditText() != null) { + val = binding.otherValue.getEditText().getText().toString().trim(); + } else { + val = ""; + } + } + + if (val.length() <= 0) { + StudioApp.getInstance() + .toast( + mContext.getString(R.string.msg_invalid_attr_value), + Toaster.Type.ERROR); + return; + } + + dialog.dismiss(); + + if (listener != null) { + listener.onClick(dialog, which, val); + } + })); + builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); + return builder.create(); } - return flagEditor(values, checked, listener); - } - - /** - * Creates a new attribute editor dialog to edit flag attributes. - * - * @param values The possible values. - * @param selected The values that must be selected by default. - * @param listener The listener that will be invoked when the user presses the positive button. - * @return The newly created dialog instance. - */ - @NonNull - public static AlertDialog flagEditor( - @NonNull CharSequence[] values, boolean[] selected, OnClickListener listener) { - final var checked = new ArrayList(); - for (var i = 0; i < values.length; i++) { - if (selected[i]) { - checked.add(values[i]); - } + /** + * @see #enumEditor(CharSequence[], int, OnClickListener) + */ + public static AlertDialog enumEditor( + CharSequence[] values, CharSequence value, OnClickListener listener) { + return enumEditor(values, findIndexOf(values, value), listener); } - final var builder = newMaterialDialogBuilder(mContext); - builder.setMultiChoiceItems( - values, - selected, - (dialog, which, isChecked) -> { - if (isChecked) { - checked.add(values[which]); - } else { - checked.remove(values[which]); - } - }); - builder.setPositiveButton( - android.R.string.ok, - (dialog, which) -> { - if (checked.size() <= 0) { - StudioApp.getInstance() - .toast(mContext.getString(R.string.msg_select_one_flag), Toaster.Type.ERROR); - return; - } - final var val = TextUtils.join("|", checked); + private static int findIndexOf(@NonNull CharSequence[] values, CharSequence value) { + for (int i = 0; i < values.length; i++) { + if (value.equals(values[i])) { + return i; + } + } - if (listener != null) { - listener.onClick(dialog, which, val); - } - }); - builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); - return builder.create(); - } + return -1; + } - @NonNull - public static AlertDialog colorPicker(final ColorPickerView.OnPickListener listener) { - final var view = new ColorPickerView(mContext); - final var builder = newMaterialDialogBuilder(mContext); - builder.setView(view); + /** + * Create a new single choice dialog for selecting enum values. + * + * @param values The value entries in the enum declaration. + * @param selected The item that will be selected when the dialog is shown. + * @param listener The click listener that will be invoked when an item is selected from the + * list. + * @return The newly created dialog instance. + */ + public static AlertDialog enumEditor( + CharSequence[] values, int selected, OnClickListener listener) { + final var builder = newMaterialDialogBuilder(mContext); + builder.setSingleChoiceItems( + values, + selected, + (dialog, which) -> { + dialog.dismiss(); + final var val = values[which].toString(); + if (listener != null) { + listener.onClick(dialog, which, val); + } + }); + return builder.show(); + } - final var dialog = builder.create(); - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + /** + * Creates a new attribute editor dialog to edit flag attributes. + * + * @param values The possible values. + * @param value The current value of the attribute. + * @param listener The listener that will be invoked when the user presses the positive button. + * @return The newly created dialog instance. + */ + @NonNull + public static AlertDialog flagEditor( + @NonNull CharSequence[] values, @NonNull CharSequence value, OnClickListener listener) { + final var checked = new boolean[values.length]; + final var vals = value.toString().split(Pattern.quote("|")); + for (var i = 0; i < values.length; i++) { + for (var val : vals) { + if (val.contentEquals(values[i])) { + checked[i] = true; + } + } + } - view.setOnPickListener( - (color, hexCode) -> { - dialog.dismiss(); - if (listener != null) { - listener.onPick(color, hexCode); - } - }); + return flagEditor(values, checked, listener); + } - return dialog; - } + /** + * Creates a new attribute editor dialog to edit flag attributes. + * + * @param values The possible values. + * @param selected The values that must be selected by default. + * @param listener The listener that will be invoked when the user presses the positive button. + * @return The newly created dialog instance. + */ + @NonNull + public static AlertDialog flagEditor( + @NonNull CharSequence[] values, boolean[] selected, OnClickListener listener) { + final var checked = new ArrayList(); + for (var i = 0; i < values.length; i++) { + if (selected[i]) { + checked.add(values[i]); + } + } + final var builder = newMaterialDialogBuilder(mContext); + builder.setMultiChoiceItems( + values, + selected, + (dialog, which, isChecked) -> { + if (isChecked) { + checked.add(values[which]); + } else { + checked.remove(values[which]); + } + }); + builder.setPositiveButton( + android.R.string.ok, + (dialog, which) -> { + if (checked.size() <= 0) { + StudioApp.getInstance() + .toast( + mContext.getString(R.string.msg_select_one_flag), + Toaster.Type.ERROR); + return; + } - public interface OnClickListener { - void onClick(DialogInterface dialog, int which, String newValue); - } + final var val = TextUtils.join("|", checked); + + if (listener != null) { + listener.onClick(dialog, which, val); + } + }); + builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.dismiss()); + return builder.create(); + } + + @NonNull + public static AlertDialog colorPicker(final ColorPickerView.OnPickListener listener) { + final var view = new ColorPickerView(mContext); + final var builder = newMaterialDialogBuilder(mContext); + builder.setView(view); + + final var dialog = builder.create(); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + + view.setOnPickListener( + (color, hexCode) -> { + dialog.dismiss(); + if (listener != null) { + listener.onPick(color, hexCode); + } + }); + + return dialog; + } + + public interface OnClickListener { + void onClick(DialogInterface dialog, int which, String newValue); + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/DialogUtils.java b/app/src/main/java/com/itsaky/androidide/utils/DialogUtils.java index 4c5c6f59b..ce858ee53 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/DialogUtils.java +++ b/app/src/main/java/com/itsaky/androidide/utils/DialogUtils.java @@ -19,9 +19,12 @@ package com.itsaky.androidide.utils; import android.content.Context; import android.content.DialogInterface; + import androidx.annotation.NonNull; + import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.itsaky.androidide.R; + import org.jetbrains.annotations.Contract; /** @@ -31,68 +34,68 @@ import org.jetbrains.annotations.Contract; */ public class DialogUtils { - /** - * Creates a new MaterialAlertDialogBuilder with the app's default style. - * - * @param context The context for the dialog builder. - * @return The new MaterialAlertDialogBuilder instance. - */ - @NonNull - @Contract("_ -> new") - public static MaterialAlertDialogBuilder newMaterialDialogBuilder(Context context) { - return new MaterialAlertDialogBuilder(context, R.style.AppTheme_MaterialAlertDialog); - } + /** + * Creates a new MaterialAlertDialogBuilder with the app's default style. + * + * @param context The context for the dialog builder. + * @return The new MaterialAlertDialogBuilder instance. + */ + @NonNull + @Contract("_ -> new") + public static MaterialAlertDialogBuilder newMaterialDialogBuilder(Context context) { + return new MaterialAlertDialogBuilder(context, R.style.AppTheme_MaterialAlertDialog); + } - /** - * Create a new alert dialog with two buttons: Yes and No. This method - * simply calls {@link #newYesNoDialog(Context, String, String, DialogInterface.OnClickListener, - * DialogInterface.OnClickListener)} with default values for title and message. - * - * @param context The context for the dialog. - * @param positiveClickListener A listener that will be invoked on the Yes button - * click. - * @param negativeClickListener A listener that will be invoked on the No button - * click. - * @return The newly created dialog. - */ - @NonNull - public static MaterialAlertDialogBuilder newYesNoDialog( - Context context, - DialogInterface.OnClickListener positiveClickListener, - DialogInterface.OnClickListener negativeClickListener) { - return newYesNoDialog( - context, - context.getString(R.string.msg_yesno_def_title), - context.getString(R.string.msg_yesno_def_message), - positiveClickListener, - negativeClickListener); - } + /** + * Create a new alert dialog with two buttons: Yes and No. This method + * simply calls {@link #newYesNoDialog(Context, String, String, DialogInterface.OnClickListener, + * DialogInterface.OnClickListener)} with default values for title and message. + * + * @param context The context for the dialog. + * @param positiveClickListener A listener that will be invoked on the Yes button + * click. + * @param negativeClickListener A listener that will be invoked on the No button + * click. + * @return The newly created dialog. + */ + @NonNull + public static MaterialAlertDialogBuilder newYesNoDialog( + Context context, + DialogInterface.OnClickListener positiveClickListener, + DialogInterface.OnClickListener negativeClickListener) { + return newYesNoDialog( + context, + context.getString(R.string.msg_yesno_def_title), + context.getString(R.string.msg_yesno_def_message), + positiveClickListener, + negativeClickListener); + } - /** - * Create a new alert dialog with two buttons: Yes and No. - * - * @param context The context for the dialog. - * @param title The title of the dialog. - * @param message The message of the dialog. - * @param positiveClickListener A listener that will be invoked on the Yes button - * click. - * @param negativeClickListener A listener that will be invoked on the No button - * click. - * @return The newly created dialog instance. - */ - @NonNull - public static MaterialAlertDialogBuilder newYesNoDialog( - Context context, - String title, - String message, - DialogInterface.OnClickListener positiveClickListener, - DialogInterface.OnClickListener negativeClickListener) { - final var builder = DialogUtils.newMaterialDialogBuilder(context); - builder.setTitle(title); - builder.setMessage(message); - builder.setPositiveButton(R.string.yes, positiveClickListener); - builder.setNegativeButton(R.string.no, negativeClickListener); + /** + * Create a new alert dialog with two buttons: Yes and No. + * + * @param context The context for the dialog. + * @param title The title of the dialog. + * @param message The message of the dialog. + * @param positiveClickListener A listener that will be invoked on the Yes button + * click. + * @param negativeClickListener A listener that will be invoked on the No button + * click. + * @return The newly created dialog instance. + */ + @NonNull + public static MaterialAlertDialogBuilder newYesNoDialog( + Context context, + String title, + String message, + DialogInterface.OnClickListener positiveClickListener, + DialogInterface.OnClickListener negativeClickListener) { + final var builder = DialogUtils.newMaterialDialogBuilder(context); + builder.setTitle(title); + builder.setMessage(message); + builder.setPositiveButton(R.string.yes, positiveClickListener); + builder.setNegativeButton(R.string.no, negativeClickListener); - return builder; - } + return builder; + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java b/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java index a6eb70c9a..b10f83d2c 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java +++ b/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java @@ -22,43 +22,45 @@ import android.graphics.RectF; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; + import androidx.annotation.NonNull; import androidx.coordinatorlayout.widget.CoordinatorLayout; + import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.itsaky.androidide.databinding.LayoutEditorBottomSheetBinding; public class EditorBottomSheetBehavior extends BottomSheetBehavior { - private LayoutEditorBottomSheetBinding binding; - private static final Logger LOG = Logger.instance("EditorBottomSheetBehavior"); + private LayoutEditorBottomSheetBinding binding; + private static final Logger LOG = Logger.instance("EditorBottomSheetBehavior"); - public EditorBottomSheetBehavior(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - } - - public void setBinding(LayoutEditorBottomSheetBinding binding) { - this.binding = binding; - } - - @Override - public boolean onInterceptTouchEvent( - @NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) { - if (binding != null) { - final var rect = getPagerRect(); - if (rect.contains(event.getX(), event.getY())) { - return false; - } + public EditorBottomSheetBehavior(Context context, AttributeSet attributeSet) { + super(context, attributeSet); } - return super.onInterceptTouchEvent(parent, child, event); - } - @NonNull - private RectF getPagerRect() { - final var rect = new RectF(); - rect.left = binding.pager.getX(); - rect.top = binding.pager.getY(); - rect.right = rect.left + binding.pager.getWidth(); - rect.bottom = rect.top + binding.pager.getHeight(); - return rect; - } + public void setBinding(LayoutEditorBottomSheetBinding binding) { + this.binding = binding; + } + + @Override + public boolean onInterceptTouchEvent( + @NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) { + if (binding != null) { + final var rect = getPagerRect(); + if (rect.contains(event.getX(), event.getY())) { + return false; + } + } + return super.onInterceptTouchEvent(parent, child, event); + } + + @NonNull + private RectF getPagerRect() { + final var rect = new RectF(); + rect.left = binding.pager.getX(); + rect.top = binding.pager.getY(); + rect.right = rect.left + binding.pager.getWidth(); + rect.bottom = rect.top + binding.pager.getHeight(); + return rect; + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java b/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java index e86a50daf..97f8fe06b 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java +++ b/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java @@ -22,117 +22,118 @@ import static com.itsaky.lsp.models.HighlightTokenKind.*; import com.itsaky.lsp.models.HighlightToken; import com.itsaky.lsp.models.HighlightTokenKind; import com.itsaky.lsp.models.Position; + import java.util.List; public class HighlightRangeHelper { - private final List highlights; + private final List highlights; - public HighlightRangeHelper(List highlights) { - this.highlights = highlights; - } - - public boolean isEnumType(int line, int column) { - return isOfKind(line, column, ENUM_TYPE); - } - - public boolean isAnnotationType(int line, int column) { - return isOfKind(line, column, ANNOTATION); - } - - public boolean isInterface(int line, int column) { - return isOfKind(line, column, INTERFACE); - } - - public boolean isEnum(int line, int column) { - return isOfKind(line, column, ENUM); - } - - public boolean isParameter(int line, int column) { - return isOfKind(line, column, PARAMETER); - } - - public boolean isExceptionParam(int line, int column) { - return isOfKind(line, column, EXCEPTION_PARAMETER); - } - - public boolean isConstructor(int line, int column) { - return isOfKind(line, column, CONSTRUCTOR); - } - - public boolean isStaticInit(int line, int column) { - return isOfKind(line, column, STATIC_INIT); - } - - public boolean isInstanceInit(int line, int column) { - return isOfKind(line, column, INSTANCE_INIT); - } - - public boolean isTypeParam(int line, int column) { - return isOfKind(line, column, TYPE_PARAMETER); - } - - public boolean isResourceVariable(int line, int column) { - return isOfKind(line, column, RESOURCE_VARIABLE); - } - - public boolean isPackageName(int line, int column) { - return isOfKind(line, column, PACKAGE_NAME); - } - - public boolean isClassName(int line, int column) { - return isOfKind(line, column, TYPE_NAME); - } - - public boolean isField(int line, int column) { - return isOfKind(line, column, FIELD); - } - - public boolean isStaticField(int line, int column) { - return isOfKind(line, column, STATIC_FIELD); - } - - public boolean isMethodDeclaration(int line, int column) { - return isOfKind(line, column, METHOD_DECLARATION); - } - - public boolean isMethodInvocation(int line, int column) { - return isOfKind(line, column, METHOD_INVOCATION); - } - - public boolean isLocal(int line, int column) { - return isOfKind(line, column, LOCAL_VARIABLE); - } - - /** - * Check if the token at the given index has the given token kind. - * - * @param line The line to check. - * @param column The column to check. - * @param kind The kind to check. - * @return {@code true} if the token at the given position has the given kind, {@code false} - * otherwise. - */ - public boolean isOfKind(int line, int column, HighlightTokenKind kind) { - final var position = new Position(line, column); - int left = 0; - int right = this.highlights.size(); - int mid; - while (left < right) { - mid = (left + right) / 2; - var token = this.highlights.get(mid); - var range = token.getRange(); - var pos = range.getStart().compareTo(position); - if (pos < 0) { - left = mid + 1; - } else if (pos > 0) { - right = mid - 1; - } else { - return token.getKind() == kind; - } + public HighlightRangeHelper(List highlights) { + this.highlights = highlights; } - return false; - } - private static final Logger LOG = Logger.instance("HighlightRangeHelper"); + public boolean isEnumType(int line, int column) { + return isOfKind(line, column, ENUM_TYPE); + } + + public boolean isAnnotationType(int line, int column) { + return isOfKind(line, column, ANNOTATION); + } + + public boolean isInterface(int line, int column) { + return isOfKind(line, column, INTERFACE); + } + + public boolean isEnum(int line, int column) { + return isOfKind(line, column, ENUM); + } + + public boolean isParameter(int line, int column) { + return isOfKind(line, column, PARAMETER); + } + + public boolean isExceptionParam(int line, int column) { + return isOfKind(line, column, EXCEPTION_PARAMETER); + } + + public boolean isConstructor(int line, int column) { + return isOfKind(line, column, CONSTRUCTOR); + } + + public boolean isStaticInit(int line, int column) { + return isOfKind(line, column, STATIC_INIT); + } + + public boolean isInstanceInit(int line, int column) { + return isOfKind(line, column, INSTANCE_INIT); + } + + public boolean isTypeParam(int line, int column) { + return isOfKind(line, column, TYPE_PARAMETER); + } + + public boolean isResourceVariable(int line, int column) { + return isOfKind(line, column, RESOURCE_VARIABLE); + } + + public boolean isPackageName(int line, int column) { + return isOfKind(line, column, PACKAGE_NAME); + } + + public boolean isClassName(int line, int column) { + return isOfKind(line, column, TYPE_NAME); + } + + public boolean isField(int line, int column) { + return isOfKind(line, column, FIELD); + } + + public boolean isStaticField(int line, int column) { + return isOfKind(line, column, STATIC_FIELD); + } + + public boolean isMethodDeclaration(int line, int column) { + return isOfKind(line, column, METHOD_DECLARATION); + } + + public boolean isMethodInvocation(int line, int column) { + return isOfKind(line, column, METHOD_INVOCATION); + } + + public boolean isLocal(int line, int column) { + return isOfKind(line, column, LOCAL_VARIABLE); + } + + /** + * Check if the token at the given index has the given token kind. + * + * @param line The line to check. + * @param column The column to check. + * @param kind The kind to check. + * @return {@code true} if the token at the given position has the given kind, {@code false} + * otherwise. + */ + public boolean isOfKind(int line, int column, HighlightTokenKind kind) { + final var position = new Position(line, column); + int left = 0; + int right = this.highlights.size(); + int mid; + while (left < right) { + mid = (left + right) / 2; + var token = this.highlights.get(mid); + var range = token.getRange(); + var pos = range.getStart().compareTo(position); + if (pos < 0) { + left = mid + 1; + } else if (pos > 0) { + right = mid - 1; + } else { + return token.getKind() == kind; + } + } + return false; + } + + private static final Logger LOG = Logger.instance("HighlightRangeHelper"); } diff --git a/app/src/main/java/com/itsaky/androidide/utils/JSONUtility.java b/app/src/main/java/com/itsaky/androidide/utils/JSONUtility.java index bfe5e9bc8..80b4f7bec 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/JSONUtility.java +++ b/app/src/main/java/com/itsaky/androidide/utils/JSONUtility.java @@ -24,6 +24,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class JSONUtility { - public static final Gson gson = new Gson(); - public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create(); + public static final Gson gson = new Gson(); + public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create(); } diff --git a/app/src/main/java/com/itsaky/androidide/utils/LSPUtils.java b/app/src/main/java/com/itsaky/androidide/utils/LSPUtils.java index 8726f5798..b102dbf23 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/LSPUtils.java +++ b/app/src/main/java/com/itsaky/androidide/utils/LSPUtils.java @@ -23,134 +23,135 @@ import com.itsaky.lsp.models.DiagnosticItem; import com.itsaky.lsp.models.DiagnosticSeverity; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; + import java.util.Comparator; import java.util.List; public class LSPUtils { - public static final Position Position_ofZero; - public static final Range Range_ofZero; + public static final Position Position_ofZero; + public static final Range Range_ofZero; - static { - Position_ofZero = new Position(0, 0); - Range_ofZero = new Range(Position_ofZero, Position_ofZero); - } - - public static boolean isEqual(Position p1, Position p2) { - if (p1 == null) return false; - if (p2 == null) return false; - - return p1.getLine() == p2.getLine() && p1.getColumn() == p2.getColumn(); - } - - public static boolean isInRange(Range range, Position position) { - final int line = position.getLine(); - final int column = position.getColumn(); - return isInRange(range, line, column); - } - - public static boolean isInRange(Range range, int line, int column) { - final int startLine = range.getStart().getLine(); - final int startCol = range.getStart().getColumn(); - final int endLine = range.getEnd().getLine(); - final int endCol = range.getEnd().getColumn(); - - return startLine <= line && line <= endLine && startCol <= column && column <= endCol; - } - - public static boolean isEqual(Range r1, Range r2) { - if (r1 == null) return false; - if (r2 == null) return false; - - return LSPUtils.isEqual(r1.getStart(), r2.getStart()) - && LSPUtils.isEqual(r1.getEnd(), r2.getEnd()); - } - - public static DiagnosticItem newInfoDiagnostic( - int line, int column, int length, String message, String text) { - final var diag = new DiagnosticItem(); - diag.setCode("todo"); - diag.setMessage(message); - diag.setRange(getSingleLineRange(line, column, length)); - diag.setSeverity(DiagnosticSeverity.INFO); - diag.setSource(text); - return diag; - } - - public static DiagnosticItem newWarningDiagnostic( - int line, int column, int length, String message, String text) { - final var diag = new DiagnosticItem(); - diag.setCode("custom_warning"); - diag.setMessage(message); - diag.setRange(getSingleLineRange(line, column, length)); - diag.setSeverity(DiagnosticSeverity.WARNING); - diag.setSource(text); - return diag; - } - - public static Range getSingleLineRange(int line, int column, int length) { - final Range range = new Range(); - range.setStart(new Position(line, column)); - range.setEnd(new Position(line, column + length)); - return range; - } - - public static int binarySearchStartPosition(List ranges, int line, int column) { - int left = 0, right = ranges.size() - 1; - while (left <= right) { - int mid = (left + right) / 2; - final Range range = ranges.get(mid); - final int l = range.getStart().getLine(); - final int c = range.getStart().getColumn(); - - // Compare lines - if (l < line) { - left = mid + 1; - } else if (l > line) { - right = mid - 1; - } else { - // Lines are same. Compare by columns - if (c < column) { - left = mid + 1; - } else if (c > column) { - right = mid - 1; - } else { - // Found! - return mid; - } - } + static { + Position_ofZero = new Position(0, 0); + Range_ofZero = new Range(Position_ofZero, Position_ofZero); } - return -1; - } + public static boolean isEqual(Position p1, Position p2) { + if (p1 == null) return false; + if (p2 == null) return false; - public static final Comparator RANGE_START_COMPARATOR = - (r1, r2) -> { - // Check if ranges are null - if (r1 == null && r2 != null) { - return 1; - } else if (r1 != null && r2 == null) { - return -1; - } else if (r1 == null) { - return 0; - } else { - final int r1StartLine = r1.getStart().getLine(); - final int r1StartCol = r1.getStart().getColumn(); - final int r2StartLine = r2.getStart().getLine(); - final int r2StartCol = r2.getStart().getColumn(); + return p1.getLine() == p2.getLine() && p1.getColumn() == p2.getColumn(); + } - // Compare by lines - if (r1StartLine < r2StartLine) { - return -1; - } else if (r1StartLine > r2StartLine) { - return 1; - } else { + public static boolean isInRange(Range range, Position position) { + final int line = position.getLine(); + final int column = position.getColumn(); + return isInRange(range, line, column); + } - // Lines are same - // Compare by columns - // Both ranges have same start position - return Integer.compare(r1StartCol, r2StartCol); - } + public static boolean isInRange(Range range, int line, int column) { + final int startLine = range.getStart().getLine(); + final int startCol = range.getStart().getColumn(); + final int endLine = range.getEnd().getLine(); + final int endCol = range.getEnd().getColumn(); + + return startLine <= line && line <= endLine && startCol <= column && column <= endCol; + } + + public static boolean isEqual(Range r1, Range r2) { + if (r1 == null) return false; + if (r2 == null) return false; + + return LSPUtils.isEqual(r1.getStart(), r2.getStart()) + && LSPUtils.isEqual(r1.getEnd(), r2.getEnd()); + } + + public static DiagnosticItem newInfoDiagnostic( + int line, int column, int length, String message, String text) { + final var diag = new DiagnosticItem(); + diag.setCode("todo"); + diag.setMessage(message); + diag.setRange(getSingleLineRange(line, column, length)); + diag.setSeverity(DiagnosticSeverity.INFO); + diag.setSource(text); + return diag; + } + + public static DiagnosticItem newWarningDiagnostic( + int line, int column, int length, String message, String text) { + final var diag = new DiagnosticItem(); + diag.setCode("custom_warning"); + diag.setMessage(message); + diag.setRange(getSingleLineRange(line, column, length)); + diag.setSeverity(DiagnosticSeverity.WARNING); + diag.setSource(text); + return diag; + } + + public static Range getSingleLineRange(int line, int column, int length) { + final Range range = new Range(); + range.setStart(new Position(line, column)); + range.setEnd(new Position(line, column + length)); + return range; + } + + public static int binarySearchStartPosition(List ranges, int line, int column) { + int left = 0, right = ranges.size() - 1; + while (left <= right) { + int mid = (left + right) / 2; + final Range range = ranges.get(mid); + final int l = range.getStart().getLine(); + final int c = range.getStart().getColumn(); + + // Compare lines + if (l < line) { + left = mid + 1; + } else if (l > line) { + right = mid - 1; + } else { + // Lines are same. Compare by columns + if (c < column) { + left = mid + 1; + } else if (c > column) { + right = mid - 1; + } else { + // Found! + return mid; + } + } } - }; + + return -1; + } + + public static final Comparator RANGE_START_COMPARATOR = + (r1, r2) -> { + // Check if ranges are null + if (r1 == null && r2 != null) { + return 1; + } else if (r1 != null && r2 == null) { + return -1; + } else if (r1 == null) { + return 0; + } else { + final int r1StartLine = r1.getStart().getLine(); + final int r1StartCol = r1.getStart().getColumn(); + final int r2StartLine = r2.getStart().getLine(); + final int r2StartCol = r2.getStart().getColumn(); + + // Compare by lines + if (r1StartLine < r2StartLine) { + return -1; + } else if (r1StartLine > r2StartLine) { + return 1; + } else { + + // Lines are same + // Compare by columns + // Both ranges have same start position + return Integer.compare(r1StartCol, r2StartCol); + } + } + }; } diff --git a/app/src/main/java/com/itsaky/androidide/utils/ProjectFinder.java b/app/src/main/java/com/itsaky/androidide/utils/ProjectFinder.java index f33a87f45..a2a97c614 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/ProjectFinder.java +++ b/app/src/main/java/com/itsaky/androidide/utils/ProjectFinder.java @@ -21,171 +21,181 @@ package com.itsaky.androidide.utils; import android.text.TextUtils; + import com.blankj.utilcode.util.FileIOUtils; import com.itsaky.androidide.project.AndroidProject; + import fr.arnaudguyon.xmltojsonlib.XmlToJson; -import java.io.File; -import java.io.FileFilter; -import java.util.regex.Pattern; + import org.json.JSONException; import org.json.JSONObject; +import java.io.File; +import java.io.FileFilter; +import java.util.regex.Pattern; + public class ProjectFinder { - public static AndroidProject fromFolder(File file) { - if (!file.isDirectory() || file.isHidden()) return null; - File settings = new File(file, "settings.gradle"); - if (!settings.exists()) return null; - String content = FileUtil.readFile(settings.getAbsolutePath()); - if (TextUtils.isEmpty(content)) return null; + public static AndroidProject fromFolder(File file) { + if (!file.isDirectory() || file.isHidden()) return null; + File settings = new File(file, "settings.gradle"); + if (!settings.exists()) return null; + String content = FileUtil.readFile(settings.getAbsolutePath()); + if (TextUtils.isEmpty(content)) return null; - AndroidProject project = new AndroidProject(); - for (File module : file.listFiles()) { - if (module.isDirectory() && !module.isHidden()) { - try { - proceedModule(file, module, project); - } catch (JSONException e) { - continue; - } - } - } - - project.setAppName(file.getName()); - project.setProjectPath(file.getAbsolutePath()); - if (project.getMainModule() == null - && project.getModulePaths().size() > 0 - && project.getModulePaths().get(0) != null) { - File f = new File(project.getModulePaths().get(0)); - if (!f.exists() || !f.isDirectory()) return null; - project.setMainModule(f.getName()); - project.setMainSourcePath(new File(f, "src/main/java").getAbsolutePath()); - } - - return project; - } - - private static void proceedModule(File file, File module, AndroidProject project) - throws JSONException { - final Pattern appPattern = - Pattern.compile("apply\\s+plugin\\s*:\\s*(['\"])com.android.application(['\"])"); - File projectGradle = new File(file, "build.gradle"); - File moduleGradle = new File(module, "build.gradle"); - File manifest = new File(module, "src/main/AndroidManifest.xml"); - File res = new File(module, "src/main/res"); - if (!module.exists() || !projectGradle.exists() || !moduleGradle.exists() || !manifest.exists()) - return; - if (!module.isDirectory() - || !moduleGradle.isFile() - || !projectGradle.isFile() - || !manifest.isFile()) return; - - File java = new File(module, "src/main/java"); - if (java.exists() && java.isDirectory()) - project.addSource(java.getAbsolutePath()).addModule(module.getAbsolutePath()); - - File libsFolder = new File(module, "libs"); - if (libsFolder.exists() && libsFolder.isDirectory()) { - File[] libs = libsFolder.listFiles(); - if (libs != null && libs.length > 0) { - for (File lib : libs) - if (lib.getName().endsWith(".jar")) project.addClasspath(lib.getAbsolutePath()); - } - } - - project.addSource( - new File(module, "build/generated/source/buildConfig/debug").getAbsolutePath()); - project.addSource( - new File(module, "build/generated/data_binding_base_class_source_out/debug/out") - .getAbsolutePath()); - - JSONObject obj = - new XmlToJson.Builder(FileUtil.readFile(manifest.getAbsolutePath())).build().toJson(); - if (!obj.has("manifest")) return; - String iconPath = null; - if (obj.has("manifest") - && obj.getJSONObject("manifest").has("application") - && obj.getJSONObject("manifest").getJSONObject("application").has("android:icon")) { - String iconRes = - obj.getJSONObject("manifest").getJSONObject("application").getString("android:icon"); - if (iconRes.startsWith("@drawable")) { - final String iconName = iconRes.replace("@drawable/", ""); - File[] drawables = listDrawables(res); - for (File drawable : drawables) { - File[] icons = listFilesNotEndingWith(drawable, ".xml"); - for (File icon : icons) { - String nameWithoutExtension = - icon.getName().substring(0, icon.getName().lastIndexOf(".")); - if (nameWithoutExtension.equals(iconName)) { - iconPath = icon.getAbsolutePath(); - break; + AndroidProject project = new AndroidProject(); + for (File module : file.listFiles()) { + if (module.isDirectory() && !module.isHidden()) { + try { + proceedModule(file, module, project); + } catch (JSONException e) { + continue; + } } - } } - } else if (iconRes.startsWith("@mipmap")) { - final String iconName = iconRes.replace("@mipmap/", ""); - File[] drawables = listMipmaps(res); - for (File drawable : drawables) { - File[] icons = listFilesNotEndingWith(drawable, ".xml"); - for (File icon : icons) { - String nameWithoutExtension = - icon.getName().substring(0, icon.getName().lastIndexOf(".")); - if (nameWithoutExtension.equals(iconName)) { - iconPath = icon.getAbsolutePath(); - break; + + project.setAppName(file.getName()); + project.setProjectPath(file.getAbsolutePath()); + if (project.getMainModule() == null + && project.getModulePaths().size() > 0 + && project.getModulePaths().get(0) != null) { + File f = new File(project.getModulePaths().get(0)); + if (!f.exists() || !f.isDirectory()) return null; + project.setMainModule(f.getName()); + project.setMainSourcePath(new File(f, "src/main/java").getAbsolutePath()); + } + + return project; + } + + private static void proceedModule(File file, File module, AndroidProject project) + throws JSONException { + final Pattern appPattern = + Pattern.compile("apply\\s+plugin\\s*:\\s*(['\"])com.android.application(['\"])"); + File projectGradle = new File(file, "build.gradle"); + File moduleGradle = new File(module, "build.gradle"); + File manifest = new File(module, "src/main/AndroidManifest.xml"); + File res = new File(module, "src/main/res"); + if (!module.exists() + || !projectGradle.exists() + || !moduleGradle.exists() + || !manifest.exists()) return; + if (!module.isDirectory() + || !moduleGradle.isFile() + || !projectGradle.isFile() + || !manifest.isFile()) return; + + File java = new File(module, "src/main/java"); + if (java.exists() && java.isDirectory()) + project.addSource(java.getAbsolutePath()).addModule(module.getAbsolutePath()); + + File libsFolder = new File(module, "libs"); + if (libsFolder.exists() && libsFolder.isDirectory()) { + File[] libs = libsFolder.listFiles(); + if (libs != null && libs.length > 0) { + for (File lib : libs) + if (lib.getName().endsWith(".jar")) project.addClasspath(lib.getAbsolutePath()); } - } } - } - } - final String packageName = obj.getJSONObject("manifest").getString("package"); - final String mainModuleName = module.getName(); - boolean isMainModule = false; - if (project.getMainModule() == null) { - String content = FileIOUtils.readFile2String(moduleGradle); - if (content != null && appPattern.matcher(content).find()) { - isMainModule = true; - project.setMainModule(mainModuleName); - project.setMainSourcePath(new File(module, "src/main/java").getAbsolutePath()); - } + project.addSource( + new File(module, "build/generated/source/buildConfig/debug").getAbsolutePath()); + project.addSource( + new File(module, "build/generated/data_binding_base_class_source_out/debug/out") + .getAbsolutePath()); + + JSONObject obj = + new XmlToJson.Builder(FileUtil.readFile(manifest.getAbsolutePath())) + .build() + .toJson(); + if (!obj.has("manifest")) return; + String iconPath = null; + if (obj.has("manifest") + && obj.getJSONObject("manifest").has("application") + && obj.getJSONObject("manifest").getJSONObject("application").has("android:icon")) { + String iconRes = + obj.getJSONObject("manifest") + .getJSONObject("application") + .getString("android:icon"); + if (iconRes.startsWith("@drawable")) { + final String iconName = iconRes.replace("@drawable/", ""); + File[] drawables = listDrawables(res); + for (File drawable : drawables) { + File[] icons = listFilesNotEndingWith(drawable, ".xml"); + for (File icon : icons) { + String nameWithoutExtension = + icon.getName().substring(0, icon.getName().lastIndexOf(".")); + if (nameWithoutExtension.equals(iconName)) { + iconPath = icon.getAbsolutePath(); + break; + } + } + } + } else if (iconRes.startsWith("@mipmap")) { + final String iconName = iconRes.replace("@mipmap/", ""); + File[] drawables = listMipmaps(res); + for (File drawable : drawables) { + File[] icons = listFilesNotEndingWith(drawable, ".xml"); + for (File icon : icons) { + String nameWithoutExtension = + icon.getName().substring(0, icon.getName().lastIndexOf(".")); + if (nameWithoutExtension.equals(iconName)) { + iconPath = icon.getAbsolutePath(); + break; + } + } + } + } + } + final String packageName = obj.getJSONObject("manifest").getString("package"); + final String mainModuleName = module.getName(); + + boolean isMainModule = false; + if (project.getMainModule() == null) { + String content = FileIOUtils.readFile2String(moduleGradle); + if (content != null && appPattern.matcher(content).find()) { + isMainModule = true; + project.setMainModule(mainModuleName); + project.setMainSourcePath(new File(module, "src/main/java").getAbsolutePath()); + } + } + + if (iconPath != null) { + project.setIconPath(iconPath); + } + + if (packageName != null && isMainModule) { + project.setPackageName(packageName); + } } - if (iconPath != null) { - project.setIconPath(iconPath); + private static File[] listDrawables(File file) { + return listFolderStartingWith(file, "drawable"); } - if (packageName != null && isMainModule) { - project.setPackageName(packageName); + private static File[] listMipmaps(File file) { + return listFolderStartingWith(file, "mipmap"); } - } - private static File[] listDrawables(File file) { - return listFolderStartingWith(file, "drawable"); - } + private static File[] listFolderStartingWith(File file, final String prefix) { + return file.listFiles( + new FileFilter() { - private static File[] listMipmaps(File file) { - return listFolderStartingWith(file, "mipmap"); - } + @Override + public boolean accept(File p1) { + return p1.isDirectory() && p1.getName().startsWith(prefix); + } + }); + } - private static File[] listFolderStartingWith(File file, final String prefix) { - return file.listFiles( - new FileFilter() { + private static File[] listFilesNotEndingWith(File file, final String suffix) { + return file.listFiles( + new FileFilter() { - @Override - public boolean accept(File p1) { - return p1.isDirectory() && p1.getName().startsWith(prefix); - } - }); - } - - private static File[] listFilesNotEndingWith(File file, final String suffix) { - return file.listFiles( - new FileFilter() { - - @Override - public boolean accept(File p1) { - return p1.isFile() && !p1.getName().endsWith(suffix); - } - }); - } + @Override + public boolean accept(File p1) { + return p1.isFile() && !p1.getName().endsWith(suffix); + } + }); + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java b/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java index 21a94e0bf..570b00419 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java +++ b/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java @@ -21,6 +21,7 @@ package com.itsaky.androidide.utils; import androidx.annotation.NonNull; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.ResourceUtils; @@ -31,190 +32,193 @@ import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.interfaces.ProjectWriterCallback; import com.itsaky.androidide.models.NewProjectDetails; import com.itsaky.androidide.models.ProjectTemplate; + import java.io.File; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ProjectWriter { - private static ProjectWriterCallback callback; + private static ProjectWriterCallback callback; - private static final String XML_TEMPLATE_PATH = "templates/xml"; + private static final String XML_TEMPLATE_PATH = "templates/xml"; - private static final String[] FILE_TO_CHANGE = { - "build.gradle", - "settings.gradle", - "app/src/main/AndroidManifest.xml", - "app/src/main/res/values/strings.xml", - "app/src/main/java/$package_name/MainActivity.java", - "app/src/main/java/$package_name/MainActivity.kt", - "app/build.gradle", - "app/src/main/java/$package_name/AndroidLauncher.java", - "app/res/values/strings.xml", - "core/build.gradle", - "core/src/main/java/$package_name/GameSuperClass.java", - "app/AndroidManifest.xml" - }; + private static final String[] FILE_TO_CHANGE = { + "build.gradle", + "settings.gradle", + "app/src/main/AndroidManifest.xml", + "app/src/main/res/values/strings.xml", + "app/src/main/java/$package_name/MainActivity.java", + "app/src/main/java/$package_name/MainActivity.kt", + "app/build.gradle", + "app/src/main/java/$package_name/AndroidLauncher.java", + "app/res/values/strings.xml", + "core/build.gradle", + "core/src/main/java/$package_name/GameSuperClass.java", + "app/AndroidManifest.xml" + }; - private static final String JAVA_PATH_REGEX = "/.*/src/.*/java|kt"; - private static final String APP_NAME = "$app_name", - PACKAGE_NAME = "$package_name", - MIN_SDK = "$min_sdk", - TARGET_SDK = "$target_sdk"; + private static final String JAVA_PATH_REGEX = "/.*/src/.*/java|kt"; + private static final String APP_NAME = "$app_name", + PACKAGE_NAME = "$package_name", + MIN_SDK = "$min_sdk", + TARGET_SDK = "$target_sdk"; - @NonNull - public static String createMenu() { - return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/menu.xml"); - } - - @NonNull - public static String createDrawable() { - return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/drawable.xml"); - } - - @NonNull - public static String createLayout() { - return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/layout.xml"); - } - - public static String getPackageName(File parentPath) { - Matcher pkgMatcher = Pattern.compile(JAVA_PATH_REGEX).matcher(parentPath.getAbsolutePath()); - if (pkgMatcher.find()) { - int end = pkgMatcher.end(); - if (end <= 0) return ""; - String name = parentPath.getAbsolutePath().substring(pkgMatcher.end()); - if (name.startsWith(File.separator)) name = name.substring(1); - return name.replace(File.separator, "."); + @NonNull + public static String createMenu() { + return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/menu.xml"); } - return null; - } - public static String createJavaClass(String packageName, String className) { - return ClassBuilder.createClass(packageName, className); - } - - public static String createJavaInterface(String packageName, String className) { - return ClassBuilder.createInterface(packageName, className); - } - - public static String createJavaEnum(String packageName, String className) { - return ClassBuilder.createEnum(packageName, className); - } - - public static String createActivity(String packageName, String className) { - return ClassBuilder.createActivity(packageName, className); - } - - public static void write( - ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback listener) - throws Exception { - write(template.getId(), details, listener); - } - - public static void write(int id, NewProjectDetails details, ProjectWriterCallback listener) - throws Exception { - callback = listener; - notifyBegin(); - final StudioApp instance = StudioApp.getInstance(); - final File temp = instance.getTempProjectDir(); - final File projectDir = new File(instance.getProjectsDir(), details.name); - if (projectDir.exists()) { - notifyFailed(instance.getString(R.string.project_exists)); - return; + @NonNull + public static String createDrawable() { + return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/drawable.xml"); } - if (temp == null) notifyFailed(instance.getString(R.string.cannot_create_temp)); - if (!FileUtils.delete(temp) || !Environment.mkdirIfNotExits(temp).exists()) - notifyFailed(instance.getString(R.string.cannot_create_temp)); - notifyTask(instance.getString(R.string.copying_assets)); - projectDir.mkdirs(); - File destZip = new File(Environment.TMP_DIR, "templates/" + id + ".zip"); - Environment.mkdirIfNotExits(destZip.getParentFile()); - if (ResourceUtils.copyFileFromAssets( - "templates/" + destZip.getName(), destZip.getAbsolutePath())) { - ZipUtils.unzipFile(destZip, temp); - notifyTask(instance.getString(R.string.writing_files)); - for (String s : FILE_TO_CHANGE) { - File file = new File(temp, s); - if (file.exists() && s.contains(PACKAGE_NAME)) { - s = s.replace(PACKAGE_NAME, details.packageName.replace(".", "/")); - File f = new File(temp, s); - FileUtils.move(file, f); - try { - File f2 = file.getParentFile(); - while (f2 != null && !f2.getName().contains(PACKAGE_NAME)) { - f2 = f2.getParentFile(); + @NonNull + public static String createLayout() { + return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/layout.xml"); + } + + public static String getPackageName(File parentPath) { + Matcher pkgMatcher = Pattern.compile(JAVA_PATH_REGEX).matcher(parentPath.getAbsolutePath()); + if (pkgMatcher.find()) { + int end = pkgMatcher.end(); + if (end <= 0) return ""; + String name = parentPath.getAbsolutePath().substring(pkgMatcher.end()); + if (name.startsWith(File.separator)) name = name.substring(1); + return name.replace(File.separator, "."); + } + return null; + } + + public static String createJavaClass(String packageName, String className) { + return ClassBuilder.createClass(packageName, className); + } + + public static String createJavaInterface(String packageName, String className) { + return ClassBuilder.createInterface(packageName, className); + } + + public static String createJavaEnum(String packageName, String className) { + return ClassBuilder.createEnum(packageName, className); + } + + public static String createActivity(String packageName, String className) { + return ClassBuilder.createActivity(packageName, className); + } + + public static void write( + ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback listener) + throws Exception { + write(template.getId(), details, listener); + } + + public static void write(int id, NewProjectDetails details, ProjectWriterCallback listener) + throws Exception { + callback = listener; + notifyBegin(); + final StudioApp instance = StudioApp.getInstance(); + final File temp = instance.getTempProjectDir(); + final File projectDir = new File(instance.getProjectsDir(), details.name); + if (projectDir.exists()) { + notifyFailed(instance.getString(R.string.project_exists)); + return; + } + if (temp == null) notifyFailed(instance.getString(R.string.cannot_create_temp)); + if (!FileUtils.delete(temp) || !Environment.mkdirIfNotExits(temp).exists()) + notifyFailed(instance.getString(R.string.cannot_create_temp)); + notifyTask(instance.getString(R.string.copying_assets)); + projectDir.mkdirs(); + File destZip = new File(Environment.TMP_DIR, "templates/" + id + ".zip"); + Environment.mkdirIfNotExits(destZip.getParentFile()); + if (ResourceUtils.copyFileFromAssets( + "templates/" + destZip.getName(), destZip.getAbsolutePath())) { + ZipUtils.unzipFile(destZip, temp); + notifyTask(instance.getString(R.string.writing_files)); + for (String s : FILE_TO_CHANGE) { + File file = new File(temp, s); + if (file.exists() && s.contains(PACKAGE_NAME)) { + s = s.replace(PACKAGE_NAME, details.packageName.replace(".", "/")); + File f = new File(temp, s); + FileUtils.move(file, f); + + try { + File f2 = file.getParentFile(); + while (f2 != null && !f2.getName().contains(PACKAGE_NAME)) { + f2 = f2.getParentFile(); + } + + FileUtils.delete(f2); + } catch (Throwable t) { + // ignored + } + file = f; + } + if (file.exists()) { + String read = FileIOUtils.readFile2String(file); + read = + read.replace(APP_NAME, details.name) + .replace(PACKAGE_NAME, details.packageName) + .replace(MIN_SDK, String.valueOf(details.minSdk)) + .replace(TARGET_SDK, String.valueOf(details.targetSdk)); + if (!FileIOUtils.writeFileFromString(file, read, false)) { + notifyFailed( + instance.getString(R.string.failed_write_file, file.getName())); + } + } } + notifyTask(instance.getString(R.string.copying_files)); + if (FileUtils.createOrExistsDir(projectDir)) { + boolean success = true; + final var files = temp == null ? null : temp.listFiles(); + if (files == null) { + success = false; + } else { + for (File f : files) { + if (!(success &= FileUtils.copy(f, new File(projectDir, f.getName())))) { + notifyFailed( + instance.getString(R.string.failed_write_file, f.getName())); + break; + } + } + } - FileUtils.delete(f2); - } catch (Throwable t) { - // ignored - } - file = f; - } - if (file.exists()) { - String read = FileIOUtils.readFile2String(file); - read = - read.replace(APP_NAME, details.name) - .replace(PACKAGE_NAME, details.packageName) - .replace(MIN_SDK, String.valueOf(details.minSdk)) - .replace(TARGET_SDK, String.valueOf(details.targetSdk)); - if (!FileIOUtils.writeFileFromString(file, read, false)) { - notifyFailed(instance.getString(R.string.failed_write_file, file.getName())); - } - } - } - notifyTask(instance.getString(R.string.copying_files)); - if (FileUtils.createOrExistsDir(projectDir)) { - boolean success = true; - final var files = temp == null ? null : temp.listFiles(); - if (files == null) { - success = false; + if (success) { + notifySuccess(projectDir); + } + } else { + notifyFailed(instance.getString(R.string.failed_create_project_dir)); + } } else { - for (File f : files) { - if (!(success &= FileUtils.copy(f, new File(projectDir, f.getName())))) { - notifyFailed(instance.getString(R.string.failed_write_file, f.getName())); - break; - } - } + notifyFailed(instance.getString(R.string.asset_copy_failed)); } - - if (success) { - notifySuccess(projectDir); - } - } else { - notifyFailed(instance.getString(R.string.failed_create_project_dir)); - } - } else { - notifyFailed(instance.getString(R.string.asset_copy_failed)); } - } - private static void notifyBegin() { - ThreadUtils.runOnUiThread( - () -> { - if (callback != null) callback.beforeBegin(); - }); - } + private static void notifyBegin() { + ThreadUtils.runOnUiThread( + () -> { + if (callback != null) callback.beforeBegin(); + }); + } - private static void notifyTask(String name) { - ThreadUtils.runOnUiThread( - () -> { - if (callback != null) callback.onProcessTask(name); - }); - } + private static void notifyTask(String name) { + ThreadUtils.runOnUiThread( + () -> { + if (callback != null) callback.onProcessTask(name); + }); + } - private static void notifySuccess(File root) { - ThreadUtils.runOnUiThread( - () -> { - if (callback != null) callback.onSuccess(root); - }); - } + private static void notifySuccess(File root) { + ThreadUtils.runOnUiThread( + () -> { + if (callback != null) callback.onSuccess(root); + }); + } - private static void notifyFailed(String reason) { - ThreadUtils.runOnUiThread( - () -> { - if (callback != null) callback.onFailed(reason); - }); - } + private static void notifyFailed(String reason) { + ThreadUtils.runOnUiThread( + () -> { + if (callback != null) callback.onFailed(reason); + }); + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java b/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java index 6391e91e5..bef17424d 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java +++ b/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java @@ -25,8 +25,10 @@ import com.itsaky.androidide.models.SearchResult; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.lsp.models.Position; import com.itsaky.lsp.models.Range; + import io.github.rosemoe.sora.text.CharPosition; import io.github.rosemoe.sora.text.Content; + import java.io.File; import java.io.FileFilter; import java.util.ArrayList; @@ -44,118 +46,124 @@ import java.util.regex.Pattern; */ public class RecursiveFileSearcher { - private static final Logger logger = Logger.instance("RecursiveFileSearcher"); + private static final Logger logger = Logger.instance("RecursiveFileSearcher"); - /** - * Search the given text in files recursively in given search directories - * - * @param text Text to search - * @param exts Extentions of file to search. Maybe null. - * @param searchDirs Directories to search in. Subdirectories will be included - * @param callback A listener that will listen to the search result - */ - public static void searchRecursiveAsync( - String text, List exts, List searchDirs, Callback callback) { - // Cannot search empty or null text - if (text == null || text.isEmpty()) { - return; - } - - // If there is no listener to the search, search is meaningless - if (callback == null) { - return; - } - - // Avoid searching if no directories are specified - if (searchDirs == null || searchDirs.isEmpty()) { - return; - } - - new TaskExecutor() - .executeAsync(new Searcher(text, exts, searchDirs), result -> callback.onResult(result)); - } - - private static class Searcher implements Callable>> { - - private final String query; - private final List exts; - private final List dirs; - - public Searcher(String query, List exts, List dirs) { - this.query = query; - this.exts = exts; - this.dirs = dirs; - } - - @Override - public Map> call() throws Exception { - final Map> result = new HashMap<>(); - for (int i = 0; i < dirs.size(); i++) { - final File dir = dirs.get(i); - final List files = - FileUtils.listFilesInDirWithFilter(dir, new MultiFileFilter(exts), true); - for (int j = 0; files != null && j < files.size(); j++) { - final File file = files.get(j); - if (file.isDirectory()) continue; - final String text = FileIOUtils.readFile2String(file); - if (text == null || text.trim().isEmpty()) continue; - final Content content = new Content(text); - final List ranges = new ArrayList<>(); - Matcher matcher = Pattern.compile(Pattern.quote(this.query)).matcher(text); - while (matcher.find()) { - final Range range = new Range(); - final CharPosition start = content.getIndexer().getCharPosition(matcher.start()); - final CharPosition end = content.getIndexer().getCharPosition(matcher.end()); - range.setStart(new Position(start.line, start.column)); - range.setEnd(new Position(end.line, end.column)); - String sub = - "..." - .concat( - text.substring( - Math.max(0, matcher.start() - 30), - Math.min(matcher.end() + 31, text.length()))) - .trim() - .concat("..."); - String match = - content.subContent(start.line, start.column, end.line, end.column).toString(); - ranges.add(new SearchResult(range, file, sub.replaceAll("\\s+", " "), match)); - } - if (ranges.size() > 0) { - result.put(file, ranges); - } + /** + * Search the given text in files recursively in given search directories + * + * @param text Text to search + * @param exts Extentions of file to search. Maybe null. + * @param searchDirs Directories to search in. Subdirectories will be included + * @param callback A listener that will listen to the search result + */ + public static void searchRecursiveAsync( + String text, List exts, List searchDirs, Callback callback) { + // Cannot search empty or null text + if (text == null || text.isEmpty()) { + return; } - } - return result; - } - } - private static class MultiFileFilter implements FileFilter { - - private final List exts; - - public MultiFileFilter(List exts) { - this.exts = exts; - } - - @Override - public boolean accept(File file) { - boolean accept = false; - if (exts == null || exts.isEmpty() || file.isDirectory()) { - accept = true; - } else { - for (String ext : exts) { - if (file.getName().endsWith(ext)) { - accept = true; - break; - } + // If there is no listener to the search, search is meaningless + if (callback == null) { + return; } - } - return accept && FileUtils.isUtf8(file); + // Avoid searching if no directories are specified + if (searchDirs == null || searchDirs.isEmpty()) { + return; + } + + new TaskExecutor() + .executeAsync( + new Searcher(text, exts, searchDirs), result -> callback.onResult(result)); } - } - public static interface Callback { - void onResult(Map> results); - } + private static class Searcher implements Callable>> { + + private final String query; + private final List exts; + private final List dirs; + + public Searcher(String query, List exts, List dirs) { + this.query = query; + this.exts = exts; + this.dirs = dirs; + } + + @Override + public Map> call() throws Exception { + final Map> result = new HashMap<>(); + for (int i = 0; i < dirs.size(); i++) { + final File dir = dirs.get(i); + final List files = + FileUtils.listFilesInDirWithFilter(dir, new MultiFileFilter(exts), true); + for (int j = 0; files != null && j < files.size(); j++) { + final File file = files.get(j); + if (file.isDirectory()) continue; + final String text = FileIOUtils.readFile2String(file); + if (text == null || text.trim().isEmpty()) continue; + final Content content = new Content(text); + final List ranges = new ArrayList<>(); + Matcher matcher = Pattern.compile(Pattern.quote(this.query)).matcher(text); + while (matcher.find()) { + final Range range = new Range(); + final CharPosition start = + content.getIndexer().getCharPosition(matcher.start()); + final CharPosition end = + content.getIndexer().getCharPosition(matcher.end()); + range.setStart(new Position(start.line, start.column)); + range.setEnd(new Position(end.line, end.column)); + String sub = + "..." + .concat( + text.substring( + Math.max(0, matcher.start() - 30), + Math.min( + matcher.end() + 31, text.length()))) + .trim() + .concat("..."); + String match = + content.subContent(start.line, start.column, end.line, end.column) + .toString(); + ranges.add( + new SearchResult(range, file, sub.replaceAll("\\s+", " "), match)); + } + if (ranges.size() > 0) { + result.put(file, ranges); + } + } + } + return result; + } + } + + private static class MultiFileFilter implements FileFilter { + + private final List exts; + + public MultiFileFilter(List exts) { + this.exts = exts; + } + + @Override + public boolean accept(File file) { + boolean accept = false; + if (exts == null || exts.isEmpty() || file.isDirectory()) { + accept = true; + } else { + for (String ext : exts) { + if (file.getName().endsWith(ext)) { + accept = true; + break; + } + } + } + + return accept && FileUtils.isUtf8(file); + } + } + + public static interface Callback { + void onResult(Map> results); + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/Symbols.java b/app/src/main/java/com/itsaky/androidide/utils/Symbols.java index 35c472d47..b8e8f5b26 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/Symbols.java +++ b/app/src/main/java/com/itsaky/androidide/utils/Symbols.java @@ -18,89 +18,91 @@ package com.itsaky.androidide.utils; import androidx.annotation.Nullable; + import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.views.SymbolInputView.Symbol; + import java.io.File; public class Symbols { - public static Symbol[] forFile(@Nullable File file) { - if (file == null) { - return new Symbol[0]; + public static Symbol[] forFile(@Nullable File file) { + if (file == null) { + return new Symbol[0]; + } + + if (file.isFile()) { + if (file.getName().endsWith(".java") || file.getName().endsWith(".gradle")) + return javaSymbols(); + + if (file.getName().endsWith(".xml")) return xmlSymbols(); + } + + return new Symbol[0]; } - if (file.isFile()) { - if (file.getName().endsWith(".java") || file.getName().endsWith(".gradle")) - return javaSymbols(); - - if (file.getName().endsWith(".xml")) return xmlSymbols(); + public static String createTabSpaces() { + int size = StudioApp.getInstance().getPrefManager().getEditorTabSize(); + StringBuilder tab = new StringBuilder(); + for (int i = 1; i <= size; i++) { + tab.append(" "); + } + return tab.toString(); } - return new Symbol[0]; - } - - public static String createTabSpaces() { - int size = StudioApp.getInstance().getPrefManager().getEditorTabSize(); - StringBuilder tab = new StringBuilder(); - for (int i = 1; i <= size; i++) { - tab.append(" "); + public static Symbol[] javaSymbols() { + return new Symbol[] { + new Symbol("↹", "\t"), + new Symbol("{", "{}"), + new Symbol("}"), + new Symbol("(", "()"), + new Symbol(")"), + new Symbol(";"), + new Symbol("="), + new Symbol("\"", "\"\""), + new Symbol("|"), + new Symbol("&"), + new Symbol("!"), + new Symbol("[", "[]"), + new Symbol("]"), + new Symbol("<", "<>"), + new Symbol(">"), + new Symbol("+"), + new Symbol("-"), + new Symbol("/"), + new Symbol("*"), + new Symbol("?"), + new Symbol(":"), + new Symbol("_") + }; } - return tab.toString(); - } - public static Symbol[] javaSymbols() { - return new Symbol[] { - new Symbol("↹", "\t"), - new Symbol("{", "{}"), - new Symbol("}"), - new Symbol("(", "()"), - new Symbol(")"), - new Symbol(";"), - new Symbol("="), - new Symbol("\"", "\"\""), - new Symbol("|"), - new Symbol("&"), - new Symbol("!"), - new Symbol("[", "[]"), - new Symbol("]"), - new Symbol("<", "<>"), - new Symbol(">"), - new Symbol("+"), - new Symbol("-"), - new Symbol("/"), - new Symbol("*"), - new Symbol("?"), - new Symbol(":"), - new Symbol("_") - }; - } - - public static Symbol[] xmlSymbols() { - return new Symbol[] { - new Symbol("↹", "\t"), - new Symbol("<", "<>"), - new Symbol(">"), - new Symbol("/"), - new Symbol("="), - new Symbol("\"", "\"\""), - new Symbol(":"), - new Symbol("@"), - new Symbol("+"), - new Symbol("(", "()"), - new Symbol(")"), - new Symbol(";"), - new Symbol(","), - new Symbol("."), - new Symbol("?"), - new Symbol("|"), - new Symbol("\\"), - new Symbol("&"), - new Symbol("[", "[]"), - new Symbol("]"), - new Symbol("{", "{}"), - new Symbol("}"), - new Symbol("_"), - new Symbol("-") - }; - } + public static Symbol[] xmlSymbols() { + return new Symbol[] { + new Symbol("↹", "\t"), + new Symbol("<", "<>"), + new Symbol(">"), + new Symbol("/"), + new Symbol("="), + new Symbol("\"", "\"\""), + new Symbol(":"), + new Symbol("@"), + new Symbol("+"), + new Symbol("(", "()"), + new Symbol(")"), + new Symbol(";"), + new Symbol(","), + new Symbol("."), + new Symbol("?"), + new Symbol("|"), + new Symbol("\\"), + new Symbol("&"), + new Symbol("[", "[]"), + new Symbol("]"), + new Symbol("{", "{}"), + new Symbol("}"), + new Symbol("_"), + new Symbol("-") + }; + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/TextWatcherAdapter.java b/app/src/main/java/com/itsaky/androidide/utils/TextWatcherAdapter.java index 2554a0caa..7063d2f79 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/TextWatcherAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/utils/TextWatcherAdapter.java @@ -19,6 +19,7 @@ package com.itsaky.androidide.utils; import android.text.Editable; import android.text.TextWatcher; + import androidx.annotation.NonNull; /** @@ -26,12 +27,12 @@ import androidx.annotation.NonNull; */ public class TextWatcherAdapter implements TextWatcher { - @Override - public void beforeTextChanged(@NonNull CharSequence s, int start, int count, int after) {} + @Override + public void beforeTextChanged(@NonNull CharSequence s, int start, int count, int after) {} - @Override - public void onTextChanged(@NonNull CharSequence s, int start, int before, int count) {} + @Override + public void onTextChanged(@NonNull CharSequence s, int start, int before, int count) {} - @Override - public void afterTextChanged(@NonNull Editable s) {} + @Override + public void afterTextChanged(@NonNull Editable s) {} } diff --git a/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java b/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java index 158e08b1b..0916a95c5 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java +++ b/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java @@ -19,25 +19,27 @@ package com.itsaky.androidide.utils; import android.graphics.Color; import android.view.View; + import androidx.core.content.ContextCompat; + import com.google.android.material.transition.MaterialArcMotion; import com.google.android.material.transition.MaterialContainerTransform; public class TransformUtils { - public static MaterialContainerTransform createContainerTransformFor( - View start, View end, View drawingView) { - MaterialContainerTransform transform = new MaterialContainerTransform(); - transform.setStartView(start); - transform.setEndView(end); - transform.addTarget(end); - transform.setDrawingViewId(drawingView.getId()); - transform.setAllContainerColors( - ContextCompat.getColor( - drawingView.getContext(), com.itsaky.androidide.R.color.primaryDarkColor)); - transform.setElevationShadowEnabled(true); - transform.setPathMotion(new MaterialArcMotion()); - transform.setScrimColor(Color.TRANSPARENT); - return transform; - } + public static MaterialContainerTransform createContainerTransformFor( + View start, View end, View drawingView) { + MaterialContainerTransform transform = new MaterialContainerTransform(); + transform.setStartView(start); + transform.setEndView(end); + transform.addTarget(end); + transform.setDrawingViewId(drawingView.getId()); + transform.setAllContainerColors( + ContextCompat.getColor( + drawingView.getContext(), com.itsaky.androidide.R.color.primaryDarkColor)); + transform.setElevationShadowEnabled(true); + transform.setPathMotion(new MaterialArcMotion()); + transform.setScrimColor(Color.TRANSPARENT); + return transform; + } } diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.java b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.java index 345e66a5e..7c0b150a5 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.java +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.java @@ -24,8 +24,10 @@ import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModel; + import com.itsaky.androidide.project.AndroidProject; import com.itsaky.androidide.project.IDEProject; + import java.io.File; import java.util.ArrayList; import java.util.List; @@ -34,147 +36,147 @@ import java.util.Objects; /** ViewModel for data used in {@link com.itsaky.androidide.EditorActivity} */ public class EditorViewModel extends ViewModel { - private final MutableLiveData> mFiles = new MutableLiveData<>(new ArrayList<>()); - private final MutableLiveData mProject = new MutableLiveData<>(null); - private final MutableLiveData mIDEProject = new MutableLiveData<>(null); - private final MutableLiveData mFilesModified = new MutableLiveData<>(false); + private final MutableLiveData> mFiles = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData mProject = new MutableLiveData<>(null); + private final MutableLiveData mIDEProject = new MutableLiveData<>(null); + private final MutableLiveData mFilesModified = new MutableLiveData<>(false); - /** - * Holds information about the currently selected editor fragment. First value in the pair is the - * index of the editor opened. Second value is the file that is opened. - */ - private final MutableLiveData> mCurrentFile = new MutableLiveData<>(null); + /** + * Holds information about the currently selected editor fragment. First value in the pair is + * the index of the editor opened. Second value is the file that is opened. + */ + private final MutableLiveData> mCurrentFile = new MutableLiveData<>(null); - /** - * Get the current {@link AndroidProject}. - * - * @return The android project. - */ - @Nullable - public AndroidProject getAndroidProject() { - return mProject.getValue(); - } - - /** - * Set the android project currently opened in the editor activity. - * - * @param project The project that is opened. - */ - public void setAndroidProject(final AndroidProject project) { - this.mProject.setValue(project); - } - - /** - * Get the {@link IDEProject} opened in the editor activity. - * - * @return The ide project. - */ - @Nullable - public IDEProject getIDEProject() { - return this.mIDEProject.getValue(); - } - - /** - * Set the IDEProject that is currently opened in the editor activity. - * - * @param project The project that is opened. - */ - public void setIDEProject(final IDEProject project) { - this.mIDEProject.setValue(project); - } - - /** - * Add the given file to the list of opened files. - * - * @param file The file that has been opened. - */ - public void addFile(final File file) { - final var files = mFiles.getValue(); - Objects.requireNonNull(files).add(file); - mFiles.setValue(files); - } - - /** - * Remove the file at the given index from the list of opened files. - * - * @param index The index of the closed file. - */ - public void removeFile(int index) { - final var files = mFiles.getValue(); - Objects.requireNonNull(files).remove(index); - mFiles.setValue(files); - } - - public void removeAllFiles() { - mFiles.setValue(new ArrayList<>()); - setCurrentFile(-1, null); - } - - /** - * Get the opened file at the given index. - * - * @param index The index of the file. - * @return The file at the given index. - */ - public File getOpenedFile(final int index) { - return Objects.requireNonNull(mFiles.getValue()).get(index); - } - - /** - * Get the number of files opened. - * - * @return The number of files opened. - */ - public int getOpenedFileCount() { - return Objects.requireNonNull(mFiles.getValue()).size(); - } - - /** - * Get the list of currently opened files. - * - * @return The list of opened files. - */ - @NonNull - public List getOpenedFiles() { - return mFiles.getValue() == null ? new ArrayList<>() : mFiles.getValue(); - } - - /** - * Add an observer to the list of opened files. - * - * @param lifecycleOwner The lifecycle owner. - * @param observer The observer. - */ - public void observeFiles(LifecycleOwner lifecycleOwner, Observer> observer) { - this.mFiles.observe(lifecycleOwner, observer); - } - - public int getCurrentFileIndex() { - if (mCurrentFile.getValue() == null) { - return -1; + /** + * Get the current {@link AndroidProject}. + * + * @return The android project. + */ + @Nullable + public AndroidProject getAndroidProject() { + return mProject.getValue(); } - return mCurrentFile.getValue().first; - } - - public File getCurrentFile() { - if (mCurrentFile.getValue() == null) { - return null; + /** + * Set the android project currently opened in the editor activity. + * + * @param project The project that is opened. + */ + public void setAndroidProject(final AndroidProject project) { + this.mProject.setValue(project); } - return mCurrentFile.getValue().second; - } + /** + * Get the {@link IDEProject} opened in the editor activity. + * + * @return The ide project. + */ + @Nullable + public IDEProject getIDEProject() { + return this.mIDEProject.getValue(); + } - public void setCurrentFile(final int index, @Nullable final File file) { - mCurrentFile.setValue(Pair.create(index, file)); - } + /** + * Set the IDEProject that is currently opened in the editor activity. + * + * @param project The project that is opened. + */ + public void setIDEProject(final IDEProject project) { + this.mIDEProject.setValue(project); + } - public void setFilesModified(boolean modified) { - this.mFilesModified.setValue(modified); - } + /** + * Add the given file to the list of opened files. + * + * @param file The file that has been opened. + */ + public void addFile(final File file) { + final var files = mFiles.getValue(); + Objects.requireNonNull(files).add(file); + mFiles.setValue(files); + } - public boolean areFilesModified() { - final var modified = this.mFilesModified.getValue(); - return modified != null && modified; - } + /** + * Remove the file at the given index from the list of opened files. + * + * @param index The index of the closed file. + */ + public void removeFile(int index) { + final var files = mFiles.getValue(); + Objects.requireNonNull(files).remove(index); + mFiles.setValue(files); + } + + public void removeAllFiles() { + mFiles.setValue(new ArrayList<>()); + setCurrentFile(-1, null); + } + + /** + * Get the opened file at the given index. + * + * @param index The index of the file. + * @return The file at the given index. + */ + public File getOpenedFile(final int index) { + return Objects.requireNonNull(mFiles.getValue()).get(index); + } + + /** + * Get the number of files opened. + * + * @return The number of files opened. + */ + public int getOpenedFileCount() { + return Objects.requireNonNull(mFiles.getValue()).size(); + } + + /** + * Get the list of currently opened files. + * + * @return The list of opened files. + */ + @NonNull + public List getOpenedFiles() { + return mFiles.getValue() == null ? new ArrayList<>() : mFiles.getValue(); + } + + /** + * Add an observer to the list of opened files. + * + * @param lifecycleOwner The lifecycle owner. + * @param observer The observer. + */ + public void observeFiles(LifecycleOwner lifecycleOwner, Observer> observer) { + this.mFiles.observe(lifecycleOwner, observer); + } + + public int getCurrentFileIndex() { + if (mCurrentFile.getValue() == null) { + return -1; + } + + return mCurrentFile.getValue().first; + } + + public File getCurrentFile() { + if (mCurrentFile.getValue() == null) { + return null; + } + + return mCurrentFile.getValue().second; + } + + public void setCurrentFile(final int index, @Nullable final File file) { + mCurrentFile.setValue(Pair.create(index, file)); + } + + public void setFilesModified(boolean modified) { + this.mFilesModified.setValue(modified); + } + + public boolean areFilesModified() { + final var modified = this.mFilesModified.getValue(); + return modified != null && modified; + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/DesignerLayoutContainer.java b/app/src/main/java/com/itsaky/androidide/views/DesignerLayoutContainer.java index 10a74b386..a8a4e00c9 100644 --- a/app/src/main/java/com/itsaky/androidide/views/DesignerLayoutContainer.java +++ b/app/src/main/java/com/itsaky/androidide/views/DesignerLayoutContainer.java @@ -25,7 +25,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; + import androidx.annotation.Nullable; + import com.itsaky.androidide.R; /** @@ -36,53 +38,55 @@ import com.itsaky.androidide.R; */ public class DesignerLayoutContainer extends LinearLayout { - private final TextView mMessage; + private final TextView mMessage; - public DesignerLayoutContainer(Context context) { - this(context, null); - } - - public DesignerLayoutContainer(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public DesignerLayoutContainer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public DesignerLayoutContainer( - Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - this.mMessage = new TextView(context); - this.mMessage.setText(context.getString(R.string.msg_empty_ui_layout)); - this.mMessage.setLayoutParams( - new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - this.mMessage.setGravity(Gravity.CENTER); - this.mMessage.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); - } - - @Override - public void onViewAdded(View child) { - if (child != this.mMessage) { - removeView(this.mMessage); - } else { - super.onViewAdded(child); + public DesignerLayoutContainer(Context context) { + this(context, null); } - } - @Override - public void onViewRemoved(View child) { - if (child != this.mMessage && getChildCount() <= 0) { - addView(mMessage); - } else { - super.onViewRemoved(child); + public DesignerLayoutContainer(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); } - } - // This is needed by XMLLayoutInflater - @Override - protected LayoutParams generateDefaultLayoutParams() { - return super.generateDefaultLayoutParams(); - } + public DesignerLayoutContainer( + Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public DesignerLayoutContainer( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + this.mMessage = new TextView(context); + this.mMessage.setText(context.getString(R.string.msg_empty_ui_layout)); + this.mMessage.setLayoutParams( + new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + this.mMessage.setGravity(Gravity.CENTER); + this.mMessage.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + } + + @Override + public void onViewAdded(View child) { + if (child != this.mMessage) { + removeView(this.mMessage); + } else { + super.onViewAdded(child); + } + } + + @Override + public void onViewRemoved(View child) { + if (child != this.mMessage && getChildCount() <= 0) { + addView(mMessage); + } else { + super.onViewRemoved(child); + } + } + + // This is needed by XMLLayoutInflater + @Override + protected LayoutParams generateDefaultLayoutParams() { + return super.generateDefaultLayoutParams(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/EmptyView.java b/app/src/main/java/com/itsaky/androidide/views/EmptyView.java index 7b67a0ddc..8185cf703 100755 --- a/app/src/main/java/com/itsaky/androidide/views/EmptyView.java +++ b/app/src/main/java/com/itsaky/androidide/views/EmptyView.java @@ -25,42 +25,43 @@ import android.util.AttributeSet; import android.util.TypedValue; import android.widget.RelativeLayout; import android.widget.TextView; + import androidx.core.content.ContextCompat; public class EmptyView extends RelativeLayout { - public EmptyView(Context context) { - this(context, null); - } + public EmptyView(Context context) { + this(context, null); + } - public EmptyView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + public EmptyView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } - public EmptyView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } + public EmptyView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } - public EmptyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + public EmptyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - init(); - } + init(); + } - private void init() { + private void init() { - removeAllViews(); + removeAllViews(); - TextView text = new TextView(getContext()); - text.setText(com.itsaky.androidide.R.string.msg_empty_view); - text.setTextColor( - ContextCompat.getColor( - getContext(), com.itsaky.androidide.R.color.secondaryTextColor_light)); - text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + TextView text = new TextView(getContext()); + text.setText(com.itsaky.androidide.R.string.msg_empty_view); + text.setTextColor( + ContextCompat.getColor( + getContext(), com.itsaky.androidide.R.color.secondaryTextColor_light)); + text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); - LayoutParams params = new LayoutParams(-2, -2); - params.addRule(CENTER_IN_PARENT); + LayoutParams params = new LayoutParams(-2, -2); + params.addRule(CENTER_IN_PARENT); - addView(text, params); - } + addView(text, params); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/MaterialBanner.java b/app/src/main/java/com/itsaky/androidide/views/MaterialBanner.java index 889241dc3..614f982d8 100755 --- a/app/src/main/java/com/itsaky/androidide/views/MaterialBanner.java +++ b/app/src/main/java/com/itsaky/androidide/views/MaterialBanner.java @@ -26,182 +26,184 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.animation.Animation; import android.view.animation.Transformation; + import androidx.annotation.DrawableRes; import androidx.annotation.StringRes; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; + import com.itsaky.androidide.databinding.LayoutBannerBinding; public class MaterialBanner extends CoordinatorLayout { - private LayoutBannerBinding binding; - private OnPositiveClickListener positiveListener; - private OnNegativeClickListener negativeListener; + private LayoutBannerBinding binding; + private OnPositiveClickListener positiveListener; + private OnNegativeClickListener negativeListener; - private String contentText; - private String leftButtonText; - private String rightButtonText; - private Drawable iconDrawable; - private int backgroundColor; - private int contentTextColor; - private int buttonTextColor; + private String contentText; + private String leftButtonText; + private String rightButtonText; + private Drawable iconDrawable; + private int backgroundColor; + private int contentTextColor; + private int buttonTextColor; - public MaterialBanner(Context context) { - this(context, null); - } + public MaterialBanner(Context context) { + this(context, null); + } - public MaterialBanner(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + public MaterialBanner(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } - public MaterialBanner(Context context, AttributeSet attrs, int style) { - super(context, attrs, style); - init(); - } + public MaterialBanner(Context context, AttributeSet attrs, int style) { + super(context, attrs, style); + init(); + } - private void init() { - binding = LayoutBannerBinding.inflate(LayoutInflater.from(getContext()), this, true); - } + private void init() { + binding = LayoutBannerBinding.inflate(LayoutInflater.from(getContext()), this, true); + } - public void show() { - this.binding.contentTextView.setText(contentText); - this.binding.contentIconView.setImageDrawable(iconDrawable); - this.binding.rightButton.setText(rightButtonText); - this.binding.leftButton.setText(leftButtonText); - this.binding.getRoot().setBackgroundColor(backgroundColor); - this.binding.contentTextView.setTextColor(contentTextColor); - this.binding.leftButton.setTextColor(buttonTextColor); - this.binding.rightButton.setTextColor(buttonTextColor); - this.binding.rightButton.setOnClickListener( - v -> { - if (positiveListener == null) dismiss(); - else { - positiveListener.onClick(MaterialBanner.this); - dismiss(); - } - }); - this.binding.leftButton.setOnClickListener( - v -> { - if (negativeListener == null) dismiss(); - else { - negativeListener.onClick(MaterialBanner.this); - dismiss(); - } - }); + public void show() { + this.binding.contentTextView.setText(contentText); + this.binding.contentIconView.setImageDrawable(iconDrawable); + this.binding.rightButton.setText(rightButtonText); + this.binding.leftButton.setText(leftButtonText); + this.binding.getRoot().setBackgroundColor(backgroundColor); + this.binding.contentTextView.setTextColor(contentTextColor); + this.binding.leftButton.setTextColor(buttonTextColor); + this.binding.rightButton.setTextColor(buttonTextColor); + this.binding.rightButton.setOnClickListener( + v -> { + if (positiveListener == null) dismiss(); + else { + positiveListener.onClick(MaterialBanner.this); + dismiss(); + } + }); + this.binding.leftButton.setOnClickListener( + v -> { + if (negativeListener == null) dismiss(); + else { + negativeListener.onClick(MaterialBanner.this); + dismiss(); + } + }); - final ConstraintLayout root = this.binding.getRoot(); - root.measure(-1, -2); - final int height = root.getMeasuredHeight(); - final LayoutParams p = (LayoutParams) root.getLayoutParams(); - p.height = 0; - root.setLayoutParams(p); - root.setVisibility(VISIBLE); - setVisibility(VISIBLE); + final ConstraintLayout root = this.binding.getRoot(); + root.measure(-1, -2); + final int height = root.getMeasuredHeight(); + final LayoutParams p = (LayoutParams) root.getLayoutParams(); + p.height = 0; + root.setLayoutParams(p); + root.setVisibility(VISIBLE); + setVisibility(VISIBLE); - Animation anim = - new Animation() { - @Override - public void applyTransformation(float time, Transformation t) { - p.height = time == 1f ? -2 : (int) (height * time); - root.setLayoutParams(p); - root.requestLayout(); - } + Animation anim = + new Animation() { + @Override + public void applyTransformation(float time, Transformation t) { + p.height = time == 1f ? -2 : (int) (height * time); + root.setLayoutParams(p); + root.requestLayout(); + } - @Override - public boolean willChangeBounds() { - return true; - } - }; + @Override + public boolean willChangeBounds() { + return true; + } + }; - anim.setDuration(200); - root.startAnimation(anim); - } + anim.setDuration(200); + root.startAnimation(anim); + } - public void dismiss() { - final ConstraintLayout root = this.binding.getRoot(); - final LayoutParams p = (LayoutParams) root.getLayoutParams(); - final int initialHeight = getMeasuredHeight(); - Animation animation = - new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - if (interpolatedTime == 1f) { - root.setVisibility(GONE); - setVisibility(GONE); - } else { - p.height = (int) (initialHeight - (initialHeight * interpolatedTime)); - root.requestLayout(); - } - } + public void dismiss() { + final ConstraintLayout root = this.binding.getRoot(); + final LayoutParams p = (LayoutParams) root.getLayoutParams(); + final int initialHeight = getMeasuredHeight(); + Animation animation = + new Animation() { + @Override + public void applyTransformation(float interpolatedTime, Transformation t) { + if (interpolatedTime == 1f) { + root.setVisibility(GONE); + setVisibility(GONE); + } else { + p.height = (int) (initialHeight - (initialHeight * interpolatedTime)); + root.requestLayout(); + } + } - @Override - public boolean willChangeBounds() { - return true; - } - }; - animation.setDuration((long) (initialHeight / getResources().getDisplayMetrics().density)); - root.startAnimation(animation); - } + @Override + public boolean willChangeBounds() { + return true; + } + }; + animation.setDuration((long) (initialHeight / getResources().getDisplayMetrics().density)); + root.startAnimation(animation); + } - public MaterialBanner setIcon(@DrawableRes int icon) { - return setIcon(ContextCompat.getDrawable(getContext(), icon)); - } + public MaterialBanner setIcon(@DrawableRes int icon) { + return setIcon(ContextCompat.getDrawable(getContext(), icon)); + } - public MaterialBanner setPositive(@StringRes int text, OnPositiveClickListener listener) { - return setPositive(getContext().getString(text), listener); - } + public MaterialBanner setPositive(@StringRes int text, OnPositiveClickListener listener) { + return setPositive(getContext().getString(text), listener); + } - public MaterialBanner setNegative(@StringRes int text, OnNegativeClickListener listener) { - return setNegative(getContext().getString(text), listener); - } + public MaterialBanner setNegative(@StringRes int text, OnNegativeClickListener listener) { + return setNegative(getContext().getString(text), listener); + } - public MaterialBanner setContentText(@StringRes int text) { - return setContentText(getContext().getString(text)); - } + public MaterialBanner setContentText(@StringRes int text) { + return setContentText(getContext().getString(text)); + } - public MaterialBanner setContentText(String contentText) { - this.contentText = contentText; - return this; - } + public MaterialBanner setContentText(String contentText) { + this.contentText = contentText; + return this; + } - public MaterialBanner setIcon(Drawable icon) { - this.iconDrawable = icon; - return this; - } + public MaterialBanner setIcon(Drawable icon) { + this.iconDrawable = icon; + return this; + } - public MaterialBanner setPositive(String text, OnPositiveClickListener listener) { - this.rightButtonText = text; - this.positiveListener = listener; - return this; - } + public MaterialBanner setPositive(String text, OnPositiveClickListener listener) { + this.rightButtonText = text; + this.positiveListener = listener; + return this; + } - public MaterialBanner setNegative(String text, OnNegativeClickListener listener) { - this.leftButtonText = text; - this.negativeListener = listener; - return this; - } + public MaterialBanner setNegative(String text, OnNegativeClickListener listener) { + this.leftButtonText = text; + this.negativeListener = listener; + return this; + } - public MaterialBanner setBannerBackgroundColor(int backgroundColor) { - this.backgroundColor = backgroundColor; - return this; - } + public MaterialBanner setBannerBackgroundColor(int backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } - public MaterialBanner setContentTextColor(int contentTextColor) { - this.contentTextColor = contentTextColor; - return this; - } + public MaterialBanner setContentTextColor(int contentTextColor) { + this.contentTextColor = contentTextColor; + return this; + } - public MaterialBanner setButtonTextColor(int buttonTextColor) { - this.buttonTextColor = buttonTextColor; - return this; - } + public MaterialBanner setButtonTextColor(int buttonTextColor) { + this.buttonTextColor = buttonTextColor; + return this; + } - public static interface OnPositiveClickListener { - public void onClick(MaterialBanner banner); - } + public static interface OnPositiveClickListener { + public void onClick(MaterialBanner banner); + } - public static interface OnNegativeClickListener { - public void onClick(MaterialBanner banner); - } + public static interface OnNegativeClickListener { + public void onClick(MaterialBanner banner); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/SymbolInputView.java b/app/src/main/java/com/itsaky/androidide/views/SymbolInputView.java index db5edf562..540801d21 100755 --- a/app/src/main/java/com/itsaky/androidide/views/SymbolInputView.java +++ b/app/src/main/java/com/itsaky/androidide/views/SymbolInputView.java @@ -19,61 +19,65 @@ package com.itsaky.androidide.views; import android.content.Context; import android.util.AttributeSet; + import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.adapters.SymbolInputAdapter; import com.itsaky.androidide.views.editor.IDEEditor; public class SymbolInputView extends RecyclerView { - private SymbolInputAdapter adapter; + private SymbolInputAdapter adapter; - public SymbolInputView(Context context) { - this(context, null); - } - - public SymbolInputView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SymbolInputView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); - } - - public void bindEditor(IDEEditor editor) { - adapter = new SymbolInputAdapter(editor); - setAdapter(adapter); - setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); - } - - public void setSymbols(Symbol... symbols) { - if (adapter != null) { - adapter.setSymbols(true, symbols); - } - } - - public static class Symbol { - public String label; - public String commit; - public int offset; - - public Symbol(String both) { - this(both, 1); + public SymbolInputView(Context context) { + this(context, null); } - public Symbol(String both, int offset) { - this(both, both, offset); + public SymbolInputView(Context context, AttributeSet attrs) { + this(context, attrs, 0); } - public Symbol(String label, String commit) { - this(label, commit, 1); + public SymbolInputView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setLayoutManager( + new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); } - public Symbol(String label, String commit, int offset) { - this.label = label; - this.commit = commit; - this.offset = offset; + public void bindEditor(IDEEditor editor) { + adapter = new SymbolInputAdapter(editor); + setAdapter(adapter); + setLayoutManager( + new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); + } + + public void setSymbols(Symbol... symbols) { + if (adapter != null) { + adapter.setSymbols(true, symbols); + } + } + + public static class Symbol { + public String label; + public String commit; + public int offset; + + public Symbol(String both) { + this(both, 1); + } + + public Symbol(String both, int offset) { + this(both, both, offset); + } + + public Symbol(String label, String commit) { + this(label, commit, 1); + } + + public Symbol(String label, String commit, int offset) { + this.label = label; + this.commit = commit; + this.offset = offset; + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/views/ValueEditorTextView.java b/app/src/main/java/com/itsaky/androidide/views/ValueEditorTextView.java index d853d3b44..613f01d45 100644 --- a/app/src/main/java/com/itsaky/androidide/views/ValueEditorTextView.java +++ b/app/src/main/java/com/itsaky/androidide/views/ValueEditorTextView.java @@ -22,11 +22,14 @@ import android.content.Context; import android.text.Editable; import android.text.TextWatcher; import android.widget.ArrayAdapter; + import androidx.annotation.NonNull; + import com.google.android.material.textfield.MaterialAutoCompleteTextView; import com.itsaky.androidide.ui.edit.IValueSuggestionProvider; import com.itsaky.androidide.utils.TextWatcherAdapter; import com.itsaky.inflater.IAttribute; + import java.util.ArrayList; import java.util.HashSet; import java.util.Objects; @@ -41,39 +44,41 @@ import java.util.Set; @SuppressLint("ViewConstructor") public class ValueEditorTextView extends MaterialAutoCompleteTextView { - private final IAttribute attribute; - private final int format; - private final Set mSuggestionProviders = new HashSet<>(); + private final IAttribute attribute; + private final int format; + private final Set mSuggestionProviders = new HashSet<>(); - private final TextWatcher watcher = - new TextWatcherAdapter() { + private final TextWatcher watcher = + new TextWatcherAdapter() { - @Override - public void afterTextChanged(@NonNull Editable s) { - final var prefix = s.toString(); - final var items = new ArrayList(); + @Override + public void afterTextChanged(@NonNull Editable s) { + final var prefix = s.toString(); + final var items = new ArrayList(); - for (IValueSuggestionProvider provider : mSuggestionProviders) { - if (provider.checkFormat(format)) { - items.addAll(provider.suggest(attribute, prefix)); - } - } + for (IValueSuggestionProvider provider : mSuggestionProviders) { + if (provider.checkFormat(format)) { + items.addAll(provider.suggest(attribute, prefix)); + } + } - setThreshold(1); - setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, items)); - } - }; + setThreshold(1); + setAdapter( + new ArrayAdapter<>( + getContext(), android.R.layout.simple_list_item_1, items)); + } + }; - public ValueEditorTextView(@NonNull Context context, IAttribute attribute, int format) { - super(context); - this.attribute = attribute; - this.format = format; + public ValueEditorTextView(@NonNull Context context, IAttribute attribute, int format) { + super(context); + this.attribute = attribute; + this.format = format; - addTextChangedListener(this.watcher); - } + addTextChangedListener(this.watcher); + } - public void registerSuggestionProvider(IValueSuggestionProvider provider) { - Objects.requireNonNull(provider); - mSuggestionProviders.add(provider); - } + public void registerSuggestionProvider(IValueSuggestionProvider provider) { + Objects.requireNonNull(provider); + mSuggestionProviders.add(provider); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java b/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java index 206850a3f..5c90515b9 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java @@ -38,8 +38,10 @@ import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.widget.FrameLayout; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.SizeUtils; import com.itsaky.androidide.adapters.CompletionListAdapter; @@ -57,9 +59,14 @@ import com.itsaky.androidide.utils.Logger; import com.itsaky.androidide.utils.TypefaceUtils; import com.itsaky.inflater.values.ValuesTableFactory; import com.itsaky.lsp.models.Range; + import io.github.rosemoe.sora.event.ContentChangeEvent; import io.github.rosemoe.sora.lang.EmptyLanguage; import io.github.rosemoe.sora.text.Content; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; + import java.io.File; import java.io.StringReader; import java.nio.file.Files; @@ -67,8 +74,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.Token; /** * A view that handles opened code editors. @@ -78,330 +83,331 @@ import org.antlr.v4.runtime.Token; @SuppressLint("ViewConstructor") // This view is always dynamically created. public class CodeEditorView extends FrameLayout { - private final File file; - private final LayoutCodeEditorBinding binding; + private final File file; + private final LayoutCodeEditorBinding binding; - private boolean isModified; - private boolean isFirstCreate; + private boolean isModified; + private boolean isFirstCreate; - private static final Logger LOG = Logger.instance("CodeEditorView"); + private static final Logger LOG = Logger.instance("CodeEditorView"); - public CodeEditorView( - @NonNull Context context, @NonNull File file, final @NonNull Range selection) { - super(context); - this.file = file; - this.isModified = false; - this.isFirstCreate = true; + public CodeEditorView( + @NonNull Context context, @NonNull File file, final @NonNull Range selection) { + super(context); + this.file = file; + this.isModified = false; + this.isFirstCreate = true; - this.binding = LayoutCodeEditorBinding.inflate(LayoutInflater.from(context)); - this.binding.editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); - this.binding.editor.setHighlightCurrentBlock(true); - this.binding.editor.getProps().autoCompletionOnComposing = true; - this.binding.editor.setAutoCompletionItemAdapter(new CompletionListAdapter()); - this.binding.editor.setDividerWidth(SizeUtils.dp2px(1)); - this.binding.editor.setColorScheme(new SchemeAndroidIDE()); - this.binding.editor.subscribeEvent( - ContentChangeEvent.class, ((event, unsubscribe) -> handleContentChange(event))); + this.binding = LayoutCodeEditorBinding.inflate(LayoutInflater.from(context)); + this.binding.editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); + this.binding.editor.setHighlightCurrentBlock(true); + this.binding.editor.getProps().autoCompletionOnComposing = true; + this.binding.editor.setAutoCompletionItemAdapter(new CompletionListAdapter()); + this.binding.editor.setDividerWidth(SizeUtils.dp2px(1)); + this.binding.editor.setColorScheme(new SchemeAndroidIDE()); + this.binding.editor.subscribeEvent( + ContentChangeEvent.class, ((event, unsubscribe) -> handleContentChange(event))); - this.binding.diagnosticTextContainer.setVisibility(GONE); + this.binding.diagnosticTextContainer.setVisibility(GONE); - removeAllViews(); - addView(this.binding.getRoot()); + removeAllViews(); + addView(this.binding.getRoot()); - selection.validate(); - CompletableFuture.runAsync( - () -> { - final var contents = FileIOUtils.readFile2String(file); - binding.editor.post( - () -> { - binding.editor.setText(contents, createEditorArgs()); - postRead(); - if (LSPUtils.isEqual(selection.getStart(), selection.getEnd())) { - getEditor() - .setSelection( - selection.getStart().getLine(), selection.getStart().getColumn()); - } else { - getEditor() - .setSelectionRegion( - selection.getStart().getLine(), - selection.getStart().getColumn(), - selection.getEnd().getLine(), - selection.getEnd().getColumn()); + selection.validate(); + CompletableFuture.runAsync( + () -> { + final var contents = FileIOUtils.readFile2String(file); + binding.editor.post( + () -> { + binding.editor.setText(contents, createEditorArgs()); + postRead(); + if (LSPUtils.isEqual(selection.getStart(), selection.getEnd())) { + getEditor() + .setSelection( + selection.getStart().getLine(), + selection.getStart().getColumn()); + } else { + getEditor() + .setSelectionRegion( + selection.getStart().getLine(), + selection.getStart().getColumn(), + selection.getEnd().getLine(), + selection.getEnd().getColumn()); + } + }); + }); + + configureEditorIfNeeded(); + } + + @NonNull + private Bundle createEditorArgs() { + var bundle = new Bundle(); + if (getFile() == null) { + return bundle; + } + + bundle.putString(IDEEditor.KEY_FILE, getFile().getAbsolutePath()); + return bundle; + } + + private void handleContentChange(@NonNull ContentChangeEvent event) { + isModified = true; + + if (event.getAction() == ContentChangeEvent.ACTION_INSERT) { + final var editor = event.getEditor(); + final var content = event.getChangedText(); + final var endLine = event.getChangeEnd().line; + final var endColumn = event.getChangeEnd().column; + if (file.getName().endsWith(".xml")) { + boolean isOpen = false; + try { + isOpen = editor.getText().charAt(editor.getCursor().getLeft() - 2) == '<'; + } catch (Throwable th) { + LOG.error(th); } - }); - }); - configureEditorIfNeeded(); - } - - @NonNull - private Bundle createEditorArgs() { - var bundle = new Bundle(); - if (getFile() == null) { - return bundle; + if (isOpen && "/".contentEquals(content)) { + closeCurrentTag(editor.getText().toString(), endLine, endColumn); + } + } + } } - bundle.putString(IDEEditor.KEY_FILE, getFile().getAbsolutePath()); - return bundle; - } + public boolean save() { - private void handleContentChange(@NonNull ContentChangeEvent event) { - isModified = true; + if (getFile() == null) { + LOG.error("Cannot save file. File instance is null."); + return false; + } - if (event.getAction() == ContentChangeEvent.ACTION_INSERT) { - final var editor = event.getEditor(); - final var content = event.getChangedText(); - final var endLine = event.getChangeEnd().line; - final var endColumn = event.getChangeEnd().column; - if (file.getName().endsWith(".xml")) { - boolean isOpen = false; + final var path = getFile().toPath(); + if (!isModified() && Files.exists(path)) { + LOG.info(getFile().getName()); + LOG.info("File was not modified. Skipping save operation."); + return false; + } + + final var lines = getLines(getEditor().getText()); try { - isOpen = editor.getText().charAt(editor.getCursor().getLeft() - 2) == '<'; - } catch (Throwable th) { - LOG.error(th); + + Files.write(getFile().toPath(), lines); + notifySaved(); + isModified = false; + + return true; + } catch (Throwable e) { + LOG.error("Failed to save file", getFile(), "\n", e); + return false; + } + } + + @NonNull + private List getLines(Content text) { + final var count = text.getLineCount(); + final var result = new ArrayList(); + + for (int i = 0; i < count; i++) { + result.add(text.getLine(i)); } - if (isOpen && "/".contentEquals(content)) { - closeCurrentTag(editor.getText().toString(), endLine, endColumn); + return result; + } + + public IDEEditor getEditor() { + return binding.editor; + } + + public LayoutCodeEditorBinding getBinding() { + return binding; + } + + @Nullable + public File getFile() { + return file; + } + + public String getText() { + return binding.editor.getText().toString(); + } + + public boolean isModified() { + return isModified; + } + + public void onPause() { + // unimplemented + } + + public void onResume() { + configureEditorIfNeeded(); + } + + public void undo() { + if (binding.editor.canUndo()) { + binding.editor.undo(); } - } - } - } - - public boolean save() { - - if (getFile() == null) { - LOG.error("Cannot save file. File instance is null."); - return false; } - final var path = getFile().toPath(); - if (!isModified() && Files.exists(path)) { - LOG.info(getFile().getName()); - LOG.info("File was not modified. Skipping save operation."); - return false; + public void redo() { + if (binding.editor.canRedo()) { + binding.editor.redo(); + } } - final var lines = getLines(getEditor().getText()); - try { - - Files.write(getFile().toPath(), lines); - notifySaved(); - isModified = false; - - return true; - } catch (Throwable e) { - LOG.error("Failed to save file", getFile(), "\n", e); - return false; - } - } - - @NonNull - private List getLines(Content text) { - final var count = text.getLineCount(); - final var result = new ArrayList(); - - for (int i = 0; i < count; i++) { - result.add(text.getLine(i)); + public void findDefinition() { + binding.editor.findDefinition(); } - return result; - } - - public IDEEditor getEditor() { - return binding.editor; - } - - public LayoutCodeEditorBinding getBinding() { - return binding; - } - - @Nullable - public File getFile() { - return file; - } - - public String getText() { - return binding.editor.getText().toString(); - } - - public boolean isModified() { - return isModified; - } - - public void onPause() { - // unimplemented - } - - public void onResume() { - configureEditorIfNeeded(); - } - - public void undo() { - if (binding.editor.canUndo()) { - binding.editor.undo(); - } - } - - public void redo() { - if (binding.editor.canRedo()) { - binding.editor.redo(); - } - } - - public void findDefinition() { - binding.editor.findDefinition(); - } - - public void findReferences() { - binding.editor.findReferences(); - } - - public void commentLine() { - binding.editor.commentLine(); - } - - public void uncommentLine() { - binding.editor.uncommentLine(); - } - - public void beginSearch() { - binding.editor.beginSearchMode(); - } - - protected void postRead() { - if (file.isFile() && file.getName().endsWith(".java")) { - binding.editor.setEditorLanguage(new JavaLanguage()); - binding.editor.setLanguageServer(StudioApp.getInstance().getJavaLanguageServer()); - } else if (file.isFile() && file.getName().endsWith(".xml")) { - binding.editor.setEditorLanguage(new XMLLanguage()); - binding.editor.setLanguageServer(StudioApp.getInstance().getXMLLanguageServer()); - } else if (file.isFile() && file.getName().endsWith(".gradle")) { - binding.editor.setEditorLanguage(new GroovyLanguage()); - } else { - binding.editor.setEditorLanguage(new EmptyLanguage()); + public void findReferences() { + binding.editor.findReferences(); } - // File must be set only after setting the language server - // This will make sure that textDocument/didOpen is sent - binding.editor.setFile(getFile()); - } - - private void configureEditorIfNeeded() { - boolean sizeChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_SIZE_CHANGED; - boolean ligaturesChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED; - boolean flagsChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED; - boolean drawHexChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED; - final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); - - if (sizeChanged) { - float textSize = prefs.getFloat(PreferenceManager.KEY_EDITOR_FONT_SIZE); - if (textSize < 6 || textSize > 32) { - textSize = 14; - } - - binding.editor.setTextSize(textSize); - ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = false; + public void commentLine() { + binding.editor.commentLine(); } - if (ligaturesChanged) { - var enabled = prefs.getBoolean(PreferenceManager.KEY_EDITOR_FONT_LIGATURES, true); - binding.editor.setLigatureEnabled(enabled); - ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = false; + public void uncommentLine() { + binding.editor.uncommentLine(); } - if (flagsChanged) { - int flags = 0; - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_LEADING, true)) { - flags |= IDEEditor.FLAG_DRAW_WHITESPACE_LEADING; - } - - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_TRAILING, false)) { - flags |= IDEEditor.FLAG_DRAW_WHITESPACE_TRAILING; - } - - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_INNER, true)) { - flags |= IDEEditor.FLAG_DRAW_WHITESPACE_INNER; - } - - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_EMPTY_LINE, true)) { - flags |= IDEEditor.FLAG_DRAW_WHITESPACE_FOR_EMPTY_LINE; - } - - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_LINE_BREAK, true)) { - flags |= IDEEditor.FLAG_DRAW_LINE_SEPARATOR; - } - - binding.editor.setNonPrintablePaintingFlags(flags); - ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = false; + public void beginSearch() { + binding.editor.beginSearchMode(); } - if (drawHexChanged) { - // TODO Do something about this - // Maybe use spans... - // binding.editor.setLineColorsEnabled (prefs.getBoolean - // (PreferenceManager.KEY_EDITOR_DRAW_HEX, true)); - ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = false; - } - - isFirstCreate = false; - } - - private void closeCurrentTag(String text, int line, int col) { - try { - XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(text))); - Token token; - boolean wasSlash = false, wasOpen = false; - ArrayList currentNames = new ArrayList<>(); - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - final int type = token.getType(); - if (type == XMLLexer.OPEN) { - wasOpen = true; - } else if (type == XMLLexer.Name) { - if (wasOpen && wasSlash && currentNames.size() > 0) { - currentNames.remove(0); - } else if (wasOpen) { - currentNames.add(0, token.getText()); - wasOpen = false; - } - } else if (type == XMLLexer.OPEN_SLASH) { - int l = token.getLine() - 1; - int c = token.getCharPositionInLine(); - if (l == line && c == col) { - break; - } else if (currentNames.size() > 0) { - currentNames.remove(0); - } - } else if (type == XMLLexer.SLASH_CLOSE || type == XMLLexer.SPECIAL_CLOSE) { - if (currentNames.size() > 0 && token.getText().trim().endsWith("/>")) { - currentNames.remove(0); - } - } else if (type == XMLLexer.SLASH) { - wasSlash = true; + protected void postRead() { + if (file.isFile() && file.getName().endsWith(".java")) { + binding.editor.setEditorLanguage(new JavaLanguage()); + binding.editor.setLanguageServer(StudioApp.getInstance().getJavaLanguageServer()); + } else if (file.isFile() && file.getName().endsWith(".xml")) { + binding.editor.setEditorLanguage(new XMLLanguage()); + binding.editor.setLanguageServer(StudioApp.getInstance().getXMLLanguageServer()); + } else if (file.isFile() && file.getName().endsWith(".gradle")) { + binding.editor.setEditorLanguage(new GroovyLanguage()); } else { - wasOpen = wasSlash = false; + binding.editor.setEditorLanguage(new EmptyLanguage()); } - } - if (currentNames.size() > 0) { - binding.editor.getText().insert(line, col + 2, currentNames.get(0)); - } - } catch (Throwable th) { - LOG.error("Unable to close current tag", th); + // File must be set only after setting the language server + // This will make sure that textDocument/didOpen is sent + binding.editor.setFile(getFile()); } - } - private void notifySaved() { - binding.editor.didSave(); + private void configureEditorIfNeeded() { + boolean sizeChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_SIZE_CHANGED; + boolean ligaturesChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED; + boolean flagsChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED; + boolean drawHexChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED; + final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); - if (Objects.requireNonNull(getFile()).getName().endsWith(".xml")) { - ValuesTableFactory.syncWithFile(getFile()); + if (sizeChanged) { + float textSize = prefs.getFloat(PreferenceManager.KEY_EDITOR_FONT_SIZE); + if (textSize < 6 || textSize > 32) { + textSize = 14; + } + + binding.editor.setTextSize(textSize); + ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = false; + } + + if (ligaturesChanged) { + var enabled = prefs.getBoolean(PreferenceManager.KEY_EDITOR_FONT_LIGATURES, true); + binding.editor.setLigatureEnabled(enabled); + ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = false; + } + + if (flagsChanged) { + int flags = 0; + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_LEADING, true)) { + flags |= IDEEditor.FLAG_DRAW_WHITESPACE_LEADING; + } + + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_TRAILING, false)) { + flags |= IDEEditor.FLAG_DRAW_WHITESPACE_TRAILING; + } + + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_INNER, true)) { + flags |= IDEEditor.FLAG_DRAW_WHITESPACE_INNER; + } + + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_EMPTY_LINE, true)) { + flags |= IDEEditor.FLAG_DRAW_WHITESPACE_FOR_EMPTY_LINE; + } + + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_LINE_BREAK, true)) { + flags |= IDEEditor.FLAG_DRAW_LINE_SEPARATOR; + } + + binding.editor.setNonPrintablePaintingFlags(flags); + ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = false; + } + + if (drawHexChanged) { + // TODO Do something about this + // Maybe use spans... + // binding.editor.setLineColorsEnabled (prefs.getBoolean + // (PreferenceManager.KEY_EDITOR_DRAW_HEX, true)); + ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = false; + } + + isFirstCreate = false; } - } - /** Mark this files as saved. Even if it not saved. */ - public void markAsSaved() { - isModified = false; - notifySaved(); - } + private void closeCurrentTag(String text, int line, int col) { + try { + XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(text))); + Token token; + boolean wasSlash = false, wasOpen = false; + ArrayList currentNames = new ArrayList<>(); + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + final int type = token.getType(); + if (type == XMLLexer.OPEN) { + wasOpen = true; + } else if (type == XMLLexer.Name) { + if (wasOpen && wasSlash && currentNames.size() > 0) { + currentNames.remove(0); + } else if (wasOpen) { + currentNames.add(0, token.getText()); + wasOpen = false; + } + } else if (type == XMLLexer.OPEN_SLASH) { + int l = token.getLine() - 1; + int c = token.getCharPositionInLine(); + if (l == line && c == col) { + break; + } else if (currentNames.size() > 0) { + currentNames.remove(0); + } + } else if (type == XMLLexer.SLASH_CLOSE || type == XMLLexer.SPECIAL_CLOSE) { + if (currentNames.size() > 0 && token.getText().trim().endsWith("/>")) { + currentNames.remove(0); + } + } else if (type == XMLLexer.SLASH) { + wasSlash = true; + } else { + wasOpen = wasSlash = false; + } + } + + if (currentNames.size() > 0) { + binding.editor.getText().insert(line, col + 2, currentNames.get(0)); + } + } catch (Throwable th) { + LOG.error("Unable to close current tag", th); + } + } + + private void notifySaved() { + binding.editor.didSave(); + + if (Objects.requireNonNull(getFile()).getName().endsWith(".xml")) { + ValuesTableFactory.syncWithFile(getFile()); + } + } + + /** Mark this files as saved. Even if it not saved. */ + public void markAsSaved() { + isModified = false; + notifySaved(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/DiagnosticWindow.java b/app/src/main/java/com/itsaky/androidide/views/editor/DiagnosticWindow.java index 212e987d4..6a0849f8c 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/DiagnosticWindow.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/DiagnosticWindow.java @@ -19,6 +19,7 @@ package com.itsaky.androidide.views.editor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.itsaky.lsp.models.DiagnosticItem; /** @@ -28,35 +29,35 @@ import com.itsaky.lsp.models.DiagnosticItem; */ public class DiagnosticWindow extends SimpleTextWindow { - /** - * Create a popup window for editor - * - * @param editor The editor - * @see #FEATURE_SCROLL_AS_CONTENT - * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED - * @see #FEATURE_HIDE_WHEN_FAST_SCROLL - */ - public DiagnosticWindow(@NonNull IDEEditor editor) { - super(editor); - } - - /** - * Show the given diagnostic item. - * - * @param diagnostic The diagnostic item to show. - */ - public void showDiagnostic(@Nullable DiagnosticItem diagnostic) { - if (diagnostic == null) { - if (isShowing()) { - dismiss(); - } - - return; + /** + * Create a popup window for editor + * + * @param editor The editor + * @see #FEATURE_SCROLL_AS_CONTENT + * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED + * @see #FEATURE_HIDE_WHEN_FAST_SCROLL + */ + public DiagnosticWindow(@NonNull IDEEditor editor) { + super(editor); } - final var message = diagnostic.getMessage(); - this.text.setText(message); + /** + * Show the given diagnostic item. + * + * @param diagnostic The diagnostic item to show. + */ + public void showDiagnostic(@Nullable DiagnosticItem diagnostic) { + if (diagnostic == null) { + if (isShowing()) { + dismiss(); + } - displayWindow(); - } + return; + } + + final var message = diagnostic.getMessage(); + this.text.setText(message); + + displayWindow(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionMode.java b/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionMode.java index 5065aeba7..8b70c2f81 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionMode.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionMode.java @@ -19,14 +19,18 @@ package com.itsaky.androidide.views.editor; import android.annotation.SuppressLint; import android.view.Menu; import android.view.MenuItem; + import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; import androidx.appcompat.view.menu.MenuBuilder; + import com.itsaky.androidide.utils.Logger; + import io.github.rosemoe.sora.event.ClickEvent; import io.github.rosemoe.sora.event.SelectionChangeEvent; import io.github.rosemoe.sora.widget.component.EditorTextActionWindow; + import java.util.Objects; import java.util.Set; import java.util.TreeSet; @@ -38,160 +42,164 @@ import java.util.TreeSet; */ public class EditorTextActionMode implements IDEEditor.ITextActionPresenter { - private IDEEditor editor; - private ActionMode actionMode; - private boolean unsubscribeEvents = false; + private IDEEditor editor; + private ActionMode actionMode; + private boolean unsubscribeEvents = false; - private final Set registeredActions = new TreeSet<>(); + private final Set registeredActions = new TreeSet<>(); - private static final Logger LOG = Logger.instance("EditorTextActionMode"); + private static final Logger LOG = Logger.instance("EditorTextActionMode"); - @Override - public void bindEditor(@NonNull IDEEditor editor) { - Objects.requireNonNull(editor, "Cannot bind with null editor"); + @Override + public void bindEditor(@NonNull IDEEditor editor) { + Objects.requireNonNull(editor, "Cannot bind with null editor"); - this.editor = editor; + this.editor = editor; - // Disable default text action window - this.editor.getComponent(EditorTextActionWindow.class).setEnabled(false); + // Disable default text action window + this.editor.getComponent(EditorTextActionWindow.class).setEnabled(false); - this.editor.subscribeEvent( - SelectionChangeEvent.class, - ((event, unsubscribe) -> { - if (unsubscribeEvents) { - unsubscribe.unsubscribe(); - return; - } - - final var left = event.getLeft(); - final var right = event.getRight(); - if (left.index != right.index) { - showAction(); - } - })); - - // When click event is received, finish the action mode if it is started - this.editor.subscribeEvent( - ClickEvent.class, - (event, unsubscribe) -> { - if (unsubscribeEvents) { - unsubscribe.unsubscribe(); - return; - } - - exit(); - }); - - clearRegisteredActions(); - } - - @Override - public void registerAction(@NonNull IDEEditor.TextAction action) { - Objects.requireNonNull(this.editor, "No editor attached!"); - - this.registeredActions.add(action); - } - - @Override - public void destroy() { - - if (actionMode != null) { - exit(); - } - - this.registeredActions.clear(); - this.editor = null; - this.actionMode = null; - this.unsubscribeEvents = true; - } - - public void clearRegisteredActions() { - this.registeredActions.clear(); - } - - private void showAction() { - if (actionMode != null) { - return; - } - - actionMode = - ((AppCompatActivity) editor.getContext()) - .startSupportActionMode( - new ActionMode.Callback() { - - @SuppressLint("RestrictedApi") - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - if (editor == null) { - return false; + this.editor.subscribeEvent( + SelectionChangeEvent.class, + ((event, unsubscribe) -> { + if (unsubscribeEvents) { + unsubscribe.unsubscribe(); + return; } - if (menu instanceof MenuBuilder) { - MenuBuilder builder = (MenuBuilder) menu; - builder.setOptionalIconsVisible(true); + final var left = event.getLeft(); + final var right = event.getRight(); + if (left.index != right.index) { + showAction(); + } + })); + + // When click event is received, finish the action mode if it is started + this.editor.subscribeEvent( + ClickEvent.class, + (event, unsubscribe) -> { + if (unsubscribeEvents) { + unsubscribe.unsubscribe(); + return; } - mode.setTitle(android.R.string.selectTextMode); - - for (var action : registeredActions) { - if (editor.shouldShowTextAction(action.id)) { - menu.add(0, action.id, 0, action.titleId) - .setIcon(action.icon) - .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM); - } - } - - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return editor != null; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - - if (editor == null) { - return false; - } - - final var id = item.getItemId(); - final var optional = - registeredActions.stream().filter(a -> a.id == id).findFirst(); - - //noinspection SimplifyOptionalCallChains - if (!optional.isPresent()) { - return false; - } - - editor.performTextAction(optional.get()); - - if (id == IDEEditor.TextAction.SELECT_ALL - || id == IDEEditor.TextAction.EXPAND_SELECTION) { - return true; - } - - return exit(); - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - actionMode = null; - - if (editor != null) { - editor.setSelection(editor.getCursorRange().getStart()); - } - } + exit(); }); - } - public boolean exit() { - if (actionMode != null) { - actionMode.finish(); - actionMode = null; - return true; + clearRegisteredActions(); + } + + @Override + public void registerAction(@NonNull IDEEditor.TextAction action) { + Objects.requireNonNull(this.editor, "No editor attached!"); + + this.registeredActions.add(action); + } + + @Override + public void destroy() { + + if (actionMode != null) { + exit(); + } + + this.registeredActions.clear(); + this.editor = null; + this.actionMode = null; + this.unsubscribeEvents = true; + } + + public void clearRegisteredActions() { + this.registeredActions.clear(); + } + + private void showAction() { + if (actionMode != null) { + return; + } + + actionMode = + ((AppCompatActivity) editor.getContext()) + .startSupportActionMode( + new ActionMode.Callback() { + + @SuppressLint("RestrictedApi") + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + if (editor == null) { + return false; + } + + if (menu instanceof MenuBuilder) { + MenuBuilder builder = (MenuBuilder) menu; + builder.setOptionalIconsVisible(true); + } + + mode.setTitle(android.R.string.selectTextMode); + + for (var action : registeredActions) { + if (editor.shouldShowTextAction(action.id)) { + menu.add(0, action.id, 0, action.titleId) + .setIcon(action.icon) + .setShowAsActionFlags( + MenuItem.SHOW_AS_ACTION_IF_ROOM); + } + } + + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return editor != null; + } + + @Override + public boolean onActionItemClicked( + ActionMode mode, MenuItem item) { + + if (editor == null) { + return false; + } + + final var id = item.getItemId(); + final var optional = + registeredActions.stream() + .filter(a -> a.id == id) + .findFirst(); + + //noinspection SimplifyOptionalCallChains + if (!optional.isPresent()) { + return false; + } + + editor.performTextAction(optional.get()); + + if (id == IDEEditor.TextAction.SELECT_ALL + || id == IDEEditor.TextAction.EXPAND_SELECTION) { + return true; + } + + return exit(); + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + actionMode = null; + + if (editor != null) { + editor.setSelection(editor.getCursorRange().getStart()); + } + } + }); + } + + public boolean exit() { + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + return true; + } + return false; } - return false; - } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionWindow.java b/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionWindow.java index 5d8893183..ab1a499e2 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionWindow.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/EditorTextActionWindow.java @@ -21,20 +21,24 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.view.View; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.itsaky.androidide.R; import com.itsaky.androidide.adapters.TextActionItemAdapter; import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.managers.PreferenceManager; + 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.widget.CodeEditor; import io.github.rosemoe.sora.widget.EditorTouchEventHandler; import io.github.rosemoe.sora.widget.base.EditorPopupWindow; + import java.util.Objects; import java.util.Set; import java.util.TreeSet; @@ -47,262 +51,268 @@ import java.util.stream.Collectors; * @see io.github.rosemoe.sora.widget.component.EditorTextActionWindow */ public class EditorTextActionWindow extends EditorPopupWindow - implements IDEEditor.ITextActionPresenter { + implements IDEEditor.ITextActionPresenter { - private IDEEditor editor; - private RecyclerView actionsList; - private EditorTouchEventHandler touchHandler; + private IDEEditor editor; + private RecyclerView actionsList; + private EditorTouchEventHandler touchHandler; - private static final long DELAY = 200; - private long mLastScroll; - private int mLastPosition; - private boolean unsubscribeEvents = false; + private static final long DELAY = 200; + private long mLastScroll; + private int mLastPosition; + private boolean unsubscribeEvents = false; - private final Set registeredActions = new TreeSet<>(); + private final Set registeredActions = new TreeSet<>(); - /** - * Create a popup window for editor - * - * @param editor The editor - * @see #FEATURE_SCROLL_AS_CONTENT - * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED - * @see #FEATURE_HIDE_WHEN_FAST_SCROLL - */ - public EditorTextActionWindow(@NonNull CodeEditor editor) { - super(editor, FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED); + /** + * Create a popup window for editor + * + * @param editor The editor + * @see #FEATURE_SCROLL_AS_CONTENT + * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED + * @see #FEATURE_HIDE_WHEN_FAST_SCROLL + */ + public EditorTextActionWindow(@NonNull CodeEditor editor) { + super(editor, FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED); - getPopup().setAnimationStyle(R.style.PopupAnimation); - } - - @Override - public void bindEditor(@NonNull IDEEditor editor) { - Objects.requireNonNull(editor, "Cannot bind with null editor"); - - this.editor = editor; - this.touchHandler = editor.getEventHandler(); - this.actionsList = new RecyclerView(editor.getContext()); - this.actionsList.setLayoutParams( - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - this.actionsList.setBackground(createBackground()); - this.actionsList.setVerticalFadingEdgeEnabled(true); - this.actionsList.setFadingEdgeLength((int) (10 * editor.getDpUnit())); - this.actionsList.setClipToOutline(true); // prevent items from being drawn outside window - this.editor - .getComponent(io.github.rosemoe.sora.widget.component.EditorTextActionWindow.class) - .setEnabled(false); - - setContentView(this.actionsList); - subscribeToEvents(); - applyLayoutManager(); - - this.registeredActions.clear(); - } - - @Override - public void registerAction(@NonNull IDEEditor.TextAction action) { - Objects.requireNonNull(editor, "No editor attached!"); - - this.registeredActions.add(action); - } - - @Override - public void destroy() { - - if (isShowing()) { - dismiss(); + getPopup().setAnimationStyle(R.style.PopupAnimation); } - this.registeredActions.clear(); - this.editor = null; - this.actionsList = null; - this.unsubscribeEvents = true; - } + @Override + public void bindEditor(@NonNull IDEEditor editor) { + Objects.requireNonNull(editor, "Cannot bind with null editor"); - private void applyLayoutManager() { - final var manager = StudioApp.getInstance().getPrefManager(); - final var horizontal = manager.getBoolean(PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP, false); - if (horizontal) { - this.actionsList.setLayoutManager( - new LinearLayoutManager( - this.actionsList.getContext(), LinearLayoutManager.HORIZONTAL, false)); - } else { - this.actionsList.setLayoutManager(new LinearLayoutManager(this.actionsList.getContext())); - } - } + this.editor = editor; + this.touchHandler = editor.getEventHandler(); + this.actionsList = new RecyclerView(editor.getContext()); + this.actionsList.setLayoutParams( + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + this.actionsList.setBackground(createBackground()); + this.actionsList.setVerticalFadingEdgeEnabled(true); + this.actionsList.setFadingEdgeLength((int) (10 * editor.getDpUnit())); + this.actionsList.setClipToOutline(true); // prevent items from being drawn outside window + this.editor + .getComponent(io.github.rosemoe.sora.widget.component.EditorTextActionWindow.class) + .setEnabled(false); - @NonNull - private Drawable createBackground() { - final var drawable = new GradientDrawable(); - drawable.setShape(GradientDrawable.RECTANGLE); - drawable.setColor(ContextCompat.getColor(editor.getContext(), R.color.primaryLightColor)); - drawable.setCornerRadius(8 * editor.getDpUnit()); - return drawable; - } + setContentView(this.actionsList); + subscribeToEvents(); + applyLayoutManager(); - @Override - public void show() { - Objects.requireNonNull(editor, "No editor attached!"); - - if (actionsList.getParent() != null) { - ((ViewGroup) actionsList.getParent()).removeView(actionsList); + this.registeredActions.clear(); } - final var dp16 = editor.getDpUnit() * 16; - final var actions = - this.registeredActions.stream() - .filter(action -> this.editor.shouldShowTextAction(action.id)) - .collect(Collectors.toList()); - this.actionsList.setAdapter(new TextActionItemAdapter(actions, this::performTextAction)); - this.actionsList.measure( - View.MeasureSpec.makeMeasureSpec( - (int) (editor.getWidth() - dp16 * 2), // 16dp margins from start and end - View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec( - (int) - ((int) (260 * editor.getDpUnit()) - - dp16 * 2), // 260dp at most and 16dp margins from top and bottom - View.MeasureSpec.AT_MOST)); - setSize(this.actionsList.getMeasuredWidth(), this.actionsList.getMeasuredHeight()); + @Override + public void registerAction(@NonNull IDEEditor.TextAction action) { + Objects.requireNonNull(editor, "No editor attached!"); - super.show(); - } + this.registeredActions.add(action); + } - private void subscribeToEvents() { - this.editor.subscribeEvent( - SelectionChangeEvent.class, - (event, unsubscribe) -> { - if (unsubscribeEvents) { - unsubscribe.unsubscribe(); + @Override + public void destroy() { + + if (isShowing()) { + dismiss(); + } + + this.registeredActions.clear(); + this.editor = null; + this.actionsList = null; + this.unsubscribeEvents = true; + } + + private void applyLayoutManager() { + final var manager = StudioApp.getInstance().getPrefManager(); + final var horizontal = + manager.getBoolean(PreferenceManager.KEY_EDITOR_HORIZONTAL_POPUP, false); + if (horizontal) { + this.actionsList.setLayoutManager( + new LinearLayoutManager( + this.actionsList.getContext(), LinearLayoutManager.HORIZONTAL, false)); + } else { + this.actionsList.setLayoutManager( + new LinearLayoutManager(this.actionsList.getContext())); + } + } + + @NonNull + private Drawable createBackground() { + final var drawable = new GradientDrawable(); + drawable.setShape(GradientDrawable.RECTANGLE); + drawable.setColor(ContextCompat.getColor(editor.getContext(), R.color.primaryLightColor)); + drawable.setCornerRadius(8 * editor.getDpUnit()); + return drawable; + } + + @Override + public void show() { + Objects.requireNonNull(editor, "No editor attached!"); + + if (actionsList.getParent() != null) { + ((ViewGroup) actionsList.getParent()).removeView(actionsList); + } + + final var dp16 = editor.getDpUnit() * 16; + final var actions = + this.registeredActions.stream() + .filter(action -> this.editor.shouldShowTextAction(action.id)) + .collect(Collectors.toList()); + this.actionsList.setAdapter(new TextActionItemAdapter(actions, this::performTextAction)); + this.actionsList.measure( + View.MeasureSpec.makeMeasureSpec( + (int) (editor.getWidth() - dp16 * 2), // 16dp margins from start and end + View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec( + (int) + ((int) (260 * editor.getDpUnit()) + - dp16 + * 2), // 260dp at most and 16dp margins from top and + // bottom + View.MeasureSpec.AT_MOST)); + setSize(this.actionsList.getMeasuredWidth(), this.actionsList.getMeasuredHeight()); + + super.show(); + } + + private void subscribeToEvents() { + this.editor.subscribeEvent( + SelectionChangeEvent.class, + (event, unsubscribe) -> { + if (unsubscribeEvents) { + unsubscribe.unsubscribe(); + return; + } + + if (touchHandler.hasAnyHeldHandle()) { + return; + } + if (event.isSelected()) { + if (!isShowing()) { + this.editor.post(this::displayWindow); + } + mLastPosition = -1; + } else { + var show = false; + if (event.getCause() == SelectionChangeEvent.CAUSE_TAP + && event.getLeft().index == mLastPosition + && !isShowing() + && !this.editor.getText().isInBatchEdit()) { + this.editor.post(this::displayWindow); + show = true; + } else { + dismiss(); + } + if (event.getCause() == SelectionChangeEvent.CAUSE_TAP && !show) { + mLastPosition = event.getLeft().index; + } else { + mLastPosition = -1; + } + } + }); + this.editor.subscribeEvent( + ScrollEvent.class, + ((event, unsubscribe) -> { + if (unsubscribeEvents) { + unsubscribe.unsubscribe(); + return; + } + + var last = mLastScroll; + mLastScroll = System.currentTimeMillis(); + if (mLastScroll - last < DELAY) { + postDisplay(); + } + })); + this.editor.subscribeEvent( + HandleStateChangeEvent.class, + ((event, unsubscribe) -> { + if (unsubscribeEvents) { + unsubscribe.unsubscribe(); + return; + } + + if (event.isHeld()) { + postDisplay(); + } + })); + } + + private void performTextAction(@NonNull IDEEditor.TextAction action) { + this.editor.performTextAction(action); + if (action.id != IDEEditor.TextAction.SELECT_ALL + && action.id != IDEEditor.TextAction.EXPAND_SELECTION) { + dismiss(); + } + } + + private int selectTop(@NonNull RectF rect) { + var rowHeight = editor.getRowHeight(); + if (rect.top - rowHeight * 3 / 2F > getHeight()) { + return (int) (rect.top - rowHeight * 3 / 2 - getHeight()); + } else { + return (int) (rect.bottom + rowHeight / 2); + } + } + + public void displayWindow() { + int top; + var cursor = this.editor.getCursor(); + if (cursor.isSelected()) { + var leftRect = this.editor.getLeftHandleDescriptor().position; + var rightRect = this.editor.getRightHandleDescriptor().position; + var top1 = selectTop(leftRect); + var top2 = selectTop(rightRect); + top = Math.min(top1, top2); + } else { + top = selectTop(this.editor.getInsertHandleDescriptor().position); + } + top = Math.max(0, Math.min(top, this.editor.getHeight() - getHeight() - 5)); + float handleLeftX = + this.editor.getOffset( + this.editor.getCursor().getLeftLine(), + this.editor.getCursor().getLeftColumn()); + float handleRightX = + this.editor.getOffset( + this.editor.getCursor().getRightLine(), + this.editor.getCursor().getRightColumn()); + int panelX = (int) ((handleLeftX + handleRightX) / 2f); + setLocationAbsolutely(panelX, top); + show(); + } + + private void postDisplay() { + if (!isShowing()) { return; - } + } - if (touchHandler.hasAnyHeldHandle()) { + dismiss(); + + if (!this.editor.getCursor().isSelected()) { return; - } - if (event.isSelected()) { - if (!isShowing()) { - this.editor.post(this::displayWindow); - } - mLastPosition = -1; - } else { - var show = false; - if (event.getCause() == SelectionChangeEvent.CAUSE_TAP - && event.getLeft().index == mLastPosition - && !isShowing() - && !this.editor.getText().isInBatchEdit()) { - this.editor.post(this::displayWindow); - show = true; - } else { - dismiss(); - } - if (event.getCause() == SelectionChangeEvent.CAUSE_TAP && !show) { - mLastPosition = event.getLeft().index; - } else { - mLastPosition = -1; - } - } - }); - this.editor.subscribeEvent( - ScrollEvent.class, - ((event, unsubscribe) -> { - if (unsubscribeEvents) { - unsubscribe.unsubscribe(); - return; - } + } - var last = mLastScroll; - mLastScroll = System.currentTimeMillis(); - if (mLastScroll - last < DELAY) { - postDisplay(); - } - })); - this.editor.subscribeEvent( - HandleStateChangeEvent.class, - ((event, unsubscribe) -> { - if (unsubscribeEvents) { - unsubscribe.unsubscribe(); - return; - } + this.editor.postDelayed( + new Runnable() { + @Override + public void run() { + if (unsubscribeEvents || editor == null) { + if (isShowing()) { + dismiss(); + } + return; + } - if (event.isHeld()) { - postDisplay(); - } - })); - } - - private void performTextAction(@NonNull IDEEditor.TextAction action) { - this.editor.performTextAction(action); - if (action.id != IDEEditor.TextAction.SELECT_ALL - && action.id != IDEEditor.TextAction.EXPAND_SELECTION) { - dismiss(); + if (!touchHandler.hasAnyHeldHandle() + && System.currentTimeMillis() - mLastScroll > DELAY + && touchHandler.getScroller().isFinished()) { + displayWindow(); + } else { + editor.postDelayed(this, DELAY); + } + } + }, + DELAY); } - } - - private int selectTop(@NonNull RectF rect) { - var rowHeight = editor.getRowHeight(); - if (rect.top - rowHeight * 3 / 2F > getHeight()) { - return (int) (rect.top - rowHeight * 3 / 2 - getHeight()); - } else { - return (int) (rect.bottom + rowHeight / 2); - } - } - - public void displayWindow() { - int top; - var cursor = this.editor.getCursor(); - if (cursor.isSelected()) { - var leftRect = this.editor.getLeftHandleDescriptor().position; - var rightRect = this.editor.getRightHandleDescriptor().position; - var top1 = selectTop(leftRect); - var top2 = selectTop(rightRect); - top = Math.min(top1, top2); - } else { - top = selectTop(this.editor.getInsertHandleDescriptor().position); - } - top = Math.max(0, Math.min(top, this.editor.getHeight() - getHeight() - 5)); - float handleLeftX = - this.editor.getOffset( - this.editor.getCursor().getLeftLine(), this.editor.getCursor().getLeftColumn()); - float handleRightX = - this.editor.getOffset( - this.editor.getCursor().getRightLine(), this.editor.getCursor().getRightColumn()); - int panelX = (int) ((handleLeftX + handleRightX) / 2f); - setLocationAbsolutely(panelX, top); - show(); - } - - private void postDisplay() { - if (!isShowing()) { - return; - } - - dismiss(); - - if (!this.editor.getCursor().isSelected()) { - return; - } - - this.editor.postDelayed( - new Runnable() { - @Override - public void run() { - if (unsubscribeEvents || editor == null) { - if (isShowing()) { - dismiss(); - } - return; - } - - if (!touchHandler.hasAnyHeldHandle() - && System.currentTimeMillis() - mLastScroll > DELAY - && touchHandler.getScroller().isFinished()) { - displayWindow(); - } else { - editor.postDelayed(this, DELAY); - } - } - }, - DELAY); - } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java b/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java index 14ea0b8d8..37dc9f0ee 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java @@ -26,10 +26,12 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.inputmethod.EditorInfo; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; + import com.blankj.utilcode.util.ThreadUtils; import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; @@ -56,974 +58,1010 @@ import com.itsaky.lsp.models.ShowDocumentParams; import com.itsaky.lsp.models.SignatureHelp; import com.itsaky.lsp.models.SignatureHelpParams; import com.itsaky.toaster.Toaster; + import io.github.rosemoe.sora.event.ContentChangeEvent; import io.github.rosemoe.sora.event.SelectionChangeEvent; import io.github.rosemoe.sora.widget.CodeEditor; + +import org.jetbrains.annotations.Contract; + import java.io.File; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import org.jetbrains.annotations.Contract; public class IDEEditor extends CodeEditor { - private int mFileVersion; - private File file; + private int mFileVersion; + private File file; - private ILanguageServer mLanguageServer; - private IDELanguageClientImpl mLanguageClient; - private SignatureHelpWindow mSignatureHelpWindow; - private DiagnosticWindow mDiagnosticWindow; + private ILanguageServer mLanguageServer; + private IDELanguageClientImpl mLanguageClient; + private SignatureHelpWindow mSignatureHelpWindow; + private DiagnosticWindow mDiagnosticWindow; - private ITextActionPresenter mTextActionPresenter; + private ITextActionPresenter mTextActionPresenter; - public static final String KEY_FILE = "editor_file"; - private static final Logger LOG = Logger.instance("IDEEditor"); + public static final String KEY_FILE = "editor_file"; + private static final Logger LOG = Logger.instance("IDEEditor"); - public IDEEditor(Context context) { - this(context, null); - } - - public IDEEditor(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - setColorScheme(new SchemeAndroidIDE()); - setTextActionPresenter(chooseTextActionPresenter()); - subscribeEvent( - SelectionChangeEvent.class, (event, unsubscribe) -> handleSelectionChange(event)); - subscribeEvent(ContentChangeEvent.class, (event, unsubscribe) -> handleContentChange(event)); - - // default editor input type + no suggestions flag - setInputType( - EditorInfo.TYPE_CLASS_TEXT - | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE - | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS - | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - } - - @NonNull - @Contract(" -> new") - public ITextActionPresenter chooseTextActionPresenter() { - final var prefs = StudioApp.getInstance().getPrefManager(); - final var usePopup = prefs.getBoolean(PreferenceManager.KEY_EDITOR_USE_POPUP, false); - if (usePopup) { - return new EditorTextActionWindow(this); - } else { - return new EditorTextActionMode(); - } - } - - /** - * Set selection to the given range. - * - * @param range The range to select. - */ - public void setSelection(@NonNull Range range) { - setSelectionRegion( - range.getStart().getLine(), - range.getStart().getColumn(), - range.getEnd().getLine(), - range.getEnd().getColumn()); - } - - /** - * Set the selection of this editor to the given position. - * - * @param position The position to select. - */ - public void setSelection(@NonNull Position position) { - setSelection(position.getLine(), position.getColumn()); - } - - /** - * Set the file that this editor is currently editing. - * - * @param file The file to set. - */ - public void setFile(File file) { - this.file = file; - - if (file != null && mLanguageServer != null) { - final var text = getText().toString(); - final var event = new DocumentOpenEvent(file.toPath(), text, mFileVersion = 0); - mLanguageServer.getDocumentHandler().onFileOpened(event); + public IDEEditor(Context context) { + this(context, null); } - if (file != null) { - getExtraArguments().putString(KEY_FILE, file.getAbsolutePath()); - } else { - getExtraArguments().remove(KEY_FILE); - } - } - - /** - * Get the file that this editor is currently editing. - * - * @return The file instance. - */ - public File getFile() { - return file; - } - - /** - * Set the language server that this editor will connect with. If the language client is not set, - * it'll be set to {@link IDELanguageClientImpl}. - * - * @param server The server to set. Provide null to disable all the language server - * features. - */ - public void setLanguageServer(ILanguageServer server) { - this.mLanguageServer = server; - - if (mLanguageClient == null && IDELanguageClientImpl.isInitialized()) { - mLanguageClient = IDELanguageClientImpl.getInstance(); - } - } - - /** - * Append the given text at the end of the editor's content. - * - * @param text The text to append. - * @return The line at which the text was appended. - */ - public int append(CharSequence text) { - final var content = getText(); - if (getLineCount() <= 0) { - return 0; - } - final int line = getLineCount() - 1; - int col = content.getColumnCount(line); - if (col < 0) { - col = 0; - } - content.insert(line, col, text); - return line; - } - - /** Set the selection of the editor's cursor to the last line of the it's content. */ - public void goToEnd() { - final var line = getText().getLineCount() - 1; - setSelection(line, 0); - } - - /** Comment the line of the left cursor. */ - public void commentLine() { - if (getFile() == null) { - return; + public IDEEditor(Context context, AttributeSet attrs) { + this(context, attrs, 0); } - final var text = getText(); - final var name = getFile().getName(); - int line = getCursor().getLeftLine(); - if (name.endsWith(".java") || name.endsWith(".gradle")) { - while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { - if (!text.getLineString(line).trim().startsWith("//")) { - text.insert(line, 0, "//"); + public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + setColorScheme(new SchemeAndroidIDE()); + setTextActionPresenter(chooseTextActionPresenter()); + subscribeEvent( + SelectionChangeEvent.class, (event, unsubscribe) -> handleSelectionChange(event)); + subscribeEvent( + ContentChangeEvent.class, (event, unsubscribe) -> handleContentChange(event)); + + // default editor input type + no suggestions flag + setInputType( + EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE + | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS + | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + } + + @NonNull + @Contract(" -> new") + public ITextActionPresenter chooseTextActionPresenter() { + final var prefs = StudioApp.getInstance().getPrefManager(); + final var usePopup = prefs.getBoolean(PreferenceManager.KEY_EDITOR_USE_POPUP, false); + if (usePopup) { + return new EditorTextActionWindow(this); + } else { + return new EditorTextActionMode(); } - line++; - } - } else if (name.endsWith(".xml")) { - while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { - final String lineString = text.getLineString(line); - if (!lineString.trim().startsWith("")) { - text.replace( - line, 0, line, text.getColumnCount(line), "")); + } + + /** + * Set selection to the given range. + * + * @param range The range to select. + */ + public void setSelection(@NonNull Range range) { + setSelectionRegion( + range.getStart().getLine(), + range.getStart().getColumn(), + range.getEnd().getLine(), + range.getEnd().getColumn()); + } + + /** + * Set the selection of this editor to the given position. + * + * @param position The position to select. + */ + public void setSelection(@NonNull Position position) { + setSelection(position.getLine(), position.getColumn()); + } + + /** + * Set the file that this editor is currently editing. + * + * @param file The file to set. + */ + public void setFile(File file) { + this.file = file; + + if (file != null && mLanguageServer != null) { + final var text = getText().toString(); + final var event = new DocumentOpenEvent(file.toPath(), text, mFileVersion = 0); + mLanguageServer.getDocumentHandler().onFileOpened(event); } - line++; - } - } - } - /** Uncomment the current line */ - public void uncommentLine() { - if (getFile() == null) { - return; - } - - final var text = getText(); - final String name = getFile().getName(); - int line = getCursor().getLeftLine(); - if (name.endsWith(".java") || name.endsWith(".gradle")) { - while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { - String l = text.getLineString(line); - if (l.trim().startsWith("//")) { - int i = l.indexOf("//"); - text.delete(line, i, line, i + 2); + if (file != null) { + getExtraArguments().putString(KEY_FILE, file.getAbsolutePath()); + } else { + getExtraArguments().remove(KEY_FILE); } - line++; - } - } else if (name.endsWith(".xml")) { - final String commentStart = ""; - while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { - String l = text.getLineString(line); - if (l.trim().startsWith(commentStart)) { - int i = l.indexOf(commentStart); - text.delete(line, i, line, i + commentStart.length()); + } + + /** + * Get the file that this editor is currently editing. + * + * @return The file instance. + */ + public File getFile() { + return file; + } + + /** + * Set the language server that this editor will connect with. If the language client is not + * set, it'll be set to {@link IDELanguageClientImpl}. + * + * @param server The server to set. Provide null to disable all the language server + * features. + */ + public void setLanguageServer(ILanguageServer server) { + this.mLanguageServer = server; + + if (mLanguageClient == null && IDELanguageClientImpl.isInitialized()) { + mLanguageClient = IDELanguageClientImpl.getInstance(); } - if (l.trim().endsWith(commentEnd)) { - int count = text.getColumnCount(line); - text.delete(line, count - commentEnd.length(), line, count); + } + + /** + * Append the given text at the end of the editor's content. + * + * @param text The text to append. + * @return The line at which the text was appended. + */ + public int append(CharSequence text) { + final var content = getText(); + if (getLineCount() <= 0) { + return 0; } - line++; - } - } - } - - /** - * If any language server is set, asks the language server to find the definition of token at the - * cursor position. - * - *

If the server returns a valid response, and the file specified in the response is same the - * file in this editor, the range specified in the response will be selected. - */ - @SuppressWarnings("deprecation") - public void findDefinition() { - if (getFile() == null) { - return; + final int line = getLineCount() - 1; + int col = content.getColumnCount(line); + if (col < 0) { + col = 0; + } + content.insert(line, col, text); + return line; } - final ProgressDialog pd = - ProgressDialog.show( - getContext(), null, getContext().getString(R.string.msg_finding_definition)); - - try { - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> { - final var provider = mLanguageServer.getDefinitionProvider(); - final var params = - new com.itsaky.lsp.models.DefinitionParams( - getFile().toPath(), - new com.itsaky.lsp.models.Position( - getCursor().getLeftLine(), getCursor().getLeftColumn())); - - return provider.findDefinitions(params); - }); - - future.whenComplete( - (result, error) -> { - if (result == null - || mLanguageClient == null - || future.isCancelled() - || future.isCompletedExceptionally()) { - LOG.error("An error occurred while finding definition", error); - showDefinitionNotFound(pd); - return; - } - - final var locations = result.getLocations(); - if (locations.size() <= 0) { - LOG.error("No definitions found", "Size:", locations.size()); - showDefinitionNotFound(pd); - return; - } - - ThreadUtils.runOnUiThread( - () -> { - if (locations.size() == 1) { - var location = locations.get(0); - if (location.getFile().equals(getFile().toPath())) { - setSelection(location.getRange()); - return; - } - mLanguageClient.showDocument( - new ShowDocumentParams(location.getFile(), location.getRange())); - } else { - mLanguageClient.showLocations(locations); - } - }); - - dismissOnUiThread(pd); - }); - } catch (Throwable th) { - LOG.error("An error occurred while finding definition", th); - showDefinitionNotFound(pd); - } - } - - /** - * Notify the user that no definitions can be found. - * - * @param pd The {@link ProgressDialog} that was shown when requesting the definitions. - */ - @SuppressWarnings("deprecation") - private void showDefinitionNotFound(final ProgressDialog pd) { - ThreadUtils.runOnUiThread( - () -> { - StudioApp.getInstance().toast(R.string.msg_no_definition, Toaster.Type.ERROR); - pd.dismiss(); - }); - } - - /** - * If any language server instance is set, finds the references to of the token at the current - * cursor position. - * - *

If the server returns a valid response, that response is forwarded to the {@link - * IDELanguageClientImpl}. - */ - public void findReferences() { - if (getFile() == null) { - return; + /** Set the selection of the editor's cursor to the last line of the it's content. */ + public void goToEnd() { + final var line = getText().getLineCount() - 1; + setSelection(line, 0); } - @SuppressWarnings("deprecation") - final ProgressDialog pd = - ProgressDialog.show( - getContext(), null, getContext().getString(R.string.msg_finding_references)); - - try { - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> { - final var provider = mLanguageServer.getReferenceProvider(); - final var referenceParams = - new ReferenceParams( - getFile().toPath(), - new com.itsaky.lsp.models.Position( - getCursor().getLeftLine(), getCursor().getLeftColumn()), - true); - return provider.findReferences(referenceParams); - }); - - future.whenComplete( - (result, error) -> { - if (result == null - || mLanguageClient == null - || future.isCancelled() - || future.isCompletedExceptionally()) { - LOG.error("An error occurred while finding references", error); - showReferencesNotFound(pd); - return; - } - - if (result.getLocations().isEmpty()) { - showReferencesNotFound(pd); - return; - } else { - if (result.getLocations().size() == 1) { - final var loc = result.getLocations().get(0); - if (loc.getFile().equals(getFile().toPath())) { - setSelection(loc.getRange()); - return; - } - } - - ThreadUtils.runOnUiThread(() -> mLanguageClient.showLocations(result.getLocations())); - } - - dismissOnUiThread(pd); - }); - } catch (Throwable th) { - LOG.error("An error occurred while finding references", th); - showReferencesNotFound(pd); - } - } - - /** - * Notify the user that no references were found for the selected token. - * - * @param pd The {@link ProgressDialog} that was shown when requesting references. - */ - @SuppressWarnings("deprecation") - private void showReferencesNotFound(final ProgressDialog pd) { - ThreadUtils.runOnUiThread( - () -> { - StudioApp.getInstance().toast(R.string.msg_no_references, Toaster.Type.ERROR); - pd.dismiss(); - }); - } - - /** - * If any language server is set, requests signature help at the cursor's position. On a valid - * response, shows the signature help in a popup window. - */ - @SuppressWarnings("unused") // accessed using reflection in CompletionItem class - public void signatureHelp() { - if (mLanguageServer != null && getFile() != null) { - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> { - final var provider = mLanguageServer.getSignatureHelpProvider(); - return provider.provideSignatures( - new SignatureHelpParams( - getFile().toPath(), - new com.itsaky.lsp.models.Position( - getCursor().getLeftLine(), getCursor().getLeftColumn()))); - }); - - future.whenComplete( - (help, error) -> { - if (help == null - || mLanguageClient == null - || future.isCancelled() - || future.isCompletedExceptionally()) { - LOG.error("An error occurred while finding signature help", error); - return; - } - - ThreadUtils.runOnUiThread(() -> showSignatureHelp(help)); - }); - } - } - - /** - * Shows the given signature help in the editor. - * - * @param help The signature help data to show. - */ - public void showSignatureHelp(SignatureHelp help) { - getSignatureHelpWindow().setupAndDisplay(help); - } - - /** - * Dismisses the given dialog on the UI thread. - * - * @param dialog The dialog to dismiss. - */ - private void dismissOnUiThread(@NonNull final Dialog dialog) { - ThreadUtils.runOnUiThread(dialog::dismiss); - } - - /** If any language server is set, notify the server that the file in this editor was saved. */ - public void didSave() { - if (mLanguageServer != null && getFile() != null) { - mLanguageServer.getDocumentHandler().onFileSaved(new DocumentSaveEvent(getFile().toPath())); - } - } - - /** Notify the language server that the file in this editor is about to be closed. */ - public void close() { - if (mLanguageServer != null && getFile() != null) { - mLanguageServer.getDocumentHandler().onFileClosed(new DocumentCloseEvent(getFile().toPath())); - LOG.info("'textDocument/didClose' was sent to the language server."); - } else { - LOG.info("No language server is available for this file"); - } - } - - /** - * Request code actions from server at the given position containing given diagnostics - * - * @return A {@link CompletableFuture}. May return {@code null}. - */ - public CompletableFuture codeActions() { - // TODO Implement this properly - return CompletableFuture.completedFuture(new CodeActionResult()); - } - - /** - * Requests code actions for the given diagnostics to the language server. - * - * @param diagnostics The diagnostics to request code actions for. - * @return The {@link CodeActionResult} from the server. - */ - public CompletableFuture codeActions(List diagnostics) { - if (mLanguageServer == null || mLanguageClient == null) { - return null; - } - - return CompletableFuture.supplyAsync( - () -> - mLanguageServer - .getCodeActionProvider() - .codeActions( - new CodeActionParams(getFile().toPath(), getCursorRange(), diagnostics))); - } - - /** - * Requests the language server to provided a semantically larger selection than the current - * selection. If a valid response is received, that range will be selected. - */ - public void expandSelection() { - if (mLanguageServer == null || getFile() == null) { - LOG.error("Cannot expand selection. Language server or file is null"); - return; - } - - //noinspection deprecation - final var pd = - ProgressDialog.show( - getContext(), null, getContext().getString(R.string.please_wait), true, false); - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> - mLanguageServer - .getSelectionProvider() - .expandSelection( - new ExpandSelectionParams(getFile().toPath(), getCursorRange()))); - - future.whenComplete( - ((range, throwable) -> { - pd.dismiss(); - - if (throwable != null) { - LOG.error("Error computing expanded selection range", throwable); + /** Comment the line of the left cursor. */ + public void commentLine() { + if (getFile() == null) { return; - } + } - ThreadUtils.runOnUiThread(() -> setSelection(range)); - })); - } - - /** - * Get the cursor's position in the form of {@link Position}. - * - * @return The {@link Position} of the cursor. - */ - @SuppressWarnings("unused") - public Position getCursorAsLSPPosition() { - return new Position(getCursor().getLeftLine(), getCursor().getLeftColumn()); - } - - /** - * Get the cursor's selection range in the form of {@link Range}. - * - * @return The {@link Range} of the cursor. - */ - public Range getCursorRange() { - final var cursor = getCursor(); - final var start = new Position(cursor.getLeftLine(), cursor.getLeftColumn()); - final var end = new Position(cursor.getRightLine(), cursor.getRightColumn()); - return new Range(start, end); - } - - /** - * Validates the range if it is invalid and returns a valid range. - * - * @param range Th range to validate. - * @return A new, validated range. - */ - public Range validateRange(@NonNull final Range range) { - final var start = range.getStart(); - final var end = range.getEnd(); - - if (start.getLine() < 0) { - start.setLine(0); - } else if (start.getLine() >= getText().getLineCount()) { - start.setLine(getText().getLineCount() - 1); + final var text = getText(); + final var name = getFile().getName(); + int line = getCursor().getLeftLine(); + if (name.endsWith(".java") || name.endsWith(".gradle")) { + while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { + if (!text.getLineString(line).trim().startsWith("//")) { + text.insert(line, 0, "//"); + } + line++; + } + } else if (name.endsWith(".xml")) { + while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { + final String lineString = text.getLineString(line); + if (!lineString.trim().startsWith("")) { + text.replace( + line, + 0, + line, + text.getColumnCount(line), + "")); + } + line++; + } + } } - if (end.getLine() < 0) { - end.setLine(0); - } else if (end.getLine() >= getText().getLineCount()) { - end.setLine(getText().getLineCount() - 1); + /** Uncomment the current line */ + public void uncommentLine() { + if (getFile() == null) { + return; + } + + final var text = getText(); + final String name = getFile().getName(); + int line = getCursor().getLeftLine(); + if (name.endsWith(".java") || name.endsWith(".gradle")) { + while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { + String l = text.getLineString(line); + if (l.trim().startsWith("//")) { + int i = l.indexOf("//"); + text.delete(line, i, line, i + 2); + } + line++; + } + } else if (name.endsWith(".xml")) { + final String commentStart = ""; + while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { + String l = text.getLineString(line); + if (l.trim().startsWith(commentStart)) { + int i = l.indexOf(commentStart); + text.delete(line, i, line, i + commentStart.length()); + } + if (l.trim().endsWith(commentEnd)) { + int count = text.getColumnCount(line); + text.delete(line, count - commentEnd.length(), line, count); + } + line++; + } + } } - if (end.getLine() < start.getLine()) { - var l = end.getLine(); - var l2 = start.getLine(); - start.setLine(l); - end.setLine(l2); + /** + * If any language server is set, asks the language server to find the definition of token at + * the cursor position. + * + *

If the server returns a valid response, and the file specified in the response is same the + * file in this editor, the range specified in the response will be selected. + */ + @SuppressWarnings("deprecation") + public void findDefinition() { + if (getFile() == null) { + return; + } + + final ProgressDialog pd = + ProgressDialog.show( + getContext(), + null, + getContext().getString(R.string.msg_finding_definition)); + + try { + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> { + final var provider = mLanguageServer.getDefinitionProvider(); + final var params = + new com.itsaky.lsp.models.DefinitionParams( + getFile().toPath(), + new com.itsaky.lsp.models.Position( + getCursor().getLeftLine(), + getCursor().getLeftColumn())); + + return provider.findDefinitions(params); + }); + + future.whenComplete( + (result, error) -> { + if (result == null + || mLanguageClient == null + || future.isCancelled() + || future.isCompletedExceptionally()) { + LOG.error("An error occurred while finding definition", error); + showDefinitionNotFound(pd); + return; + } + + final var locations = result.getLocations(); + if (locations.size() <= 0) { + LOG.error("No definitions found", "Size:", locations.size()); + showDefinitionNotFound(pd); + return; + } + + ThreadUtils.runOnUiThread( + () -> { + if (locations.size() == 1) { + var location = locations.get(0); + if (location.getFile().equals(getFile().toPath())) { + setSelection(location.getRange()); + return; + } + mLanguageClient.showDocument( + new ShowDocumentParams( + location.getFile(), location.getRange())); + } else { + mLanguageClient.showLocations(locations); + } + }); + + dismissOnUiThread(pd); + }); + } catch (Throwable th) { + LOG.error("An error occurred while finding definition", th); + showDefinitionNotFound(pd); + } } - if (start.getColumn() < 0) { - start.setColumn(0); - } else if (start.getColumn() >= getText().getColumnCount(start.getLine())) { - start.setColumn(getText().getColumnCount(start.getLine()) - 1); - } - - if (end.getColumn() < 0) { - end.setColumn(0); - } else if (end.getColumn() >= getText().getColumnCount(end.getLine())) { - end.setColumn(getText().getColumnCount(end.getLine()) - 1); - } - - if (end.getColumn() < start.getColumn()) { - final var c = start.getColumn(); - final var c2 = end.getColumn(); - start.setColumn(c2); - end.setColumn(c); - } - - return new Range(start, end); - } - - /** - * Set the text action presenter of this editor. - * - * @param actionPresenter The presenter to set. Must not be null. - */ - public void setTextActionPresenter(@NonNull ITextActionPresenter actionPresenter) { - Objects.requireNonNull(actionPresenter, "Cannot set text action presenter to null"); - - if (mTextActionPresenter != null) { - mTextActionPresenter.destroy(); - mTextActionPresenter = null; - } - - this.mTextActionPresenter = actionPresenter; - - actionPresenter.bindEditor(this); - registerActionsTo(actionPresenter); - } - - /** - * Get the text action presenter attached with this editor. - * - * @return The attached text action presenter. - */ - @SuppressWarnings("unused") - public ITextActionPresenter getTextActionPresenter() { - return mTextActionPresenter; - } - - /** - * Register the editor's actions to the given action presenter. - * - * @param actionPresenter The action presenter to register actions to. - */ - public void registerActionsTo(@NonNull ITextActionPresenter actionPresenter) { - Objects.requireNonNull( - actionPresenter, "Cannot register actions to null text action presenter"); - - var index = -1; - - TypedArray array = - getContext() - .getTheme() - .obtainStyledAttributes( - new int[] { - android.R.attr.actionModeSelectAllDrawable, - android.R.attr.actionModeCutDrawable, - android.R.attr.actionModeCopyDrawable, - android.R.attr.actionModePasteDrawable, + /** + * Notify the user that no definitions can be found. + * + * @param pd The {@link ProgressDialog} that was shown when requesting the definitions. + */ + @SuppressWarnings("deprecation") + private void showDefinitionNotFound(final ProgressDialog pd) { + ThreadUtils.runOnUiThread( + () -> { + StudioApp.getInstance().toast(R.string.msg_no_definition, Toaster.Type.ERROR); + pd.dismiss(); }); - - actionPresenter.registerAction( - new TextAction( - createTextActionDrawable(R.drawable.ic_expand_selection), - R.string.action_expand_selection, - TextAction.EXPAND_SELECTION, - index++)); - - actionPresenter.registerAction( - new TextAction( - array.getDrawable(0), android.R.string.selectAll, TextAction.SELECT_ALL, index++)); - - actionPresenter.registerAction( - new TextAction(array.getDrawable(1), android.R.string.cut, TextAction.CUT, index++)); - - actionPresenter.registerAction( - new TextAction(array.getDrawable(2), android.R.string.copy, TextAction.COPY, index++)); - - actionPresenter.registerAction( - new TextAction(array.getDrawable(3), android.R.string.paste, TextAction.PASTE, index++)); - - actionPresenter.registerAction( - new TextAction( - createTextActionDrawable(R.drawable.ic_goto_definition), - R.string.menu_navigate_definition, - TextAction.GOTO_DEFINITION, - index++)); - - actionPresenter.registerAction( - new TextAction( - createTextActionDrawable(R.drawable.ic_find_references), - R.string.menu_navigate_references, - TextAction.FIND_REFERENCES, - index++)); - actionPresenter.registerAction( - new TextAction( - createTextActionDrawable(R.drawable.ic_comment_line), - R.string.menu_comment_line, - TextAction.COMMENT_LINE, - index++)); - - //noinspection UnusedAssignment - actionPresenter.registerAction( - new TextAction( - createTextActionDrawable(R.drawable.ic_uncomment_line), - R.string.menu_uncomment_line, - TextAction.UNCOMMENT_LINE, - index++)); - - array.recycle(); - } - - /** - * Called by text action presenters to check if the action with the given ID should be shown or - * not. - * - * @param actionId The action ID to check. - * @return true if the action should be shown, false otherwise. - */ - public boolean shouldShowTextAction(int actionId) { - final var capabilities = mLanguageServer != null ? mLanguageServer.getCapabilities() : null; - final var notNull = capabilities != null; - final var expand = notNull && capabilities.getSmartSelectionsEnabled(); - final var definitions = notNull && capabilities.getDefinitionsAvailable(); - final var references = notNull && capabilities.getReferencesAvailable(); - final var commentUncomment = - getFile() != null - && (getFile().getName().endsWith(".java") - || getFile().getName().endsWith(".gradle") - || getFile().getName().endsWith(".xml")); - switch (actionId) { - case TextAction.CUT: - case TextAction.PASTE: - return isEditable(); - case TextAction.GOTO_DEFINITION: - return definitions; - case TextAction.EXPAND_SELECTION: - return expand; - case TextAction.FIND_REFERENCES: - return references; - case TextAction.COMMENT_LINE: - case TextAction.UNCOMMENT_LINE: - return commentUncomment; } - return true; - } - - /** - * Performs the given text action in this editor. - * - * @param action The action to perform. - */ - public void performTextAction(@NonNull TextAction action) { - Objects.requireNonNull(action, "Cannot perform null text action"); - - switch (action.id) { - case TextAction.SELECT_ALL: - selectAll(); - break; - case TextAction.CUT: - cutText(); - break; - case TextAction.COPY: - copyText(); - break; - case TextAction.PASTE: - pasteText(); - break; - case TextAction.GOTO_DEFINITION: - findDefinition(); - break; - case TextAction.FIND_REFERENCES: - findReferences(); - break; - case TextAction.COMMENT_LINE: - commentLine(); - break; - case TextAction.UNCOMMENT_LINE: - uncommentLine(); - break; - case TextAction.EXPAND_SELECTION: - expandSelection(); - break; - } - } - - private Drawable createTextActionDrawable(int icon) { - final Drawable d = ContextCompat.getDrawable(getContext(), icon); - if (d != null) { - d.setColorFilter( - ContextCompat.getColor(getContext(), secondaryColor), PorterDuff.Mode.SRC_ATOP); - } - return d; - } - - private void handleSelectionChange(SelectionChangeEvent event) { - if (event.isSelected() || !(getEditorLanguage() instanceof IDELanguage)) { - // do not show diagnostics when text is selected - // or if we cannot get diagnostics - return; - } - - final var diagnostics = ((IDELanguage) getEditorLanguage()).getDiagnostics(); - final var line = event.getLeft().line; - final var column = event.getLeft().column; - - // diagnostics are expected to be sorted, so, we do a binary search - getDiagnosticWindow().showDiagnostic(binarySearchDiagnostic(diagnostics, line, column)); - } - - @Nullable - private DiagnosticItem binarySearchDiagnostic( - @NonNull List diagnostics, int line, int column) { - - if (diagnostics.isEmpty()) { - return null; - } - - final var pos = new Position(line, column); - int left = 0; - int right = diagnostics.size() - 1; - int mid; - while (left < right) { - mid = (left + right) / 2; - var d = diagnostics.get(mid); - var r = d.getRange(); - var c = r.containsForBinarySearch(pos); - if (c < 0) { - right = mid - 1; - } else if (c > 0) { - left = mid + 1; - } else { - return d; - } - } - - return null; - } - - /** - * Notify the language server that the content of this file has been changed. - * - * @param event The content change event. - */ - private void handleContentChange(ContentChangeEvent event) { - - if (getFile() == null) { - return; - } - - StudioApp.getInstance() - .getJavaLanguageServer() - .getDocumentHandler() - .onContentChange(new DocumentChangeEvent(getFile().toPath(), getText(), mFileVersion + 1)); - - checkForSignatureHelp(event); - } - - /** - * Checks if the content change event should trigger signature help. Signature help trigger - * characters are : - * - *

    - *
  • '(' (parentheses) - *
  • ',' (comma) - *
- * - * @param event The content change event. - */ - private void checkForSignatureHelp(@NonNull ContentChangeEvent event) { - if (event.getAction() != ContentChangeEvent.ACTION_INSERT - || event.getChangedText().length() != 1) { - return; - } - - final var ch = event.getChangedText().charAt(0); - if (ch == '(' || ch == ',') { - signatureHelp(); - } - } - - private SignatureHelpWindow getSignatureHelpWindow() { - if (mSignatureHelpWindow == null) { - mSignatureHelpWindow = new SignatureHelpWindow(this); - } - - return mSignatureHelpWindow; - } - - private DiagnosticWindow getDiagnosticWindow() { - if (mDiagnosticWindow == null) { - mDiagnosticWindow = new DiagnosticWindow(this); - } - - return mDiagnosticWindow; - } - - /** - * A text action presenter presents text actions (cut, copy, paste, etc).
- * - *

The presenter handles its visibility itself. - * - * @author Akash Yadav - */ - public interface ITextActionPresenter { - /** - * Bind the action presenter with the given editor instance. + * If any language server instance is set, finds the references to of the token at the current + * cursor position. * - * @param editor The editor to bind with. + *

If the server returns a valid response, that response is forwarded to the {@link + * IDELanguageClientImpl}. */ - void bindEditor(@NonNull IDEEditor editor); + public void findReferences() { + if (getFile() == null) { + return; + } - /** - * Register the text action with this presenter. - * - * @param action The action to register. - */ - void registerAction(@NonNull TextAction action); + @SuppressWarnings("deprecation") + final ProgressDialog pd = + ProgressDialog.show( + getContext(), + null, + getContext().getString(R.string.msg_finding_references)); - /** - * Destroy this action presenter. The presenter should unsubscribe from any subscribed events - * and release any held resources. - */ - void destroy(); - } + try { + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> { + final var provider = mLanguageServer.getReferenceProvider(); + final var referenceParams = + new ReferenceParams( + getFile().toPath(), + new com.itsaky.lsp.models.Position( + getCursor().getLeftLine(), + getCursor().getLeftColumn()), + true); + return provider.findReferences(referenceParams); + }); - /** - * A model class for text actions. - * - * @author Akash Yadav - */ - public static class TextAction implements Comparable { + future.whenComplete( + (result, error) -> { + if (result == null + || mLanguageClient == null + || future.isCancelled() + || future.isCompletedExceptionally()) { + LOG.error("An error occurred while finding references", error); + showReferencesNotFound(pd); + return; + } - public static final int EXPAND_SELECTION = 4; - public static final int GOTO_DEFINITION = 5; - public static final int FIND_REFERENCES = 6; - public static final int COMMENT_LINE = 7; - public static final int UNCOMMENT_LINE = 8; + if (result.getLocations().isEmpty()) { + showReferencesNotFound(pd); + return; + } else { + if (result.getLocations().size() == 1) { + final var loc = result.getLocations().get(0); + if (loc.getFile().equals(getFile().toPath())) { + setSelection(loc.getRange()); + return; + } + } - // common action IDs - public static final int PASTE = 3; - public static final int COPY = 2; - public static final int CUT = 1; - public static final int SELECT_ALL = 0; + ThreadUtils.runOnUiThread( + () -> mLanguageClient.showLocations(result.getLocations())); + } - /** The drawable resource id for this text action. */ - public Drawable icon; - - /** The string resource id for this text action. */ - @StringRes public int titleId; - - /** The ID of this text action; */ - public final int id; - - /** The index at which this action should be placed. */ - public final int index; - - public TextAction(Drawable icon, int titleId, int id, int index) { - this.icon = icon; - this.titleId = titleId; - this.id = id; - this.index = index; + dismissOnUiThread(pd); + }); + } catch (Throwable th) { + LOG.error("An error occurred while finding references", th); + showReferencesNotFound(pd); + } } - @Override - public boolean equals(Object o) { - if (this == o) { + /** + * Notify the user that no references were found for the selected token. + * + * @param pd The {@link ProgressDialog} that was shown when requesting references. + */ + @SuppressWarnings("deprecation") + private void showReferencesNotFound(final ProgressDialog pd) { + ThreadUtils.runOnUiThread( + () -> { + StudioApp.getInstance().toast(R.string.msg_no_references, Toaster.Type.ERROR); + pd.dismiss(); + }); + } + + /** + * If any language server is set, requests signature help at the cursor's position. On a valid + * response, shows the signature help in a popup window. + */ + @SuppressWarnings("unused") // accessed using reflection in CompletionItem class + public void signatureHelp() { + if (mLanguageServer != null && getFile() != null) { + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> { + final var provider = mLanguageServer.getSignatureHelpProvider(); + return provider.provideSignatures( + new SignatureHelpParams( + getFile().toPath(), + new com.itsaky.lsp.models.Position( + getCursor().getLeftLine(), + getCursor().getLeftColumn()))); + }); + + future.whenComplete( + (help, error) -> { + if (help == null + || mLanguageClient == null + || future.isCancelled() + || future.isCompletedExceptionally()) { + LOG.error("An error occurred while finding signature help", error); + return; + } + + ThreadUtils.runOnUiThread(() -> showSignatureHelp(help)); + }); + } + } + + /** + * Shows the given signature help in the editor. + * + * @param help The signature help data to show. + */ + public void showSignatureHelp(SignatureHelp help) { + getSignatureHelpWindow().setupAndDisplay(help); + } + + /** + * Dismisses the given dialog on the UI thread. + * + * @param dialog The dialog to dismiss. + */ + private void dismissOnUiThread(@NonNull final Dialog dialog) { + ThreadUtils.runOnUiThread(dialog::dismiss); + } + + /** If any language server is set, notify the server that the file in this editor was saved. */ + public void didSave() { + if (mLanguageServer != null && getFile() != null) { + mLanguageServer + .getDocumentHandler() + .onFileSaved(new DocumentSaveEvent(getFile().toPath())); + } + } + + /** Notify the language server that the file in this editor is about to be closed. */ + public void close() { + if (mLanguageServer != null && getFile() != null) { + mLanguageServer + .getDocumentHandler() + .onFileClosed(new DocumentCloseEvent(getFile().toPath())); + LOG.info("'textDocument/didClose' was sent to the language server."); + } else { + LOG.info("No language server is available for this file"); + } + } + + /** + * Request code actions from server at the given position containing given diagnostics + * + * @return A {@link CompletableFuture}. May return {@code null}. + */ + public CompletableFuture codeActions() { + // TODO Implement this properly + return CompletableFuture.completedFuture(new CodeActionResult()); + } + + /** + * Requests code actions for the given diagnostics to the language server. + * + * @param diagnostics The diagnostics to request code actions for. + * @return The {@link CodeActionResult} from the server. + */ + public CompletableFuture codeActions(List diagnostics) { + if (mLanguageServer == null || mLanguageClient == null) { + return null; + } + + return CompletableFuture.supplyAsync( + () -> + mLanguageServer + .getCodeActionProvider() + .codeActions( + new CodeActionParams( + getFile().toPath(), + getCursorRange(), + diagnostics))); + } + + /** + * Requests the language server to provided a semantically larger selection than the current + * selection. If a valid response is received, that range will be selected. + */ + public void expandSelection() { + if (mLanguageServer == null || getFile() == null) { + LOG.error("Cannot expand selection. Language server or file is null"); + return; + } + + //noinspection deprecation + final var pd = + ProgressDialog.show( + getContext(), + null, + getContext().getString(R.string.please_wait), + true, + false); + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> + mLanguageServer + .getSelectionProvider() + .expandSelection( + new ExpandSelectionParams( + getFile().toPath(), getCursorRange()))); + + future.whenComplete( + ((range, throwable) -> { + pd.dismiss(); + + if (throwable != null) { + LOG.error("Error computing expanded selection range", throwable); + return; + } + + ThreadUtils.runOnUiThread(() -> setSelection(range)); + })); + } + + /** + * Get the cursor's position in the form of {@link Position}. + * + * @return The {@link Position} of the cursor. + */ + @SuppressWarnings("unused") + public Position getCursorAsLSPPosition() { + return new Position(getCursor().getLeftLine(), getCursor().getLeftColumn()); + } + + /** + * Get the cursor's selection range in the form of {@link Range}. + * + * @return The {@link Range} of the cursor. + */ + public Range getCursorRange() { + final var cursor = getCursor(); + final var start = new Position(cursor.getLeftLine(), cursor.getLeftColumn()); + final var end = new Position(cursor.getRightLine(), cursor.getRightColumn()); + return new Range(start, end); + } + + /** + * Validates the range if it is invalid and returns a valid range. + * + * @param range Th range to validate. + * @return A new, validated range. + */ + public Range validateRange(@NonNull final Range range) { + final var start = range.getStart(); + final var end = range.getEnd(); + + if (start.getLine() < 0) { + start.setLine(0); + } else if (start.getLine() >= getText().getLineCount()) { + start.setLine(getText().getLineCount() - 1); + } + + if (end.getLine() < 0) { + end.setLine(0); + } else if (end.getLine() >= getText().getLineCount()) { + end.setLine(getText().getLineCount() - 1); + } + + if (end.getLine() < start.getLine()) { + var l = end.getLine(); + var l2 = start.getLine(); + start.setLine(l); + end.setLine(l2); + } + + if (start.getColumn() < 0) { + start.setColumn(0); + } else if (start.getColumn() >= getText().getColumnCount(start.getLine())) { + start.setColumn(getText().getColumnCount(start.getLine()) - 1); + } + + if (end.getColumn() < 0) { + end.setColumn(0); + } else if (end.getColumn() >= getText().getColumnCount(end.getLine())) { + end.setColumn(getText().getColumnCount(end.getLine()) - 1); + } + + if (end.getColumn() < start.getColumn()) { + final var c = start.getColumn(); + final var c2 = end.getColumn(); + start.setColumn(c2); + end.setColumn(c); + } + + return new Range(start, end); + } + + /** + * Set the text action presenter of this editor. + * + * @param actionPresenter The presenter to set. Must not be null. + */ + public void setTextActionPresenter(@NonNull ITextActionPresenter actionPresenter) { + Objects.requireNonNull(actionPresenter, "Cannot set text action presenter to null"); + + if (mTextActionPresenter != null) { + mTextActionPresenter.destroy(); + mTextActionPresenter = null; + } + + this.mTextActionPresenter = actionPresenter; + + actionPresenter.bindEditor(this); + registerActionsTo(actionPresenter); + } + + /** + * Get the text action presenter attached with this editor. + * + * @return The attached text action presenter. + */ + @SuppressWarnings("unused") + public ITextActionPresenter getTextActionPresenter() { + return mTextActionPresenter; + } + + /** + * Register the editor's actions to the given action presenter. + * + * @param actionPresenter The action presenter to register actions to. + */ + public void registerActionsTo(@NonNull ITextActionPresenter actionPresenter) { + Objects.requireNonNull( + actionPresenter, "Cannot register actions to null text action presenter"); + + var index = -1; + + TypedArray array = + getContext() + .getTheme() + .obtainStyledAttributes( + new int[] { + android.R.attr.actionModeSelectAllDrawable, + android.R.attr.actionModeCutDrawable, + android.R.attr.actionModeCopyDrawable, + android.R.attr.actionModePasteDrawable, + }); + + actionPresenter.registerAction( + new TextAction( + createTextActionDrawable(R.drawable.ic_expand_selection), + R.string.action_expand_selection, + TextAction.EXPAND_SELECTION, + index++)); + + actionPresenter.registerAction( + new TextAction( + array.getDrawable(0), + android.R.string.selectAll, + TextAction.SELECT_ALL, + index++)); + + actionPresenter.registerAction( + new TextAction( + array.getDrawable(1), android.R.string.cut, TextAction.CUT, index++)); + + actionPresenter.registerAction( + new TextAction( + array.getDrawable(2), android.R.string.copy, TextAction.COPY, index++)); + + actionPresenter.registerAction( + new TextAction( + array.getDrawable(3), android.R.string.paste, TextAction.PASTE, index++)); + + actionPresenter.registerAction( + new TextAction( + createTextActionDrawable(R.drawable.ic_goto_definition), + R.string.menu_navigate_definition, + TextAction.GOTO_DEFINITION, + index++)); + + actionPresenter.registerAction( + new TextAction( + createTextActionDrawable(R.drawable.ic_find_references), + R.string.menu_navigate_references, + TextAction.FIND_REFERENCES, + index++)); + actionPresenter.registerAction( + new TextAction( + createTextActionDrawable(R.drawable.ic_comment_line), + R.string.menu_comment_line, + TextAction.COMMENT_LINE, + index++)); + + //noinspection UnusedAssignment + actionPresenter.registerAction( + new TextAction( + createTextActionDrawable(R.drawable.ic_uncomment_line), + R.string.menu_uncomment_line, + TextAction.UNCOMMENT_LINE, + index++)); + + array.recycle(); + } + + /** + * Called by text action presenters to check if the action with the given ID should be shown or + * not. + * + * @param actionId The action ID to check. + * @return true if the action should be shown, false otherwise. + */ + public boolean shouldShowTextAction(int actionId) { + final var capabilities = mLanguageServer != null ? mLanguageServer.getCapabilities() : null; + final var notNull = capabilities != null; + final var expand = notNull && capabilities.getSmartSelectionsEnabled(); + final var definitions = notNull && capabilities.getDefinitionsAvailable(); + final var references = notNull && capabilities.getReferencesAvailable(); + final var commentUncomment = + getFile() != null + && (getFile().getName().endsWith(".java") + || getFile().getName().endsWith(".gradle") + || getFile().getName().endsWith(".xml")); + switch (actionId) { + case TextAction.CUT: + case TextAction.PASTE: + return isEditable(); + case TextAction.GOTO_DEFINITION: + return definitions; + case TextAction.EXPAND_SELECTION: + return expand; + case TextAction.FIND_REFERENCES: + return references; + case TextAction.COMMENT_LINE: + case TextAction.UNCOMMENT_LINE: + return commentUncomment; + } + return true; - } - if (!(o instanceof TextAction)) { - return false; - } - TextAction that = (TextAction) o; - return id == that.id; } - @Override - public int hashCode() { - return Objects.hash(id); + /** + * Performs the given text action in this editor. + * + * @param action The action to perform. + */ + public void performTextAction(@NonNull TextAction action) { + Objects.requireNonNull(action, "Cannot perform null text action"); + + switch (action.id) { + case TextAction.SELECT_ALL: + selectAll(); + break; + case TextAction.CUT: + cutText(); + break; + case TextAction.COPY: + copyText(); + break; + case TextAction.PASTE: + pasteText(); + break; + case TextAction.GOTO_DEFINITION: + findDefinition(); + break; + case TextAction.FIND_REFERENCES: + findReferences(); + break; + case TextAction.COMMENT_LINE: + commentLine(); + break; + case TextAction.UNCOMMENT_LINE: + uncommentLine(); + break; + case TextAction.EXPAND_SELECTION: + expandSelection(); + break; + } } - @Override - public int compareTo(TextAction o) { - return Integer.compare(this.index, o.index); + private Drawable createTextActionDrawable(int icon) { + final Drawable d = ContextCompat.getDrawable(getContext(), icon); + if (d != null) { + d.setColorFilter( + ContextCompat.getColor(getContext(), secondaryColor), PorterDuff.Mode.SRC_ATOP); + } + return d; + } + + private void handleSelectionChange(SelectionChangeEvent event) { + if (event.isSelected() || !(getEditorLanguage() instanceof IDELanguage)) { + // do not show diagnostics when text is selected + // or if we cannot get diagnostics + return; + } + + final var diagnostics = ((IDELanguage) getEditorLanguage()).getDiagnostics(); + final var line = event.getLeft().line; + final var column = event.getLeft().column; + + // diagnostics are expected to be sorted, so, we do a binary search + getDiagnosticWindow().showDiagnostic(binarySearchDiagnostic(diagnostics, line, column)); + } + + @Nullable + private DiagnosticItem binarySearchDiagnostic( + @NonNull List diagnostics, int line, int column) { + + if (diagnostics.isEmpty()) { + return null; + } + + final var pos = new Position(line, column); + int left = 0; + int right = diagnostics.size() - 1; + int mid; + while (left < right) { + mid = (left + right) / 2; + var d = diagnostics.get(mid); + var r = d.getRange(); + var c = r.containsForBinarySearch(pos); + if (c < 0) { + right = mid - 1; + } else if (c > 0) { + left = mid + 1; + } else { + return d; + } + } + + return null; + } + + /** + * Notify the language server that the content of this file has been changed. + * + * @param event The content change event. + */ + private void handleContentChange(ContentChangeEvent event) { + + if (getFile() == null) { + return; + } + + StudioApp.getInstance() + .getJavaLanguageServer() + .getDocumentHandler() + .onContentChange( + new DocumentChangeEvent(getFile().toPath(), getText(), mFileVersion + 1)); + + checkForSignatureHelp(event); + } + + /** + * Checks if the content change event should trigger signature help. Signature help trigger + * characters are : + * + *

    + *
  • '(' (parentheses) + *
  • ',' (comma) + *
+ * + * @param event The content change event. + */ + private void checkForSignatureHelp(@NonNull ContentChangeEvent event) { + if (event.getAction() != ContentChangeEvent.ACTION_INSERT + || event.getChangedText().length() != 1) { + return; + } + + final var ch = event.getChangedText().charAt(0); + if (ch == '(' || ch == ',') { + signatureHelp(); + } + } + + private SignatureHelpWindow getSignatureHelpWindow() { + if (mSignatureHelpWindow == null) { + mSignatureHelpWindow = new SignatureHelpWindow(this); + } + + return mSignatureHelpWindow; + } + + private DiagnosticWindow getDiagnosticWindow() { + if (mDiagnosticWindow == null) { + mDiagnosticWindow = new DiagnosticWindow(this); + } + + return mDiagnosticWindow; + } + + /** + * A text action presenter presents text actions (cut, copy, paste, etc).
+ * + *

The presenter handles its visibility itself. + * + * @author Akash Yadav + */ + public interface ITextActionPresenter { + + /** + * Bind the action presenter with the given editor instance. + * + * @param editor The editor to bind with. + */ + void bindEditor(@NonNull IDEEditor editor); + + /** + * Register the text action with this presenter. + * + * @param action The action to register. + */ + void registerAction(@NonNull TextAction action); + + /** + * Destroy this action presenter. The presenter should unsubscribe from any subscribed + * events and release any held resources. + */ + void destroy(); + } + + /** + * A model class for text actions. + * + * @author Akash Yadav + */ + public static class TextAction implements Comparable { + + public static final int EXPAND_SELECTION = 4; + public static final int GOTO_DEFINITION = 5; + public static final int FIND_REFERENCES = 6; + public static final int COMMENT_LINE = 7; + public static final int UNCOMMENT_LINE = 8; + + // common action IDs + public static final int PASTE = 3; + public static final int COPY = 2; + public static final int CUT = 1; + public static final int SELECT_ALL = 0; + + /** The drawable resource id for this text action. */ + public Drawable icon; + + /** The string resource id for this text action. */ + @StringRes public int titleId; + + /** The ID of this text action; */ + public final int id; + + /** The index at which this action should be placed. */ + public final int index; + + public TextAction(Drawable icon, int titleId, int id, int index) { + this.icon = icon; + this.titleId = titleId; + this.id = id; + this.index = index; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TextAction)) { + return false; + } + TextAction that = (TextAction) o; + return id == that.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public int compareTo(TextAction o) { + return Integer.compare(this.index, o.index); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/SignatureHelpWindow.java b/app/src/main/java/com/itsaky/androidide/views/editor/SignatureHelpWindow.java index 998562f7e..71251b5f0 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/SignatureHelpWindow.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/SignatureHelpWindow.java @@ -20,11 +20,14 @@ package com.itsaky.androidide.views.editor; import android.text.SpannableStringBuilder; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.itsaky.androidide.utils.Logger; import com.itsaky.lsp.models.SignatureHelp; import com.itsaky.lsp.models.SignatureInformation; + import io.github.rosemoe.sora.event.SelectionChangeEvent; import io.github.rosemoe.sora.widget.base.EditorPopupWindow; @@ -35,142 +38,148 @@ import io.github.rosemoe.sora.widget.base.EditorPopupWindow; */ public class SignatureHelpWindow extends SimpleTextWindow { - private static final Logger LOG = Logger.instance("SignatureHelpWindow"); + private static final Logger LOG = Logger.instance("SignatureHelpWindow"); - /** - * Create a signature help popup window for editor - * - * @param editor The editor. - */ - public SignatureHelpWindow(@NonNull IDEEditor editor) { - super(editor); + /** + * Create a signature help popup window for editor + * + * @param editor The editor. + */ + public SignatureHelpWindow(@NonNull IDEEditor editor) { + super(editor); - editor.subscribeEvent( - SelectionChangeEvent.class, - (event, unsubscribe) -> { - if (isShowing()) { - dismiss(); - } - }); - } - - public void setupAndDisplay(SignatureHelp signature) { - if (signature == null || signature.getSignatures().isEmpty()) { - if (isShowing()) { - dismiss(); - } - - return; + editor.subscribeEvent( + SelectionChangeEvent.class, + (event, unsubscribe) -> { + if (isShowing()) { + dismiss(); + } + }); } - final var signatureText = createSignatureText(signature); + public void setupAndDisplay(SignatureHelp signature) { + if (signature == null || signature.getSignatures().isEmpty()) { + if (isShowing()) { + dismiss(); + } - if (signatureText == null) { - return; + return; + } + + final var signatureText = createSignatureText(signature); + + if (signatureText == null) { + return; + } + + this.text.setText(signatureText); + displayWindow(); } - this.text.setText(signatureText); - displayWindow(); - } + @Nullable + private CharSequence createSignatureText(@NonNull SignatureHelp signature) { + final var signatures = signature.getSignatures(); + final var activeSignature = signature.getActiveSignature(); + final var activeParameter = signature.getActiveParameter(); + final SpannableStringBuilder sb = new SpannableStringBuilder(); - @Nullable - private CharSequence createSignatureText(@NonNull SignatureHelp signature) { - final var signatures = signature.getSignatures(); - final var activeSignature = signature.getActiveSignature(); - final var activeParameter = signature.getActiveParameter(); - final SpannableStringBuilder sb = new SpannableStringBuilder(); + if (activeSignature < 0 || activeParameter < 0) { + LOG.debug("activeSignature:", activeSignature, "activeParameter:", activeParameter); + return null; + } - if (activeSignature < 0 || activeParameter < 0) { - LOG.debug("activeSignature:", activeSignature, "activeParameter:", activeParameter); - return null; + var count = signatures.size(); + if (activeSignature >= count) { + LOG.debug("Active signature is invalid", "Size is " + count); + return null; + } + + // remove all with non-applicable signatures + signatures.removeIf( + info -> { + final var remove = activeParameter >= info.getParameters().size(); + if (remove) { + LOG.debug( + "Removing", + info, + "params=" + info.getParameters().size(), + "active=" + activeParameter); + } + return remove; + }); + + count = signatures.size(); + for (var i = 0; i < count; i++) { + final var info = signatures.get(i); + formatSignature(info, activeParameter, i == activeSignature, sb); + if (i != count - 1) { + sb.append('\n'); + } + } + + return sb; } - var count = signatures.size(); - if (activeSignature >= count) { - LOG.debug("Active signature is invalid", "Size is " + count); - return null; - } + /** + * Formats (highlights) a method signature + * + * @param signature Signature information + * @param paramIndex Currently active parameter index + * @param isCurrentSignature true if the given signature is the active signature. + * @param result The builder to append spanned text to. + */ + private void formatSignature( + @NonNull SignatureInformation signature, + int paramIndex, + boolean isCurrentSignature, + SpannableStringBuilder result) { - // remove all with non-applicable signatures - signatures.removeIf( - info -> { - final var remove = activeParameter >= info.getParameters().size(); - if (remove) { - LOG.debug( - "Removing", - info, - "params=" + info.getParameters().size(), - "active=" + activeParameter); - } - return remove; - }); + final var currentIndex = Math.max(0, result.length() - 1); + String name = signature.getLabel(); + name = name.substring(0, name.indexOf("(")); - count = signatures.size(); - for (var i = 0; i < count; i++) { - final var info = signatures.get(i); - formatSignature(info, activeParameter, i == activeSignature, sb); - if (i != count - 1) { - sb.append('\n'); - } - } - - return sb; - } - - /** - * Formats (highlights) a method signature - * - * @param signature Signature information - * @param paramIndex Currently active parameter index - * @param isCurrentSignature true if the given signature is the active signature. - * @param result The builder to append spanned text to. - */ - private void formatSignature( - @NonNull SignatureInformation signature, - int paramIndex, - boolean isCurrentSignature, - SpannableStringBuilder result) { - - final var currentIndex = Math.max(0, result.length() - 1); - String name = signature.getLabel(); - name = name.substring(0, name.indexOf("(")); - - result.append( - name, new ForegroundColorSpan(0xffffffff), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - result.append( - "(", new ForegroundColorSpan(0xff4fc3f7), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - - var params = signature.getParameters(); - for (int i = 0; i < params.size(); i++) { - int color = i == paramIndex ? 0xffff6060 : 0xffffffff; - final var info = params.get(i); - if (i == params.size() - 1) { result.append( - info.getLabel() + "", - new ForegroundColorSpan(color), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { + name, + new ForegroundColorSpan(0xffffffff), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); result.append( - info.getLabel() + "", - new ForegroundColorSpan(color), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - result.append( - ",", - new ForegroundColorSpan(0xff4fc3f7), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - result.append(" "); - } - } - result.append( - ")", new ForegroundColorSpan(0xff4fc3f7), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + "(", + new ForegroundColorSpan(0xff4fc3f7), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - if (isCurrentSignature) { - // set background span to little light color than the text's background - result.setSpan( - new BackgroundColorSpan(0xff373737), - currentIndex, - result.length(), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + var params = signature.getParameters(); + for (int i = 0; i < params.size(); i++) { + int color = i == paramIndex ? 0xffff6060 : 0xffffffff; + final var info = params.get(i); + if (i == params.size() - 1) { + result.append( + info.getLabel() + "", + new ForegroundColorSpan(color), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + result.append( + info.getLabel() + "", + new ForegroundColorSpan(color), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + result.append( + ",", + new ForegroundColorSpan(0xff4fc3f7), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + result.append(" "); + } + } + result.append( + ")", + new ForegroundColorSpan(0xff4fc3f7), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + + if (isCurrentSignature) { + // set background span to little light color than the text's background + result.setSpan( + new BackgroundColorSpan(0xff373737), + currentIndex, + result.length(), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + } } - } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/SimpleTextWindow.java b/app/src/main/java/com/itsaky/androidide/views/editor/SimpleTextWindow.java index f421b9a53..bff50f462 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/SimpleTextWindow.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/SimpleTextWindow.java @@ -23,10 +23,13 @@ import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; + import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; + import com.blankj.utilcode.util.SizeUtils; import com.itsaky.androidide.R; + import io.github.rosemoe.sora.widget.base.EditorPopupWindow; /** @@ -36,59 +39,63 @@ import io.github.rosemoe.sora.widget.base.EditorPopupWindow; */ public abstract class SimpleTextWindow extends EditorPopupWindow { - protected final TextView text; + protected final TextView text; - /** - * Create a popup window for editor - * - * @param editor The editor - * @see #FEATURE_SCROLL_AS_CONTENT - * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED - * @see #FEATURE_HIDE_WHEN_FAST_SCROLL - */ - public SimpleTextWindow(@NonNull IDEEditor editor) { - super(editor, getFeatureFlags()); + /** + * Create a popup window for editor + * + * @param editor The editor + * @see #FEATURE_SCROLL_AS_CONTENT + * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED + * @see #FEATURE_HIDE_WHEN_FAST_SCROLL + */ + public SimpleTextWindow(@NonNull IDEEditor editor) { + super(editor, getFeatureFlags()); - final var context = editor.getContext(); - final var dp4 = SizeUtils.dp2px(4); - final var dp8 = dp4 * 2; + final var context = editor.getContext(); + final var dp4 = SizeUtils.dp2px(4); + final var dp8 = dp4 * 2; - this.text = new TextView(context); - this.text.setBackground(createBackground()); - this.text.setTextColor(ContextCompat.getColor(context, R.color.primaryTextColor)); - this.text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); - this.text.setClickable(false); - this.text.setFocusable(false); - this.text.setPaddingRelative(dp8, dp4, dp8, dp4); - this.text.setLayoutParams( - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + this.text = new TextView(context); + this.text.setBackground(createBackground()); + this.text.setTextColor(ContextCompat.getColor(context, R.color.primaryTextColor)); + this.text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + this.text.setClickable(false); + this.text.setFocusable(false); + this.text.setPaddingRelative(dp8, dp4, dp8, dp4); + this.text.setLayoutParams( + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - setContentView(this.text); - } + setContentView(this.text); + } - public void displayWindow() { - this.text.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - setSize(this.text.getMeasuredWidth(), this.text.getMeasuredHeight()); + public void displayWindow() { + this.text.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + setSize(this.text.getMeasuredWidth(), this.text.getMeasuredHeight()); - final var line = getEditor().getCursor().getLeftLine(); - final var column = getEditor().getCursor().getLeftColumn(); - int x = (int) ((getEditor().getOffset(line, column) - (getWidth() / 2))); - int y = (int) (getEditor().getRowHeight() * line) - getEditor().getOffsetY() - getHeight() - 5; - setLocationAbsolutely(x, y); - show(); - } + final var line = getEditor().getCursor().getLeftLine(); + final var column = getEditor().getCursor().getLeftColumn(); + int x = (int) ((getEditor().getOffset(line, column) - (getWidth() / 2))); + int y = + (int) (getEditor().getRowHeight() * line) + - getEditor().getOffsetY() + - getHeight() + - 5; + setLocationAbsolutely(x, y); + show(); + } - protected Drawable createBackground() { - GradientDrawable background = new GradientDrawable(); - background.setShape(GradientDrawable.RECTANGLE); - background.setColor(0xff212121); - background.setStroke(1, 0xffffffff); - background.setCornerRadius(8); - return background; - } + protected Drawable createBackground() { + GradientDrawable background = new GradientDrawable(); + background.setShape(GradientDrawable.RECTANGLE); + background.setColor(0xff212121); + background.setStroke(1, 0xffffffff); + background.setCornerRadius(8); + return background; + } - private static int getFeatureFlags() { - return FEATURE_SCROLL_AS_CONTENT | FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED; - } + private static int getFeatureFlags() { + return FEATURE_SCROLL_AS_CONTENT | FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED; + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/transition/ArcMotionPlus.java b/app/src/main/java/com/itsaky/androidide/views/transition/ArcMotionPlus.java index a4aa4307d..d6ccd4f7c 100755 --- a/app/src/main/java/com/itsaky/androidide/views/transition/ArcMotionPlus.java +++ b/app/src/main/java/com/itsaky/androidide/views/transition/ArcMotionPlus.java @@ -41,92 +41,93 @@ package com.itsaky.androidide.views.transition; import android.content.Context; import android.graphics.Path; import android.util.AttributeSet; + import androidx.transition.PathMotion; public class ArcMotionPlus extends PathMotion { - private static final float DEFAULT_ARC_ANGLE = 90f; - private static final boolean DEFAULT_REFLECT = false; + private static final float DEFAULT_ARC_ANGLE = 90f; + private static final boolean DEFAULT_REFLECT = false; - private boolean isReflectedArc = DEFAULT_REFLECT; - private float arcAngle = DEFAULT_ARC_ANGLE; + private boolean isReflectedArc = DEFAULT_REFLECT; + private float arcAngle = DEFAULT_ARC_ANGLE; - public ArcMotionPlus() {} + public ArcMotionPlus() {} - public ArcMotionPlus(Context context, AttributeSet attrs) { - super(context, attrs); - } - - /** - * Returns if we are reflecting the arc - * - *

The default value is false. - * - * @return Is the arc reflected - */ - public boolean isReflectedArc() { - return isReflectedArc; - } - - /** - * Sets whether we should reflect the arc about the line drawn between the start and end points - * - *

The default value is false. - * - * @param isReflectedArc Is the arc reflected about the line between start and end points - */ - public void setReflectedArc(boolean isReflectedArc) { - this.isReflectedArc = isReflectedArc; - } - - /** - * Returns the arc angle that describes the curve. The larger the angle the more pronounce the - * curve will be. Min value is 1, Max value si 179 - * - *

The default value is 90. - * - * @return The angle of the arc on a circle describing the Path between two points. - */ - public float getArcAngle() { - return arcAngle; - } - - /** - * Sets the arc angle that describes the curve. The larger the angle the more pronounce the curve - * will be. Min value is 1, Max value si 179 - * - *

The default value is 90. - * - * @param angle The angle of the arc on a circle describing the Path between two points. - */ - public ArcMotionPlus setArcAngle(float angle) { - arcAngle = angle; - return this; - } - - @Override - public Path getPath(float startX, float startY, float endX, float endY) { - float controlP1X, controlP1Y, controlP2X, controlP2Y; - - Path path = new Path(); - path.moveTo(startX, startY); - - CubicBezierArc cubicBezierArc = new CubicBezierArc(arcAngle, startX, startY, endX, endY); - - if (isReflectedArc) { - controlP1X = cubicBezierArc.getReflectedControlPoint1().x; - controlP1Y = cubicBezierArc.getReflectedControlPoint1().y; - controlP2X = cubicBezierArc.getReflectedControlPoint2().x; - controlP2Y = cubicBezierArc.getReflectedControlPoint2().y; - - } else { - controlP1X = cubicBezierArc.getControlPoint1().x; - controlP1Y = cubicBezierArc.getControlPoint1().y; - controlP2X = cubicBezierArc.getControlPoint2().x; - controlP2Y = cubicBezierArc.getControlPoint2().y; + public ArcMotionPlus(Context context, AttributeSet attrs) { + super(context, attrs); } - path.cubicTo(controlP1X, controlP1Y, controlP2X, controlP2Y, endX, endY); - return path; - } + /** + * Returns if we are reflecting the arc + * + *

The default value is false. + * + * @return Is the arc reflected + */ + public boolean isReflectedArc() { + return isReflectedArc; + } + + /** + * Sets whether we should reflect the arc about the line drawn between the start and end points + * + *

The default value is false. + * + * @param isReflectedArc Is the arc reflected about the line between start and end points + */ + public void setReflectedArc(boolean isReflectedArc) { + this.isReflectedArc = isReflectedArc; + } + + /** + * Returns the arc angle that describes the curve. The larger the angle the more pronounce the + * curve will be. Min value is 1, Max value si 179 + * + *

The default value is 90. + * + * @return The angle of the arc on a circle describing the Path between two points. + */ + public float getArcAngle() { + return arcAngle; + } + + /** + * Sets the arc angle that describes the curve. The larger the angle the more pronounce the + * curve will be. Min value is 1, Max value si 179 + * + *

The default value is 90. + * + * @param angle The angle of the arc on a circle describing the Path between two points. + */ + public ArcMotionPlus setArcAngle(float angle) { + arcAngle = angle; + return this; + } + + @Override + public Path getPath(float startX, float startY, float endX, float endY) { + float controlP1X, controlP1Y, controlP2X, controlP2Y; + + Path path = new Path(); + path.moveTo(startX, startY); + + CubicBezierArc cubicBezierArc = new CubicBezierArc(arcAngle, startX, startY, endX, endY); + + if (isReflectedArc) { + controlP1X = cubicBezierArc.getReflectedControlPoint1().x; + controlP1Y = cubicBezierArc.getReflectedControlPoint1().y; + controlP2X = cubicBezierArc.getReflectedControlPoint2().x; + controlP2Y = cubicBezierArc.getReflectedControlPoint2().y; + + } else { + controlP1X = cubicBezierArc.getControlPoint1().x; + controlP1Y = cubicBezierArc.getControlPoint1().y; + controlP2X = cubicBezierArc.getControlPoint2().x; + controlP2Y = cubicBezierArc.getControlPoint2().y; + } + + path.cubicTo(controlP1X, controlP1Y, controlP2X, controlP2Y, endX, endY); + return path; + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/transition/CubicBezierArc.java b/app/src/main/java/com/itsaky/androidide/views/transition/CubicBezierArc.java index dc49823b4..bb4a7dfd1 100755 --- a/app/src/main/java/com/itsaky/androidide/views/transition/CubicBezierArc.java +++ b/app/src/main/java/com/itsaky/androidide/views/transition/CubicBezierArc.java @@ -24,154 +24,154 @@ package com.itsaky.androidide.views.transition; */ public class CubicBezierArc { - private Point startPoint; - private Point endPoint; - private Point controlPoint1; - private Point controlPoint2; - private Point reflectedControlPoint1; - private Point reflectedControlPoint2; + private Point startPoint; + private Point endPoint; + private Point controlPoint1; + private Point controlPoint2; + private Point reflectedControlPoint1; + private Point reflectedControlPoint2; - /** - * Create a CubicBezierArc class with arc angle and start and end points - * - * @param angle The angle used to describe the arc, the greater the angle the more curved the arc - * will be (min = 1 , max = 179) - * @param startX the x coord of the start point - * @param startY the y coord of the start point - * @param endX the x coord of the end point - * @param endY the y coord of the end point - */ - public CubicBezierArc(float angle, float startX, float startY, float endX, float endY) { + /** + * Create a CubicBezierArc class with arc angle and start and end points + * + * @param angle The angle used to describe the arc, the greater the angle the more curved the + * arc will be (min = 1 , max = 179) + * @param startX the x coord of the start point + * @param startY the y coord of the start point + * @param endX the x coord of the end point + * @param endY the y coord of the end point + */ + public CubicBezierArc(float angle, float startX, float startY, float endX, float endY) { - Point start = new Point(startX, startY); - Point end = new Point(endX, endY); + Point start = new Point(startX, startY); + Point end = new Point(endX, endY); - if (start.x == end.x && start.y == end.y) { - throw new IllegalArgumentException("Start and end points cannot be the same"); + if (start.x == end.x && start.y == end.y) { + throw new IllegalArgumentException("Start and end points cannot be the same"); + } + if (angle < 1 || angle > 179) { + throw new IllegalArgumentException("Arc angle must be between 1 and 179 degrees"); + } + startPoint = start; + endPoint = end; + calculateControlPoints(angle, start, end); } - if (angle < 1 || angle > 179) { - throw new IllegalArgumentException("Arc angle must be between 1 and 179 degrees"); + + /** + * Returns the start point + * + * @return The start point + */ + public Point getStartPoint() { + return startPoint; } - startPoint = start; - endPoint = end; - calculateControlPoints(angle, start, end); - } - /** - * Returns the start point - * - * @return The start point - */ - public Point getStartPoint() { - return startPoint; - } - - /** - * Returns the end point - * - * @return The end point - */ - public Point getEndPoint() { - return endPoint; - } - - /** - * Returns the first control point - * - * @return The first control point - */ - public Point getControlPoint1() { - return controlPoint1; - } - - /** - * Returns the second control point - * - * @return The second control point - */ - public Point getControlPoint2() { - return controlPoint2; - } - - /** - * Returns the first control point when reflected about line between start and end points - * - * @return Reflected control point 1 - */ - public Point getReflectedControlPoint1() { - return reflectedControlPoint1; - } - - /** - * Returns the second control point when reflected about line between start and end points - * - * @return Reflected control point 2 - */ - public Point getReflectedControlPoint2() { - return reflectedControlPoint2; - } - - public static class Point { - public float x; - public float y; - - public Point() {} - - public Point(float x, float y) { - this.x = x; - this.y = y; + /** + * Returns the end point + * + * @return The end point + */ + public Point getEndPoint() { + return endPoint; } - } - private Point getTrianglePoint(float angle, Point a, Point b) { - double angleRadians = Math.toRadians(angle); - Point thirdPoint = new Point(); - thirdPoint.x = (float) Math.tan(angleRadians) * (b.y - a.y) * -1; - thirdPoint.y = (float) Math.tan(angleRadians) * (b.x - a.x); - thirdPoint.x = thirdPoint.x + a.x; - thirdPoint.y = thirdPoint.y + a.y; - return thirdPoint; - } - - private Point getReflectedPointAboutLine(Point start, Point end, Point reflect) { - Point reflectedPoint = new Point(); - if (start.x != end.x) { - float m = (start.y - end.y) / (start.x - end.x); - float c = end.y - (m * end.x); - float d = (reflect.x + (reflect.y - c) * m) / (1 + (m * m)); - reflectedPoint.x = (2 * d) - reflect.x; - reflectedPoint.y = (2 * d * m) - reflect.y + (2 * c); - } else { - reflectedPoint.y = reflect.y; - reflectedPoint.x = start.x - (reflect.x - start.x); + /** + * Returns the first control point + * + * @return The first control point + */ + public Point getControlPoint1() { + return controlPoint1; } - return reflectedPoint; - } - private void calculateControlPoints(float angle, Point start, Point end) { - double angleRadians = Math.toRadians(angle); + /** + * Returns the second control point + * + * @return The second control point + */ + public Point getControlPoint2() { + return controlPoint2; + } - float deltaX = start.x - end.x; - float deltaY = start.y - end.y; - float halfChordLength = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2; - float radius = halfChordLength / (float) Math.sin(angleRadians / 2.0f); - // The length of the line from the start or end point to the control point - float controlLength = (float) ((4f / 3f) * Math.tan(angleRadians / 4)) * radius; + /** + * Returns the first control point when reflected about line between start and end points + * + * @return Reflected control point 1 + */ + public Point getReflectedControlPoint1() { + return reflectedControlPoint1; + } - float angleToControl = (float) Math.toDegrees(Math.atan(controlLength / radius)); + /** + * Returns the second control point when reflected about line between start and end points + * + * @return Reflected control point 2 + */ + public Point getReflectedControlPoint2() { + return reflectedControlPoint2; + } - // The mid point of the line from start to end - Point midPointChord = new Point((start.x + end.x) / 2, (start.y + end.y) / 2); + public static class Point { + public float x; + public float y; - // Angle between line from circle centre to control point, and line between control points - float chordRadiusAngle = 180 - 90 - (angle / 2); + public Point() {} - Point center = getTrianglePoint(chordRadiusAngle, midPointChord, end); - controlPoint2 = getTrianglePoint(angleToControl, end, center); - controlPoint1 = getReflectedPointAboutLine(center, midPointChord, controlPoint2); + public Point(float x, float y) { + this.x = x; + this.y = y; + } + } - // Get reflected control points - reflectedControlPoint1 = getReflectedPointAboutLine(start, end, controlPoint1); - reflectedControlPoint2 = getReflectedPointAboutLine(start, end, controlPoint2); - } + private Point getTrianglePoint(float angle, Point a, Point b) { + double angleRadians = Math.toRadians(angle); + Point thirdPoint = new Point(); + thirdPoint.x = (float) Math.tan(angleRadians) * (b.y - a.y) * -1; + thirdPoint.y = (float) Math.tan(angleRadians) * (b.x - a.x); + thirdPoint.x = thirdPoint.x + a.x; + thirdPoint.y = thirdPoint.y + a.y; + return thirdPoint; + } + + private Point getReflectedPointAboutLine(Point start, Point end, Point reflect) { + Point reflectedPoint = new Point(); + if (start.x != end.x) { + float m = (start.y - end.y) / (start.x - end.x); + float c = end.y - (m * end.x); + float d = (reflect.x + (reflect.y - c) * m) / (1 + (m * m)); + reflectedPoint.x = (2 * d) - reflect.x; + reflectedPoint.y = (2 * d * m) - reflect.y + (2 * c); + } else { + reflectedPoint.y = reflect.y; + reflectedPoint.x = start.x - (reflect.x - start.x); + } + return reflectedPoint; + } + + private void calculateControlPoints(float angle, Point start, Point end) { + double angleRadians = Math.toRadians(angle); + + float deltaX = start.x - end.x; + float deltaY = start.y - end.y; + float halfChordLength = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2; + float radius = halfChordLength / (float) Math.sin(angleRadians / 2.0f); + // The length of the line from the start or end point to the control point + float controlLength = (float) ((4f / 3f) * Math.tan(angleRadians / 4)) * radius; + + float angleToControl = (float) Math.toDegrees(Math.atan(controlLength / radius)); + + // The mid point of the line from start to end + Point midPointChord = new Point((start.x + end.x) / 2, (start.y + end.y) / 2); + + // Angle between line from circle centre to control point, and line between control points + float chordRadiusAngle = 180 - 90 - (angle / 2); + + Point center = getTrianglePoint(chordRadiusAngle, midPointChord, end); + controlPoint2 = getTrianglePoint(angleToControl, end, center); + controlPoint1 = getReflectedPointAboutLine(center, midPointChord, controlPoint2); + + // Get reflected control points + reflectedControlPoint1 = getReflectedPointAboutLine(start, end, controlPoint1); + reflectedControlPoint2 = getReflectedPointAboutLine(start, end, controlPoint2); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/transition/ProgressTransition.java b/app/src/main/java/com/itsaky/androidide/views/transition/ProgressTransition.java index bd367e8ca..9bc24ee61 100755 --- a/app/src/main/java/com/itsaky/androidide/views/transition/ProgressTransition.java +++ b/app/src/main/java/com/itsaky/androidide/views/transition/ProgressTransition.java @@ -22,56 +22,58 @@ import android.animation.ObjectAnimator; import android.util.Property; import android.view.ViewGroup; import android.widget.ProgressBar; + import androidx.transition.Transition; import androidx.transition.TransitionValues; + import com.itsaky.androidide.utils.IntProperty; public class ProgressTransition extends Transition { - private static final String PROPNAME_PROGRESS = "ProgressTransition:progress"; - private static final Property PROGRESS_PROPERTY = - new IntProperty(PROPNAME_PROGRESS) { + private static final String PROPNAME_PROGRESS = "ProgressTransition:progress"; + private static final Property PROGRESS_PROPERTY = + new IntProperty(PROPNAME_PROGRESS) { - @Override - public void setValue(ProgressBar progressBar, int value) { - progressBar.setProgress(value); - } + @Override + public void setValue(ProgressBar progressBar, int value) { + progressBar.setProgress(value); + } - @Override - public Integer get(ProgressBar progressBar) { - return progressBar.getProgress(); - } - }; + @Override + public Integer get(ProgressBar progressBar) { + return progressBar.getProgress(); + } + }; - @Override - public void captureStartValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - private void captureValues(TransitionValues transitionValues) { - if (transitionValues.view instanceof ProgressBar) { - ProgressBar progressBar = ((ProgressBar) transitionValues.view); - transitionValues.values.put(PROPNAME_PROGRESS, progressBar.getProgress()); + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); } - } - @Override - public Animator createAnimator( - ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { - if (startValues != null && endValues != null && endValues.view instanceof ProgressBar) { - ProgressBar progressBar = (ProgressBar) endValues.view; - int start = (Integer) startValues.values.get(PROPNAME_PROGRESS); - int end = (Integer) endValues.values.get(PROPNAME_PROGRESS); - if (start != end) { - progressBar.setProgress(start); - return ObjectAnimator.ofInt(progressBar, PROGRESS_PROPERTY, end); - } + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + private void captureValues(TransitionValues transitionValues) { + if (transitionValues.view instanceof ProgressBar) { + ProgressBar progressBar = ((ProgressBar) transitionValues.view); + transitionValues.values.put(PROPNAME_PROGRESS, progressBar.getProgress()); + } + } + + @Override + public Animator createAnimator( + ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { + if (startValues != null && endValues != null && endValues.view instanceof ProgressBar) { + ProgressBar progressBar = (ProgressBar) endValues.view; + int start = (Integer) startValues.values.get(PROPNAME_PROGRESS); + int end = (Integer) endValues.values.get(PROPNAME_PROGRESS); + if (start != end) { + progressBar.setProgress(start); + return ObjectAnimator.ofInt(progressBar, PROGRESS_PROPERTY, end); + } + } + return null; } - return null; - } } diff --git a/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java b/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java index be89a221f..a9bb2af07 100755 --- a/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java +++ b/common/src/main/java/com/itsaky/androidide/app/BaseApplication.java @@ -24,9 +24,11 @@ import android.content.Intent; import android.graphics.Color; import android.net.Uri; import android.os.Build; + import androidx.annotation.NonNull; import androidx.core.app.NotificationManagerCompat; import androidx.multidex.MultiDexApplication; + import com.blankj.utilcode.util.ThrowableUtils; import com.itsaky.androidide.common.R; import com.itsaky.androidide.managers.PreferenceManager; @@ -37,232 +39,234 @@ import com.itsaky.androidide.utils.FileUtil; import com.itsaky.androidide.utils.JavaCharacter; import com.itsaky.androidide.utils.StudioUtils; import com.itsaky.toaster.Toaster; + +import org.jetbrains.annotations.Contract; + import java.io.File; import java.util.Arrays; -import org.jetbrains.annotations.Contract; public abstract class BaseApplication extends MultiDexApplication { - private static BaseApplication instance; - private StudioUtils mUtils; - private PreferenceManager mPrefsManager; + private static BaseApplication instance; + private StudioUtils mUtils; + private PreferenceManager mPrefsManager; - public static final String NOTIFICATION_ID_UPDATE = "17571"; - public static final String NOTIFICATION_ID_DEVS = "17572"; - public static final String TELEGRAM_GROUP_URL = "https://t.me/androidide_discussions"; - public static final String GITHUB_URL = "https://github.com/itsaky/AndroidIDE"; - public static final String WEBSITE = "https://androidide.com"; - public static final String EMAIL = "contact@androidide.com"; + public static final String NOTIFICATION_ID_UPDATE = "17571"; + public static final String NOTIFICATION_ID_DEVS = "17572"; + public static final String TELEGRAM_GROUP_URL = "https://t.me/androidide_discussions"; + public static final String GITHUB_URL = "https://github.com/itsaky/AndroidIDE"; + public static final String WEBSITE = "https://androidide.com"; + public static final String EMAIL = "contact@androidide.com"; - @Override - public void onCreate() { - instance = this; - Environment.init(); - super.onCreate(); + @Override + public void onCreate() { + instance = this; + Environment.init(); + super.onCreate(); - mPrefsManager = new PreferenceManager(this); - JavaCharacter.initMap(); - ToolsManager.init(this, null); + mPrefsManager = new PreferenceManager(this); + JavaCharacter.initMap(); + ToolsManager.init(this, null); - createNotificationChannels(); - } - - @SuppressLint("NewApi") - private void createNotificationChannels() { - NotificationChannel updateChannel = - new NotificationChannel( - NOTIFICATION_ID_UPDATE, - getNotificationChannelNameForId(NOTIFICATION_ID_UPDATE), - NotificationManager.IMPORTANCE_HIGH); - updateChannel.enableLights(true); - updateChannel.enableVibration(true); - updateChannel.setLightColor(Color.RED); - updateChannel.setVibrationPattern(new long[] {10, 50, 10}); - NotificationManagerCompat.from(this).createNotificationChannel(updateChannel); - - NotificationChannel devsChannels = - new NotificationChannel( - NOTIFICATION_ID_DEVS, - getNotificationChannelNameForId(NOTIFICATION_ID_DEVS), - NotificationManager.IMPORTANCE_MIN); - devsChannels.enableLights(true); - devsChannels.enableVibration(true); - devsChannels.setLightColor(Color.BLUE); - devsChannels.setVibrationPattern(new long[] {10, 50, 10}); - NotificationManagerCompat.from(this).createNotificationChannel(devsChannels); - } - - public String getNotificationChannelNameForId(@NonNull String id) { - if (id.equals(NOTIFICATION_ID_UPDATE)) return getUpdateNotificationChannelName(); - else if (id.equals(NOTIFICATION_ID_DEVS)) return getDevNotificationChannelName(); - else return "AndroidIDE Notifications"; - } - - private String getUpdateNotificationChannelName() { - return getString(R.string.cms_channel_id_update); - } - - private String getDevNotificationChannelName() { - return getString(R.string.cms_channel_id_devs); - } - - public static BaseApplication getBaseInstance() { - return instance; - } - - public ShellServer newShell(ShellServer.Callback callback) { - return newShell(callback, true); - } - - public ShellServer newShell(ShellServer.Callback callback, boolean redirectErrors) { - ShellServer shellServer = - new ShellServer( - callback, - "sh", - Environment.mkdirIfNotExits(getRootDir()).getAbsolutePath(), - Environment.getEnvironment(), - redirectErrors); - shellServer.start(); - return shellServer; - } - - public void writeException(Throwable th) { - FileUtil.writeFile( - new File(FileUtil.getExternalStorageDir(), "idelog.txt").getAbsolutePath(), - ThrowableUtils.getFullStackTrace(th)); - } - - public File getRootDir() { - return new File(getIDEDataDir(), "framework"); - } - - @SuppressLint("SdCardPath") - public File getIDEDataDir() { - return Environment.mkdirIfNotExits(new File("/data/data/com.itsaky.androidide/files")); - } - - @NonNull - @Contract(" -> new") - public final File getLogSenderDir() { - return new File(getRootDir(), "logsender"); - } - - public final File getTempProjectDir() { - return Environment.mkdirIfNotExits(new File(Environment.TMP_DIR, "tempProject")); - } - - public boolean isFrameworkInstalled() { - return getRootDir().exists() && mPrefsManager.isFrameworkInstalled(); - } - - public PreferenceManager getPrefManager() { - return mPrefsManager; - } - - public File getProjectsDir() { - return Environment.PROJECTS_DIR; - } - - public File[] listProjects() { - return getProjectsDir().listFiles(File::isDirectory); - } - - public static boolean isAbiSupported() { - return isAarch64() || isArmv7a(); - } - - public static boolean isAarch64() { - return Arrays.asList(Build.SUPPORTED_ABIS).contains("arm64-v8a"); - } - - public static boolean isArmv7a() { - return Arrays.asList(Build.SUPPORTED_ABIS).contains("armeabi-v7a"); - } - - @NonNull - public static String getArch() { - if (BaseApplication.isAarch64()) { - return "arm64-v8a"; - } else if (BaseApplication.isArmv7a()) { - return "armeabi-v7a"; + createNotificationChannels(); } - throw new UnsupportedOperationException("Device not supported"); - } - public StudioUtils getUtils() { - return mUtils == null ? mUtils = new StudioUtils(this) : mUtils; - } + @SuppressLint("NewApi") + private void createNotificationChannels() { + NotificationChannel updateChannel = + new NotificationChannel( + NOTIFICATION_ID_UPDATE, + getNotificationChannelNameForId(NOTIFICATION_ID_UPDATE), + NotificationManager.IMPORTANCE_HIGH); + updateChannel.enableLights(true); + updateChannel.enableVibration(true); + updateChannel.setLightColor(Color.RED); + updateChannel.setVibrationPattern(new long[] {10, 50, 10}); + NotificationManagerCompat.from(this).createNotificationChannel(updateChannel); - public void toast(String msg, Toaster.Type type) { - getUtils().toast(msg, type); - } - - public void toast(int msg, Toaster.Type type) { - getUtils().toast(msg, type); - } - - public void toastLong(String msg, Toaster.Type type) { - getUtils().toastLong(msg, type); - } - - public void toastLong(int msg, Toaster.Type type) { - getUtils().toastLong(msg, type); - } - - public void openTelegramGroup() { - try { - Intent open = new Intent(); - open.setAction(Intent.ACTION_VIEW); - open.setData(Uri.parse(BaseApplication.TELEGRAM_GROUP_URL)); - open.setPackage("org.telegram.messenger"); - open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(open); - } catch (Throwable th) { - try { - Intent open = new Intent(); - open.setAction(Intent.ACTION_VIEW); - open.setData(Uri.parse(BaseApplication.TELEGRAM_GROUP_URL)); - open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(open); - } catch (Throwable th2) { - toast(th2.getMessage(), Toaster.Type.ERROR); - } + NotificationChannel devsChannels = + new NotificationChannel( + NOTIFICATION_ID_DEVS, + getNotificationChannelNameForId(NOTIFICATION_ID_DEVS), + NotificationManager.IMPORTANCE_MIN); + devsChannels.enableLights(true); + devsChannels.enableVibration(true); + devsChannels.setLightColor(Color.BLUE); + devsChannels.setVibrationPattern(new long[] {10, 50, 10}); + NotificationManagerCompat.from(this).createNotificationChannel(devsChannels); } - } - public void openGitHub() { - try { - Intent open = new Intent(); - open.setAction(Intent.ACTION_VIEW); - open.setData(Uri.parse(BaseApplication.GITHUB_URL)); - open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(open); - } catch (Throwable th) { - toast(th.getMessage(), Toaster.Type.ERROR); + public String getNotificationChannelNameForId(@NonNull String id) { + if (id.equals(NOTIFICATION_ID_UPDATE)) return getUpdateNotificationChannelName(); + else if (id.equals(NOTIFICATION_ID_DEVS)) return getDevNotificationChannelName(); + else return "AndroidIDE Notifications"; } - } - public void openWebsite() { - try { - Intent open = new Intent(); - open.setAction(Intent.ACTION_VIEW); - open.setData(Uri.parse(BaseApplication.WEBSITE)); - open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(open); - } catch (Throwable th) { - toast(th.getMessage(), Toaster.Type.ERROR); + private String getUpdateNotificationChannelName() { + return getString(R.string.cms_channel_id_update); } - } - public void emailUs() { - try { - Intent open = new Intent(); - open.setAction(Intent.ACTION_VIEW); - open.setData(Uri.parse("mailto:" + EMAIL)); - open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(open); - } catch (Throwable th) { - toast(th.getMessage(), Toaster.Type.ERROR); + private String getDevNotificationChannelName() { + return getString(R.string.cms_channel_id_devs); + } + + public static BaseApplication getBaseInstance() { + return instance; + } + + public ShellServer newShell(ShellServer.Callback callback) { + return newShell(callback, true); + } + + public ShellServer newShell(ShellServer.Callback callback, boolean redirectErrors) { + ShellServer shellServer = + new ShellServer( + callback, + "sh", + Environment.mkdirIfNotExits(getRootDir()).getAbsolutePath(), + Environment.getEnvironment(), + redirectErrors); + shellServer.start(); + return shellServer; + } + + public void writeException(Throwable th) { + FileUtil.writeFile( + new File(FileUtil.getExternalStorageDir(), "idelog.txt").getAbsolutePath(), + ThrowableUtils.getFullStackTrace(th)); + } + + public File getRootDir() { + return new File(getIDEDataDir(), "framework"); + } + + @SuppressLint("SdCardPath") + public File getIDEDataDir() { + return Environment.mkdirIfNotExits(new File("/data/data/com.itsaky.androidide/files")); + } + + @NonNull + @Contract(" -> new") + public final File getLogSenderDir() { + return new File(getRootDir(), "logsender"); + } + + public final File getTempProjectDir() { + return Environment.mkdirIfNotExits(new File(Environment.TMP_DIR, "tempProject")); + } + + public boolean isFrameworkInstalled() { + return getRootDir().exists() && mPrefsManager.isFrameworkInstalled(); + } + + public PreferenceManager getPrefManager() { + return mPrefsManager; + } + + public File getProjectsDir() { + return Environment.PROJECTS_DIR; + } + + public File[] listProjects() { + return getProjectsDir().listFiles(File::isDirectory); + } + + public static boolean isAbiSupported() { + return isAarch64() || isArmv7a(); + } + + public static boolean isAarch64() { + return Arrays.asList(Build.SUPPORTED_ABIS).contains("arm64-v8a"); + } + + public static boolean isArmv7a() { + return Arrays.asList(Build.SUPPORTED_ABIS).contains("armeabi-v7a"); + } + + @NonNull + public static String getArch() { + if (BaseApplication.isAarch64()) { + return "arm64-v8a"; + } else if (BaseApplication.isArmv7a()) { + return "armeabi-v7a"; + } + throw new UnsupportedOperationException("Device not supported"); + } + + public StudioUtils getUtils() { + return mUtils == null ? mUtils = new StudioUtils(this) : mUtils; + } + + public void toast(String msg, Toaster.Type type) { + getUtils().toast(msg, type); + } + + public void toast(int msg, Toaster.Type type) { + getUtils().toast(msg, type); + } + + public void toastLong(String msg, Toaster.Type type) { + getUtils().toastLong(msg, type); + } + + public void toastLong(int msg, Toaster.Type type) { + getUtils().toastLong(msg, type); + } + + public void openTelegramGroup() { + try { + Intent open = new Intent(); + open.setAction(Intent.ACTION_VIEW); + open.setData(Uri.parse(BaseApplication.TELEGRAM_GROUP_URL)); + open.setPackage("org.telegram.messenger"); + open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(open); + } catch (Throwable th) { + try { + Intent open = new Intent(); + open.setAction(Intent.ACTION_VIEW); + open.setData(Uri.parse(BaseApplication.TELEGRAM_GROUP_URL)); + open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(open); + } catch (Throwable th2) { + toast(th2.getMessage(), Toaster.Type.ERROR); + } + } + } + + public void openGitHub() { + try { + Intent open = new Intent(); + open.setAction(Intent.ACTION_VIEW); + open.setData(Uri.parse(BaseApplication.GITHUB_URL)); + open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(open); + } catch (Throwable th) { + toast(th.getMessage(), Toaster.Type.ERROR); + } + } + + public void openWebsite() { + try { + Intent open = new Intent(); + open.setAction(Intent.ACTION_VIEW); + open.setData(Uri.parse(BaseApplication.WEBSITE)); + open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(open); + } catch (Throwable th) { + toast(th.getMessage(), Toaster.Type.ERROR); + } + } + + public void emailUs() { + try { + Intent open = new Intent(); + open.setAction(Intent.ACTION_VIEW); + open.setData(Uri.parse("mailto:" + EMAIL)); + open.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(open); + } catch (Throwable th) { + toast(th.getMessage(), Toaster.Type.ERROR); + } } - } } diff --git a/common/src/main/java/com/itsaky/androidide/colorpicker/ColorPickerView.java b/common/src/main/java/com/itsaky/androidide/colorpicker/ColorPickerView.java index 4f8ad1467..7c4839692 100644 --- a/common/src/main/java/com/itsaky/androidide/colorpicker/ColorPickerView.java +++ b/common/src/main/java/com/itsaky/androidide/colorpicker/ColorPickerView.java @@ -31,9 +31,11 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.LinearLayout; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; + import com.google.android.material.slider.Slider; import com.itsaky.androidide.common.R; import com.itsaky.androidide.common.databinding.LayoutColorPickerBinding; @@ -46,182 +48,183 @@ import com.itsaky.androidide.utils.Logger; */ public class ColorPickerView extends LinearLayout { - private int alpha; - private int red; - private int green; - private int blue; + private int alpha; + private int red; + private int green; + private int blue; - private OnPickListener mPickListener; - private final LayoutColorPickerBinding binding; - private final int DEFAULT_COLOR; + private OnPickListener mPickListener; + private final LayoutColorPickerBinding binding; + private final int DEFAULT_COLOR; - private static final Logger LOG = Logger.instance("ColorPickerView"); + private static final Logger LOG = Logger.instance("ColorPickerView"); - private final Slider.OnChangeListener mValueChangeListener = - (slider, value, fromUser) -> { + private final Slider.OnChangeListener mValueChangeListener = + (slider, value, fromUser) -> { - // If it's not from user, then the values have already been updated - if (fromUser) { - final var id = slider.getId(); - final var intVal = (int) value; - if (id == R.id.alpha_slider) { - alpha = intVal; - } else if (id == R.id.red_slider) { - red = intVal; - } else if (id == R.id.green_slider) { - green = intVal; - } else if (id == R.id.blue_slider) { - blue = intVal; - } + // If it's not from user, then the values have already been updated + if (fromUser) { + final var id = slider.getId(); + final var intVal = (int) value; + if (id == R.id.alpha_slider) { + alpha = intVal; + } else if (id == R.id.red_slider) { + red = intVal; + } else if (id == R.id.green_slider) { + green = intVal; + } else if (id == R.id.blue_slider) { + blue = intVal; + } - updatePreview(); - } - }; + updatePreview(); + } + }; - public ColorPickerView(Context context) { - this(context, null); - } + public ColorPickerView(Context context) { + this(context, null); + } - public ColorPickerView(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } + public ColorPickerView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } - public ColorPickerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } + public ColorPickerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } - public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + public ColorPickerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - this.DEFAULT_COLOR = ContextCompat.getColor(context, R.color.color_picker_default_color); + this.DEFAULT_COLOR = ContextCompat.getColor(context, R.color.color_picker_default_color); - this.binding = LayoutColorPickerBinding.inflate(LayoutInflater.from(context)); + this.binding = LayoutColorPickerBinding.inflate(LayoutInflater.from(context)); - removeAllViews(); - addView( - binding.getRoot(), - new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + removeAllViews(); + addView( + binding.getRoot(), + new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - init(); - } - - /** - * Set a listener that will be notified when the user picks a color. - * - * @param listener The listener to set. - */ - public void setOnPickListener(OnPickListener listener) { - this.mPickListener = listener; - } - - private void init() { - - setColor(DEFAULT_COLOR); - - this.binding.alphaSlider.addOnChangeListener(mValueChangeListener); - this.binding.redSlider.addOnChangeListener(mValueChangeListener); - this.binding.greenSlider.addOnChangeListener(mValueChangeListener); - this.binding.blueSlider.addOnChangeListener(mValueChangeListener); - - this.binding.hexField.getEditText().addTextChangedListener(new ColorTextWatcher()); - - this.binding.pick.setOnClickListener( - v -> { - if (mPickListener != null) { - mPickListener.onPick(getColor(), getHexColor()); - } - }); - } - - /** - * Get the hex color code of the current color value. - * - * @return The hex color code of the current color value. - */ - @NonNull - public String getHexColor() { - return "#" + Integer.toHexString(getColor()); - } - - /** - * Set the color value of this picker to the given hex color code. - * - * @param hexCode The hex color code. - * @see #setColor(int) - */ - public void setColor(final String hexCode) { - setColor(parseColor(hexCode)); - } - - /** - * Set the color value for this color picker. this will in turn update value of the sliders and - * the color preview. - * - * @param color The new color value. - */ - public void setColor(int color) { - this.alpha = alpha(color); - this.red = red(color); - this.green = green(color); - this.blue = blue(color); - - this.binding.alphaSlider.setValue(alpha); - this.binding.redSlider.setValue(red); - this.binding.greenSlider.setValue(green); - this.binding.blueSlider.setValue(blue); - - updatePreview(); - } - - /** - * Get the int value of the color from the current values of alpha, red, green and blue color - * values. - * - * @return The int color value. - */ - public int getColor() { - return argb(alpha, red, green, blue); - } - - /** Call this method to update the preview of the current color. */ - private void updatePreview() { - this.binding.colorPreview.setBackgroundColor(getColor()); - } - - /** - * A listener that can be used to get notified when the user picks a color. - * - * @author Akash Yadav - */ - public interface OnPickListener { + init(); + } /** - * Called when the user picks a color using the 'Pick' button. + * Set a listener that will be notified when the user picks a color. * - * @param color The integer value of the color. - * @param hexCode The hex color code of the color. + * @param listener The listener to set. */ - void onPick(int color, String hexCode); - } - - /** A TextWatcher that is used to listen for text updates in the hex color input field. */ - private class ColorTextWatcher implements TextWatcher { - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void afterTextChanged(Editable s) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - try { - final var color = parseColor("#" + s); - setColor(color); - } catch (Exception e) { - // May happen due to invalid color values - // Simply ignore - } + public void setOnPickListener(OnPickListener listener) { + this.mPickListener = listener; + } + + private void init() { + + setColor(DEFAULT_COLOR); + + this.binding.alphaSlider.addOnChangeListener(mValueChangeListener); + this.binding.redSlider.addOnChangeListener(mValueChangeListener); + this.binding.greenSlider.addOnChangeListener(mValueChangeListener); + this.binding.blueSlider.addOnChangeListener(mValueChangeListener); + + this.binding.hexField.getEditText().addTextChangedListener(new ColorTextWatcher()); + + this.binding.pick.setOnClickListener( + v -> { + if (mPickListener != null) { + mPickListener.onPick(getColor(), getHexColor()); + } + }); + } + + /** + * Get the hex color code of the current color value. + * + * @return The hex color code of the current color value. + */ + @NonNull + public String getHexColor() { + return "#" + Integer.toHexString(getColor()); + } + + /** + * Set the color value of this picker to the given hex color code. + * + * @param hexCode The hex color code. + * @see #setColor(int) + */ + public void setColor(final String hexCode) { + setColor(parseColor(hexCode)); + } + + /** + * Set the color value for this color picker. this will in turn update value of the sliders and + * the color preview. + * + * @param color The new color value. + */ + public void setColor(int color) { + this.alpha = alpha(color); + this.red = red(color); + this.green = green(color); + this.blue = blue(color); + + this.binding.alphaSlider.setValue(alpha); + this.binding.redSlider.setValue(red); + this.binding.greenSlider.setValue(green); + this.binding.blueSlider.setValue(blue); + + updatePreview(); + } + + /** + * Get the int value of the color from the current values of alpha, red, green and blue color + * values. + * + * @return The int color value. + */ + public int getColor() { + return argb(alpha, red, green, blue); + } + + /** Call this method to update the preview of the current color. */ + private void updatePreview() { + this.binding.colorPreview.setBackgroundColor(getColor()); + } + + /** + * A listener that can be used to get notified when the user picks a color. + * + * @author Akash Yadav + */ + public interface OnPickListener { + + /** + * Called when the user picks a color using the 'Pick' button. + * + * @param color The integer value of the color. + * @param hexCode The hex color code of the color. + */ + void onPick(int color, String hexCode); + } + + /** A TextWatcher that is used to listen for text updates in the hex color input field. */ + private class ColorTextWatcher implements TextWatcher { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void afterTextChanged(Editable s) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + try { + final var color = parseColor("#" + s); + setColor(color); + } catch (Exception e) { + // May happen due to invalid color values + // Simply ignore + } + } } - } } diff --git a/common/src/main/java/com/itsaky/androidide/interfaces/Filter.java b/common/src/main/java/com/itsaky/androidide/interfaces/Filter.java index bb1d4b25e..c0a4ddd9c 100755 --- a/common/src/main/java/com/itsaky/androidide/interfaces/Filter.java +++ b/common/src/main/java/com/itsaky/androidide/interfaces/Filter.java @@ -19,5 +19,5 @@ package com.itsaky.androidide.interfaces; public interface Filter { - boolean accept(T t); + boolean accept(T t); } diff --git a/common/src/main/java/com/itsaky/androidide/interfaces/ProjectWriterCallback.java b/common/src/main/java/com/itsaky/androidide/interfaces/ProjectWriterCallback.java index 11116b232..cf69c3e6a 100755 --- a/common/src/main/java/com/itsaky/androidide/interfaces/ProjectWriterCallback.java +++ b/common/src/main/java/com/itsaky/androidide/interfaces/ProjectWriterCallback.java @@ -20,11 +20,11 @@ package com.itsaky.androidide.interfaces; import java.io.File; public interface ProjectWriterCallback { - public void beforeBegin(); + public void beforeBegin(); - public void onProcessTask(String taskName); + public void onProcessTask(String taskName); - public void onSuccess(File rootDir); + public void onSuccess(File rootDir); - public void onFailed(String reason); + public void onFailed(String reason); } diff --git a/common/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java b/common/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java index f43d0dfdd..a4b024c15 100755 --- a/common/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java +++ b/common/src/main/java/com/itsaky/androidide/managers/PreferenceManager.java @@ -23,165 +23,165 @@ import android.content.SharedPreferences; public class PreferenceManager { - private final SharedPreferences prefs; + private final SharedPreferences prefs; - public static final String KEY_FRAMEWORK_DOWNLOADED = "framework_downloaded"; - public static final String KEY_FRAMEWORK_INSTALLED = "framework_installed"; - public static final String KEY_IS_FIRST_PROJECT_BUILD = "project_isFirstBuild"; + public static final String KEY_FRAMEWORK_DOWNLOADED = "framework_downloaded"; + public static final String KEY_FRAMEWORK_INSTALLED = "framework_installed"; + public static final String KEY_IS_FIRST_PROJECT_BUILD = "project_isFirstBuild"; - public static final String KEY_OPEN_PROJECTS = "idepref_general_autoOpenProjects"; - public static final String KEY_CONFIRM_PROJECT_OPEN = "idepref_general_confirmProjectOpen"; - public static final String KEY_TERMINAL_USE_SYSTEM_SHELL = "idepref_general_terminalShell"; + public static final String KEY_OPEN_PROJECTS = "idepref_general_autoOpenProjects"; + public static final String KEY_CONFIRM_PROJECT_OPEN = "idepref_general_confirmProjectOpen"; + public static final String KEY_TERMINAL_USE_SYSTEM_SHELL = "idepref_general_terminalShell"; - public static final String KEY_EDITOR_FLAG_WS_LEADING = "idepref_editor_wsLeading"; - public static final String KEY_EDITOR_FLAG_WS_TRAILING = "idepref_editor_wsTrailing"; - public static final String KEY_EDITOR_FLAG_WS_INNER = "idepref_editor_wsInner"; - public static final String KEY_EDITOR_FLAG_WS_EMPTY_LINE = "idepref_editor_wsEmptyLine"; - public static final String KEY_EDITOR_FLAG_LINE_BREAK = "idepref_editor_lineBreak"; - public static final String KEY_EDITOR_DRAW_HEX = "idepref_editor_drawHexColors"; - public static final String KEY_EDITOR_FONT_SIZE = "idepref_editor_fontSize"; - public static final String KEY_EDITOR_PRINTABLE_CHARS = "idepref_editor_nonPrintableFlags"; - public static final String KEY_EDITOR_TAB_SIZE = "idepref_editor_tabSize"; - public static final String KEY_EDITOR_AUTO_SAVE = "idepref_editor_autoSave"; - public static final String KEY_EDITOR_FONT_LIGATURES = "idepref_editor_fontLigatures"; - public static final String KEY_EDITOR_USE_POPUP = "idepref_editor_usePopup"; - public static final String KEY_EDITOR_HORIZONTAL_POPUP = "idepref_editor_horizontalPopup"; + public static final String KEY_EDITOR_FLAG_WS_LEADING = "idepref_editor_wsLeading"; + public static final String KEY_EDITOR_FLAG_WS_TRAILING = "idepref_editor_wsTrailing"; + public static final String KEY_EDITOR_FLAG_WS_INNER = "idepref_editor_wsInner"; + public static final String KEY_EDITOR_FLAG_WS_EMPTY_LINE = "idepref_editor_wsEmptyLine"; + public static final String KEY_EDITOR_FLAG_LINE_BREAK = "idepref_editor_lineBreak"; + public static final String KEY_EDITOR_DRAW_HEX = "idepref_editor_drawHexColors"; + public static final String KEY_EDITOR_FONT_SIZE = "idepref_editor_fontSize"; + public static final String KEY_EDITOR_PRINTABLE_CHARS = "idepref_editor_nonPrintableFlags"; + public static final String KEY_EDITOR_TAB_SIZE = "idepref_editor_tabSize"; + public static final String KEY_EDITOR_AUTO_SAVE = "idepref_editor_autoSave"; + public static final String KEY_EDITOR_FONT_LIGATURES = "idepref_editor_fontLigatures"; + public static final String KEY_EDITOR_USE_POPUP = "idepref_editor_usePopup"; + public static final String KEY_EDITOR_HORIZONTAL_POPUP = "idepref_editor_horizontalPopup"; - public static final String KEY_GRADLE_CMD_STACK_TRACE = "idepref_gradleCmd_stacktrace"; - public static final String KEY_GRADLE_CMD_DEBUG = "idepref_gradleCmd_debug"; - public static final String KEY_GRADLE_CMD_SCAN = "idepref_gradleCmd_scan"; - public static final String KEY_GRADLE_CMD_INFO = "idepref_gradleCmd_info"; - public static final String KEY_GRADLE_CMD_WARNING_MODE = "idepref_gradleCmd_warningMode"; + public static final String KEY_GRADLE_CMD_STACK_TRACE = "idepref_gradleCmd_stacktrace"; + public static final String KEY_GRADLE_CMD_DEBUG = "idepref_gradleCmd_debug"; + public static final String KEY_GRADLE_CMD_SCAN = "idepref_gradleCmd_scan"; + public static final String KEY_GRADLE_CMD_INFO = "idepref_gradleCmd_info"; + public static final String KEY_GRADLE_CMD_WARNING_MODE = "idepref_gradleCmd_warningMode"; - public static final String KEY_LAST_OPENED_PROJECT = "ide_last_project"; - public static final String NO_OPENED_PROJECT = ""; + public static final String KEY_LAST_OPENED_PROJECT = "ide_last_project"; + public static final String NO_OPENED_PROJECT = ""; - @SuppressLint("CommitPrefEdits") - public PreferenceManager(Context ctx) { - this.prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(ctx); - } + @SuppressLint("CommitPrefEdits") + public PreferenceManager(Context ctx) { + this.prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(ctx); + } - public PreferenceManager putInt(String key, int val) { - prefs.edit().putInt(key, val).apply(); - return this; - } + public PreferenceManager putInt(String key, int val) { + prefs.edit().putInt(key, val).apply(); + return this; + } - public int getInt(String key) { - return prefs.getInt(key, 0); - } + public int getInt(String key) { + return prefs.getInt(key, 0); + } - public int getInt(String key, int def) { - return prefs.getInt(key, def); - } + public int getInt(String key, int def) { + return prefs.getInt(key, def); + } - public PreferenceManager putFloat(String key, float val) { - prefs.edit().putFloat(key, val).apply(); - return this; - } + public PreferenceManager putFloat(String key, float val) { + prefs.edit().putFloat(key, val).apply(); + return this; + } - public float getFloat(String key) { - return prefs.getFloat(key, 0f); - } + public float getFloat(String key) { + return prefs.getFloat(key, 0f); + } - public float getFloat(String key, float def) { - return prefs.getFloat(key, def); - } + public float getFloat(String key, float def) { + return prefs.getFloat(key, def); + } - public PreferenceManager putBoolean(String key, boolean value) { - prefs.edit().putBoolean(key, value).apply(); - return this; - } + public PreferenceManager putBoolean(String key, boolean value) { + prefs.edit().putBoolean(key, value).apply(); + return this; + } - public boolean getBoolean(String key) { - return getBoolean(key, false); - } + public boolean getBoolean(String key) { + return getBoolean(key, false); + } - public boolean getBoolean(String key, boolean defaultValue) { - return prefs.getBoolean(key, defaultValue); - } + public boolean getBoolean(String key, boolean defaultValue) { + return prefs.getBoolean(key, defaultValue); + } - public PreferenceManager putString(String key, String value) { - prefs.edit().putString(key, value).apply(); - return this; - } + public PreferenceManager putString(String key, String value) { + prefs.edit().putString(key, value).apply(); + return this; + } - public String getString(String key) { - return prefs.getString(key, null); - } + public String getString(String key) { + return prefs.getString(key, null); + } - public String getString(String key, String defaultValue) { - return prefs.getString(key, defaultValue); - } + public String getString(String key, String defaultValue) { + return prefs.getString(key, defaultValue); + } - public PreferenceManager setOpenedProject(String root) { - return putString(KEY_LAST_OPENED_PROJECT, root); - } + public PreferenceManager setOpenedProject(String root) { + return putString(KEY_LAST_OPENED_PROJECT, root); + } - public String getOpenedProject() { - return getString(KEY_LAST_OPENED_PROJECT, NO_OPENED_PROJECT); - } + public String getOpenedProject() { + return getString(KEY_LAST_OPENED_PROJECT, NO_OPENED_PROJECT); + } - public boolean wasProjectOpened() { - return !getOpenedProject().equals(NO_OPENED_PROJECT); - } + public boolean wasProjectOpened() { + return !getOpenedProject().equals(NO_OPENED_PROJECT); + } - public boolean isFrameworkDownloaded() { - return getBoolean(KEY_FRAMEWORK_DOWNLOADED); - } + public boolean isFrameworkDownloaded() { + return getBoolean(KEY_FRAMEWORK_DOWNLOADED); + } - public boolean isFrameworkInstalled() { - return isFrameworkDownloaded() && getBoolean(KEY_FRAMEWORK_INSTALLED); - } + public boolean isFrameworkInstalled() { + return isFrameworkDownloaded() && getBoolean(KEY_FRAMEWORK_INSTALLED); + } - public boolean isStackTraceEnabled() { - return getBoolean(KEY_GRADLE_CMD_STACK_TRACE); - } + public boolean isStackTraceEnabled() { + return getBoolean(KEY_GRADLE_CMD_STACK_TRACE); + } - public boolean isGradleInfoEnabled() { - return getBoolean(KEY_GRADLE_CMD_INFO, true); - } + public boolean isGradleInfoEnabled() { + return getBoolean(KEY_GRADLE_CMD_INFO, true); + } - public boolean isGradleDebugEnabled() { - return getBoolean(KEY_GRADLE_CMD_DEBUG); - } + public boolean isGradleDebugEnabled() { + return getBoolean(KEY_GRADLE_CMD_DEBUG); + } - public boolean isGradleScanEnabled() { - return getBoolean(KEY_GRADLE_CMD_SCAN); - } + public boolean isGradleScanEnabled() { + return getBoolean(KEY_GRADLE_CMD_SCAN); + } - public boolean isGradleWarningEnabled() { - return getBoolean(KEY_GRADLE_CMD_WARNING_MODE); - } + public boolean isGradleWarningEnabled() { + return getBoolean(KEY_GRADLE_CMD_WARNING_MODE); + } - public PreferenceManager setGradleStacktraceEnabled(boolean enabled) { - return putBoolean(KEY_GRADLE_CMD_STACK_TRACE, enabled); - } + public PreferenceManager setGradleStacktraceEnabled(boolean enabled) { + return putBoolean(KEY_GRADLE_CMD_STACK_TRACE, enabled); + } - public PreferenceManager setGradleInfoEnabled(boolean enabled) { - return putBoolean(KEY_GRADLE_CMD_INFO, enabled); - } + public PreferenceManager setGradleInfoEnabled(boolean enabled) { + return putBoolean(KEY_GRADLE_CMD_INFO, enabled); + } - public PreferenceManager setGradleDebugEnabled(boolean enabled) { - return putBoolean(KEY_GRADLE_CMD_DEBUG, enabled); - } + public PreferenceManager setGradleDebugEnabled(boolean enabled) { + return putBoolean(KEY_GRADLE_CMD_DEBUG, enabled); + } - public PreferenceManager setGradleScanEnabled(boolean enabled) { - return putBoolean(KEY_GRADLE_CMD_SCAN, enabled); - } + public PreferenceManager setGradleScanEnabled(boolean enabled) { + return putBoolean(KEY_GRADLE_CMD_SCAN, enabled); + } - public PreferenceManager setGradleWarningEnabled(boolean enabled) { - return putBoolean(KEY_GRADLE_CMD_WARNING_MODE, enabled); - } + public PreferenceManager setGradleWarningEnabled(boolean enabled) { + return putBoolean(KEY_GRADLE_CMD_WARNING_MODE, enabled); + } - public int getEditorTabSize() { - return getInt(KEY_EDITOR_TAB_SIZE, 4); - } + public int getEditorTabSize() { + return getInt(KEY_EDITOR_TAB_SIZE, 4); + } - public boolean autoOpenProject() { - return getBoolean(KEY_OPEN_PROJECTS, true); - } + public boolean autoOpenProject() { + return getBoolean(KEY_OPEN_PROJECTS, true); + } - public boolean confirmProjectOpen() { - return getBoolean(KEY_CONFIRM_PROJECT_OPEN, false); - } + public boolean confirmProjectOpen() { + return getBoolean(KEY_CONFIRM_PROJECT_OPEN, false); + } } diff --git a/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java b/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java index 4946287b9..3aa4bc8c1 100755 --- a/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java +++ b/common/src/main/java/com/itsaky/androidide/managers/ToolsManager.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.managers; import androidx.annotation.NonNull; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.ResourceUtils; @@ -25,119 +26,122 @@ import com.blankj.utilcode.util.ZipUtils; import com.itsaky.androidide.app.BaseApplication; import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.Logger; + +import org.jetbrains.annotations.Contract; + import java.io.File; import java.io.IOException; -import org.jetbrains.annotations.Contract; public class ToolsManager { - private static PreferenceManager prefs; + private static PreferenceManager prefs; - public static final int LOG_SENDER_VERSION = 2; + public static final int LOG_SENDER_VERSION = 2; - public static final String KEY_LOG_SENDER_VERSION = "tools_logsenderVersion"; + public static final String KEY_LOG_SENDER_VERSION = "tools_logsenderVersion"; - public static String ARCH_SPECIFIC_ASSET_DATA_DIR = "data/" + BaseApplication.getArch(); - public static String COMMON_ASSET_DATA_DIR = "data/common"; + public static String ARCH_SPECIFIC_ASSET_DATA_DIR = "data/" + BaseApplication.getArch(); + public static String COMMON_ASSET_DATA_DIR = "data/common"; - public static void init(@NonNull BaseApplication app, Runnable onFinish) { - prefs = app.getPrefManager(); + public static void init(@NonNull BaseApplication app, Runnable onFinish) { + prefs = app.getPrefManager(); - copyBusyboxIfNeeded(); - extractLogsenderIfNeeded(); - extractLibHooks(); - extractGradlePlugin(); - writeInitScript(); - rewriteProjectData(); + copyBusyboxIfNeeded(); + extractLogsenderIfNeeded(); + extractLibHooks(); + extractGradlePlugin(); + writeInitScript(); + rewriteProjectData(); - if (onFinish != null) { - onFinish.run(); - } - } - - private static void extractGradlePlugin() { - final var repoDir = new File(Environment.ANDROIDIDE_HOME, "repo"); - FileUtils.createOrExistsDir(repoDir); - - final var zip = new File(Environment.TMP_DIR, "gradle-plugin.zip"); - if (zip.exists()) { - FileUtils.delete(zip); + if (onFinish != null) { + onFinish.run(); + } } - ResourceUtils.copyFileFromAssets(getCommonAsset("gradle-plugin.zip"), zip.getAbsolutePath()); - try { - ZipUtils.unzipFile(zip, repoDir); - } catch (Throwable e) { - LOG.error("Unable to extract gradle plugin zip file"); - } - } + private static void extractGradlePlugin() { + final var repoDir = new File(Environment.ANDROIDIDE_HOME, "repo"); + FileUtils.createOrExistsDir(repoDir); - public static void extractLibHooks() { - if (!Environment.LIB_HOOK.exists()) { - ResourceUtils.copyFileFromAssets( - getArchSpecificAsset("libhook.so"), Environment.LIB_HOOK.getAbsolutePath()); - } + final var zip = new File(Environment.TMP_DIR, "gradle-plugin.zip"); + if (zip.exists()) { + FileUtils.delete(zip); + } - if (!Environment.LIB_HOOK2.exists()) { - ResourceUtils.copyFileFromAssets( - getArchSpecificAsset("libhook2.so"), Environment.LIB_HOOK2.getAbsolutePath()); - } - } - - private static void rewriteProjectData() { - FileIOUtils.writeFileFromString(Environment.PROJECT_DATA_FILE, "/**********************/"); - } - - private static void copyBusyboxIfNeeded() { - File exec = Environment.BUSYBOX; - if (exec.exists()) return; - Environment.mkdirIfNotExits(exec.getParentFile()); - ResourceUtils.copyFileFromAssets(getArchSpecificAsset("busybox"), exec.getAbsolutePath()); - if (!exec.canExecute()) { - if (!exec.setExecutable(true)) { - LOG.error("Cannot set busybox executable permissions."); - } - } - } - - private static void writeInitScript() { - if (Environment.INIT_SCRIPT.exists()) { - FileUtils.delete(Environment.INIT_SCRIPT); - } - FileIOUtils.writeFileFromString(Environment.INIT_SCRIPT, readInitScript()); - } - - @NonNull - private static String readInitScript() { - return ResourceUtils.readAssets2String(getCommonAsset("androidide.init.gradle")); - } - - private static void extractLogsenderIfNeeded() { - try { - final boolean isOld = LOG_SENDER_VERSION > prefs.getInt(KEY_LOG_SENDER_VERSION, 0); - if (isOld) { - final File logsenderZip = new File(Environment.TMP_DIR, "logsender.zip"); ResourceUtils.copyFileFromAssets( - getCommonAsset("logsender.zip"), logsenderZip.getAbsolutePath()); - ZipUtils.unzipFile(logsenderZip, Environment.HOME); - prefs.putInt(KEY_LOG_SENDER_VERSION, LOG_SENDER_VERSION); - } - } catch (IOException e) { - LOG.error("Error extracting log sender", e); + getCommonAsset("gradle-plugin.zip"), zip.getAbsolutePath()); + try { + ZipUtils.unzipFile(zip, repoDir); + } catch (Throwable e) { + LOG.error("Unable to extract gradle plugin zip file"); + } } - } - @NonNull - @Contract(pure = true) - public static String getArchSpecificAsset(String name) { - return ARCH_SPECIFIC_ASSET_DATA_DIR + "/" + name; - } + public static void extractLibHooks() { + if (!Environment.LIB_HOOK.exists()) { + ResourceUtils.copyFileFromAssets( + getArchSpecificAsset("libhook.so"), Environment.LIB_HOOK.getAbsolutePath()); + } - @NonNull - @Contract(pure = true) - public static String getCommonAsset(String name) { - return COMMON_ASSET_DATA_DIR + "/" + name; - } + if (!Environment.LIB_HOOK2.exists()) { + ResourceUtils.copyFileFromAssets( + getArchSpecificAsset("libhook2.so"), Environment.LIB_HOOK2.getAbsolutePath()); + } + } - private static final Logger LOG = Logger.instance("ToolsManager"); + private static void rewriteProjectData() { + FileIOUtils.writeFileFromString(Environment.PROJECT_DATA_FILE, "/**********************/"); + } + + private static void copyBusyboxIfNeeded() { + File exec = Environment.BUSYBOX; + if (exec.exists()) return; + Environment.mkdirIfNotExits(exec.getParentFile()); + ResourceUtils.copyFileFromAssets(getArchSpecificAsset("busybox"), exec.getAbsolutePath()); + if (!exec.canExecute()) { + if (!exec.setExecutable(true)) { + LOG.error("Cannot set busybox executable permissions."); + } + } + } + + private static void writeInitScript() { + if (Environment.INIT_SCRIPT.exists()) { + FileUtils.delete(Environment.INIT_SCRIPT); + } + FileIOUtils.writeFileFromString(Environment.INIT_SCRIPT, readInitScript()); + } + + @NonNull + private static String readInitScript() { + return ResourceUtils.readAssets2String(getCommonAsset("androidide.init.gradle")); + } + + private static void extractLogsenderIfNeeded() { + try { + final boolean isOld = LOG_SENDER_VERSION > prefs.getInt(KEY_LOG_SENDER_VERSION, 0); + if (isOld) { + final File logsenderZip = new File(Environment.TMP_DIR, "logsender.zip"); + ResourceUtils.copyFileFromAssets( + getCommonAsset("logsender.zip"), logsenderZip.getAbsolutePath()); + ZipUtils.unzipFile(logsenderZip, Environment.HOME); + prefs.putInt(KEY_LOG_SENDER_VERSION, LOG_SENDER_VERSION); + } + } catch (IOException e) { + LOG.error("Error extracting log sender", e); + } + } + + @NonNull + @Contract(pure = true) + public static String getArchSpecificAsset(String name) { + return ARCH_SPECIFIC_ASSET_DATA_DIR + "/" + name; + } + + @NonNull + @Contract(pure = true) + public static String getCommonAsset(String name) { + return COMMON_ASSET_DATA_DIR + "/" + name; + } + + private static final Logger LOG = Logger.instance("ToolsManager"); } diff --git a/common/src/main/java/com/itsaky/androidide/managers/VersionedFileManager.java b/common/src/main/java/com/itsaky/androidide/managers/VersionedFileManager.java index e65993ff8..5f32da904 100755 --- a/common/src/main/java/com/itsaky/androidide/managers/VersionedFileManager.java +++ b/common/src/main/java/com/itsaky/androidide/managers/VersionedFileManager.java @@ -22,22 +22,22 @@ import java.util.HashMap; public class VersionedFileManager { - private static final HashMap map = new HashMap<>(); + private static final HashMap map = new HashMap<>(); - public static int fileOpened(File file) { - // Initial version of file should always be 1 - map.put(file, 1); - return 1; - } - - public static int incrementVersion(File file) { - if (!map.containsKey(file)) { - map.put(file, 1); + public static int fileOpened(File file) { + // Initial version of file should always be 1 + map.put(file, 1); + return 1; } - int version = map.get(file); - version++; - map.put(file, version); - return version; - } + public static int incrementVersion(File file) { + if (!map.containsKey(file)) { + map.put(file, 1); + } + + int version = map.get(file); + version++; + map.put(file, version); + return version; + } } diff --git a/common/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java b/common/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java index 70e630224..72d40b2cd 100755 --- a/common/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java +++ b/common/src/main/java/com/itsaky/androidide/models/ConstantsBridge.java @@ -19,42 +19,42 @@ package com.itsaky.androidide.models; public class ConstantsBridge { - public static boolean EDITOR_PREF_SIZE_CHANGED = false; - public static boolean EDITOR_PREF_LIGATURES_CHANGED = false; - public static boolean EDITOR_PREF_FLAGS_CHANGED = false; - public static boolean EDITOR_PREF_DRAW_HEX_CHANGED = false; - public static boolean CLASS_LOAD_SUCCESS = true; + public static boolean EDITOR_PREF_SIZE_CHANGED = false; + public static boolean EDITOR_PREF_LIGATURES_CHANGED = false; + public static boolean EDITOR_PREF_FLAGS_CHANGED = false; + public static boolean EDITOR_PREF_DRAW_HEX_CHANGED = false; + public static boolean CLASS_LOAD_SUCCESS = true; - public static boolean SPLASH_TO_MAIN = false; + public static boolean SPLASH_TO_MAIN = false; - // Always compared in lowercase - public static final String CUSTOM_COMMENT_WARNING_TOKEN = "#warn "; + // Always compared in lowercase + public static final String CUSTOM_COMMENT_WARNING_TOKEN = "#warn "; - public static final String VIRTUAL_KEYS = - "[" - + "\n [" - + "\n \"ESC\"," - + "\n {" - + "\n \"key\": \"/\"," - + "\n \"popup\": \"\\\\\"" - + "\n }," - + "\n {" - + "\n \"key\": \"-\"," - + "\n \"popup\": \"|\"" - + "\n }," - + "\n \"HOME\"," - + "\n \"UP\"," - + "\n \"END\"," - + "\n \"PGUP\"" - + "\n ]," - + "\n [" - + "\n \"TAB\"," - + "\n \"CTRL\"," - + "\n \"ALT\"," - + "\n \"LEFT\"," - + "\n \"DOWN\"," - + "\n \"RIGHT\"," - + "\n \"PGDN\"" - + "\n ]" - + "\n]"; + public static final String VIRTUAL_KEYS = + "[" + + "\n [" + + "\n \"ESC\"," + + "\n {" + + "\n \"key\": \"/\"," + + "\n \"popup\": \"\\\\\"" + + "\n }," + + "\n {" + + "\n \"key\": \"-\"," + + "\n \"popup\": \"|\"" + + "\n }," + + "\n \"HOME\"," + + "\n \"UP\"," + + "\n \"END\"," + + "\n \"PGUP\"" + + "\n ]," + + "\n [" + + "\n \"TAB\"," + + "\n \"CTRL\"," + + "\n \"ALT\"," + + "\n \"LEFT\"," + + "\n \"DOWN\"," + + "\n \"RIGHT\"," + + "\n \"PGDN\"" + + "\n ]" + + "\n]"; } diff --git a/common/src/main/java/com/itsaky/androidide/models/DexPair.java b/common/src/main/java/com/itsaky/androidide/models/DexPair.java index 32d1b3454..84dcbf01e 100755 --- a/common/src/main/java/com/itsaky/androidide/models/DexPair.java +++ b/common/src/main/java/com/itsaky/androidide/models/DexPair.java @@ -22,41 +22,41 @@ import android.os.Parcelable; public class DexPair implements Parcelable { - @Override - public int describeContents() { - return 0; - } + @Override + public int describeContents() { + return 0; + } - @Override - public void writeToParcel(Parcel p1, int p2) { - p1.writeString(first); - p1.writeString(second); - } + @Override + public void writeToParcel(Parcel p1, int p2) { + p1.writeString(first); + p1.writeString(second); + } - public String first; - public String second; + public String first; + public String second; - private DexPair(Parcel in) { - first = in.readString(); - second = in.readString(); - } + private DexPair(Parcel in) { + first = in.readString(); + second = in.readString(); + } - public DexPair(String first, String second) { - this.first = first; - this.second = second; - } + public DexPair(String first, String second) { + this.first = first; + this.second = second; + } - public static final Creator CREATOR = - new Creator() { + public static final Creator CREATOR = + new Creator() { - @Override - public DexPair createFromParcel(Parcel p1) { - return new DexPair(p1); - } + @Override + public DexPair createFromParcel(Parcel p1) { + return new DexPair(p1); + } - @Override - public DexPair[] newArray(int p1) { - return new DexPair[p1]; - } - }; + @Override + public DexPair[] newArray(int p1) { + return new DexPair[p1]; + } + }; } diff --git a/common/src/main/java/com/itsaky/androidide/models/SaveResult.java b/common/src/main/java/com/itsaky/androidide/models/SaveResult.java index 779ce62d7..dbc410bd1 100755 --- a/common/src/main/java/com/itsaky/androidide/models/SaveResult.java +++ b/common/src/main/java/com/itsaky/androidide/models/SaveResult.java @@ -20,16 +20,16 @@ package com.itsaky.androidide.models; /** Result obtained when files are saved */ public final class SaveResult { - /** Were any Gradle files saved? */ - public boolean gradleSaved = false; + /** Were any Gradle files saved? */ + public boolean gradleSaved = false; - /** Were any XML files saved? */ - public boolean xmlSaved = false; + /** Were any XML files saved? */ + public boolean xmlSaved = false; - public SaveResult() {} + public SaveResult() {} - public SaveResult(boolean gradleSaved, boolean xmlSaved) { - this.gradleSaved = gradleSaved; - this.xmlSaved = xmlSaved; - } + public SaveResult(boolean gradleSaved, boolean xmlSaved) { + this.gradleSaved = gradleSaved; + this.xmlSaved = xmlSaved; + } } diff --git a/common/src/main/java/com/itsaky/androidide/models/SheetOption.java b/common/src/main/java/com/itsaky/androidide/models/SheetOption.java index 8d5919282..6f1b1fa3e 100755 --- a/common/src/main/java/com/itsaky/androidide/models/SheetOption.java +++ b/common/src/main/java/com/itsaky/androidide/models/SheetOption.java @@ -18,35 +18,35 @@ package com.itsaky.androidide.models; public final class SheetOption { - public int id; - public int iconRes; - public int titleRes; - public Object extra; + public int id; + public int iconRes; + public int titleRes; + public Object extra; - public SheetOption(int id, int iconRes, int titleRes) { - this.id = id; - this.iconRes = iconRes; - this.titleRes = titleRes; - } - - public SheetOption(int id, int iconRes, int titleRes, Object extra) { - this.id = id; - this.iconRes = iconRes; - this.titleRes = titleRes; - this.extra = extra; - } - - public SheetOption setExtra(Object extra) { - this.extra = extra; - return this; - } - - @Override - public boolean equals(Object obj) { - if (obj != null && obj instanceof SheetOption) { - SheetOption that = (SheetOption) obj; - return this.iconRes == that.iconRes && this.titleRes == that.titleRes; + public SheetOption(int id, int iconRes, int titleRes) { + this.id = id; + this.iconRes = iconRes; + this.titleRes = titleRes; + } + + public SheetOption(int id, int iconRes, int titleRes, Object extra) { + this.id = id; + this.iconRes = iconRes; + this.titleRes = titleRes; + this.extra = extra; + } + + public SheetOption setExtra(Object extra) { + this.extra = extra; + return this; + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof SheetOption) { + SheetOption that = (SheetOption) obj; + return this.iconRes == that.iconRes && this.titleRes == that.titleRes; + } + return false; } - return false; - } } diff --git a/common/src/main/java/com/itsaky/androidide/shell/CommonProcessExecutor.java b/common/src/main/java/com/itsaky/androidide/shell/CommonProcessExecutor.java index ea56d21d6..c120a59d7 100755 --- a/common/src/main/java/com/itsaky/androidide/shell/CommonProcessExecutor.java +++ b/common/src/main/java/com/itsaky/androidide/shell/CommonProcessExecutor.java @@ -18,6 +18,7 @@ package com.itsaky.androidide.shell; import com.itsaky.androidide.utils.Environment; + import java.io.File; import java.io.IOException; import java.util.Map; @@ -25,97 +26,100 @@ import java.util.Map; /** Creates subprocesses using a {@link ProcessBuilder} */ public class CommonProcessExecutor implements IProcessExecutor { - final File HOME = getHome(); + final File HOME = getHome(); - @Override - public int exec(ProcessStreamsHolder holder, String... args) - throws IOException, InterruptedException { - return exec(holder, true, args); - } - - @Override - public int exec(ProcessStreamsHolder holder, boolean redirectErr, String... args) - throws IOException, InterruptedException { - return exec(holder, HOME.getAbsolutePath(), redirectErr, args); - } - - @Override - public int exec(ProcessStreamsHolder holder, String cwd, boolean redirectErr, String[] args) - throws IOException, InterruptedException { - final Process proc = newProcess(args, cwd, redirectErr, holder); - return proc.waitFor(); - } - - @Override - public void execAsync( - ProcessStreamsHolder holder, IProcessExitListener onExit, boolean redirectErr, String... args) - throws IOException { - execAsync(holder, onExit, HOME.getAbsolutePath(), redirectErr, args); - } - - @Override - public void execAsync( - ProcessStreamsHolder holder, - IProcessExitListener onExit, - String cwd, - boolean redirectErr, - String... args) - throws IOException { - final Process proc = newProcess(args, cwd, redirectErr, holder); - final Thread exitListener = - new Thread(new ExitListenerRunnable(proc, onExit), "ProcessExitListener"); - exitListener.start(); - } - - private Process newProcess( - String[] args, String cwd, boolean redirectErr, ProcessStreamsHolder holder) - throws IOException { - return newProcess(args, new File(cwd), redirectErr, holder); - } - - private Process newProcess( - String[] args, File cwd, boolean redirectErr, ProcessStreamsHolder holder) - throws IOException { - final ProcessBuilder builder = new ProcessBuilder(args); - builder.directory(cwd); - builder.redirectErrorStream(redirectErr); - builder.environment().putAll(getEnv()); - - final Process proc = builder.start(); - holder.in = proc.getInputStream(); - holder.err = proc.getErrorStream(); - holder.out = proc.getOutputStream(); - - return proc; - } - - private static File getHome() { - return Environment.HOME; - } - - private static Map getEnv() { - return Environment.getEnvironment(); - } - - private static class ExitListenerRunnable implements Runnable { - - private final Process proc; - private final IProcessExitListener listener; - - public ExitListenerRunnable(Process proc, IProcessExitListener listener) { - this.proc = proc; - this.listener = listener; + @Override + public int exec(ProcessStreamsHolder holder, String... args) + throws IOException, InterruptedException { + return exec(holder, true, args); } @Override - public void run() { - try { - proc.waitFor(); - } catch (InterruptedException e) { - // Ignored - } - - listener.onExit(proc.exitValue()); + public int exec(ProcessStreamsHolder holder, boolean redirectErr, String... args) + throws IOException, InterruptedException { + return exec(holder, HOME.getAbsolutePath(), redirectErr, args); + } + + @Override + public int exec(ProcessStreamsHolder holder, String cwd, boolean redirectErr, String[] args) + throws IOException, InterruptedException { + final Process proc = newProcess(args, cwd, redirectErr, holder); + return proc.waitFor(); + } + + @Override + public void execAsync( + ProcessStreamsHolder holder, + IProcessExitListener onExit, + boolean redirectErr, + String... args) + throws IOException { + execAsync(holder, onExit, HOME.getAbsolutePath(), redirectErr, args); + } + + @Override + public void execAsync( + ProcessStreamsHolder holder, + IProcessExitListener onExit, + String cwd, + boolean redirectErr, + String... args) + throws IOException { + final Process proc = newProcess(args, cwd, redirectErr, holder); + final Thread exitListener = + new Thread(new ExitListenerRunnable(proc, onExit), "ProcessExitListener"); + exitListener.start(); + } + + private Process newProcess( + String[] args, String cwd, boolean redirectErr, ProcessStreamsHolder holder) + throws IOException { + return newProcess(args, new File(cwd), redirectErr, holder); + } + + private Process newProcess( + String[] args, File cwd, boolean redirectErr, ProcessStreamsHolder holder) + throws IOException { + final ProcessBuilder builder = new ProcessBuilder(args); + builder.directory(cwd); + builder.redirectErrorStream(redirectErr); + builder.environment().putAll(getEnv()); + + final Process proc = builder.start(); + holder.in = proc.getInputStream(); + holder.err = proc.getErrorStream(); + holder.out = proc.getOutputStream(); + + return proc; + } + + private static File getHome() { + return Environment.HOME; + } + + private static Map getEnv() { + return Environment.getEnvironment(); + } + + private static class ExitListenerRunnable implements Runnable { + + private final Process proc; + private final IProcessExitListener listener; + + public ExitListenerRunnable(Process proc, IProcessExitListener listener) { + this.proc = proc; + this.listener = listener; + } + + @Override + public void run() { + try { + proc.waitFor(); + } catch (InterruptedException e) { + // Ignored + } + + listener.onExit(proc.exitValue()); + } } - } } diff --git a/common/src/main/java/com/itsaky/androidide/shell/IProcessExecutor.java b/common/src/main/java/com/itsaky/androidide/shell/IProcessExecutor.java index f8b57cc72..d3231f184 100755 --- a/common/src/main/java/com/itsaky/androidide/shell/IProcessExecutor.java +++ b/common/src/main/java/com/itsaky/androidide/shell/IProcessExecutor.java @@ -28,60 +28,65 @@ import java.io.IOException; */ public interface IProcessExecutor { - /** - * @see exec(ProcessStreamsHolder, boolean, String[]) - */ - int exec(ProcessStreamsHolder holder, String... args) throws IOException, InterruptedException; + /** + * @see exec(ProcessStreamsHolder, boolean, String[]) + */ + int exec(ProcessStreamsHolder holder, String... args) throws IOException, InterruptedException; - /** - * Executes the given command with its attributes. This method WILL block the current thread. - * - * @param holder A holder which will be provided the process's input, error and output stream - * @param redirectErr Whether to redirect error stream to output stream - * @param args Arguments for {@code cmd} - * @return The exit code of the command - */ - int exec(ProcessStreamsHolder holder, boolean redirectErr, String... args) - throws IOException, InterruptedException; + /** + * Executes the given command with its attributes. This method WILL block the current thread. + * + * @param holder A holder which will be provided the process's input, error and output stream + * @param redirectErr Whether to redirect error stream to output stream + * @param args Arguments for {@code cmd} + * @return The exit code of the command + */ + int exec(ProcessStreamsHolder holder, boolean redirectErr, String... args) + throws IOException, InterruptedException; - /** - * Executes the given command with its attributes. This method WILL block the current thread. - * - * @param holder A holder which will be provided the process's input, error and output stream - * @param redirectErr Whether to redirect error stream to output stream - * @param cwd The working directory of the process - * @param args Arguments for {@code cmd} - * @return The exit code of the command - */ - int exec(ProcessStreamsHolder holder, String cwd, boolean redirectErr, String... args) - throws IOException, InterruptedException; + /** + * Executes the given command with its attributes. This method WILL block the current thread. + * + * @param holder A holder which will be provided the process's input, error and output stream + * @param redirectErr Whether to redirect error stream to output stream + * @param cwd The working directory of the process + * @param args Arguments for {@code cmd} + * @return The exit code of the command + */ + int exec(ProcessStreamsHolder holder, String cwd, boolean redirectErr, String... args) + throws IOException, InterruptedException; - /** - * Executes the given command with its attributes. This method will not block the current thread. - * - * @param holder A holder which will be provided the process's input, error and output stream - * @param redirectErr Whether to redirect error stream to output stream - * @param onExit A listener that will be called once the process exits - * @param args Arguments for {@code cmd} - */ - void execAsync( - ProcessStreamsHolder holder, IProcessExitListener onExit, boolean redirectErr, String... args) - throws IOException; + /** + * Executes the given command with its attributes. This method will not block the current + * thread. + * + * @param holder A holder which will be provided the process's input, error and output stream + * @param redirectErr Whether to redirect error stream to output stream + * @param onExit A listener that will be called once the process exits + * @param args Arguments for {@code cmd} + */ + void execAsync( + ProcessStreamsHolder holder, + IProcessExitListener onExit, + boolean redirectErr, + String... args) + throws IOException; - /** - * Executes the given command with its attributes. This method will not block the current thread. - * - * @param holder A holder which will be provided the process's input, error and output stream - * @param redirectErr Whether to redirect error stream to output stream - * @param cwd The working directory of the process - * @param onExit A listener that will be called once the process exits - * @param args Arguments for {@code cmd} - */ - void execAsync( - ProcessStreamsHolder holder, - IProcessExitListener onExit, - String cwd, - boolean redirectErr, - String... args) - throws IOException; + /** + * Executes the given command with its attributes. This method will not block the current + * thread. + * + * @param holder A holder which will be provided the process's input, error and output stream + * @param redirectErr Whether to redirect error stream to output stream + * @param cwd The working directory of the process + * @param onExit A listener that will be called once the process exits + * @param args Arguments for {@code cmd} + */ + void execAsync( + ProcessStreamsHolder holder, + IProcessExitListener onExit, + String cwd, + boolean redirectErr, + String... args) + throws IOException; } diff --git a/common/src/main/java/com/itsaky/androidide/shell/IProcessExitListener.java b/common/src/main/java/com/itsaky/androidide/shell/IProcessExitListener.java index 270360596..7bd826980 100755 --- a/common/src/main/java/com/itsaky/androidide/shell/IProcessExitListener.java +++ b/common/src/main/java/com/itsaky/androidide/shell/IProcessExitListener.java @@ -20,5 +20,5 @@ package com.itsaky.androidide.shell; public interface IProcessExitListener { - void onExit(int code); + void onExit(int code); } diff --git a/common/src/main/java/com/itsaky/androidide/shell/ProcessExecutorFactory.java b/common/src/main/java/com/itsaky/androidide/shell/ProcessExecutorFactory.java index c1ae432dd..6be24a587 100755 --- a/common/src/main/java/com/itsaky/androidide/shell/ProcessExecutorFactory.java +++ b/common/src/main/java/com/itsaky/androidide/shell/ProcessExecutorFactory.java @@ -17,13 +17,12 @@ **************************************************************************************/ package com.itsaky.androidide.shell; - /** Provides static access to the process executor */ public class ProcessExecutorFactory { - private static final CommonProcessExecutor executor = new CommonProcessExecutor(); + private static final CommonProcessExecutor executor = new CommonProcessExecutor(); - public static IProcessExecutor commonExecutor() { - return executor; - } + public static IProcessExecutor commonExecutor() { + return executor; + } } diff --git a/common/src/main/java/com/itsaky/androidide/shell/ProcessStreamsHolder.java b/common/src/main/java/com/itsaky/androidide/shell/ProcessStreamsHolder.java index 035c58180..25ee4d485 100755 --- a/common/src/main/java/com/itsaky/androidide/shell/ProcessStreamsHolder.java +++ b/common/src/main/java/com/itsaky/androidide/shell/ProcessStreamsHolder.java @@ -24,7 +24,7 @@ import java.io.OutputStream; /** Holder input, error and output streams for a {@link java.lang.Process} */ public class ProcessStreamsHolder { - public InputStream in; - public InputStream err; - public OutputStream out; + public InputStream in; + public InputStream err; + public OutputStream out; } diff --git a/common/src/main/java/com/itsaky/androidide/shell/ShellServer.java b/common/src/main/java/com/itsaky/androidide/shell/ShellServer.java index e662468bf..52c95292f 100755 --- a/common/src/main/java/com/itsaky/androidide/shell/ShellServer.java +++ b/common/src/main/java/com/itsaky/androidide/shell/ShellServer.java @@ -20,7 +20,9 @@ package com.itsaky.androidide.shell; import androidx.annotation.NonNull; + import com.itsaky.androidide.utils.Logger; + import java.io.BufferedReader; import java.io.Closeable; import java.io.File; @@ -34,128 +36,128 @@ import java.util.Map; public class ShellServer extends Thread { - private final Callback callback; - private BufferedReader output; - private Process process; + private final Callback callback; + private BufferedReader output; + private Process process; - private static final Logger LOG = Logger.instance("ShellServer"); + private static final Logger LOG = Logger.instance("ShellServer"); - public ShellServer( - Callback callback, - String command, - String dirPath, - Map env, - boolean redirectErrors) { - this.callback = callback; + public ShellServer( + Callback callback, + String command, + String dirPath, + Map env, + boolean redirectErrors) { + this.callback = callback; - ProcessBuilder processBuilder = new ProcessBuilder(command); - processBuilder.directory(new File(dirPath)); - processBuilder.redirectErrorStream(redirectErrors); - processBuilder.environment().putAll(env); + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.directory(new File(dirPath)); + processBuilder.redirectErrorStream(redirectErrors); + processBuilder.environment().putAll(env); - try { - this.process = processBuilder.start(); - this.output = new BufferedReader(new InputStreamReader(process.getInputStream())); - } catch (Throwable th) { - if (callback != null) { - String out = getFullStackTrace(th).concat("\n"); - callback.output(out); - } - } - } - - public InputStream getProcessInputStream() { - return process.getInputStream(); - } - - public OutputStream getProcessOutputStream() { - return process.getOutputStream(); - } - - public void append(String str) { - append(str, true); - } - - public void bgAppend(String str) { - append(str, false); - } - - public void append(String str, boolean z) { - if (z) { - try { - if (str.endsWith("\n")) { - output(str); - } else { - output(str.concat("\n")); + try { + this.process = processBuilder.start(); + this.output = new BufferedReader(new InputStreamReader(process.getInputStream())); + } catch (Throwable th) { + if (callback != null) { + String out = getFullStackTrace(th).concat("\n"); + callback.output(out); + } } - } catch (Throwable th) { - output(th.toString().concat("\n")); - return; - } } - try { - this.process.getOutputStream().write(str.concat("\n").getBytes()); - this.process.getOutputStream().flush(); - } catch (Throwable e) { - LOG.error("Unable to write to shell server", e); + + public InputStream getProcessInputStream() { + return process.getInputStream(); } - } - public void append(@NonNull String... strArr) { - for (String append : strArr) { - append(append); + public OutputStream getProcessOutputStream() { + return process.getOutputStream(); } - } - public void exit() { - append("exit"); - } - - public void output(String str) { - if (this.callback != null) { - this.callback.output(str); + public void append(String str) { + append(str, true); } - } - @Override - public void run() { - while (true) { - try { - String readLine = this.output.readLine(); - if (readLine == null) { - break; + public void bgAppend(String str) { + append(str, false); + } + + public void append(String str, boolean z) { + if (z) { + try { + if (str.endsWith("\n")) { + output(str); + } else { + output(str.concat("\n")); + } + } catch (Throwable th) { + output(th.toString().concat("\n")); + return; + } + } + try { + this.process.getOutputStream().write(str.concat("\n").getBytes()); + this.process.getOutputStream().flush(); + } catch (Throwable e) { + LOG.error("Unable to write to shell server", e); } - output(readLine.concat("\n")); - } catch (Throwable th) { - output(th.toString().concat("\n")); - } } - closeIOQuietly( - this.output, - this.process.getInputStream(), - this.process.getErrorStream(), - this.process.getOutputStream()); - this.process.destroy(); - } - @NonNull - private String getFullStackTrace(@NonNull Throwable th) { - StringWriter sw = new StringWriter(); - th.printStackTrace(new PrintWriter(sw)); - return sw.toString(); - } - - private void closeIOQuietly(@NonNull Closeable... toClose) { - for (Closeable c : toClose) { - try { - c.close(); - } catch (IOException e) { - // ignored - } + public void append(@NonNull String... strArr) { + for (String append : strArr) { + append(append); + } } - } - public interface Callback { - void output(String charSequence); - } + public void exit() { + append("exit"); + } + + public void output(String str) { + if (this.callback != null) { + this.callback.output(str); + } + } + + @Override + public void run() { + while (true) { + try { + String readLine = this.output.readLine(); + if (readLine == null) { + break; + } + output(readLine.concat("\n")); + } catch (Throwable th) { + output(th.toString().concat("\n")); + } + } + closeIOQuietly( + this.output, + this.process.getInputStream(), + this.process.getErrorStream(), + this.process.getOutputStream()); + this.process.destroy(); + } + + @NonNull + private String getFullStackTrace(@NonNull Throwable th) { + StringWriter sw = new StringWriter(); + th.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } + + private void closeIOQuietly(@NonNull Closeable... toClose) { + for (Closeable c : toClose) { + try { + c.close(); + } catch (IOException e) { + // ignored + } + } + } + + public interface Callback { + void output(String charSequence); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/BootstrapInstaller.java b/common/src/main/java/com/itsaky/androidide/utils/BootstrapInstaller.java index 76242af0a..3d66543e5 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/BootstrapInstaller.java +++ b/common/src/main/java/com/itsaky/androidide/utils/BootstrapInstaller.java @@ -22,12 +22,15 @@ import static com.itsaky.androidide.utils.Environment.SYSROOT; import android.content.Context; import android.system.ErrnoException; import android.system.Os; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Pair; + import com.blankj.utilcode.util.FileUtils; import com.itsaky.androidide.common.R; import com.itsaky.androidide.managers.ToolsManager; + import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; @@ -50,142 +53,156 @@ import java.util.zip.ZipInputStream; */ public class BootstrapInstaller { - private static final Logger LOG = Logger.instance("BootstrapInstaller"); + private static final Logger LOG = Logger.instance("BootstrapInstaller"); - @NonNull - public static CompletableFuture doInstall( - final Context context, @Nullable final UpdateListener listener) { - return CompletableFuture.runAsync( - () -> { - var msg = "Opening bootstrap zip input stream..."; - LOG.debug(msg); - notify(listener, msg); - try (final var assetIn = - context.getAssets().open(ToolsManager.getArchSpecificAsset("bootstrap.zip")); - final var zip = new ZipInputStream(assetIn)) { + @NonNull + public static CompletableFuture doInstall( + final Context context, @Nullable final UpdateListener listener) { + return CompletableFuture.runAsync( + () -> { + var msg = "Opening bootstrap zip input stream..."; + LOG.debug(msg); + notify(listener, msg); + try (final var assetIn = + context.getAssets() + .open( + ToolsManager.getArchSpecificAsset( + "bootstrap.zip")); + final var zip = new ZipInputStream(assetIn)) { - final var buffer = new byte[8096]; - final var symlinks = new ArrayList>(50); + final var buffer = new byte[8096]; + final var symlinks = new ArrayList>(50); - ZipEntry entry; - while ((entry = zip.getNextEntry()) != null) { - msg = "Reading zip entry " + entry.getName(); - notify(listener, msg); - LOG.verbose(msg); - if (entry.getName().equals("SYMLINKS.txt")) { - msg = "Reading SYMLINKS.txt..."; - notify(listener, msg); - LOG.debug(msg); - final var symlinksReader = new BufferedReader(new InputStreamReader(zip)); - String line; - while ((line = symlinksReader.readLine()) != null) { - String[] parts = line.split("←"); - if (parts.length != 2) { - final var err = "Malformed symlink line: " + line; - LOG.error(err); - throw new CompletionException(new InstallationException(err)); - } - String oldPath = parts[0]; - String newPath = SYSROOT.getAbsolutePath() + "/" + parts[1]; - symlinks.add(Pair.create(oldPath, newPath)); + ZipEntry entry; + while ((entry = zip.getNextEntry()) != null) { + msg = "Reading zip entry " + entry.getName(); + notify(listener, msg); + LOG.verbose(msg); + if (entry.getName().equals("SYMLINKS.txt")) { + msg = "Reading SYMLINKS.txt..."; + notify(listener, msg); + LOG.debug(msg); + final var symlinksReader = + new BufferedReader(new InputStreamReader(zip)); + String line; + while ((line = symlinksReader.readLine()) != null) { + String[] parts = line.split("←"); + if (parts.length != 2) { + final var err = "Malformed symlink line: " + line; + LOG.error(err); + throw new CompletionException( + new InstallationException(err)); + } + String oldPath = parts[0]; + String newPath = SYSROOT.getAbsolutePath() + "/" + parts[1]; + symlinks.add(Pair.create(oldPath, newPath)); - final var parentFile = new File(newPath).getParentFile(); - if (!FileUtils.createOrExistsDir(parentFile)) { - LOG.error("Cannot create symlink parent directory."); - throw new CompletionException( - new InstallationException("Unable to create directory: " + parentFile)); - } - } - } else { - String zipEntryName = entry.getName(); - File targetFile = new File(SYSROOT, zipEntryName); - boolean isDirectory = entry.isDirectory(); + final var parentFile = new File(newPath).getParentFile(); + if (!FileUtils.createOrExistsDir(parentFile)) { + LOG.error("Cannot create symlink parent directory."); + throw new CompletionException( + new InstallationException( + "Unable to create directory: " + + parentFile)); + } + } + } else { + String zipEntryName = entry.getName(); + File targetFile = new File(SYSROOT, zipEntryName); + boolean isDirectory = entry.isDirectory(); - final var ___ = isDirectory ? targetFile : targetFile.getParentFile(); - if (!FileUtils.createOrExistsDir(___)) { - LOG.error("Cannot create target file parent directory"); - throw new CompletionException( - new InstallationException("Unable to create directory: " + ___)); - } + final var ___ = + isDirectory ? targetFile : targetFile.getParentFile(); + if (!FileUtils.createOrExistsDir(___)) { + LOG.error("Cannot create target file parent directory"); + throw new CompletionException( + new InstallationException( + "Unable to create directory: " + ___)); + } - // If the file exists and it is not a directory - // Delete that file - final var targetFilePath = targetFile.toPath(); - if (Files.exists(targetFilePath) && !Files.isDirectory(targetFilePath)) { - try { - Files.delete(targetFilePath); - } catch (Throwable th) { - throw new CompletionException(th); - } - } + // If the file exists and it is not a directory + // Delete that file + final var targetFilePath = targetFile.toPath(); + if (Files.exists(targetFilePath) + && !Files.isDirectory(targetFilePath)) { + try { + Files.delete(targetFilePath); + } catch (Throwable th) { + throw new CompletionException(th); + } + } - if (!isDirectory) { - try (final var outStream = new FileOutputStream(targetFile)) { - int readBytes; - while ((readBytes = zip.read(buffer)) != -1) - outStream.write(buffer, 0, readBytes); - } + if (!isDirectory) { + try (final var outStream = new FileOutputStream(targetFile)) { + int readBytes; + while ((readBytes = zip.read(buffer)) != -1) + outStream.write(buffer, 0, readBytes); + } - if (zipEntryName.startsWith("bin/") - || zipEntryName.startsWith("libexec") - || zipEntryName.startsWith("lib/apt/apt-helper") - || zipEntryName.startsWith("lib/apt/methods")) { + if (zipEntryName.startsWith("bin/") + || zipEntryName.startsWith("libexec") + || zipEntryName.startsWith("lib/apt/apt-helper") + || zipEntryName.startsWith("lib/apt/methods")) { - notify(listener, context.getString(R.string.msg_chmod_exec)); - LOG.verbose("chmod 0700", zipEntryName); + notify( + listener, + context.getString(R.string.msg_chmod_exec)); + LOG.verbose("chmod 0700", zipEntryName); - //noinspection OctalInteger - Os.chmod(targetFile.getAbsolutePath(), 0700); - } - } - } - } + //noinspection OctalInteger + Os.chmod(targetFile.getAbsolutePath(), 0700); + } + } + } + } - if (symlinks.isEmpty()) { - LOG.error("No SYMLINKS.txt file encountered..."); - throw new CompletionException( - new InstallationException("No SYMLINKS.txt encountered")); - } + if (symlinks.isEmpty()) { + LOG.error("No SYMLINKS.txt file encountered..."); + throw new CompletionException( + new InstallationException("No SYMLINKS.txt encountered")); + } - for (Pair symlink : symlinks) { - notify( - listener, context.getString(R.string.msg_linking, symlink.second, symlink.first)); + for (Pair symlink : symlinks) { + notify( + listener, + context.getString( + R.string.msg_linking, symlink.second, symlink.first)); - final var target = Paths.get(symlink.second); - if (Files.exists(target) && !Files.isDirectory(target)) { - try { - Files.delete(target); - } catch (Throwable throwable) { - throw new CompletionException(throwable); - } - } - Os.symlink(symlink.first, symlink.second); - } + final var target = Paths.get(symlink.second); + if (Files.exists(target) && !Files.isDirectory(target)) { + try { + Files.delete(target); + } catch (Throwable throwable) { + throw new CompletionException(throwable); + } + } + Os.symlink(symlink.first, symlink.second); + } - notify(listener, context.getString(R.string.msg_extracting_hooks)); - ToolsManager.extractLibHooks(); + notify(listener, context.getString(R.string.msg_extracting_hooks)); + ToolsManager.extractLibHooks(); - LOG.info("Bootstrap packages installed successfully."); - } catch (IOException | ErrnoException e) { - LOG.error("Failed to extract bootstrap", e); - throw new RuntimeException(e); - } - }); - } - - private static void notify(@Nullable UpdateListener listener, String message) { - if (listener != null) { - listener.onUpdate(message); + LOG.info("Bootstrap packages installed successfully."); + } catch (IOException | ErrnoException e) { + LOG.error("Failed to extract bootstrap", e); + throw new RuntimeException(e); + } + }); } - } - public interface UpdateListener { - void onUpdate(String message); - } - - public static class InstallationException extends RuntimeException { - public InstallationException(String message) { - super(message); + private static void notify(@Nullable UpdateListener listener, String message) { + if (listener != null) { + listener.onUpdate(message); + } + } + + public interface UpdateListener { + void onUpdate(String message); + } + + public static class InstallationException extends RuntimeException { + public InstallationException(String message) { + super(message); + } } - } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/CharSequenceInputStream.java b/common/src/main/java/com/itsaky/androidide/utils/CharSequenceInputStream.java index ecefaefdc..85da071c0 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/CharSequenceInputStream.java +++ b/common/src/main/java/com/itsaky/androidide/utils/CharSequenceInputStream.java @@ -37,258 +37,261 @@ import java.util.Objects; */ public class CharSequenceInputStream extends InputStream { - private static final int BUFFER_SIZE = 2048; + private static final int BUFFER_SIZE = 2048; - private static final int NO_MARK = -1; + private static final int NO_MARK = -1; - private final CharsetEncoder charsetEncoder; - private final CharBuffer cBuf; - private final ByteBuffer bBuf; + private final CharsetEncoder charsetEncoder; + private final CharBuffer cBuf; + private final ByteBuffer bBuf; - private int cBufMark; // position in cBuf - private int bBufMark; // position in bBuf + private int cBufMark; // position in cBuf + private int bBufMark; // position in bBuf - /** - * Constructs a new instance with a buffer size of 2048. - * - * @param cs the input character sequence. - * @param charset the character set name to use. - * @throws IllegalArgumentException if the buffer is not large enough to hold a complete - * character. - */ - public CharSequenceInputStream(final CharSequence cs, final Charset charset) { - this(cs, charset, BUFFER_SIZE); - } - - /** - * Constructs a new instance. - * - * @param cs the input character sequence. - * @param charset the character set name to use, null maps to the default Charset. - * @param bufferSize the buffer size to use. - * @throws IllegalArgumentException if the buffer is not large enough to hold a complete - * character. - */ - public CharSequenceInputStream( - final CharSequence cs, final Charset charset, final int bufferSize) { - // @formatter:off - this.charsetEncoder = - toCharset(charset) - .newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - // @formatter:on - // Ensure that buffer is long enough to hold a complete character - this.bBuf = - ByteBuffer.allocate(ReaderInputStream.checkMinBufferSize(charsetEncoder, bufferSize)); - this.bBuf.flip(); - this.cBuf = CharBuffer.wrap(cs); - this.cBufMark = NO_MARK; - this.bBufMark = NO_MARK; - } - - /** - * Constructs a new instance with a buffer size of 2048. - * - * @param cs the input character sequence. - * @param charset the character set name to use. - * @throws IllegalArgumentException if the buffer is not large enough to hold a complete - * character. - */ - public CharSequenceInputStream(final CharSequence cs, final String charset) { - this(cs, charset, BUFFER_SIZE); - } - - /** - * Constructs a new instance. - * - * @param cs the input character sequence. - * @param charset the character set name to use, null maps to the default Charset. - * @param bufferSize the buffer size to use. - * @throws IllegalArgumentException if the buffer is not large enough to hold a complete - * character. - */ - public CharSequenceInputStream( - final CharSequence cs, final String charset, final int bufferSize) { - this(cs, toCharset(charset), bufferSize); - } - - /** - * Return an estimate of the number of bytes remaining in the byte stream. - * - * @return the count of bytes that can be read without blocking (or returning EOF). - * @throws IOException if an error occurs (probably not possible). - */ - @Override - public int available() throws IOException { - // The cached entries are in bbuf; since encoding always creates at least one byte - // per character, we can add the two to get a better estimate (e.g. if bbuf is empty) - // Note that the previous implementation (2.4) could return zero even though there were - // encoded bytes still available. - return this.bBuf.remaining() + this.cBuf.remaining(); - } - - @Override - public void close() throws IOException { - // noop - } - - /** - * Fills the byte output buffer from the input char buffer. - * - * @throws CharacterCodingException an error encoding data. - */ - private void fillBuffer() throws CharacterCodingException { - this.bBuf.compact(); - final CoderResult result = this.charsetEncoder.encode(this.cBuf, this.bBuf, true); - if (result.isError()) { - result.throwException(); + /** + * Constructs a new instance with a buffer size of 2048. + * + * @param cs the input character sequence. + * @param charset the character set name to use. + * @throws IllegalArgumentException if the buffer is not large enough to hold a complete + * character. + */ + public CharSequenceInputStream(final CharSequence cs, final Charset charset) { + this(cs, charset, BUFFER_SIZE); } - this.bBuf.flip(); - } - /** - * Gets the CharsetEncoder. - * - * @return the CharsetEncoder. - */ - CharsetEncoder getCharsetEncoder() { - return charsetEncoder; - } - - /** - * {@inheritDoc} - * - * @param readlimit max read limit (ignored). - */ - @Override - public synchronized void mark(final int readlimit) { - this.cBufMark = this.cBuf.position(); - this.bBufMark = this.bBuf.position(); - this.cBuf.mark(); - this.bBuf.mark(); - // It would be nice to be able to use mark & reset on the cbuf and bbuf; - // however the bbuf is re-used so that won't work - } - - @Override - public boolean markSupported() { - return true; - } - - @Override - public int read() throws IOException { - for (; ; ) { - if (this.bBuf.hasRemaining()) { - return this.bBuf.get() & 0xFF; - } - fillBuffer(); - if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) { - return -1; - } + /** + * Constructs a new instance. + * + * @param cs the input character sequence. + * @param charset the character set name to use, null maps to the default Charset. + * @param bufferSize the buffer size to use. + * @throws IllegalArgumentException if the buffer is not large enough to hold a complete + * character. + */ + public CharSequenceInputStream( + final CharSequence cs, final Charset charset, final int bufferSize) { + // @formatter:off + this.charsetEncoder = + toCharset(charset) + .newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + // @formatter:on + // Ensure that buffer is long enough to hold a complete character + this.bBuf = + ByteBuffer.allocate( + ReaderInputStream.checkMinBufferSize(charsetEncoder, bufferSize)); + this.bBuf.flip(); + this.cBuf = CharBuffer.wrap(cs); + this.cBufMark = NO_MARK; + this.bBufMark = NO_MARK; } - } - @Override - public int read(final byte[] b) throws IOException { - return read(b, 0, b.length); - } + /** + * Constructs a new instance with a buffer size of 2048. + * + * @param cs the input character sequence. + * @param charset the character set name to use. + * @throws IllegalArgumentException if the buffer is not large enough to hold a complete + * character. + */ + public CharSequenceInputStream(final CharSequence cs, final String charset) { + this(cs, charset, BUFFER_SIZE); + } - @Override - public int read(final byte[] array, int off, int len) throws IOException { - Objects.requireNonNull(array, "array"); - if (len < 0 || (off + len) > array.length) { - throw new IndexOutOfBoundsException( - "Array Size=" + array.length + ", offset=" + off + ", length=" + len); + /** + * Constructs a new instance. + * + * @param cs the input character sequence. + * @param charset the character set name to use, null maps to the default Charset. + * @param bufferSize the buffer size to use. + * @throws IllegalArgumentException if the buffer is not large enough to hold a complete + * character. + */ + public CharSequenceInputStream( + final CharSequence cs, final String charset, final int bufferSize) { + this(cs, toCharset(charset), bufferSize); } - if (len == 0) { - return 0; // must return 0 for zero length read + + /** + * Return an estimate of the number of bytes remaining in the byte stream. + * + * @return the count of bytes that can be read without blocking (or returning EOF). + * @throws IOException if an error occurs (probably not possible). + */ + @Override + public int available() throws IOException { + // The cached entries are in bbuf; since encoding always creates at least one byte + // per character, we can add the two to get a better estimate (e.g. if bbuf is empty) + // Note that the previous implementation (2.4) could return zero even though there were + // encoded bytes still available. + return this.bBuf.remaining() + this.cBuf.remaining(); } - if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) { - return -1; + + @Override + public void close() throws IOException { + // noop } - int bytesRead = 0; - while (len > 0) { - if (this.bBuf.hasRemaining()) { - final int chunk = Math.min(this.bBuf.remaining(), len); - this.bBuf.get(array, off, chunk); - off += chunk; - len -= chunk; - bytesRead += chunk; - } else { - fillBuffer(); + + /** + * Fills the byte output buffer from the input char buffer. + * + * @throws CharacterCodingException an error encoding data. + */ + private void fillBuffer() throws CharacterCodingException { + this.bBuf.compact(); + final CoderResult result = this.charsetEncoder.encode(this.cBuf, this.bBuf, true); + if (result.isError()) { + result.throwException(); + } + this.bBuf.flip(); + } + + /** + * Gets the CharsetEncoder. + * + * @return the CharsetEncoder. + */ + CharsetEncoder getCharsetEncoder() { + return charsetEncoder; + } + + /** + * {@inheritDoc} + * + * @param readlimit max read limit (ignored). + */ + @Override + public synchronized void mark(final int readlimit) { + this.cBufMark = this.cBuf.position(); + this.bBufMark = this.bBuf.position(); + this.cBuf.mark(); + this.bBuf.mark(); + // It would be nice to be able to use mark & reset on the cbuf and bbuf; + // however the bbuf is re-used so that won't work + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public int read() throws IOException { + for (; ; ) { + if (this.bBuf.hasRemaining()) { + return this.bBuf.get() & 0xFF; + } + fillBuffer(); + if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) { + return -1; + } + } + } + + @Override + public int read(final byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(final byte[] array, int off, int len) throws IOException { + Objects.requireNonNull(array, "array"); + if (len < 0 || (off + len) > array.length) { + throw new IndexOutOfBoundsException( + "Array Size=" + array.length + ", offset=" + off + ", length=" + len); + } + if (len == 0) { + return 0; // must return 0 for zero length read + } if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) { - break; + return -1; } - } - } - return bytesRead == 0 && !this.cBuf.hasRemaining() ? -1 : bytesRead; - } - - @Override - public synchronized void reset() throws IOException { - // - // This is not the most efficient implementation, as it re-encodes from the beginning. - // - // Since the bbuf is re-used, in general it's necessary to re-encode the data. - // - // It should be possible to apply some optimisations however: - // + use mark/reset on the cbuf and bbuf. This would only work if the buffer had not been - // (re)filled since - // the mark. The code would have to catch InvalidMarkException - does not seem possible to check - // if mark is - // valid otherwise. + Try saving the state of the cbuf before each fillBuffer; it might be - // possible to - // restart from there. - // - if (this.cBufMark != NO_MARK) { - // if cbuf is at 0, we have not started reading anything, so skip re-encoding - if (this.cBuf.position() != 0) { - this.charsetEncoder.reset(); - this.cBuf.rewind(); - this.bBuf.rewind(); - this.bBuf.limit(0); // rewind does not clear the buffer - while (this.cBuf.position() < this.cBufMark) { - this.bBuf - .rewind(); // empty the buffer (we only refill when empty during normal processing) - this.bBuf.limit(0); - fillBuffer(); + int bytesRead = 0; + while (len > 0) { + if (this.bBuf.hasRemaining()) { + final int chunk = Math.min(this.bBuf.remaining(), len); + this.bBuf.get(array, off, chunk); + off += chunk; + len -= chunk; + bytesRead += chunk; + } else { + fillBuffer(); + if (!this.bBuf.hasRemaining() && !this.cBuf.hasRemaining()) { + break; + } + } } - } - if (this.cBuf.position() != this.cBufMark) { - throw new IllegalStateException( - "Unexpected CharBuffer position: actual=" - + cBuf.position() - + " " - + "expected=" - + this.cBufMark); - } - this.bBuf.position(this.bBufMark); - this.cBufMark = NO_MARK; - this.bBufMark = NO_MARK; + return bytesRead == 0 && !this.cBuf.hasRemaining() ? -1 : bytesRead; } - } - @Override - public long skip(long n) throws IOException { - // - // This could be made more efficient by using position to skip within the current buffer. - // - long skipped = 0; - while (n > 0 && available() > 0) { - //noinspection ResultOfMethodCallIgnored - this.read(); - n--; - skipped++; + @Override + public synchronized void reset() throws IOException { + // + // This is not the most efficient implementation, as it re-encodes from the beginning. + // + // Since the bbuf is re-used, in general it's necessary to re-encode the data. + // + // It should be possible to apply some optimisations however: + // + use mark/reset on the cbuf and bbuf. This would only work if the buffer had not been + // (re)filled since + // the mark. The code would have to catch InvalidMarkException - does not seem possible to + // check + // if mark is + // valid otherwise. + Try saving the state of the cbuf before each fillBuffer; it might be + // possible to + // restart from there. + // + if (this.cBufMark != NO_MARK) { + // if cbuf is at 0, we have not started reading anything, so skip re-encoding + if (this.cBuf.position() != 0) { + this.charsetEncoder.reset(); + this.cBuf.rewind(); + this.bBuf.rewind(); + this.bBuf.limit(0); // rewind does not clear the buffer + while (this.cBuf.position() < this.cBufMark) { + this.bBuf + .rewind(); // empty the buffer (we only refill when empty during normal + // processing) + this.bBuf.limit(0); + fillBuffer(); + } + } + if (this.cBuf.position() != this.cBufMark) { + throw new IllegalStateException( + "Unexpected CharBuffer position: actual=" + + cBuf.position() + + " " + + "expected=" + + this.cBufMark); + } + this.bBuf.position(this.bBufMark); + this.cBufMark = NO_MARK; + this.bBufMark = NO_MARK; + } } - return skipped; - } - static Charset toCharset(final Charset charset) { - return charset == null ? Charset.defaultCharset() : charset; - } + @Override + public long skip(long n) throws IOException { + // + // This could be made more efficient by using position to skip within the current buffer. + // + long skipped = 0; + while (n > 0 && available() > 0) { + //noinspection ResultOfMethodCallIgnored + this.read(); + n--; + skipped++; + } + return skipped; + } - static Charset toCharset(final String charsetName) throws UnsupportedCharsetException { - return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName); - } + static Charset toCharset(final Charset charset) { + return charset == null ? Charset.defaultCharset() : charset; + } + + static Charset toCharset(final String charsetName) throws UnsupportedCharsetException { + return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/CharSequenceReader.java b/common/src/main/java/com/itsaky/androidide/utils/CharSequenceReader.java index 273fa1f77..154883081 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/CharSequenceReader.java +++ b/common/src/main/java/com/itsaky/androidide/utils/CharSequenceReader.java @@ -17,6 +17,7 @@ package com.itsaky.androidide.utils; import androidx.annotation.NonNull; + import java.io.Reader; import java.io.Serializable; import java.util.Objects; @@ -31,259 +32,262 @@ import java.util.Objects; */ public class CharSequenceReader extends Reader implements Serializable { - private static final long serialVersionUID = 3724187752191401220L; - private final CharSequence charSequence; - private int idx; - private int mark; + private static final long serialVersionUID = 3724187752191401220L; + private final CharSequence charSequence; + private int idx; + private int mark; - /** - * The start index in the character sequence, inclusive. - * - *

When de-serializing a CharSequenceReader that was serialized before this fields was added, - * this field will be initialized to 0, which gives the same behavior as before: start reading - * from the start. - * - * @see #start() - * @since 2.7 - */ - private final int start; - - /** - * The end index in the character sequence, exclusive. - * - *

When de-serializing a CharSequenceReader that was serialized before this fields was added, - * this field will be initialized to {@code null}, which gives the same behavior as before: stop - * reading at the CharSequence's length. If this field was an int instead, it would be initialized - * to 0 when the CharSequenceReader is de-serialized, causing it to not return any characters at - * all. - * - * @see #end() - * @since 2.7 - */ - private final Integer end; - - /** - * Constructs a new instance with the specified character sequence. - * - * @param charSequence The character sequence, may be {@code null} - */ - public CharSequenceReader(final CharSequence charSequence) { - this(charSequence, 0); - } - - /** - * Constructs a new instance with a portion of the specified character sequence. - * - *

The start index is not strictly enforced to be within the bounds of the character sequence. - * This allows the character sequence to grow or shrink in size without risking any {@link - * IndexOutOfBoundsException} to be thrown. Instead, if the character sequence grows smaller than - * the start index, this instance will act as if all characters have been read. - * - * @param charSequence The character sequence, may be {@code null} - * @param start The start index in the character sequence, inclusive - * @throws IllegalArgumentException if the start index is negative - * @since 2.7 - */ - public CharSequenceReader(final CharSequence charSequence, final int start) { - this(charSequence, start, Integer.MAX_VALUE); - } - - /** - * Constructs a new instance with a portion of the specified character sequence. - * - *

The start and end indexes are not strictly enforced to be within the bounds of the character - * sequence. This allows the character sequence to grow or shrink in size without risking any - * {@link IndexOutOfBoundsException} to be thrown. Instead, if the character sequence grows - * smaller than the start index, this instance will act as if all characters have been read; if - * the character sequence grows smaller than the end, this instance will use the actual character - * sequence length. - * - * @param charSequence The character sequence, may be {@code null} - * @param start The start index in the character sequence, inclusive - * @param end The end index in the character sequence, exclusive - * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller - * than the start index - * @since 2.7 - */ - public CharSequenceReader(final CharSequence charSequence, final int start, final int end) { - if (start < 0) { - throw new IllegalArgumentException("Start index is less than zero: " + start); - } - if (end < start) { - throw new IllegalArgumentException("End index is less than start " + start + ": " + end); - } - // Don't check the start and end indexes against the CharSequence, - // to let it grow and shrink without breaking existing behavior. - - this.charSequence = charSequence != null ? charSequence : ""; - this.start = start; - this.end = end; - - this.idx = start; - this.mark = start; - } - - /** Close resets the file back to the start and removes any marked position. */ - @Override - public void close() { - idx = start; - mark = start; - } - - /** - * Returns the index in the character sequence to end reading at, taking into account its length. - * - * @return The end index in the character sequence (exclusive). - */ - private int end() { - /* - * end == null for de-serialized instances that were serialized before start and end were added. - * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence. + /** + * The start index in the character sequence, inclusive. + * + *

When de-serializing a CharSequenceReader that was serialized before this fields was added, + * this field will be initialized to 0, which gives the same behavior as before: start reading + * from the start. + * + * @see #start() + * @since 2.7 */ - return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end); - } + private final int start; - /** - * Mark the current position. - * - * @param readAheadLimit ignored - */ - @Override - public void mark(final int readAheadLimit) { - mark = idx; - } + /** + * The end index in the character sequence, exclusive. + * + *

When de-serializing a CharSequenceReader that was serialized before this fields was added, + * this field will be initialized to {@code null}, which gives the same behavior as before: stop + * reading at the CharSequence's length. If this field was an int instead, it would be + * initialized to 0 when the CharSequenceReader is de-serialized, causing it to not return any + * characters at all. + * + * @see #end() + * @since 2.7 + */ + private final Integer end; - /** - * Mark is supported (returns true). - * - * @return {@code true} - */ - @Override - public boolean markSupported() { - return true; - } - - /** - * Read a single character. - * - * @return the next character from the character sequence or -1 if the end has been reached. - */ - @Override - public int read() { - if (idx >= end()) { - return -1; - } - return charSequence.charAt(idx++); - } - - /** - * Read the specified number of characters into the array. - * - * @param array The array to store the characters in - * @param offset The starting position in the array to store - * @param length The maximum number of characters to read - * @return The number of characters read or -1 if there are no more - */ - @Override - public int read(final char[] array, final int offset, final int length) { - if (idx >= end()) { - return -1; - } - Objects.requireNonNull(array, "array"); - if (length < 0 || offset < 0 || offset + length > array.length) { - throw new IndexOutOfBoundsException( - "Array Size=" + array.length + ", offset=" + offset + ", length=" + length); + /** + * Constructs a new instance with the specified character sequence. + * + * @param charSequence The character sequence, may be {@code null} + */ + public CharSequenceReader(final CharSequence charSequence) { + this(charSequence, 0); } - if (charSequence instanceof String) { - final int count = Math.min(length, end() - idx); - ((String) charSequence).getChars(idx, idx + count, array, offset); - idx += count; - return count; - } - if (charSequence instanceof StringBuilder) { - final int count = Math.min(length, end() - idx); - ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset); - idx += count; - return count; - } - if (charSequence instanceof StringBuffer) { - final int count = Math.min(length, end() - idx); - ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset); - idx += count; - return count; + /** + * Constructs a new instance with a portion of the specified character sequence. + * + *

The start index is not strictly enforced to be within the bounds of the character + * sequence. This allows the character sequence to grow or shrink in size without risking any + * {@link IndexOutOfBoundsException} to be thrown. Instead, if the character sequence grows + * smaller than the start index, this instance will act as if all characters have been read. + * + * @param charSequence The character sequence, may be {@code null} + * @param start The start index in the character sequence, inclusive + * @throws IllegalArgumentException if the start index is negative + * @since 2.7 + */ + public CharSequenceReader(final CharSequence charSequence, final int start) { + this(charSequence, start, Integer.MAX_VALUE); } - int count = 0; - for (int i = 0; i < length; i++) { - final int c = read(); - if (c == -1) { + /** + * Constructs a new instance with a portion of the specified character sequence. + * + *

The start and end indexes are not strictly enforced to be within the bounds of the + * character sequence. This allows the character sequence to grow or shrink in size without + * risking any {@link IndexOutOfBoundsException} to be thrown. Instead, if the character + * sequence grows smaller than the start index, this instance will act as if all characters have + * been read; if the character sequence grows smaller than the end, this instance will use the + * actual character sequence length. + * + * @param charSequence The character sequence, may be {@code null} + * @param start The start index in the character sequence, inclusive + * @param end The end index in the character sequence, exclusive + * @throws IllegalArgumentException if the start index is negative, or if the end index is + * smaller than the start index + * @since 2.7 + */ + public CharSequenceReader(final CharSequence charSequence, final int start, final int end) { + if (start < 0) { + throw new IllegalArgumentException("Start index is less than zero: " + start); + } + if (end < start) { + throw new IllegalArgumentException( + "End index is less than start " + start + ": " + end); + } + // Don't check the start and end indexes against the CharSequence, + // to let it grow and shrink without breaking existing behavior. + + this.charSequence = charSequence != null ? charSequence : ""; + this.start = start; + this.end = end; + + this.idx = start; + this.mark = start; + } + + /** Close resets the file back to the start and removes any marked position. */ + @Override + public void close() { + idx = start; + mark = start; + } + + /** + * Returns the index in the character sequence to end reading at, taking into account its + * length. + * + * @return The end index in the character sequence (exclusive). + */ + private int end() { + /* + * end == null for de-serialized instances that were serialized before start and end were added. + * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence. + */ + return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end); + } + + /** + * Mark the current position. + * + * @param readAheadLimit ignored + */ + @Override + public void mark(final int readAheadLimit) { + mark = idx; + } + + /** + * Mark is supported (returns true). + * + * @return {@code true} + */ + @Override + public boolean markSupported() { + return true; + } + + /** + * Read a single character. + * + * @return the next character from the character sequence or -1 if the end has been reached. + */ + @Override + public int read() { + if (idx >= end()) { + return -1; + } + return charSequence.charAt(idx++); + } + + /** + * Read the specified number of characters into the array. + * + * @param array The array to store the characters in + * @param offset The starting position in the array to store + * @param length The maximum number of characters to read + * @return The number of characters read or -1 if there are no more + */ + @Override + public int read(final char[] array, final int offset, final int length) { + if (idx >= end()) { + return -1; + } + Objects.requireNonNull(array, "array"); + if (length < 0 || offset < 0 || offset + length > array.length) { + throw new IndexOutOfBoundsException( + "Array Size=" + array.length + ", offset=" + offset + ", length=" + length); + } + + if (charSequence instanceof String) { + final int count = Math.min(length, end() - idx); + ((String) charSequence).getChars(idx, idx + count, array, offset); + idx += count; + return count; + } + if (charSequence instanceof StringBuilder) { + final int count = Math.min(length, end() - idx); + ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset); + idx += count; + return count; + } + if (charSequence instanceof StringBuffer) { + final int count = Math.min(length, end() - idx); + ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset); + idx += count; + return count; + } + + int count = 0; + for (int i = 0; i < length; i++) { + final int c = read(); + if (c == -1) { + return count; + } + array[offset + i] = (char) c; + count++; + } return count; - } - array[offset + i] = (char) c; - count++; } - return count; - } - /** - * Tells whether this stream is ready to be read. - * - * @return {@code true} if more characters from the character sequence are available, or {@code - * false} otherwise. - */ - @Override - public boolean ready() { - return idx < end(); - } - - /** - * Reset the reader to the last marked position (or the beginning if mark has not been called). - */ - @Override - public void reset() { - idx = mark; - } - - /** - * Skip the specified number of characters. - * - * @param n The number of characters to skip - * @return The actual number of characters skipped - */ - @Override - public long skip(final long n) { - if (n < 0) { - throw new IllegalArgumentException("Number of characters to skip is less than zero: " + n); + /** + * Tells whether this stream is ready to be read. + * + * @return {@code true} if more characters from the character sequence are available, or {@code + * false} otherwise. + */ + @Override + public boolean ready() { + return idx < end(); } - if (idx >= end()) { - return 0; + + /** + * Reset the reader to the last marked position (or the beginning if mark has not been called). + */ + @Override + public void reset() { + idx = mark; } - final int dest = (int) Math.min(end(), idx + n); - final int count = dest - idx; - idx = dest; - return count; - } - /** - * Returns the index in the character sequence to start reading from, taking into account its - * length. - * - * @return The start index in the character sequence (inclusive). - */ - private int start() { - return Math.min(charSequence.length(), start); - } + /** + * Skip the specified number of characters. + * + * @param n The number of characters to skip + * @return The actual number of characters skipped + */ + @Override + public long skip(final long n) { + if (n < 0) { + throw new IllegalArgumentException( + "Number of characters to skip is less than zero: " + n); + } + if (idx >= end()) { + return 0; + } + final int dest = (int) Math.min(end(), idx + n); + final int count = dest - idx; + idx = dest; + return count; + } - /** - * Return a String representation of the underlying character sequence. - * - * @return The contents of the character sequence - */ - @NonNull - @Override - public String toString() { - final CharSequence subSequence = charSequence.subSequence(start(), end()); - return subSequence.toString(); - } + /** + * Returns the index in the character sequence to start reading from, taking into account its + * length. + * + * @return The start index in the character sequence (inclusive). + */ + private int start() { + return Math.min(charSequence.length(), start); + } + + /** + * Return a String representation of the underlying character sequence. + * + * @return The contents of the character sequence + */ + @NonNull + @Override + public String toString() { + final CharSequence subSequence = charSequence.subSequence(start(), end()); + return subSequence.toString(); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/Environment.java b/common/src/main/java/com/itsaky/androidide/utils/Environment.java index b517d9492..7f8577913 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/Environment.java +++ b/common/src/main/java/com/itsaky/androidide/utils/Environment.java @@ -18,10 +18,13 @@ package com.itsaky.androidide.utils; import android.annotation.SuppressLint; import android.os.Build; + import androidx.annotation.NonNull; + import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.itsaky.androidide.app.BaseApplication; + import java.io.File; import java.io.StringReader; import java.util.ArrayList; @@ -33,244 +36,244 @@ import java.util.Properties; @SuppressLint("SdCardPath") public final class Environment { - public static File ROOT; - public static File SYSROOT; - public static File HOME; - public static File ANDROIDIDE_HOME; - public static File JAVA_HOME; - public static File ANDROID_HOME; - public static File TMP_DIR; - public static File BIN_DIR; - public static File LIB_DIR; - public static File PROJECTS_DIR; - public static File IDE_PROPS_FILE; - public static File LIB_HOOK; - public static File LIB_HOOK2; + public static File ROOT; + public static File SYSROOT; + public static File HOME; + public static File ANDROIDIDE_HOME; + public static File JAVA_HOME; + public static File ANDROID_HOME; + public static File TMP_DIR; + public static File BIN_DIR; + public static File LIB_DIR; + public static File PROJECTS_DIR; + public static File IDE_PROPS_FILE; + public static File LIB_HOOK; + public static File LIB_HOOK2; - /** - * JDK modules used by the java language server for completions. This version of JDK modules - * contains only the classes included in android.jar - */ - public static File COMPILER_MODULE; + /** + * JDK modules used by the java language server for completions. This version of JDK modules + * contains only the classes included in android.jar + */ + public static File COMPILER_MODULE; - public static File INIT_SCRIPT; - public static File PROJECT_DATA_FILE; + public static File INIT_SCRIPT; + public static File PROJECT_DATA_FILE; - public static File GRADLE_PROPS; - public static File GRADLE_USER_HOME; + public static File GRADLE_PROPS; + public static File GRADLE_USER_HOME; - public static File JAVA; - public static File BUSYBOX; - public static File SHELL; - public static File LOGIN_SHELL; + public static File JAVA; + public static File BUSYBOX; + public static File SHELL; + public static File LOGIN_SHELL; - public static File BOOTCLASSPATH; + public static File BOOTCLASSPATH; - public static final String SAMPLE_GRADLE_PROP_CONTENTS = - "# Specify global Gradle properties in this file\n" - + "# These properties will be applicable for every project you build with Gradle."; - public static final Map IDE_PROPS = new HashMap<>(); - public static final Map ENV_VARS = new HashMap<>(); + public static final String SAMPLE_GRADLE_PROP_CONTENTS = + "# Specify global Gradle properties in this file\n" + + "# These properties will be applicable for every project you build with Gradle."; + public static final Map IDE_PROPS = new HashMap<>(); + public static final Map ENV_VARS = new HashMap<>(); - public static final String PROJECTS_FOLDER = "AndroidIDEProjects"; + public static final String PROJECTS_FOLDER = "AndroidIDEProjects"; - private static final String DEFAULT_JAVA_HOME = - "/data/data/com.itsaky.androidide/files/framework/jdk"; - private static final String DEFAULT_ANDROID_HOME = - "/data/data/com.itsaky.androidide/files/framework/android-sdk"; + private static final String DEFAULT_JAVA_HOME = + "/data/data/com.itsaky.androidide/files/framework/jdk"; + private static final String DEFAULT_ANDROID_HOME = + "/data/data/com.itsaky.androidide/files/framework/android-sdk"; - private static final Logger LOG = Logger.instance("Environment"); + private static final Logger LOG = Logger.instance("Environment"); - public static void init() { - final BaseApplication app = BaseApplication.getBaseInstance(); - ROOT = app.getIDEDataDir(); - SYSROOT = mkdirIfNotExits(new File(app.getIDEDataDir(), "sysroot")); - HOME = mkdirIfNotExits(app.getRootDir()); - ANDROIDIDE_HOME = mkdirIfNotExits(new File(HOME, ".androidide")); - TMP_DIR = mkdirIfNotExits(new File(SYSROOT, "tmp")); - BIN_DIR = mkdirIfNotExits(new File(SYSROOT, "bin")); - LIB_DIR = mkdirIfNotExits(new File(SYSROOT, "lib")); - PROJECTS_DIR = mkdirIfNotExits(new File(FileUtil.getExternalStorageDir(), PROJECTS_FOLDER)); - COMPILER_MODULE = mkdirIfNotExits(new File(ANDROIDIDE_HOME, "compiler-module")); + public static void init() { + final BaseApplication app = BaseApplication.getBaseInstance(); + ROOT = app.getIDEDataDir(); + SYSROOT = mkdirIfNotExits(new File(app.getIDEDataDir(), "sysroot")); + HOME = mkdirIfNotExits(app.getRootDir()); + ANDROIDIDE_HOME = mkdirIfNotExits(new File(HOME, ".androidide")); + TMP_DIR = mkdirIfNotExits(new File(SYSROOT, "tmp")); + BIN_DIR = mkdirIfNotExits(new File(SYSROOT, "bin")); + LIB_DIR = mkdirIfNotExits(new File(SYSROOT, "lib")); + PROJECTS_DIR = mkdirIfNotExits(new File(FileUtil.getExternalStorageDir(), PROJECTS_FOLDER)); + COMPILER_MODULE = mkdirIfNotExits(new File(ANDROIDIDE_HOME, "compiler-module")); - IDE_PROPS_FILE = new File(SYSROOT, "etc/ide-environment.properties"); - LIB_HOOK = new File(LIB_DIR, "libhook.so"); - LIB_HOOK2 = new File(LIB_DIR, "libhook2.so"); - PROJECT_DATA_FILE = new File(TMP_DIR, "ide_project"); + IDE_PROPS_FILE = new File(SYSROOT, "etc/ide-environment.properties"); + LIB_HOOK = new File(LIB_DIR, "libhook.so"); + LIB_HOOK2 = new File(LIB_DIR, "libhook2.so"); + PROJECT_DATA_FILE = new File(TMP_DIR, "ide_project"); - INIT_SCRIPT = - new File( - mkdirIfNotExits(new File(app.getIDEDataDir(), "init")), - "data/common/androidide.init.gradle"); - BOOTCLASSPATH = new File(""); - GRADLE_USER_HOME = new File(HOME, ".gradle"); - GRADLE_PROPS = new File(GRADLE_USER_HOME, "gradle.properties"); + INIT_SCRIPT = + new File( + mkdirIfNotExits(new File(app.getIDEDataDir(), "init")), + "data/common/androidide.init.gradle"); + BOOTCLASSPATH = new File(""); + GRADLE_USER_HOME = new File(HOME, ".gradle"); + GRADLE_PROPS = new File(GRADLE_USER_HOME, "gradle.properties"); - IDE_PROPS.putAll(readProperties()); - ANDROID_HOME = new File(readProp("ANDROID_HOME", DEFAULT_ANDROID_HOME)); - JAVA_HOME = new File(readProp("JAVA_HOME", DEFAULT_JAVA_HOME)); + IDE_PROPS.putAll(readProperties()); + ANDROID_HOME = new File(readProp("ANDROID_HOME", DEFAULT_ANDROID_HOME)); + JAVA_HOME = new File(readProp("JAVA_HOME", DEFAULT_JAVA_HOME)); - JAVA = new File(JAVA_HOME, "bin/java"); - BUSYBOX = new File(BIN_DIR, "busybox"); - SHELL = new File(BIN_DIR, "bash"); - LOGIN_SHELL = new File(BIN_DIR, "login"); + JAVA = new File(JAVA_HOME, "bin/java"); + BUSYBOX = new File(BIN_DIR, "busybox"); + SHELL = new File(BIN_DIR, "bash"); + LOGIN_SHELL = new File(BIN_DIR, "login"); - setExecutable(JAVA); - setExecutable(BUSYBOX); - setExecutable(SHELL); + setExecutable(JAVA); + setExecutable(BUSYBOX); + setExecutable(SHELL); - System.setProperty("user.home", HOME.getAbsolutePath()); - System.setProperty("java.home", JAVA_HOME.getAbsolutePath()); - } - - public static void setExecutable(@NonNull final File file) { - if (!file.setExecutable(true)) { - LOG.error("Unable to set executable permissions to file", file); - } - } - - @NonNull - private static Map readProperties() { - final Map props = new HashMap<>(); - if (IDE_PROPS_FILE == null || !IDE_PROPS_FILE.exists()) return props; - try { - Properties p = new Properties(); - p.load(new StringReader(FileIOUtils.readFile2String(IDE_PROPS_FILE))); - for (@SuppressWarnings("rawtypes") Map.Entry entry : p.entrySet()) { - props.put(entry.getKey() + "", entry.getValue() + ""); - } - } catch (Throwable th) { - LOG.error("Unable to read properties file", th); - } - return props; - } - - public static void setBootClasspath(@NonNull File file) { - BOOTCLASSPATH = new File(file.getAbsolutePath()); - } - - public static Map getEnvironment() { - - if (!ENV_VARS.isEmpty()) { - return ENV_VARS; + System.setProperty("user.home", HOME.getAbsolutePath()); + System.setProperty("java.home", JAVA_HOME.getAbsolutePath()); } - ENV_VARS.put("HOME", HOME.getAbsolutePath()); - ENV_VARS.put("ANDROID_HOME", ANDROID_HOME.getAbsolutePath()); - ENV_VARS.put("JAVA_HOME", JAVA_HOME.getAbsolutePath()); - ENV_VARS.put("GRADLE_USER_HOME", GRADLE_USER_HOME.getAbsolutePath()); - ENV_VARS.put("TMPDIR", TMP_DIR.getAbsolutePath()); - ENV_VARS.put("PROJECTS", PROJECTS_DIR.getAbsolutePath()); - ENV_VARS.put("LANG", "en_US.UTF-8"); - ENV_VARS.put("LC_ALL", "en_US.UTF-8"); - - ENV_VARS.put("SYSROOT", SYSROOT.getAbsolutePath()); - - ENV_VARS.put("BUSYBOX", BUSYBOX.getAbsolutePath()); - ENV_VARS.put("SHELL", SHELL.getAbsolutePath()); - ENV_VARS.put("CONFIG_SHELL", SHELL.getAbsolutePath()); - ENV_VARS.put("TERM", "screen"); - - // If LD_LIBRARY_PATH is set, append $SYSROOT/lib to it, - // else set it to $SYSROOT/lib - String ld = System.getenv("LD_LIBRARY_PATH"); - if (ld == null || ld.trim().length() <= 0) { - ld = ""; - } else { - ld += File.pathSeparator; - } - ld += LIB_DIR.getAbsolutePath(); - ENV_VARS.put("LD_LIBRARY_PATH", ld); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R - && BaseApplication.isAarch64() - && LIB_HOOK.exists()) { - // Required for JDK 11 - ENV_VARS.put("LD_PRELOAD", LIB_HOOK.getAbsolutePath()); + public static void setExecutable(@NonNull final File file) { + if (!file.setExecutable(true)) { + LOG.error("Unable to set executable permissions to file", file); + } } - addToEnvIfPresent(ENV_VARS, "ANDROID_ART_ROOT"); - addToEnvIfPresent(ENV_VARS, "DEX2OATBOOTCLASSPATH"); - addToEnvIfPresent(ENV_VARS, "ANDROID_I18N_ROOT"); - addToEnvIfPresent(ENV_VARS, "ANDROID_RUNTIME_ROOT"); - addToEnvIfPresent(ENV_VARS, "ANDROID_TZDATA_ROOT"); - addToEnvIfPresent(ENV_VARS, "ANDROID_DATA"); - addToEnvIfPresent(ENV_VARS, "ANDROID_ROOT"); - - String path = createPath(); - - ENV_VARS.put("PATH", path); - - for (String key : IDE_PROPS.keySet()) { - if (!blacklistedVariables().contains(key.trim())) { - ENV_VARS.put(key, readProp(key, "")); - } + @NonNull + private static Map readProperties() { + final Map props = new HashMap<>(); + if (IDE_PROPS_FILE == null || !IDE_PROPS_FILE.exists()) return props; + try { + Properties p = new Properties(); + p.load(new StringReader(FileIOUtils.readFile2String(IDE_PROPS_FILE))); + for (@SuppressWarnings("rawtypes") Map.Entry entry : p.entrySet()) { + props.put(entry.getKey() + "", entry.getValue() + ""); + } + } catch (Throwable th) { + LOG.error("Unable to read properties file", th); + } + return props; } - return ENV_VARS; - } - - @NonNull - private static String createPath() { - String path = ""; - path += String.format("%s/bin", JAVA_HOME.getAbsolutePath()); - path += String.format(":%s/cmdline-tools/latest/bin", ANDROID_HOME.getAbsolutePath()); - path += String.format(":%s/cmake/bin", ANDROID_HOME.getAbsolutePath()); - path += String.format(":%s/bin", SYSROOT.getAbsolutePath()); - path += String.format(":%s", System.getenv("PATH")); - return path; - } - - public static void addToEnvIfPresent(Map environment, String name) { - String value = System.getenv(name); - if (value != null) { - environment.put(name, value); - } - } - - public static String readPropOrNull(String key) { - return readProp(key, null); - } - - public static String readProp(String key, String defaultValue) { - String value = IDE_PROPS.getOrDefault(key, defaultValue); - if (value == null) { - return defaultValue; - } - if (value.contains("$HOME")) { - value = value.replace("$HOME", HOME.getAbsolutePath()); - } - if (value.contains("$SYSROOT")) { - value = value.replace("$SYSROOT", SYSROOT.getAbsolutePath()); - } - if (value.contains("$PATH")) { - value = value.replace("$PATH", createPath()); - } - return value; - } - - public static File mkdirIfNotExits(File in) { - if (in != null && !in.exists()) { - FileUtils.createOrExistsDir(in); + public static void setBootClasspath(@NonNull File file) { + BOOTCLASSPATH = new File(file.getAbsolutePath()); } - return in; - } + public static Map getEnvironment() { - public static boolean isCompilerModuleInstalled() { - final var modules = new File(COMPILER_MODULE, "lib/modules"); - final var release = new File(COMPILER_MODULE, "release"); - return modules.exists() && modules.isFile() && release.exists() && release.isFile(); - } + if (!ENV_VARS.isEmpty()) { + return ENV_VARS; + } - private static final List blacklist = new ArrayList<>(); + ENV_VARS.put("HOME", HOME.getAbsolutePath()); + ENV_VARS.put("ANDROID_HOME", ANDROID_HOME.getAbsolutePath()); + ENV_VARS.put("JAVA_HOME", JAVA_HOME.getAbsolutePath()); + ENV_VARS.put("GRADLE_USER_HOME", GRADLE_USER_HOME.getAbsolutePath()); + ENV_VARS.put("TMPDIR", TMP_DIR.getAbsolutePath()); + ENV_VARS.put("PROJECTS", PROJECTS_DIR.getAbsolutePath()); + ENV_VARS.put("LANG", "en_US.UTF-8"); + ENV_VARS.put("LC_ALL", "en_US.UTF-8"); - private static List blacklistedVariables() { - if (blacklist.isEmpty()) { - blacklist.add("HOME"); - blacklist.add("SYSROOT"); - blacklist.add("JLS_HOME"); + ENV_VARS.put("SYSROOT", SYSROOT.getAbsolutePath()); + + ENV_VARS.put("BUSYBOX", BUSYBOX.getAbsolutePath()); + ENV_VARS.put("SHELL", SHELL.getAbsolutePath()); + ENV_VARS.put("CONFIG_SHELL", SHELL.getAbsolutePath()); + ENV_VARS.put("TERM", "screen"); + + // If LD_LIBRARY_PATH is set, append $SYSROOT/lib to it, + // else set it to $SYSROOT/lib + String ld = System.getenv("LD_LIBRARY_PATH"); + if (ld == null || ld.trim().length() <= 0) { + ld = ""; + } else { + ld += File.pathSeparator; + } + ld += LIB_DIR.getAbsolutePath(); + ENV_VARS.put("LD_LIBRARY_PATH", ld); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R + && BaseApplication.isAarch64() + && LIB_HOOK.exists()) { + // Required for JDK 11 + ENV_VARS.put("LD_PRELOAD", LIB_HOOK.getAbsolutePath()); + } + + addToEnvIfPresent(ENV_VARS, "ANDROID_ART_ROOT"); + addToEnvIfPresent(ENV_VARS, "DEX2OATBOOTCLASSPATH"); + addToEnvIfPresent(ENV_VARS, "ANDROID_I18N_ROOT"); + addToEnvIfPresent(ENV_VARS, "ANDROID_RUNTIME_ROOT"); + addToEnvIfPresent(ENV_VARS, "ANDROID_TZDATA_ROOT"); + addToEnvIfPresent(ENV_VARS, "ANDROID_DATA"); + addToEnvIfPresent(ENV_VARS, "ANDROID_ROOT"); + + String path = createPath(); + + ENV_VARS.put("PATH", path); + + for (String key : IDE_PROPS.keySet()) { + if (!blacklistedVariables().contains(key.trim())) { + ENV_VARS.put(key, readProp(key, "")); + } + } + + return ENV_VARS; + } + + @NonNull + private static String createPath() { + String path = ""; + path += String.format("%s/bin", JAVA_HOME.getAbsolutePath()); + path += String.format(":%s/cmdline-tools/latest/bin", ANDROID_HOME.getAbsolutePath()); + path += String.format(":%s/cmake/bin", ANDROID_HOME.getAbsolutePath()); + path += String.format(":%s/bin", SYSROOT.getAbsolutePath()); + path += String.format(":%s", System.getenv("PATH")); + return path; + } + + public static void addToEnvIfPresent(Map environment, String name) { + String value = System.getenv(name); + if (value != null) { + environment.put(name, value); + } + } + + public static String readPropOrNull(String key) { + return readProp(key, null); + } + + public static String readProp(String key, String defaultValue) { + String value = IDE_PROPS.getOrDefault(key, defaultValue); + if (value == null) { + return defaultValue; + } + if (value.contains("$HOME")) { + value = value.replace("$HOME", HOME.getAbsolutePath()); + } + if (value.contains("$SYSROOT")) { + value = value.replace("$SYSROOT", SYSROOT.getAbsolutePath()); + } + if (value.contains("$PATH")) { + value = value.replace("$PATH", createPath()); + } + return value; + } + + public static File mkdirIfNotExits(File in) { + if (in != null && !in.exists()) { + FileUtils.createOrExistsDir(in); + } + + return in; + } + + public static boolean isCompilerModuleInstalled() { + final var modules = new File(COMPILER_MODULE, "lib/modules"); + final var release = new File(COMPILER_MODULE, "release"); + return modules.exists() && modules.isFile() && release.exists() && release.isFile(); + } + + private static final List blacklist = new ArrayList<>(); + + private static List blacklistedVariables() { + if (blacklist.isEmpty()) { + blacklist.add("HOME"); + blacklist.add("SYSROOT"); + blacklist.add("JLS_HOME"); + } + return blacklist; } - return blacklist; - } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/FileUtil.java b/common/src/main/java/com/itsaky/androidide/utils/FileUtil.java index 53c3ae1f6..db8165a4b 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/FileUtil.java +++ b/common/src/main/java/com/itsaky/androidide/utils/FileUtil.java @@ -42,6 +42,7 @@ import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.text.TextUtils; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -54,547 +55,555 @@ import java.util.List; public class FileUtil { - private static void createNewFile(String path) { - int lastSep = path.lastIndexOf(File.separator); - if (lastSep > 0) { - String dirPath = path.substring(0, lastSep); - makeDir(dirPath); - } - - File file = new File(path); - - try { - if (!file.exists()) file.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static String readFile(String path) { - createNewFile(path); - - StringBuilder sb = new StringBuilder(); - FileReader fr = null; - try { - fr = new FileReader(new File(path)); - - char[] buff = new char[1024]; - int length = 0; - - while ((length = fr.read(buff)) > 0) { - sb.append(new String(buff, 0, length)); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (fr != null) { - try { - fr.close(); - } catch (Exception e) { - e.printStackTrace(); + private static void createNewFile(String path) { + int lastSep = path.lastIndexOf(File.separator); + if (lastSep > 0) { + String dirPath = path.substring(0, lastSep); + makeDir(dirPath); } - } - } - return sb.toString(); - } + File file = new File(path); - public static List readFileAsLineList(String path) { - createNewFile(path); - List lines = new ArrayList<>(); - FileReader fr = null; - try { - fr = new FileReader(new File(path)); - char[] buff = new char[1024]; - int length = 0; - while ((length = fr.read(buff)) > 0) { - lines.add(new String(buff, 0, length)); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (fr != null) { try { - fr.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - return lines; - } - - public static void writeFile(String path, String str) { - createNewFile(path); - FileWriter fileWriter = null; - - try { - fileWriter = new FileWriter(new File(path), false); - fileWriter.write(str); - fileWriter.flush(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - if (fileWriter != null) fileWriter.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static void copyFile(String sourcePath, String destPath) { - if (!isExistFile(sourcePath)) return; - createNewFile(destPath); - - FileInputStream fis = null; - FileOutputStream fos = null; - - try { - fis = new FileInputStream(sourcePath); - fos = new FileOutputStream(destPath, false); - - byte[] buff = new byte[1024]; - int length = 0; - - while ((length = fis.read(buff)) > 0) { - fos.write(buff, 0, length); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (fis != null) { - try { - fis.close(); + if (!file.exists()) file.createNewFile(); } catch (IOException e) { - e.printStackTrace(); + e.printStackTrace(); } - } - if (fos != null) { + } + + public static String readFile(String path) { + createNewFile(path); + + StringBuilder sb = new StringBuilder(); + FileReader fr = null; try { - fos.close(); + fr = new FileReader(new File(path)); + + char[] buff = new char[1024]; + int length = 0; + + while ((length = fr.read(buff)) > 0) { + sb.append(new String(buff, 0, length)); + } } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - public static void moveFile(String sourcePath, String destPath) { - copyFile(sourcePath, destPath); - deleteFile(sourcePath); - } - - public static void deleteFile(String path) { - File file = new File(path); - - if (!file.exists()) return; - - if (file.isFile()) { - file.delete(); - return; - } - - File[] fileArr = file.listFiles(); - - if (fileArr != null) { - for (File subFile : fileArr) { - if (subFile.isDirectory()) { - deleteFile(subFile.getAbsolutePath()); + e.printStackTrace(); + } finally { + if (fr != null) { + try { + fr.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } } - if (subFile.isFile()) { - subFile.delete(); - } - } + return sb.toString(); } - file.delete(); - } - - public static boolean isExistFile(String path) { - File file = new File(path); - return file.exists(); - } - - public static void makeDir(String path) { - if (!isExistFile(path)) { - File file = new File(path); - file.mkdirs(); - } - } - - public static void listDir(String path, ArrayList list) { - File dir = new File(path); - if (!dir.exists() || dir.isFile()) return; - - File[] listFiles = dir.listFiles(); - if (listFiles == null || listFiles.length <= 0) return; - - if (list == null) return; - list.clear(); - for (File file : listFiles) { - list.add(file.getAbsolutePath()); - } - } - - public static boolean isDirectory(String path) { - if (!isExistFile(path)) return false; - return new File(path).isDirectory(); - } - - public static boolean isFile(String path) { - if (!isExistFile(path)) return false; - return new File(path).isFile(); - } - - public static long getFileLength(String path) { - if (!isExistFile(path)) return 0; - return new File(path).length(); - } - - public static String getExternalStorageDir() { - return Environment.getExternalStorageDirectory().getAbsolutePath(); - } - - public static String getPackageDataDir(Context context) { - return context.getExternalFilesDir(null).getAbsolutePath(); - } - - public static String getPublicDir(String type) { - return Environment.getExternalStoragePublicDirectory(type).getAbsolutePath(); - } - - public static String convertUriToFilePath(final Context context, final Uri uri) { - String path = null; - if (DocumentsContract.isDocumentUri(context, uri)) { - if (isExternalStorageDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - if ("primary".equalsIgnoreCase(type)) { - path = Environment.getExternalStorageDirectory() + "/" + split[1]; - } - } else if (isDownloadsDocument(uri)) { - final String id = DocumentsContract.getDocumentId(uri); - - if (!TextUtils.isEmpty(id)) { - if (id.startsWith("raw:")) { - return id.replaceFirst("raw:", ""); - } + public static List readFileAsLineList(String path) { + createNewFile(path); + List lines = new ArrayList<>(); + FileReader fr = null; + try { + fr = new FileReader(new File(path)); + char[] buff = new char[1024]; + int length = 0; + while ((length = fr.read(buff)) > 0) { + lines.add(new String(buff, 0, length)); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (fr != null) { + try { + fr.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } } - final Uri contentUri = - ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - - path = getDataColumn(context, contentUri, null, null); - } else if (isMediaDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - - final String selection = MediaStore.Audio.Media._ID + "=?"; - final String[] selectionArgs = new String[] {split[1]}; - - path = getDataColumn(context, contentUri, selection, selectionArgs); - } - } else if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme())) { - path = getDataColumn(context, uri, null, null); - } else if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) { - path = uri.getPath(); + return lines; } - if (path != null) { - try { - return URLDecoder.decode(path, "UTF-8"); - } catch (Exception e) { + public static void writeFile(String path, String str) { + createNewFile(path); + FileWriter fileWriter = null; + + try { + fileWriter = new FileWriter(new File(path), false); + fileWriter.write(str); + fileWriter.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (fileWriter != null) fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static void copyFile(String sourcePath, String destPath) { + if (!isExistFile(sourcePath)) return; + createNewFile(destPath); + + FileInputStream fis = null; + FileOutputStream fos = null; + + try { + fis = new FileInputStream(sourcePath); + fos = new FileOutputStream(destPath, false); + + byte[] buff = new byte[1024]; + int length = 0; + + while ((length = fis.read(buff)) > 0) { + fos.write(buff, 0, length); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static void moveFile(String sourcePath, String destPath) { + copyFile(sourcePath, destPath); + deleteFile(sourcePath); + } + + public static void deleteFile(String path) { + File file = new File(path); + + if (!file.exists()) return; + + if (file.isFile()) { + file.delete(); + return; + } + + File[] fileArr = file.listFiles(); + + if (fileArr != null) { + for (File subFile : fileArr) { + if (subFile.isDirectory()) { + deleteFile(subFile.getAbsolutePath()); + } + + if (subFile.isFile()) { + subFile.delete(); + } + } + } + + file.delete(); + } + + public static boolean isExistFile(String path) { + File file = new File(path); + return file.exists(); + } + + public static void makeDir(String path) { + if (!isExistFile(path)) { + File file = new File(path); + file.mkdirs(); + } + } + + public static void listDir(String path, ArrayList list) { + File dir = new File(path); + if (!dir.exists() || dir.isFile()) return; + + File[] listFiles = dir.listFiles(); + if (listFiles == null || listFiles.length <= 0) return; + + if (list == null) return; + list.clear(); + for (File file : listFiles) { + list.add(file.getAbsolutePath()); + } + } + + public static boolean isDirectory(String path) { + if (!isExistFile(path)) return false; + return new File(path).isDirectory(); + } + + public static boolean isFile(String path) { + if (!isExistFile(path)) return false; + return new File(path).isFile(); + } + + public static long getFileLength(String path) { + if (!isExistFile(path)) return 0; + return new File(path).length(); + } + + public static String getExternalStorageDir() { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } + + public static String getPackageDataDir(Context context) { + return context.getExternalFilesDir(null).getAbsolutePath(); + } + + public static String getPublicDir(String type) { + return Environment.getExternalStoragePublicDirectory(type).getAbsolutePath(); + } + + public static String convertUriToFilePath(final Context context, final Uri uri) { + String path = null; + if (DocumentsContract.isDocumentUri(context, uri)) { + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + path = Environment.getExternalStorageDirectory() + "/" + split[1]; + } + } else if (isDownloadsDocument(uri)) { + final String id = DocumentsContract.getDocumentId(uri); + + if (!TextUtils.isEmpty(id)) { + if (id.startsWith("raw:")) { + return id.replaceFirst("raw:", ""); + } + } + + final Uri contentUri = + ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), + Long.valueOf(id)); + + path = getDataColumn(context, contentUri, null, null); + } else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = MediaStore.Audio.Media._ID + "=?"; + final String[] selectionArgs = new String[] {split[1]}; + + path = getDataColumn(context, contentUri, selection, selectionArgs); + } + } else if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme())) { + path = getDataColumn(context, uri, null, null); + } else if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) { + path = uri.getPath(); + } + + if (path != null) { + try { + return URLDecoder.decode(path, "UTF-8"); + } catch (Exception e) { + return null; + } + } return null; - } } - return null; - } - private static String getDataColumn( - Context context, Uri uri, String selection, String[] selectionArgs) { - Cursor cursor = null; + private static String getDataColumn( + Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; - final String column = MediaStore.Images.Media.DATA; - final String[] projection = {column}; + final String column = MediaStore.Images.Media.DATA; + final String[] projection = {column}; - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); - if (cursor != null && cursor.moveToFirst()) { - final int column_index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(column_index); - } - } catch (Exception e) { + try { + cursor = + context.getContentResolver() + .query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch (Exception e) { - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - private static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - private static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - private static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } - - private static void saveBitmap(Bitmap bitmap, String destPath) { - FileOutputStream out = null; - FileUtil.createNewFile(destPath); - try { - out = new FileOutputStream(new File(destPath)); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - if (out != null) { - out.close(); + } finally { + if (cursor != null) { + cursor.close(); + } } - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - public static Bitmap getScaledBitmap(String path, int max) { - Bitmap src = BitmapFactory.decodeFile(path); - - int width = src.getWidth(); - int height = src.getHeight(); - float rate = 0.0f; - - if (width > height) { - rate = max / (float) width; - height = (int) (height * rate); - width = max; - } else { - rate = max / (float) height; - width = (int) (width * rate); - height = max; + return null; } - return Bitmap.createScaledBitmap(src, width, height, true); - } - - public static int calculateInSampleSize( - BitmapFactory.Options options, int reqWidth, int reqHeight) { - final int width = options.outWidth; - final int height = options.outHeight; - int inSampleSize = 1; - - if (height > reqHeight || width > reqWidth) { - final int halfHeight = height / 2; - final int halfWidth = width / 2; - - while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { - inSampleSize *= 2; - } + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); } - return inSampleSize; - } + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } - public static Bitmap decodeSampleBitmapFromPath(String path, int reqWidth, int reqHeight) { - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(path, options); + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + private static void saveBitmap(Bitmap bitmap, String destPath) { + FileOutputStream out = null; + FileUtil.createNewFile(destPath); + try { + out = new FileOutputStream(new File(destPath)); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } - options.inJustDecodeBounds = false; - return BitmapFactory.decodeFile(path, options); - } + public static Bitmap getScaledBitmap(String path, int max) { + Bitmap src = BitmapFactory.decodeFile(path); - public static void resizeBitmapFileRetainRatio(String fromPath, String destPath, int max) { - if (!isExistFile(fromPath)) return; - Bitmap bitmap = getScaledBitmap(fromPath, max); - saveBitmap(bitmap, destPath); - } + int width = src.getWidth(); + int height = src.getHeight(); + float rate = 0.0f; - public static void resizeBitmapFileToSquare(String fromPath, String destPath, int max) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - Bitmap bitmap = Bitmap.createScaledBitmap(src, max, max, true); - saveBitmap(bitmap, destPath); - } + if (width > height) { + rate = max / (float) width; + height = (int) (height * rate); + width = max; + } else { + rate = max / (float) height; + width = (int) (width * rate); + height = max; + } - public static void resizeBitmapFileToCircle(String fromPath, String destPath) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); + return Bitmap.createScaledBitmap(src, width, height, true); + } - final int color = 0xff424242; - final Paint paint = new Paint(); - final Rect rect = new Rect(0, 0, src.getWidth(), src.getHeight()); + public static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + final int width = options.outWidth; + final int height = options.outHeight; + int inSampleSize = 1; - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - paint.setColor(color); - canvas.drawCircle(src.getWidth() / 2, src.getHeight() / 2, src.getWidth() / 2, paint); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(src, rect, rect, paint); + if (height > reqHeight || width > reqWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; - saveBitmap(bitmap, destPath); - } + while ((halfHeight / inSampleSize) >= reqHeight + && (halfWidth / inSampleSize) >= reqWidth) { + inSampleSize *= 2; + } + } - public static void resizeBitmapFileWithRoundedBorder( - String fromPath, String destPath, int pixels) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); + return inSampleSize; + } - final int color = 0xff424242; - final Paint paint = new Paint(); - final Rect rect = new Rect(0, 0, src.getWidth(), src.getHeight()); - final RectF rectF = new RectF(rect); - final float roundPx = pixels; + public static Bitmap decodeSampleBitmapFromPath(String path, int reqWidth, int reqHeight) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, options); - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - paint.setColor(color); - canvas.drawRoundRect(rectF, roundPx, roundPx, paint); + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(src, rect, rect, paint); + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(path, options); + } - saveBitmap(bitmap, destPath); - } + public static void resizeBitmapFileRetainRatio(String fromPath, String destPath, int max) { + if (!isExistFile(fromPath)) return; + Bitmap bitmap = getScaledBitmap(fromPath, max); + saveBitmap(bitmap, destPath); + } - public static void cropBitmapFileFromCenter(String fromPath, String destPath, int w, int h) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); + public static void resizeBitmapFileToSquare(String fromPath, String destPath, int max) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + Bitmap bitmap = Bitmap.createScaledBitmap(src, max, max, true); + saveBitmap(bitmap, destPath); + } - int width = src.getWidth(); - int height = src.getHeight(); + public static void resizeBitmapFileToCircle(String fromPath, String destPath) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + Bitmap bitmap = + Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); - if (width < w && height < h) return; + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, src.getWidth(), src.getHeight()); - int x = 0; - int y = 0; + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawCircle(src.getWidth() / 2, src.getHeight() / 2, src.getWidth() / 2, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(src, rect, rect, paint); - if (width > w) x = (width - w) / 2; + saveBitmap(bitmap, destPath); + } - if (height > h) y = (height - h) / 2; + public static void resizeBitmapFileWithRoundedBorder( + String fromPath, String destPath, int pixels) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + Bitmap bitmap = + Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); - int cw = w; - int ch = h; + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, src.getWidth(), src.getHeight()); + final RectF rectF = new RectF(rect); + final float roundPx = pixels; - if (w > width) cw = width; + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawRoundRect(rectF, roundPx, roundPx, paint); - if (h > height) ch = height; + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(src, rect, rect, paint); - Bitmap bitmap = Bitmap.createBitmap(src, x, y, cw, ch); - saveBitmap(bitmap, destPath); - } + saveBitmap(bitmap, destPath); + } - public static void rotateBitmapFile(String fromPath, String destPath, float angle) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - Matrix matrix = new Matrix(); - matrix.postRotate(angle); - Bitmap bitmap = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); - saveBitmap(bitmap, destPath); - } + public static void cropBitmapFileFromCenter(String fromPath, String destPath, int w, int h) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); - public static void scaleBitmapFile(String fromPath, String destPath, float x, float y) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - Matrix matrix = new Matrix(); - matrix.postScale(x, y); + int width = src.getWidth(); + int height = src.getHeight(); - int w = src.getWidth(); - int h = src.getHeight(); + if (width < w && height < h) return; - Bitmap bitmap = Bitmap.createBitmap(src, 0, 0, w, h, matrix, true); - saveBitmap(bitmap, destPath); - } + int x = 0; + int y = 0; - public static void skewBitmapFile(String fromPath, String destPath, float x, float y) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - Matrix matrix = new Matrix(); - matrix.postSkew(x, y); + if (width > w) x = (width - w) / 2; - int w = src.getWidth(); - int h = src.getHeight(); + if (height > h) y = (height - h) / 2; - Bitmap bitmap = Bitmap.createBitmap(src, 0, 0, w, h, matrix, true); - saveBitmap(bitmap, destPath); - } + int cw = w; + int ch = h; - public static void setBitmapFileColorFilter(String fromPath, String destPath, int color) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - Bitmap bitmap = Bitmap.createBitmap(src, 0, 0, src.getWidth() - 1, src.getHeight() - 1); - Paint p = new Paint(); - ColorFilter filter = new LightingColorFilter(color, 1); - p.setColorFilter(filter); - Canvas canvas = new Canvas(bitmap); - canvas.drawBitmap(bitmap, 0, 0, p); - saveBitmap(bitmap, destPath); - } + if (w > width) cw = width; - public static void setBitmapFileBrightness(String fromPath, String destPath, float brightness) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - ColorMatrix cm = - new ColorMatrix( - new float[] { - 1, 0, 0, 0, brightness, - 0, 1, 0, 0, brightness, - 0, 0, 1, 0, brightness, - 0, 0, 0, 1, 0 - }); + if (h > height) ch = height; - Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig()); - Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(cm)); - canvas.drawBitmap(src, 0, 0, paint); - saveBitmap(bitmap, destPath); - } + Bitmap bitmap = Bitmap.createBitmap(src, x, y, cw, ch); + saveBitmap(bitmap, destPath); + } - public static void setBitmapFileContrast(String fromPath, String destPath, float contrast) { - if (!isExistFile(fromPath)) return; - Bitmap src = BitmapFactory.decodeFile(fromPath); - ColorMatrix cm = - new ColorMatrix( - new float[] { - contrast, 0, 0, 0, 0, 0, contrast, 0, 0, 0, 0, 0, contrast, 0, 0, 0, 0, 0, 1, 0 - }); + public static void rotateBitmapFile(String fromPath, String destPath, float angle) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + Matrix matrix = new Matrix(); + matrix.postRotate(angle); + Bitmap bitmap = + Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); + saveBitmap(bitmap, destPath); + } - Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig()); - Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(cm)); - canvas.drawBitmap(src, 0, 0, paint); + public static void scaleBitmapFile(String fromPath, String destPath, float x, float y) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + Matrix matrix = new Matrix(); + matrix.postScale(x, y); - saveBitmap(bitmap, destPath); - } + int w = src.getWidth(); + int h = src.getHeight(); + + Bitmap bitmap = Bitmap.createBitmap(src, 0, 0, w, h, matrix, true); + saveBitmap(bitmap, destPath); + } + + public static void skewBitmapFile(String fromPath, String destPath, float x, float y) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + Matrix matrix = new Matrix(); + matrix.postSkew(x, y); + + int w = src.getWidth(); + int h = src.getHeight(); + + Bitmap bitmap = Bitmap.createBitmap(src, 0, 0, w, h, matrix, true); + saveBitmap(bitmap, destPath); + } + + public static void setBitmapFileColorFilter(String fromPath, String destPath, int color) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + Bitmap bitmap = Bitmap.createBitmap(src, 0, 0, src.getWidth() - 1, src.getHeight() - 1); + Paint p = new Paint(); + ColorFilter filter = new LightingColorFilter(color, 1); + p.setColorFilter(filter); + Canvas canvas = new Canvas(bitmap); + canvas.drawBitmap(bitmap, 0, 0, p); + saveBitmap(bitmap, destPath); + } + + public static void setBitmapFileBrightness(String fromPath, String destPath, float brightness) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + ColorMatrix cm = + new ColorMatrix( + new float[] { + 1, 0, 0, 0, brightness, + 0, 1, 0, 0, brightness, + 0, 0, 1, 0, brightness, + 0, 0, 0, 1, 0 + }); + + Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig()); + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(cm)); + canvas.drawBitmap(src, 0, 0, paint); + saveBitmap(bitmap, destPath); + } + + public static void setBitmapFileContrast(String fromPath, String destPath, float contrast) { + if (!isExistFile(fromPath)) return; + Bitmap src = BitmapFactory.decodeFile(fromPath); + ColorMatrix cm = + new ColorMatrix( + new float[] { + contrast, 0, 0, 0, 0, 0, contrast, 0, 0, 0, 0, 0, contrast, 0, 0, 0, 0, + 0, 1, 0 + }); + + Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig()); + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(cm)); + canvas.drawBitmap(src, 0, 0, paint); + + saveBitmap(bitmap, destPath); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/InputStreamLineReader.java b/common/src/main/java/com/itsaky/androidide/utils/InputStreamLineReader.java index 66dbe14b1..5c6ce3bf4 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/InputStreamLineReader.java +++ b/common/src/main/java/com/itsaky/androidide/utils/InputStreamLineReader.java @@ -27,28 +27,28 @@ import java.io.InputStreamReader; /** Reads an input stream line by line */ public class InputStreamLineReader implements Runnable { - private final InputStream in; - private final OnReadListener listener; + private final InputStream in; + private final OnReadListener listener; - public InputStreamLineReader(InputStream in, OnReadListener listener) { - this.in = in; - this.listener = listener; - } - - @Override - public void run() { - try { - final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - String line; - while ((line = reader.readLine()) != null && listener != null) { - listener.onRead(line); - } - } catch (Throwable th) { - // ignored + public InputStreamLineReader(InputStream in, OnReadListener listener) { + this.in = in; + this.listener = listener; } - } - public static interface OnReadListener { - void onRead(String line); - } + @Override + public void run() { + try { + final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line; + while ((line = reader.readLine()) != null && listener != null) { + listener.onRead(line); + } + } catch (Throwable th) { + // ignored + } + } + + public static interface OnReadListener { + void onRead(String line); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/InputStreamWriter.java b/common/src/main/java/com/itsaky/androidide/utils/InputStreamWriter.java index 3ed59eed0..f0497bad6 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/InputStreamWriter.java +++ b/common/src/main/java/com/itsaky/androidide/utils/InputStreamWriter.java @@ -31,33 +31,33 @@ import java.io.InputStreamReader; /** Writes an InputStream to a file */ public class InputStreamWriter implements Runnable { - private final InputStream in; - private final File out; + private final InputStream in; + private final File out; - private final BufferedOutputStream os; + private final BufferedOutputStream os; - public InputStreamWriter(InputStream in, File out) throws FileNotFoundException { - this.in = in; - this.out = out; + public InputStreamWriter(InputStream in, File out) throws FileNotFoundException { + this.in = in; + this.out = out; - this.os = new BufferedOutputStream(new FileOutputStream(out, true)); - } - - @Override - public void run() { - try { - final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - while (true) { - final String data = reader.readLine(); - if (data == null) { - break; - } - - os.write(data.concat("\n").getBytes()); - os.flush(); - } - } catch (Throwable th) { - // ignored + this.os = new BufferedOutputStream(new FileOutputStream(out, true)); + } + + @Override + public void run() { + try { + final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + while (true) { + final String data = reader.readLine(); + if (data == null) { + break; + } + + os.write(data.concat("\n").getBytes()); + os.flush(); + } + } catch (Throwable th) { + // ignored + } } - } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/IntProperty.java b/common/src/main/java/com/itsaky/androidide/utils/IntProperty.java index 6c666d19c..dd34ef7d1 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/IntProperty.java +++ b/common/src/main/java/com/itsaky/androidide/utils/IntProperty.java @@ -28,17 +28,17 @@ import android.util.Property; *

Copied to support earlier android versions. */ public abstract class IntProperty extends Property { - public IntProperty(String name) { - super(Integer.class, name); - } - /** - * A type-specific variant of {@link #set(Object, Integer)} that is faster when dealing with - * fields of type int. - */ - public abstract void setValue(T object, int value); + public IntProperty(String name) { + super(Integer.class, name); + } + /** + * A type-specific variant of {@link #set(Object, Integer)} that is faster when dealing with + * fields of type int. + */ + public abstract void setValue(T object, int value); - @Override - public final void set(T object, Integer value) { - setValue(object, value.intValue()); - } + @Override + public final void set(T object, Integer value) { + setValue(object, value.intValue()); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/JavaCharacter.java b/common/src/main/java/com/itsaky/androidide/utils/JavaCharacter.java index c2909ed24..412626931 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/JavaCharacter.java +++ b/common/src/main/java/com/itsaky/androidide/utils/JavaCharacter.java @@ -24,67 +24,67 @@ import java.util.Arrays; */ public class JavaCharacter { - /** Compressed bit set for isJavaIdentifierStart() */ - private static int[] state_start; + /** Compressed bit set for isJavaIdentifierStart() */ + private static int[] state_start; - /** Compressed bit set for isJavaIdentifierPart() */ - private static int[] state_part; + /** Compressed bit set for isJavaIdentifierPart() */ + private static int[] state_part; - /** - * Get bit in compressed bit set - * - * @param values Compressed bit set - * @param bitIndex Target index - * @return Boolean value at the index - */ - private static boolean get(int[] values, int bitIndex) { - return ((values[bitIndex / 32] & (1 << (bitIndex % 32))) != 0); - } - - /** - * Make the given position's bit true - * - * @param values Compressed bit set - * @param bitIndex Index of bit - */ - private static void set(int[] values, int bitIndex) { - values[bitIndex / 32] |= (1 << (bitIndex % 32)); - } - - /** Init maps */ - public static void initMap() { - if (state_start != null) { - return; + /** + * Get bit in compressed bit set + * + * @param values Compressed bit set + * @param bitIndex Target index + * @return Boolean value at the index + */ + private static boolean get(int[] values, int bitIndex) { + return ((values[bitIndex / 32] & (1 << (bitIndex % 32))) != 0); } - state_part = new int[2048]; - state_start = new int[2048]; - Arrays.fill(state_part, 0); - Arrays.fill(state_start, 0); - for (int i = 0; i <= 65535; i++) { - if (Character.isJavaIdentifierPart((char) i)) { - set(state_part, i); - } - if (Character.isJavaIdentifierStart((char) i)) { - set(state_start, i); - } + + /** + * Make the given position's bit true + * + * @param values Compressed bit set + * @param bitIndex Index of bit + */ + private static void set(int[] values, int bitIndex) { + values[bitIndex / 32] |= (1 << (bitIndex % 32)); } - } - /** - * @param key Character - * @return Whether a identifier part - * @see Character#isJavaIdentifierPart(char) - */ - public static boolean isJavaIdentifierPart(int key) { - return get(state_part, key); - } + /** Init maps */ + public static void initMap() { + if (state_start != null) { + return; + } + state_part = new int[2048]; + state_start = new int[2048]; + Arrays.fill(state_part, 0); + Arrays.fill(state_start, 0); + for (int i = 0; i <= 65535; i++) { + if (Character.isJavaIdentifierPart((char) i)) { + set(state_part, i); + } + if (Character.isJavaIdentifierStart((char) i)) { + set(state_start, i); + } + } + } - /** - * @param key Character - * @return Whether a identifier start - * @see Character#isJavaIdentifierStart(char) - */ - public static boolean isJavaIdentifierStart(int key) { - return get(state_start, key); - } + /** + * @param key Character + * @return Whether a identifier part + * @see Character#isJavaIdentifierPart(char) + */ + public static boolean isJavaIdentifierPart(int key) { + return get(state_part, key); + } + + /** + * @param key Character + * @return Whether a identifier start + * @see Character#isJavaIdentifierStart(char) + */ + public static boolean isJavaIdentifierStart(int key) { + return get(state_start, key); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/Logger.java b/common/src/main/java/com/itsaky/androidide/utils/Logger.java index 3ec96460c..1ffe6e82c 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/Logger.java +++ b/common/src/main/java/com/itsaky/androidide/utils/Logger.java @@ -18,12 +18,16 @@ package com.itsaky.androidide.utils; import android.util.Log; + import androidx.annotation.NonNull; + import com.blankj.utilcode.util.ThrowableUtils; + +import org.jetbrains.annotations.Contract; + import java.util.ArrayList; import java.util.List; import java.util.Objects; -import org.jetbrains.annotations.Contract; /** * Logger for the IDE. @@ -35,105 +39,107 @@ import org.jetbrains.annotations.Contract; */ public class Logger { - private String TAG; + private String TAG; - private static Logger instance; - private static final String MSG_SEPARATOR = " "; // Separate messages with a space. - private static final List logListeners = new ArrayList<>(); + private static Logger instance; + private static final String MSG_SEPARATOR = " "; // Separate messages with a space. + private static final List logListeners = new ArrayList<>(); - public static final int DEBUG = 0; - public static final int WARNING = 0; - public static final int ERROR = 0; - public static final int INFO = 0; - public static final int VERBOSE = 0; + public static final int DEBUG = 0; + public static final int WARNING = 0; + public static final int ERROR = 0; + public static final int INFO = 0; + public static final int VERBOSE = 0; - public static Logger instance() { - return instance == null ? instance = createInstance("AndroidIDE") : instance; - } - - public static void addLogListener(LogListener listener) { - logListeners.add(Objects.requireNonNull(listener)); - } - - public static void removeLogListener(LogListener listener) { - logListeners.remove(Objects.requireNonNull(listener)); - } - - @NonNull - @Contract("_ -> new") - private static Logger createInstance(String tag) { - return new Logger(tag); - } - - @NonNull - @Contract("_ -> new") - public static Logger instance(String tag) { - return createInstance(tag); - } - - private Logger(String tag) { - TAG = tag; - } - - public Logger warn(Object... messages) { - final var msg = generateMessage(messages); - Log.w(TAG, msg); - notifyListener(WARNING, msg); - return this; - } - - public Logger debug(Object... messages) { - final var msg = generateMessage(messages); - Log.d(TAG, msg); - notifyListener(DEBUG, msg); - return this; - } - - public Logger error(Object... messages) { - final var msg = generateMessage(messages); - Log.e(TAG, msg); - notifyListener(ERROR, msg); - return this; - } - - public Logger verbose(Object... messages) { - final var msg = generateMessage(messages); - Log.v(TAG, msg); - notifyListener(VERBOSE, msg); - return this; - } - - public Logger info(Object... messages) { - final var msg = generateMessage(messages); - Log.i(TAG, msg); - notifyListener(INFO, msg); - return this; - } - - @NonNull - protected String generateMessage(Object... messages) { - StringBuilder sb = new StringBuilder(); - if (messages == null) { - return "null"; + public static Logger instance() { + return instance == null ? instance = createInstance("AndroidIDE") : instance; } - for (Object msg : messages) { - sb.append( - msg instanceof Throwable ? ThrowableUtils.getFullStackTrace(((Throwable) msg)) : msg); - sb.append(MSG_SEPARATOR); + public static void addLogListener(LogListener listener) { + logListeners.add(Objects.requireNonNull(listener)); } - return sb.toString(); - } - - private void notifyListener(int priority, String msg) { - for (final var listener : logListeners) { - listener.log(priority, TAG, msg); + public static void removeLogListener(LogListener listener) { + logListeners.remove(Objects.requireNonNull(listener)); } - } - /** A listener which can be used to listen to log events. */ - public interface LogListener { - void log(int priority, String tag, String message); - } + @NonNull + @Contract("_ -> new") + private static Logger createInstance(String tag) { + return new Logger(tag); + } + + @NonNull + @Contract("_ -> new") + public static Logger instance(String tag) { + return createInstance(tag); + } + + private Logger(String tag) { + TAG = tag; + } + + public Logger warn(Object... messages) { + final var msg = generateMessage(messages); + Log.w(TAG, msg); + notifyListener(WARNING, msg); + return this; + } + + public Logger debug(Object... messages) { + final var msg = generateMessage(messages); + Log.d(TAG, msg); + notifyListener(DEBUG, msg); + return this; + } + + public Logger error(Object... messages) { + final var msg = generateMessage(messages); + Log.e(TAG, msg); + notifyListener(ERROR, msg); + return this; + } + + public Logger verbose(Object... messages) { + final var msg = generateMessage(messages); + Log.v(TAG, msg); + notifyListener(VERBOSE, msg); + return this; + } + + public Logger info(Object... messages) { + final var msg = generateMessage(messages); + Log.i(TAG, msg); + notifyListener(INFO, msg); + return this; + } + + @NonNull + protected String generateMessage(Object... messages) { + StringBuilder sb = new StringBuilder(); + if (messages == null) { + return "null"; + } + + for (Object msg : messages) { + sb.append( + msg instanceof Throwable + ? ThrowableUtils.getFullStackTrace(((Throwable) msg)) + : msg); + sb.append(MSG_SEPARATOR); + } + + return sb.toString(); + } + + private void notifyListener(int priority, String msg) { + for (final var listener : logListeners) { + listener.log(priority, TAG, msg); + } + } + + /** A listener which can be used to listen to log events. */ + public interface LogListener { + void log(int priority, String tag, String message); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/ReaderInputStream.java b/common/src/main/java/com/itsaky/androidide/utils/ReaderInputStream.java index b421d5538..c14971d5b 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/ReaderInputStream.java +++ b/common/src/main/java/com/itsaky/androidide/utils/ReaderInputStream.java @@ -77,261 +77,261 @@ import java.util.Objects; * @since 2.0 */ public class ReaderInputStream extends InputStream { - private static final int DEFAULT_BUFFER_SIZE = 1024; + private static final int DEFAULT_BUFFER_SIZE = 1024; - static int checkMinBufferSize(final CharsetEncoder charsetEncoder, final int bufferSize) { - final float minRequired = minBufferSize(charsetEncoder); - if (bufferSize < minRequired) { - throw new IllegalArgumentException( - String.format( - "Buffer size %,d must be at least %s for a CharsetEncoder %s.", - bufferSize, minRequired, charsetEncoder.charset().displayName())); + static int checkMinBufferSize(final CharsetEncoder charsetEncoder, final int bufferSize) { + final float minRequired = minBufferSize(charsetEncoder); + if (bufferSize < minRequired) { + throw new IllegalArgumentException( + String.format( + "Buffer size %,d must be at least %s for a CharsetEncoder %s.", + bufferSize, minRequired, charsetEncoder.charset().displayName())); + } + return bufferSize; } - return bufferSize; - } - static float minBufferSize(final CharsetEncoder charsetEncoder) { - return charsetEncoder.maxBytesPerChar() * 2; - } - - private final Reader reader; - - private final CharsetEncoder charsetEncoder; - - /** - * CharBuffer used as input for the decoder. It should be reasonably large as we read data from - * the underlying Reader into this buffer. - */ - private final CharBuffer encoderIn; - /** - * ByteBuffer used as output for the decoder. This buffer can be small as it is only used to - * transfer data from the decoder to the buffer provided by the caller. - */ - private final ByteBuffer encoderOut; - - private CoderResult lastCoderResult; - - private boolean endOfInput; - - /** - * Constructs a new {@link ReaderInputStream} that uses the default character encoding with a - * default input buffer size of {@value #DEFAULT_BUFFER_SIZE} characters. - * - * @param reader the target {@link Reader} - * @deprecated 2.5 use {@link #ReaderInputStream(Reader, Charset)} instead - */ - @Deprecated - public ReaderInputStream(final Reader reader) { - this(reader, Charset.defaultCharset()); - } - - /** - * Constructs a new {@link ReaderInputStream} with a default input buffer size of {@value - * #DEFAULT_BUFFER_SIZE} characters. - * - * @param reader the target {@link Reader} - * @param charset the charset encoding - */ - public ReaderInputStream(final Reader reader, final Charset charset) { - this(reader, charset, DEFAULT_BUFFER_SIZE); - } - - /** - * Constructs a new {@link ReaderInputStream}. - * - * @param reader the target {@link Reader}. - * @param charset the charset encoding. - * @param bufferSize the size of the input buffer in number of characters. - */ - public ReaderInputStream(final Reader reader, final Charset charset, final int bufferSize) { - // @formatter:off - this( - reader, - toCharset(charset) - .newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE), - bufferSize); - // @formatter:on - } - - /** - * Constructs a new {@link ReaderInputStream}. - * - * @param reader the target {@link Reader} - * @param charsetEncoder the charset encoder - * @since 2.1 - */ - public ReaderInputStream(final Reader reader, final CharsetEncoder charsetEncoder) { - this(reader, charsetEncoder, DEFAULT_BUFFER_SIZE); - } - - /** - * Constructs a new {@link ReaderInputStream}. - * - * @param reader the target {@link Reader} - * @param charsetEncoder the charset encoder, null defauls to the default Charset encoder. - * @param bufferSize the size of the input buffer in number of characters - * @since 2.1 - */ - public ReaderInputStream( - final Reader reader, final CharsetEncoder charsetEncoder, final int bufferSize) { - this.reader = reader; - this.charsetEncoder = toCharsetEncoder(charsetEncoder); - this.encoderIn = CharBuffer.allocate(checkMinBufferSize(this.charsetEncoder, bufferSize)); - this.encoderIn.flip(); - this.encoderOut = ByteBuffer.allocate(128); - this.encoderOut.flip(); - } - - /** - * Constructs a new {@link ReaderInputStream} with a default input buffer size of {@value - * #DEFAULT_BUFFER_SIZE} characters. - * - * @param reader the target {@link Reader} - * @param charsetName the name of the charset encoding - */ - public ReaderInputStream(final Reader reader, final String charsetName) { - this(reader, charsetName, DEFAULT_BUFFER_SIZE); - } - - /** - * Constructs a new {@link ReaderInputStream}. - * - * @param reader the target {@link Reader} - * @param charsetName the name of the charset encoding, null maps to the default Charset. - * @param bufferSize the size of the input buffer in number of characters - */ - public ReaderInputStream(final Reader reader, final String charsetName, final int bufferSize) { - this(reader, toCharset(charsetName), bufferSize); - } - - /** - * Close the stream. This method will cause the underlying {@link Reader} to be closed. - * - * @throws IOException if an I/O error occurs. - */ - @Override - public void close() throws IOException { - reader.close(); - } - - /** - * Fills the internal char buffer from the reader. - * - * @throws IOException If an I/O error occurs - */ - private void fillBuffer() throws IOException { - if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) { - encoderIn.compact(); - final int position = encoderIn.position(); - // We don't use Reader#read(CharBuffer) here because it is more efficient - // to write directly to the underlying char array (the default implementation - // copies data to a temporary char array). - final int c = reader.read(encoderIn.array(), position, encoderIn.remaining()); - if (c == -1) { - endOfInput = true; - } else { - encoderIn.position(position + c); - } - encoderIn.flip(); + static float minBufferSize(final CharsetEncoder charsetEncoder) { + return charsetEncoder.maxBytesPerChar() * 2; } - encoderOut.compact(); - lastCoderResult = charsetEncoder.encode(encoderIn, encoderOut, endOfInput); - if (endOfInput) { - lastCoderResult = charsetEncoder.flush(encoderOut); + + private final Reader reader; + + private final CharsetEncoder charsetEncoder; + + /** + * CharBuffer used as input for the decoder. It should be reasonably large as we read data from + * the underlying Reader into this buffer. + */ + private final CharBuffer encoderIn; + /** + * ByteBuffer used as output for the decoder. This buffer can be small as it is only used to + * transfer data from the decoder to the buffer provided by the caller. + */ + private final ByteBuffer encoderOut; + + private CoderResult lastCoderResult; + + private boolean endOfInput; + + /** + * Constructs a new {@link ReaderInputStream} that uses the default character encoding with a + * default input buffer size of {@value #DEFAULT_BUFFER_SIZE} characters. + * + * @param reader the target {@link Reader} + * @deprecated 2.5 use {@link #ReaderInputStream(Reader, Charset)} instead + */ + @Deprecated + public ReaderInputStream(final Reader reader) { + this(reader, Charset.defaultCharset()); } - if (lastCoderResult.isError()) { - lastCoderResult.throwException(); + + /** + * Constructs a new {@link ReaderInputStream} with a default input buffer size of {@value + * #DEFAULT_BUFFER_SIZE} characters. + * + * @param reader the target {@link Reader} + * @param charset the charset encoding + */ + public ReaderInputStream(final Reader reader, final Charset charset) { + this(reader, charset, DEFAULT_BUFFER_SIZE); } - encoderOut.flip(); - } - /** - * Gets the CharsetEncoder. - * - * @return the CharsetEncoder. - */ - CharsetEncoder getCharsetEncoder() { - return charsetEncoder; - } - - /** - * Read a single byte. - * - * @return either the byte read or {@code -1} if the end of the stream has been reached - * @throws IOException if an I/O error occurs. - */ - @Override - public int read() throws IOException { - for (; ; ) { - if (encoderOut.hasRemaining()) { - return encoderOut.get() & 0xFF; - } - fillBuffer(); - if (endOfInput && !encoderOut.hasRemaining()) { - return -1; - } + /** + * Constructs a new {@link ReaderInputStream}. + * + * @param reader the target {@link Reader}. + * @param charset the charset encoding. + * @param bufferSize the size of the input buffer in number of characters. + */ + public ReaderInputStream(final Reader reader, final Charset charset, final int bufferSize) { + // @formatter:off + this( + reader, + toCharset(charset) + .newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE), + bufferSize); + // @formatter:on } - } - /** - * Read the specified number of bytes into an array. - * - * @param b the byte array to read into - * @return the number of bytes read or {@code -1} if the end of the stream has been reached - * @throws IOException if an I/O error occurs. - */ - @Override - public int read(final byte[] b) throws IOException { - return read(b, 0, b.length); - } - - /** - * Read the specified number of bytes into an array. - * - * @param array the byte array to read into - * @param off the offset to start reading bytes into - * @param len the number of bytes to read - * @return the number of bytes read or {@code -1} if the end of the stream has been reached - * @throws IOException if an I/O error occurs. - */ - @Override - public int read(final byte[] array, int off, int len) throws IOException { - Objects.requireNonNull(array, "array"); - if (len < 0 || off < 0 || (off + len) > array.length) { - throw new IndexOutOfBoundsException( - "Array size=" + array.length + ", offset=" + off + ", length=" + len); + /** + * Constructs a new {@link ReaderInputStream}. + * + * @param reader the target {@link Reader} + * @param charsetEncoder the charset encoder + * @since 2.1 + */ + public ReaderInputStream(final Reader reader, final CharsetEncoder charsetEncoder) { + this(reader, charsetEncoder, DEFAULT_BUFFER_SIZE); } - int read = 0; - if (len == 0) { - return 0; // Always return 0 if len == 0 + + /** + * Constructs a new {@link ReaderInputStream}. + * + * @param reader the target {@link Reader} + * @param charsetEncoder the charset encoder, null defauls to the default Charset encoder. + * @param bufferSize the size of the input buffer in number of characters + * @since 2.1 + */ + public ReaderInputStream( + final Reader reader, final CharsetEncoder charsetEncoder, final int bufferSize) { + this.reader = reader; + this.charsetEncoder = toCharsetEncoder(charsetEncoder); + this.encoderIn = CharBuffer.allocate(checkMinBufferSize(this.charsetEncoder, bufferSize)); + this.encoderIn.flip(); + this.encoderOut = ByteBuffer.allocate(128); + this.encoderOut.flip(); } - while (len > 0) { - if (encoderOut.hasRemaining()) { // Data from the last read not fully copied - final int c = Math.min(encoderOut.remaining(), len); - encoderOut.get(array, off, c); - off += c; - len -= c; - read += c; - } else if (endOfInput) { // Already reach EOF in the last read - break; - } else { // Read again - fillBuffer(); - } + + /** + * Constructs a new {@link ReaderInputStream} with a default input buffer size of {@value + * #DEFAULT_BUFFER_SIZE} characters. + * + * @param reader the target {@link Reader} + * @param charsetName the name of the charset encoding + */ + public ReaderInputStream(final Reader reader, final String charsetName) { + this(reader, charsetName, DEFAULT_BUFFER_SIZE); } - return read == 0 && endOfInput ? -1 : read; - } - static Charset toCharset(final Charset charset) { - return charset == null ? Charset.defaultCharset() : charset; - } + /** + * Constructs a new {@link ReaderInputStream}. + * + * @param reader the target {@link Reader} + * @param charsetName the name of the charset encoding, null maps to the default Charset. + * @param bufferSize the size of the input buffer in number of characters + */ + public ReaderInputStream(final Reader reader, final String charsetName, final int bufferSize) { + this(reader, toCharset(charsetName), bufferSize); + } - static Charset toCharset(final String charsetName) throws UnsupportedCharsetException { - return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName); - } + /** + * Close the stream. This method will cause the underlying {@link Reader} to be closed. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + reader.close(); + } - static CharsetEncoder toCharsetEncoder(CharsetEncoder charsetEncoder) { - return charsetEncoder != null ? charsetEncoder : Charset.defaultCharset().newEncoder(); - } + /** + * Fills the internal char buffer from the reader. + * + * @throws IOException If an I/O error occurs + */ + private void fillBuffer() throws IOException { + if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) { + encoderIn.compact(); + final int position = encoderIn.position(); + // We don't use Reader#read(CharBuffer) here because it is more efficient + // to write directly to the underlying char array (the default implementation + // copies data to a temporary char array). + final int c = reader.read(encoderIn.array(), position, encoderIn.remaining()); + if (c == -1) { + endOfInput = true; + } else { + encoderIn.position(position + c); + } + encoderIn.flip(); + } + encoderOut.compact(); + lastCoderResult = charsetEncoder.encode(encoderIn, encoderOut, endOfInput); + if (endOfInput) { + lastCoderResult = charsetEncoder.flush(encoderOut); + } + if (lastCoderResult.isError()) { + lastCoderResult.throwException(); + } + encoderOut.flip(); + } + + /** + * Gets the CharsetEncoder. + * + * @return the CharsetEncoder. + */ + CharsetEncoder getCharsetEncoder() { + return charsetEncoder; + } + + /** + * Read a single byte. + * + * @return either the byte read or {@code -1} if the end of the stream has been reached + * @throws IOException if an I/O error occurs. + */ + @Override + public int read() throws IOException { + for (; ; ) { + if (encoderOut.hasRemaining()) { + return encoderOut.get() & 0xFF; + } + fillBuffer(); + if (endOfInput && !encoderOut.hasRemaining()) { + return -1; + } + } + } + + /** + * Read the specified number of bytes into an array. + * + * @param b the byte array to read into + * @return the number of bytes read or {@code -1} if the end of the stream has been reached + * @throws IOException if an I/O error occurs. + */ + @Override + public int read(final byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Read the specified number of bytes into an array. + * + * @param array the byte array to read into + * @param off the offset to start reading bytes into + * @param len the number of bytes to read + * @return the number of bytes read or {@code -1} if the end of the stream has been reached + * @throws IOException if an I/O error occurs. + */ + @Override + public int read(final byte[] array, int off, int len) throws IOException { + Objects.requireNonNull(array, "array"); + if (len < 0 || off < 0 || (off + len) > array.length) { + throw new IndexOutOfBoundsException( + "Array size=" + array.length + ", offset=" + off + ", length=" + len); + } + int read = 0; + if (len == 0) { + return 0; // Always return 0 if len == 0 + } + while (len > 0) { + if (encoderOut.hasRemaining()) { // Data from the last read not fully copied + final int c = Math.min(encoderOut.remaining(), len); + encoderOut.get(array, off, c); + off += c; + len -= c; + read += c; + } else if (endOfInput) { // Already reach EOF in the last read + break; + } else { // Read again + fillBuffer(); + } + } + return read == 0 && endOfInput ? -1 : read; + } + + static Charset toCharset(final Charset charset) { + return charset == null ? Charset.defaultCharset() : charset; + } + + static Charset toCharset(final String charsetName) throws UnsupportedCharsetException { + return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName); + } + + static CharsetEncoder toCharsetEncoder(CharsetEncoder charsetEncoder) { + return charsetEncoder != null ? charsetEncoder : Charset.defaultCharset().newEncoder(); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/StudioUtils.java b/common/src/main/java/com/itsaky/androidide/utils/StudioUtils.java index d2f7341ea..1922ac01a 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/StudioUtils.java +++ b/common/src/main/java/com/itsaky/androidide/utils/StudioUtils.java @@ -19,44 +19,45 @@ package com.itsaky.androidide.utils; import android.content.Context; import android.util.TypedValue; + import com.itsaky.toaster.Toaster; public class StudioUtils { - private Context mContext; - private Toaster mToaster; + private Context mContext; + private Toaster mToaster; - public StudioUtils(Context mContext) { - this.mContext = mContext; - mToaster = getToaster(); - } + public StudioUtils(Context mContext) { + this.mContext = mContext; + mToaster = getToaster(); + } - private Toaster getToaster() { - return mToaster == null ? mToaster = new Toaster(mContext) : mToaster; - } + private Toaster getToaster() { + return mToaster == null ? mToaster = new Toaster(mContext) : mToaster; + } - public void toast(String msg, Toaster.Type type) { - getToaster().setDuration(Toaster.SHORT).setText(msg).setType(type).show(); - } + public void toast(String msg, Toaster.Type type) { + getToaster().setDuration(Toaster.SHORT).setText(msg).setType(type).show(); + } - public void toast(int msg, Toaster.Type type) { - getToaster().setDuration(Toaster.SHORT).setText(msg).setType(type).show(); - } + public void toast(int msg, Toaster.Type type) { + getToaster().setDuration(Toaster.SHORT).setText(msg).setType(type).show(); + } - public void toastLong(String msg, Toaster.Type type) { - getToaster().setDuration(Toaster.LONG).setText(msg).setType(type).show(); - } + public void toastLong(String msg, Toaster.Type type) { + getToaster().setDuration(Toaster.LONG).setText(msg).setType(type).show(); + } - public void toastLong(int msg, Toaster.Type type) { - getToaster().setDuration(Toaster.LONG).setText(msg).setType(type).show(); - } + public void toastLong(int msg, Toaster.Type type) { + getToaster().setDuration(Toaster.LONG).setText(msg).setType(type).show(); + } - public float toDp(int px) { - return TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, px, mContext.getResources().getDisplayMetrics()); - } + public float toDp(int px) { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, px, mContext.getResources().getDisplayMetrics()); + } - public float toPx(int dp) { - return TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_PX, dp, mContext.getResources().getDisplayMetrics()); - } + public float toPx(int dp) { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_PX, dp, mContext.getResources().getDisplayMetrics()); + } } diff --git a/common/src/main/java/com/itsaky/androidide/utils/TypefaceUtils.java b/common/src/main/java/com/itsaky/androidide/utils/TypefaceUtils.java index 7df683c53..acef986c1 100755 --- a/common/src/main/java/com/itsaky/androidide/utils/TypefaceUtils.java +++ b/common/src/main/java/com/itsaky/androidide/utils/TypefaceUtils.java @@ -18,22 +18,23 @@ package com.itsaky.androidide.utils; import android.graphics.Typeface; + import com.itsaky.androidide.app.BaseApplication; public class TypefaceUtils { - public static Typeface quicksand() { - return Typeface.createFromAsset( - BaseApplication.getBaseInstance().getAssets(), "fonts/quicksand.ttf"); - } + public static Typeface quicksand() { + return Typeface.createFromAsset( + BaseApplication.getBaseInstance().getAssets(), "fonts/quicksand.ttf"); + } - public static Typeface jetbrainsMono() { - return Typeface.createFromAsset( - BaseApplication.getBaseInstance().getAssets(), "fonts/jetbrains-mono.ttf"); - } + public static Typeface jetbrainsMono() { + return Typeface.createFromAsset( + BaseApplication.getBaseInstance().getAssets(), "fonts/jetbrains-mono.ttf"); + } - public static Typeface josefinSans() { - return Typeface.createFromAsset( - BaseApplication.getBaseInstance().getAssets(), "fonts/josefin-sans.ttf"); - } + public static Typeface josefinSans() { + return Typeface.createFromAsset( + BaseApplication.getBaseInstance().getAssets(), "fonts/josefin-sans.ttf"); + } } diff --git a/common/src/main/java/com/itsaky/toaster/ToastWrapper.java b/common/src/main/java/com/itsaky/toaster/ToastWrapper.java index 6ff981e4b..ef3171a4c 100755 --- a/common/src/main/java/com/itsaky/toaster/ToastWrapper.java +++ b/common/src/main/java/com/itsaky/toaster/ToastWrapper.java @@ -27,62 +27,64 @@ import android.graphics.Color; import android.util.AttributeSet; import android.view.ViewAnimationUtils; import android.widget.LinearLayout; + import com.google.android.material.animation.ArgbEvaluatorCompat; public class ToastWrapper extends LinearLayout { - private Toaster mToaster; + private Toaster mToaster; - public ToastWrapper(Context context) { - super(context); - } + public ToastWrapper(Context context) { + super(context); + } - public ToastWrapper(Context context, AttributeSet attrs) { - super(context, attrs); - } + public ToastWrapper(Context context, AttributeSet attrs) { + super(context, attrs); + } - public ToastWrapper(Context context, AttributeSet attrs, int style) { - super(context, attrs, style); - } + public ToastWrapper(Context context, AttributeSet attrs, int style) { + super(context, attrs, style); + } - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - startRevealAnimation(); - startIconColorChangeAnimation(); - } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + startRevealAnimation(); + startIconColorChangeAnimation(); + } - protected void startIconColorChangeAnimation() { - ValueAnimator anim = - ValueAnimator.ofObject(new ArgbEvaluatorCompat(), Color.WHITE, mToaster.getIconColor()); - anim.setDuration(Toaster.REVEAL_ANIM_DURATION + 300); - anim.addUpdateListener( - new ValueAnimator.AnimatorUpdateListener() { + protected void startIconColorChangeAnimation() { + ValueAnimator anim = + ValueAnimator.ofObject( + new ArgbEvaluatorCompat(), Color.WHITE, mToaster.getIconColor()); + anim.setDuration(Toaster.REVEAL_ANIM_DURATION + 300); + anim.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator p1) { - mToaster.getIcon().setTint((int) p1.getAnimatedValue()); - } - }); - anim.start(); - } + @Override + public void onAnimationUpdate(ValueAnimator p1) { + mToaster.getIcon().setTint((int) p1.getAnimatedValue()); + } + }); + anim.start(); + } - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - setBackgroundColor(Color.TRANSPARENT); - } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + setBackgroundColor(Color.TRANSPARENT); + } - public void setToasterInstance(Toaster toaster) { - this.mToaster = toaster; - } + public void setToasterInstance(Toaster toaster) { + this.mToaster = toaster; + } - protected void startRevealAnimation() { - setBackgroundDrawable(mToaster.getBackroundDrawable()); - int x = (getLeft()); // + getRight()) / 2; - int y = (getTop() + getBottom()) / 2; - int endRadius = Math.max(getHeight(), getWidth()); - Animator anim = ViewAnimationUtils.createCircularReveal(this, x, y, 0, endRadius); - anim.setDuration(Toaster.REVEAL_ANIM_DURATION); - anim.start(); - } + protected void startRevealAnimation() { + setBackgroundDrawable(mToaster.getBackroundDrawable()); + int x = (getLeft()); // + getRight()) / 2; + int y = (getTop() + getBottom()) / 2; + int endRadius = Math.max(getHeight(), getWidth()); + Animator anim = ViewAnimationUtils.createCircularReveal(this, x, y, 0, endRadius); + anim.setDuration(Toaster.REVEAL_ANIM_DURATION); + anim.start(); + } } diff --git a/common/src/main/java/com/itsaky/toaster/Toaster.java b/common/src/main/java/com/itsaky/toaster/Toaster.java index 32fac3216..5121cfc3d 100755 --- a/common/src/main/java/com/itsaky/toaster/Toaster.java +++ b/common/src/main/java/com/itsaky/toaster/Toaster.java @@ -25,164 +25,171 @@ import android.graphics.drawable.GradientDrawable; import android.util.TypedValue; import android.view.LayoutInflater; import android.widget.Toast; + import androidx.core.content.ContextCompat; import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; public class Toaster { - private com.itsaky.androidide.common.databinding.LayoutToastBinding binding; + private com.itsaky.androidide.common.databinding.LayoutToastBinding binding; - private Toast mToast; - private Context mContext; - private Gravity mGravity; - private Type mType; - private Drawable mDrawable; - private AnimatedVectorDrawableCompat mIcon; + private Toast mToast; + private Context mContext; + private Gravity mGravity; + private Type mType; + private Drawable mDrawable; + private AnimatedVectorDrawableCompat mIcon; - private int mIconColor = Color.DKGRAY; - private int mDuration = 0; + private int mIconColor = Color.DKGRAY; + private int mDuration = 0; - public static final int COLOR_SUCCESS = Color.parseColor("#4CAF50"); - public static final int COLOR_ERROR = Color.parseColor("#F44336"); - public static final int REVEAL_ANIM_DURATION = 500; - public static final int SHORT = 2000; - public static final int LONG = 3500; + public static final int COLOR_SUCCESS = Color.parseColor("#4CAF50"); + public static final int COLOR_ERROR = Color.parseColor("#F44336"); + public static final int REVEAL_ANIM_DURATION = 500; + public static final int SHORT = 2000; + public static final int LONG = 3500; - public Toaster(Context context) { - this.mContext = context; - this.mType = Type.INFO; + public Toaster(Context context) { + this.mContext = context; + this.mType = Type.INFO; - this.mDrawable = createBackgroundDrawable(); + this.mDrawable = createBackgroundDrawable(); - createView(); - } + createView(); + } - protected void createView() { - binding = - com.itsaky.androidide.common.databinding.LayoutToastBinding.inflate( - LayoutInflater.from(mContext)); - binding.toastToastWrapper.setToasterInstance(this); - } + protected void createView() { + binding = + com.itsaky.androidide.common.databinding.LayoutToastBinding.inflate( + LayoutInflater.from(mContext)); + binding.toastToastWrapper.setToasterInstance(this); + } - protected Drawable createBackgroundDrawable() { - GradientDrawable drawable = new GradientDrawable(); - drawable.setShape(GradientDrawable.RECTANGLE); - drawable.setColor( - ContextCompat.getColor( - mContext, com.itsaky.androidide.common.R.color.color_toast_background)); - drawable.setCornerRadius(25); - return drawable; - } + protected Drawable createBackgroundDrawable() { + GradientDrawable drawable = new GradientDrawable(); + drawable.setShape(GradientDrawable.RECTANGLE); + drawable.setColor( + ContextCompat.getColor( + mContext, com.itsaky.androidide.common.R.color.color_toast_background)); + drawable.setCornerRadius(25); + return drawable; + } - protected AnimatedVectorDrawableCompat createToastIconAnimation(int tintColor) { - AnimatedVectorDrawableCompat imageDrawable = - AnimatedVectorDrawableCompat.create( - mContext, - mType == Type.SUCCESS - ? com.itsaky.androidide.common.R.drawable.ic_ok - : com.itsaky.androidide.common.R.drawable.ic_error); - imageDrawable.setTint(tintColor); - imageDrawable.setTintMode(PorterDuff.Mode.SRC_ATOP); - binding.toastImage.setImageDrawable(imageDrawable); - return imageDrawable; - } + protected AnimatedVectorDrawableCompat createToastIconAnimation(int tintColor) { + AnimatedVectorDrawableCompat imageDrawable = + AnimatedVectorDrawableCompat.create( + mContext, + mType == Type.SUCCESS + ? com.itsaky.androidide.common.R.drawable.ic_ok + : com.itsaky.androidide.common.R.drawable.ic_error); + imageDrawable.setTint(tintColor); + imageDrawable.setTintMode(PorterDuff.Mode.SRC_ATOP); + binding.toastImage.setImageDrawable(imageDrawable); + return imageDrawable; + } - public void show() { - int dp16 = toPx(16); - mIconColor = - mType == Type.SUCCESS ? COLOR_SUCCESS : mType == Type.ERROR ? COLOR_ERROR : Color.DKGRAY; - int gravity = - mGravity == Gravity.TOP_LEFT - ? (android.view.Gravity.TOP | android.view.Gravity.START) - : mGravity == Gravity.TOP_RIGHT - ? (android.view.Gravity.TOP | android.view.Gravity.END) - : mGravity == Gravity.BOTTOM_RIGHT - ? (android.view.Gravity.BOTTOM | android.view.Gravity.END) - : mGravity == Gravity.BOTTOM_LEFT - ? (android.view.Gravity.BOTTOM | android.view.Gravity.START) - : android.view.Gravity.TOP | android.view.Gravity.END; + public void show() { + int dp16 = toPx(16); + mIconColor = + mType == Type.SUCCESS + ? COLOR_SUCCESS + : mType == Type.ERROR ? COLOR_ERROR : Color.DKGRAY; + int gravity = + mGravity == Gravity.TOP_LEFT + ? (android.view.Gravity.TOP | android.view.Gravity.START) + : mGravity == Gravity.TOP_RIGHT + ? (android.view.Gravity.TOP | android.view.Gravity.END) + : mGravity == Gravity.BOTTOM_RIGHT + ? (android.view.Gravity.BOTTOM | android.view.Gravity.END) + : mGravity == Gravity.BOTTOM_LEFT + ? (android.view.Gravity.BOTTOM + | android.view.Gravity.START) + : android.view.Gravity.TOP + | android.view.Gravity.END; - mIcon = createToastIconAnimation(mIconColor); + mIcon = createToastIconAnimation(mIconColor); - mToast = Toast.makeText(mContext, binding.toastText.getText(), mDuration); - mToast.setGravity(gravity, dp16, dp16); - mToast.setDuration(mDuration); - mToast.setView(binding.getRoot()); - mToast.show(); + mToast = Toast.makeText(mContext, binding.toastText.getText(), mDuration); + mToast.setGravity(gravity, dp16, dp16); + mToast.setDuration(mDuration); + mToast.setView(binding.getRoot()); + mToast.show(); - binding.toastImage.setImageDrawable(mIcon); - mIcon.start(); - } + binding.toastImage.setImageDrawable(mIcon); + mIcon.start(); + } - public Toaster setText(String text) { - if (binding.toastText == null) createView(); + public Toaster setText(String text) { + if (binding.toastText == null) createView(); - binding.toastText.setText(text); - return this; - } + binding.toastText.setText(text); + return this; + } - public Toaster setText(int textResId) { - if (binding.toastText == null) createView(); + public Toaster setText(int textResId) { + if (binding.toastText == null) createView(); - binding.toastText.setText(textResId); - return this; - } + binding.toastText.setText(textResId); + return this; + } - public Toaster setDuration(int mDuration) { - this.mDuration = mDuration + REVEAL_ANIM_DURATION; - return this; - } + public Toaster setDuration(int mDuration) { + this.mDuration = mDuration + REVEAL_ANIM_DURATION; + return this; + } - public int getDuration() { - return mDuration; - } + public int getDuration() { + return mDuration; + } - public Toaster setType(Type type) { - this.mType = type; - return this; - } + public Toaster setType(Type type) { + this.mType = type; + return this; + } - public Type getType() { - return mType; - } + public Type getType() { + return mType; + } - public Toaster setGravity(Gravity mGravity) { - this.mGravity = mGravity; - return this; - } + public Toaster setGravity(Gravity mGravity) { + this.mGravity = mGravity; + return this; + } - private int toPx(int dp) { - return (int) - (TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, dp, mContext.getResources().getDisplayMetrics())); - } + private int toPx(int dp) { + return (int) + (TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp, + mContext.getResources().getDisplayMetrics())); + } - public Gravity getGravity() { - return mGravity; - } + public Gravity getGravity() { + return mGravity; + } - public AnimatedVectorDrawableCompat getIcon() { - return mIcon; - } + public AnimatedVectorDrawableCompat getIcon() { + return mIcon; + } - public Drawable getBackroundDrawable() { - return mDrawable; - } + public Drawable getBackroundDrawable() { + return mDrawable; + } - public int getIconColor() { - return mIconColor; - } + public int getIconColor() { + return mIconColor; + } - public enum Type { - ERROR, - SUCCESS, - INFO - } + public enum Type { + ERROR, + SUCCESS, + INFO + } - public enum Gravity { - TOP_LEFT, - TOP_RIGHT, - BOTTOM_LEFT, - BOTTOM_RIGHT - } + public enum Gravity { + TOP_LEFT, + TOP_RIGHT, + BOTTOM_LEFT, + BOTTOM_RIGHT + } } diff --git a/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButton.java b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButton.java index da6d35341..bc757768a 100755 --- a/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButton.java +++ b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButton.java @@ -1,50 +1,51 @@ package com.itsaky.androidide.views.virtualkeys; import androidx.annotation.NonNull; + import java.util.HashMap; /** The {@link Class} that implements special buttons for {@link VirtualKeysView}. */ public class SpecialButton { - private static final HashMap map = new HashMap<>(); + private static final HashMap map = new HashMap<>(); - public static final SpecialButton CTRL = new SpecialButton("CTRL"); - public static final SpecialButton ALT = new SpecialButton("ALT"); - public static final SpecialButton SHIFT = new SpecialButton("SHIFT"); - public static final SpecialButton FN = new SpecialButton("FN"); + public static final SpecialButton CTRL = new SpecialButton("CTRL"); + public static final SpecialButton ALT = new SpecialButton("ALT"); + public static final SpecialButton SHIFT = new SpecialButton("SHIFT"); + public static final SpecialButton FN = new SpecialButton("FN"); - /** The special button key. */ - private final String key; + /** The special button key. */ + private final String key; - /** - * Initialize a {@link SpecialButton}. - * - * @param key The unique key name for the special button. The key is registered in {@link #map} - * with which the {@link SpecialButton} can be retrieved via a call to {@link - * #valueOf(String)}. - */ - public SpecialButton(@NonNull final String key) { - this.key = key; - map.put(key, this); - } + /** + * Initialize a {@link SpecialButton}. + * + * @param key The unique key name for the special button. The key is registered in {@link #map} + * with which the {@link SpecialButton} can be retrieved via a call to {@link + * #valueOf(String)}. + */ + public SpecialButton(@NonNull final String key) { + this.key = key; + map.put(key, this); + } - /** Get {@link #key} for this {@link SpecialButton}. */ - public String getKey() { - return key; - } + /** Get {@link #key} for this {@link SpecialButton}. */ + public String getKey() { + return key; + } - /** - * Get the {@link SpecialButton} for {@code key}. - * - * @param key The unique key name for the special button. - */ - public static SpecialButton valueOf(String key) { - return map.get(key); - } + /** + * Get the {@link SpecialButton} for {@code key}. + * + * @param key The unique key name for the special button. + */ + public static SpecialButton valueOf(String key) { + return map.get(key); + } - @NonNull - @Override - public String toString() { - return key; - } + @NonNull + @Override + public String toString() { + return key; + } } diff --git a/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButtonState.java b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButtonState.java index 098795657..9ae82ba38 100755 --- a/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButtonState.java +++ b/emulatorview/src/main/java/com/itsaky/androidide/views/virtualkeys/SpecialButtonState.java @@ -1,54 +1,55 @@ package com.itsaky.androidide.views.virtualkeys; import android.widget.Button; + import java.util.ArrayList; import java.util.List; /** The {@link Class} that maintains a state of a {@link SpecialButton} */ public class SpecialButtonState { - /** If special button has been created for the {@link VirtualKeysView}. */ - boolean isCreated = false; - /** If special button is active. */ - boolean isActive = false; - /** - * If special button is locked due to long hold on it and should not be deactivated if its state - * is read. - */ - boolean isLocked = false; + /** If special button has been created for the {@link VirtualKeysView}. */ + boolean isCreated = false; + /** If special button is active. */ + boolean isActive = false; + /** + * If special button is locked due to long hold on it and should not be deactivated if its state + * is read. + */ + boolean isLocked = false; - List