diff --git a/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt b/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt index c9c3ed075..528eb13e2 100644 --- a/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt +++ b/actions/src/main/java/com/itsaky/androidide/actions/internal/DefaultActionsRegistry.kt @@ -31,6 +31,7 @@ import com.itsaky.androidide.actions.locations.CodeActionsMenu import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.utils.resolveAttr import java.util.concurrent.CompletableFuture +import java.util.concurrent.ConcurrentHashMap /** * Default implementation for the [ActionsRegistry] @@ -40,7 +41,7 @@ import java.util.concurrent.CompletableFuture class DefaultActionsRegistry : ActionsRegistry() { private val log = ILogger.newInstance("DefaultActionsRegistry") - private val actions: MutableMap> = mutableMapOf() + private val actions = ConcurrentHashMap>() private val listeners = mutableSetOf() init { @@ -49,7 +50,7 @@ class DefaultActionsRegistry : ActionsRegistry() { override fun getActions(location: ActionItem.Location): MutableMap { if (actions[location.id] == null) { - actions[location.id] = mutableMapOf() + actions[location.id] = hashMapOf() } return actions[location.id]!! diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b23a7d43b..b812d14fd 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,7 +36,7 @@ @@ -47,17 +47,17 @@ + android:configChanges="orientation|screenSize" /> ? = null protected var isDestroying = false + protected var isConfigChange = false + protected var wasInitializing = false + protected val log: ILogger = ILogger.newInstance("EditorActivity") protected val logReceiver: LogReceiver = LogReceiver().setLogListener(::appendApkLog) var uiDesignerResultLauncher: ActivityResultLauncher? = null val viewModel by viewModels() - var binding: ActivityEditorBinding? = null + lateinit var binding: ActivityEditorBinding protected set private val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - val binding = this@BaseEditorActivity.binding ?: return if (binding.root.isDrawerOpen(GravityCompat.END)) { binding.root.closeDrawer(GravityCompat.END) } else if (binding.root.isDrawerOpen(GravityCompat.START)) { @@ -159,6 +161,7 @@ abstract class BaseEditorActivity : protected abstract fun doConfirmProjectClose() protected open fun preDestroy() { + viewModel.isConfigChange = !isDestroying try { unregisterReceiver(logReceiver) } catch (th: Throwable) { @@ -167,28 +170,27 @@ abstract class BaseEditorActivity : } protected open fun postDestroy() { - Lookup.DEFAULT.unregisterAll() - ApiVersionsRegistry.getInstance().clear() - ResourceTableRegistry.getInstance().clear() - WidgetTableRegistry.getInstance().clear() - - binding = null - binding = null + if (isDestroying) { + Lookup.DEFAULT.unregisterAll() + ApiVersionsRegistry.getInstance().clear() + ResourceTableRegistry.getInstance().clear() + WidgetTableRegistry.getInstance().clear() + } } override fun bindLayout(): View { this.binding = ActivityEditorBinding.inflate(layoutInflater) - this.diagnosticInfoBinding = this.binding!!.diagnosticInfo - return this.binding!!.root + this.diagnosticInfoBinding = this.binding.diagnosticInfo + return this.binding.root } @Subscribe(threadMode = MAIN) open fun onInstallationResult(event: InstallationResultEvent) { val intent = event.intent - if (binding == null || isDestroying) { + if (isDestroying) { return } - val binding = this.binding ?: return + val packageName = onResult(this, intent) if (packageName != null) { Snackbar.make(binding.realContainer, string.msg_action_open_application, Snackbar.LENGTH_LONG) @@ -209,15 +211,20 @@ abstract class BaseEditorActivity : projectPath = savedInstanceState.getString(KEY_PROJECT_PATH)!! } + this.wasInitializing = viewModel.isInitializing + this.isConfigChange = viewModel.isConfigChange + viewModel.isConfigChange = false + viewModel.isInitializing = false + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) lifecycle.addObserver(mLifecycleObserver) - setSupportActionBar(binding?.editorToolbar) + setSupportActionBar(binding.editorToolbar) dmonStatusFragment = getDaemonStatusFragment() setupDrawerToggle() - binding?.tabs?.addOnTabSelectedListener(this) + binding.tabs.addOnTabSelectedListener(this) setupViews() @@ -289,7 +296,7 @@ abstract class BaseEditorActivity : id.editornav_terminal -> openTerminal() } - binding?.root?.closeDrawer(GravityCompat.START) + binding.root.closeDrawer(GravityCompat.START) return false } @@ -324,7 +331,7 @@ abstract class BaseEditorActivity : } open fun appendApkLog(line: LogLine) { - binding?.bottomSheet?.appendApkLog(line) + binding.bottomSheet.appendApkLog(line) } open fun handleSearchResults(map: Map>?) { @@ -347,11 +354,11 @@ abstract class BaseEditorActivity : } open fun setSearchResultAdapter(adapter: SearchListAdapter) { - binding?.bottomSheet?.setSearchResultAdapter(adapter) + binding.bottomSheet.setSearchResultAdapter(adapter) } open fun setDiagnosticsAdapter(adapter: DiagnosticsAdapter) { - binding?.bottomSheet?.setDiagnosticsAdapter(adapter) + binding.bottomSheet.setDiagnosticsAdapter(adapter) } open fun showDaemonStatus() { @@ -387,23 +394,19 @@ abstract class BaseEditorActivity : } val index = - binding - ?.bottomSheet - ?.pagerAdapter - ?.findIndexOfFragmentByClass(SearchResultFragment::class.java) - ?: -1 + binding.bottomSheet.pagerAdapter.findIndexOfFragmentByClass(SearchResultFragment::class.java) - if (index >= 0 && index < (binding?.bottomSheet?.binding?.tabs?.tabCount ?: 0)) { - binding?.bottomSheet?.binding?.tabs?.getTabAt(index)?.select() + if (index >= 0 && index < binding.bottomSheet.binding.tabs.tabCount) { + binding.bottomSheet.binding.tabs.getTabAt(index)?.select() } } open fun handleDiagnosticsResultVisibility(errorVisible: Boolean) { - binding?.bottomSheet?.handleDiagnosticsResultVisibility(errorVisible) + binding.bottomSheet.handleDiagnosticsResultVisibility(errorVisible) } open fun handleSearchResultVisibility(errorVisible: Boolean) { - binding?.bottomSheet?.handleSearchResultVisibility(errorVisible) + binding.bottomSheet.handleSearchResultVisibility(errorVisible) } open fun showFirstBuildNotice() { @@ -461,7 +464,6 @@ abstract class BaseEditorActivity : } private fun setupDrawerToggle() { - val binding = this.binding ?: return val toggle = ActionBarDrawerToggle( this, @@ -479,23 +481,17 @@ abstract class BaseEditorActivity : private fun onBuildStatusChanged() { val visible = viewModel.isBuildInProgress || viewModel.isInitializing - binding?.buildProgressIndicator?.visibility = if (visible) View.VISIBLE else View.GONE + binding.buildProgressIndicator.visibility = if (visible) View.VISIBLE else View.GONE invalidateOptionsMenu() } private fun setupViews() { viewModel._isBuildInProgress.observe(this) { onBuildStatusChanged() } viewModel._isInitializing.observe(this) { onBuildStatusChanged() } - viewModel._statusText.observe(this) { - binding?.bottomSheet?.setStatus(it.first, it.second) - } + viewModel._statusText.observe(this) { binding.bottomSheet.setStatus(it.first, it.second) } viewModel.observeFiles(this) { files -> - if (binding == null) { - return@observeFiles - } - - binding?.apply { + binding.apply { if (files == null || files.isEmpty()) { tabs.visibility = View.GONE viewContainer.displayedChild = 1 @@ -525,11 +521,11 @@ abstract class BaseEditorActivity : } private fun setupNoEditorView() { - binding?.noEditorSummary?.movementMethod = LinkMovementMethod() + binding.noEditorSummary.movementMethod = LinkMovementMethod() val filesSpan: ClickableSpan = object : ClickableSpan() { override fun onClick(widget: View) { - binding?.root?.openDrawer(GravityCompat.END) + binding.root.openDrawer(GravityCompat.END) } } val bottomSheetSpan: ClickableSpan = @@ -541,7 +537,7 @@ abstract class BaseEditorActivity : val sb = SpannableStringBuilder() appendClickableSpan(sb, string.msg_swipe_for_files, filesSpan) appendClickableSpan(sb, string.msg_swipe_for_output, bottomSheetSpan) - binding?.noEditorSummary?.text = sb + binding.noEditorSummary.text = sb } private fun appendClickableSpan( @@ -564,7 +560,7 @@ abstract class BaseEditorActivity : } private fun setupBottomSheet() { - editorBottomSheet = BottomSheetBehavior.from(binding!!.bottomSheet) + editorBottomSheet = BottomSheetBehavior.from(binding.bottomSheet) editorBottomSheet?.addBottomSheetCallback( object : BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { @@ -577,7 +573,7 @@ abstract class BaseEditorActivity : } override fun onSlide(bottomSheet: View, slideOffset: Float) { - binding?.apply { + binding.apply { val editorScale = 1 - slideOffset * (1 - EDITOR_CONTAINER_SCALE_FACTOR) this.bottomSheet.onSlide(slideOffset) this.viewContainer.scaleX = editorScale @@ -590,7 +586,7 @@ abstract class BaseEditorActivity : val observer: OnGlobalLayoutListener = object : OnGlobalLayoutListener { override fun onGlobalLayout() { - binding?.let { + binding.let { it.viewContainer.pivotY = 0f it.viewContainer.pivotX = it.viewContainer.width / 2f it.viewContainer.viewTreeObserver.removeOnGlobalLayoutListener(this) @@ -598,14 +594,14 @@ abstract class BaseEditorActivity : } } - binding?.apply { + binding.apply { viewContainer.viewTreeObserver.addOnGlobalLayoutListener(observer) bottomSheet.setOffsetAnchor(editorToolbar) } } private fun refreshSymbolInput(editor: CodeEditorView) { - binding?.bottomSheet?.refreshSymbolInput(editor) + binding.bottomSheet.refreshSymbolInput(editor) } private fun setupDiagnosticInfo() { @@ -625,7 +621,7 @@ abstract class BaseEditorActivity : private fun onSoftInputChanged() { invalidateOptionsMenu() - binding?.bottomSheet?.onSoftInputChanged() + binding.bottomSheet.onSoftInputChanged() } private fun openTerminal() { @@ -652,7 +648,7 @@ abstract class BaseEditorActivity : } private fun getSyncBanner(): MaterialBanner? { - return binding?.run { + return binding.run { return@run syncBanner .setContentTextColor(resolveAttr(attr.colorOnPrimaryContainer)) .setBannerBackgroundColor(resolveAttr(attr.colorPrimaryContainer)) @@ -666,7 +662,7 @@ abstract class BaseEditorActivity : return object : SingleSessionCallback() { override fun onCreated(sessionId: Int) { log.debug("on session created:", sessionId) - binding?.apply { + binding.apply { bottomSheet.setActionText(getString(string.msg_installing_apk)) bottomSheet.setActionProgress(0) bottomSheet.showChild(EditorBottomSheet.CHILD_ACTION) @@ -674,11 +670,11 @@ abstract class BaseEditorActivity : } override fun onProgressChanged(sessionId: Int, progress: Float) { - binding?.bottomSheet?.setActionProgress((progress * 100f).toInt()) + binding.bottomSheet.setActionProgress((progress * 100f).toInt()) } override fun onFinished(sessionId: Int, success: Boolean) { - binding?.apply { + binding.apply { bottomSheet.showChild(EditorBottomSheet.CHILD_HEADER) bottomSheet.setActionProgress(0) if (!success) { diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt index 2a032e7a6..637975ba4 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt @@ -51,43 +51,47 @@ import java.io.File * @author Akash Yadav */ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { - + override fun doOpenFile(file: File, selection: Range?) { openFileAndSelect(file, selection) } - + override fun doCloseAll(runAfter: () -> Unit) { closeAll(runAfter) } - + override fun doSaveAll(): Boolean { return saveAll() } - + override fun provideCurrentEditor(): CodeEditorView? { return getCurrentEditor() } - + override fun provideEditorAt(index: Int): CodeEditorView? { return getEditorAtIndex(index) } - + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel._displayedFile.observe(this) { this.binding?.editorContainer?.displayedChild = it } + viewModel._displayedFile.observe(this) { this.binding.editorContainer.displayedChild = it } viewModel._fileTreeDrawerOpened.observe(this) { opened -> - this.binding?.editorDrawerLayout?.apply { + this.binding.editorDrawerLayout.apply { if (opened) openDrawer(GravityCompat.END) else closeDrawer(GravityCompat.END) } } + + if (viewModel.getOpenedFileCount() != binding.tabs.tabCount) { + // The activity proabably has been restarted after a configuration change + // restore the opened files + + val files = viewModel.getOpenedFiles() + viewModel.removeAllFiles() + files.forEach(this::openFile) + } } - override fun onDestroy() { - super.onDestroy() - binding = null - } - @SuppressLint("RestrictedApi") override fun onCreateOptionsMenu(menu: Menu?): Boolean { if (menu is MenuBuilder) { @@ -95,7 +99,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } return true } - + override fun onPrepareOptionsMenu(menu: Menu): Boolean { ensureToolbarMenu(menu) return true @@ -108,7 +112,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } override fun getEditorAtIndex(index: Int): CodeEditorView? { - return binding?.editorContainer?.getChildAt(index) as CodeEditorView? + return binding.editorContainer.getChildAt(index) as CodeEditorView? } override fun openFileAndSelect(file: File, selection: Range?) { @@ -134,7 +138,6 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { return null } - val binding = this.binding ?: return null val index = openFileAndGetIndex(file, range) val tab = binding.tabs.getTabAt(index) if (tab != null && index >= 0 && !tab.isSelected) { @@ -154,7 +157,6 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } override fun openFileAndGetIndex(file: File, selection: Range?): Int { - val binding = this.binding ?: return -1 val openedFileIndex = findIndexOfEditorByFile(file) if (openedFileIndex != -1) { log.error("File is already opened. File: $file") @@ -181,7 +183,6 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } override fun getEditorForFile(file: File): CodeEditorView? { - val binding = this.binding ?: return null for (i in 0 until viewModel.getOpenedFileCount()) { val editor = binding.editorContainer.getChildAt(i) as CodeEditorView if (file == editor.file) { @@ -302,7 +303,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } viewModel.removeFile(index) - binding?.apply { + binding.apply { tabs.removeTabAt(index) editorContainer.removeViewAt(index) } @@ -311,7 +312,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { return } - binding?.tabs?.requestLayout() + binding.tabs.requestLayout() } override fun closeAll() { @@ -341,7 +342,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { notifyFilesUnsaved(unsavedFiles) { closeOthers() } } } - + open fun ensureToolbarMenu(menu: Menu) { menu.clear() val data = ActionData() @@ -377,7 +378,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } viewModel.removeAllFiles() - binding?.apply { + binding.apply { tabs.removeAllTabs() tabs.requestLayout() editorContainer.removeAllViews() diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt index 3661f880e..719668ca0 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt @@ -33,14 +33,16 @@ import com.itsaky.androidide.databinding.LayoutSearchProjectBinding import com.itsaky.androidide.fragments.sheets.ProgressSheet import com.itsaky.androidide.handlers.EditorBuildEventListener import com.itsaky.androidide.handlers.LspHandler.connectClient -import com.itsaky.androidide.handlers.LspHandler.destroy +import com.itsaky.androidide.handlers.LspHandler.destroyLanguageServers import com.itsaky.androidide.interfaces.IProjectHandler import com.itsaky.androidide.lookup.Lookup import com.itsaky.androidide.lsp.IDELanguageClientImpl import com.itsaky.androidide.preferences.internal.NO_OPENED_PROJECT import com.itsaky.androidide.preferences.internal.lastOpenedProject +import com.itsaky.androidide.projects.ProjectManager.cachedInitResult import com.itsaky.androidide.projects.ProjectManager.getProjectDirPath import com.itsaky.androidide.projects.ProjectManager.notifyProjectUpdate +import com.itsaky.androidide.projects.ProjectManager.projectInitialized import com.itsaky.androidide.projects.ProjectManager.projectPath import com.itsaky.androidide.projects.ProjectManager.rootProject import com.itsaky.androidide.projects.ProjectManager.setupProject @@ -54,16 +56,17 @@ import com.itsaky.androidide.utils.RecursiveFileSearcher import com.itsaky.toaster.Toaster.Type.ERROR import com.itsaky.toaster.toast import java.io.File +import java.util.concurrent.CompletableFuture import java.util.regex.Pattern import java.util.stream.Collectors /** @author Akash Yadav */ @Suppress("MemberVisibilityCanBePrivate") abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { - + protected var mSearchingProgress: ProgressSheet? = null protected var mFindInProjectDialog: AlertDialog? = null - + val findInProjectDialog: AlertDialog get() { if (mFindInProjectDialog == null) { @@ -71,47 +74,57 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { } return mFindInProjectDialog!! } - + protected val mBuildEventListener = EditorBuildEventListener() private val mGradleServiceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { val buildService = (service as GradleServiceBinder).service - log.info("Gradle build service has been started...") - Lookup.DEFAULT.update(BuildService.KEY_BUILD_SERVICE, buildService) - buildService.setEventListener(mBuildEventListener).startToolingServer { - initializeProject() - } + onGradleBuildServiceConnected(buildService) } override fun onServiceDisconnected(name: ComponentName?) { - log.info("Disconnected from Gradle build service...") + log.info("Disconnected from Gradle build service") } } - + abstract fun doCloseAll(runAfter: () -> Unit) - + override fun doDismissSearchProgress() { if (mSearchingProgress?.isShowing == true) { mSearchingProgress!!.dismiss() } } - + override fun doConfirmProjectClose() { confirmProjectClose() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + mBuildEventListener.setActivity(this as EditorHandlerActivity) startServices() } - + override fun preDestroy() { - closeProject(false) + if (isDestroying) { + closeProject(false) + projectInitialized = false + cachedInitResult = null + } + super.preDestroy() - unbindService(mGradleServiceConnection) + + if (isDestroying) { + try { + app.unbindService(mGradleServiceConnection) + } catch (err: Throwable) { + log.error("Unable to unbind service", err) + } finally { + Lookup.DEFAULT.unregister(BuildService.KEY_BUILD_SERVICE) + } + } } override fun setStatus(status: CharSequence, @GravityInt gravity: Int) { @@ -119,7 +132,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { } override fun appendBuildOutput(str: String) { - binding?.bottomSheet?.appendBuildOut(str) + binding.bottomSheet.appendBuildOut(str) } override fun notifySyncNeeded() { @@ -127,11 +140,19 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { } override fun startServices() { + + val service = Lookup.DEFAULT.lookup(BuildService.KEY_BUILD_SERVICE) as GradleBuildService? + if (service != null) { + log.info("Reusing already started Gradle build service") + onGradleBuildServiceConnected(service) + return + } + if ( - bindService( + app.bindService( Intent(this, GradleBuildService::class.java), mGradleServiceConnection, - BIND_AUTO_CREATE + BIND_AUTO_CREATE or BIND_IMPORTANT ) ) { log.info("Bind request for Gradle build service was successful...") @@ -143,13 +164,22 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { } override fun initializeProject() { - val projectPath = projectPath val projectDir = File(projectPath) if (!projectDir.exists()) { log.error("Project directory does not exist. Cannot initialize project") return } - + + val initialized = projectInitialized && cachedInitResult != null + + log.debug(projectInitialized, cachedInitResult, wasInitializing) + + // When returning after a configuration change between the initialization process, + // we do not want to start another project initialization + if (initialized && !wasInitializing) { + return + } + //noinspection ConstantConditions ThreadUtils.runOnUiThread { preProjectInit() } @@ -158,16 +188,27 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { log.error("No build service found. Cannot initialize project.") return } + + val future = + if (!(isConfigChange && wasInitializing) && !initialized) { + buildService.initializeProject(projectDir.absolutePath) + } else { + // The project initialization was in progress before the configuration change + // In this case, we should not start another project initialization + CompletableFuture.supplyAsync { + log.warn("Project has already been initialized. Skipping initialization process.") + cachedInitResult + } + } - val future = buildService.initializeProject(projectDir.absolutePath) - future.whenCompleteAsync { result: InitializeResult?, error: Throwable? -> + future.whenCompleteAsync { result, error -> if (result == null || error != null) { log.error("An error occurred initializing the project with Tooling API", error) setStatus(getString(string.msg_project_initialization_failed)) return@whenCompleteAsync } - - onProjectInitialized() + + onProjectInitialized(result) } } @@ -176,86 +217,100 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { if (IDELanguageClientImpl.isInitialized()) { IDELanguageClientImpl.shutdown() } - destroy(isChangingConfigurations) + destroyLanguageServers(isChangingConfigurations) } catch (err: Throwable) { log.error("Unable to stop editor services. Please report this issue.", err) } } - protected open fun onProjectInitialized() { + protected fun onGradleBuildServiceConnected(service: GradleBuildService) { + log.info("Connected to Gradle build service") + + service.setEventListener(mBuildEventListener) + + if (!service.isToolingServerStarted) { + service.startToolingServer { initializeProject() } + } else { + initializeProject() + } + } + + protected open fun onProjectInitialized(result: InitializeResult) { + if (isConfigChange && projectInitialized && result == cachedInitResult) { + return + } + + cachedInitResult = result setupProject() notifyProjectUpdate() ThreadUtils.runOnUiThread { postProjectInit() } } - + protected open fun postProjectInit() { - this.binding ?: return initialSetup() setStatus(getString(string.msg_project_initialized)) viewModel.isInitializing = false - + projectInitialized = true + if (mFindInProjectDialog?.isShowing == true) { mFindInProjectDialog!!.dismiss() } - + mFindInProjectDialog = null // Create the dialog again if needed } - + protected open fun createFindInProjectDialog(): AlertDialog? { if (rootProject == null) { log.warn("No root project model found. Is the project initialized?") toast(getString(string.msg_project_not_initialized), ERROR) return null } - - val moduleDirs = try { - rootProject!!.subModules.stream() - .map(Project::projectDir) - .collect(Collectors.toList()) - } catch (e: Throwable) { - toast(getString(string.msg_no_modules), ERROR) - emptyList() - } - + + val moduleDirs = + try { + rootProject!!.subModules.stream().map(Project::projectDir).collect(Collectors.toList()) + } catch (e: Throwable) { + toast(getString(string.msg_no_modules), ERROR) + emptyList() + } + return createFindInProjectDialog(moduleDirs) } - + protected open fun createFindInProjectDialog(moduleDirs: List): AlertDialog? { val srcDirs = mutableListOf() val binding = LayoutSearchProjectBinding.inflate(layoutInflater) binding.modulesContainer.removeAllViews() - + for (i in moduleDirs.indices) { val module = moduleDirs[i] val src = File(module, "src") - + if (!module.exists() || !module.isDirectory || !src.exists() || !src.isDirectory) { continue } - + val check = CheckBox(this) check.text = module.name check.isChecked = true - + val params = MarginLayoutParams(-2, -2) params.bottomMargin = SizeUtils.dp2px(4f) binding.modulesContainer.addView(check, params) srcDirs.add(src) } - + val builder = newMaterialDialogBuilder(this) builder.setTitle(string.menu_find_project) builder.setView(binding.root) builder.setCancelable(false) - builder.setPositiveButton( - string.menu_find - ) { dialog, _ -> + builder.setPositiveButton(string.menu_find) { dialog, _ -> val text = binding.input.editText!!.text.toString().trim() if (text.isEmpty()) { toast(string.msg_empty_search_query, ERROR) return@setPositiveButton } - + val searchDirs = mutableListOf() for (i in 0 until binding.modulesContainer.childCount) { val check = binding.modulesContainer.getChildAt(i) as CheckBox @@ -263,12 +318,16 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { searchDirs.add(srcDirs[i]) } } - + val extensions = binding.filter.editText!!.text.toString().trim() val extensionList = mutableListOf() if (extensions.isNotEmpty()) { if (extensions.contains("|")) { - for (str in extensions.split(Pattern.quote("|").toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) { + for (str in + extensions + .split(Pattern.quote("|").toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray()) { if (str.trim().isEmpty()) { continue } @@ -278,26 +337,27 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { extensionList.add(extensions) } } - + if (searchDirs.isEmpty()) { toast(string.msg_select_search_modules, ERROR) } else { dialog.dismiss() - + getProgressSheet(string.msg_searching_project)?.apply { show(supportFragmentManager, "search_in_project_progress") } - - RecursiveFileSearcher.searchRecursiveAsync( - text, extensionList, searchDirs) { results -> handleSearchResults(results) } + + RecursiveFileSearcher.searchRecursiveAsync(text, extensionList, searchDirs) { results -> + handleSearchResults(results) + } } } - + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() } mFindInProjectDialog = builder.create() return mFindInProjectDialog } - + private fun initialSetup() { lastOpenedProject = getProjectDirPath() try { @@ -306,21 +366,21 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { log.warn("Project not initialized. Skipping initial setup...") return } - + var projectName = rootProject.name if (projectName.isEmpty()) { projectName = File(getProjectDirPath()).name } - + supportActionBar!!.subtitle = projectName } catch (th: Throwable) { // ignored } } - + private fun closeProject(manualFinish: Boolean) { stopServices() - + // Make sure we close files // This will make sure that file contents are not erased. doCloseAll { @@ -330,42 +390,41 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler { } } } - + private fun confirmProjectClose() { val builder = newMaterialDialogBuilder(this) builder.setTitle(string.title_confirm_project_close) builder.setMessage(string.msg_confirm_project_close) builder.setNegativeButton(string.no, null) - builder.setPositiveButton( - string.yes - ) { dialog, _ -> + builder.setPositiveButton(string.yes) { dialog, _ -> dialog.dismiss() closeProject(true) } builder.show() } - + private fun initLspClient() { if (!IDELanguageClientImpl.isInitialized()) { IDELanguageClientImpl.initialize(this as EditorHandlerActivity) } connectClient(IDELanguageClientImpl.getInstance()) } - + private fun preProjectInit() { setStatus(getString(string.msg_initializing_project)) viewModel.isInitializing = true } - + open fun getProgressSheet(msg: Int): ProgressSheet? { doDismissSearchProgress() - - mSearchingProgress = ProgressSheet().apply { - isCancelable = false - setMessage(getString(msg)) - setSubMessageEnabled(false) - } - + + mSearchingProgress = + ProgressSheet().apply { + isCancelable = false + setMessage(getString(msg)) + setSubMessageEnabled(false) + } + return mSearchingProgress } } diff --git a/app/src/main/java/com/itsaky/androidide/app/IDEApplication.java b/app/src/main/java/com/itsaky/androidide/app/IDEApplication.java index 7fff1b30c..61adb5a31 100755 --- a/app/src/main/java/com/itsaky/androidide/app/IDEApplication.java +++ b/app/src/main/java/com/itsaky/androidide/app/IDEApplication.java @@ -38,7 +38,7 @@ public class IDEApplication extends BaseApplication { private static final ILogger LOG = ILogger.newInstance("IDEApplication"); private static IDEApplication instance; private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; - + public static IDEApplication getInstance() { return instance; } @@ -49,7 +49,7 @@ public class IDEApplication extends BaseApplication { this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this::handleCrash); super.onCreate(); - + EventBus.builder() .addIndex(new AppEventsIndex()) .addIndex(new ProjectsApiEventsIndex()) diff --git a/app/src/main/java/com/itsaky/androidide/handlers/EditorBuildEventListener.kt b/app/src/main/java/com/itsaky/androidide/handlers/EditorBuildEventListener.kt index 7e8623984..aecf2abda 100644 --- a/app/src/main/java/com/itsaky/androidide/handlers/EditorBuildEventListener.kt +++ b/app/src/main/java/com/itsaky/androidide/handlers/EditorBuildEventListener.kt @@ -37,6 +37,9 @@ class EditorBuildEventListener : GradleBuildService.EventListener { fun setActivity(activity: EditorHandlerActivity) { this.activityReference = WeakReference(activity) } + + fun releaes() { + } override fun prepareBuild() { val isFirstBuild = isFirstBuild diff --git a/app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt b/app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt index aabd960b4..acee8276f 100644 --- a/app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt +++ b/app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt @@ -103,7 +103,7 @@ class FileTreeActionHandler : BaseEventHandler() { } val context = event[Context::class.java]!! as EditorHandlerActivity - context.binding?.root?.closeDrawer(GravityCompat.END) + context.binding.root.closeDrawer(GravityCompat.END) if (event.file.name.endsWith(".apk")) { ApkInstaller.installApk( context, diff --git a/app/src/main/java/com/itsaky/androidide/handlers/LspHandler.kt b/app/src/main/java/com/itsaky/androidide/handlers/LspHandler.kt index 930b69d51..9f6132936 100644 --- a/app/src/main/java/com/itsaky/androidide/handlers/LspHandler.kt +++ b/app/src/main/java/com/itsaky/androidide/handlers/LspHandler.kt @@ -39,7 +39,7 @@ object LspHandler { ILanguageServerRegistry.getDefault().connectClient(client) } - fun destroy(isConfigurationChange: Boolean) { + fun destroyLanguageServers(isConfigurationChange: Boolean) { if (isConfigurationChange) { return } diff --git a/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java b/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java index 4d557cf1c..b472ff4cd 100644 --- a/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java +++ b/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java @@ -37,6 +37,7 @@ import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.text.TextUtils; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -96,7 +97,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin private final IBinder mBinder = new GradleServiceBinder(); private boolean isToolingServerStarted = false; private boolean isBuildInProgress = false; - private Thread toolingServerThread; + private ToolingServerRunner toolingServerThread; private NotificationManager notificationManager; private IToolingApiServer server; private EventListener eventListener; @@ -105,8 +106,14 @@ public class GradleBuildService extends Service implements BuildService, IToolin public void onCreate() { notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); showNotification(getString(R.string.build_status_idle), false); + + Lookup.DEFAULT.update(BuildService.KEY_BUILD_SERVICE, this); } - + + public boolean isToolingServerStarted() { + return isToolingServerStarted; + } + private void showNotification(final String message, final boolean isProgress) { LOG.info("Showing notification to user..."); startForeground(NOTIFICATION_ID, buildNotification(message, isProgress)); @@ -163,6 +170,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin @Nullable @Override public IBinder onBind(Intent intent) { + LOG.debug("onBind() called with: intent = [" + intent + "]"); return mBinder; } @@ -397,8 +405,15 @@ public class GradleBuildService extends Service implements BuildService, IToolin public void startToolingServer(@Nullable OnServerStartListener listener) { if (toolingServerThread == null || !toolingServerThread.isAlive()) { - toolingServerThread = new Thread(new ToolingServerRunner(listener)); + toolingServerThread = new ToolingServerRunner(listener); toolingServerThread.start(); + return; + } + + if (toolingServerThread.isStarted && listener != null) { + listener.onServerStarted(); + } else { + toolingServerThread.setListener(listener); } } @@ -465,14 +480,19 @@ public class GradleBuildService extends Service implements BuildService, IToolin } } - private class ToolingServerRunner implements Runnable { + private class ToolingServerRunner extends Thread { - @Nullable private final OnServerStartListener listener; + @Nullable private OnServerStartListener listener; + private boolean isStarted = false; public ToolingServerRunner(@Nullable OnServerStartListener listener) { this.listener = listener; } - + + public void setListener(@Nullable final OnServerStartListener listener) { + this.listener = listener; + } + @Override public void run() { try { @@ -525,6 +545,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin ProjectManager.INSTANCE.setupProject(); + isStarted = true; if (listener != null) { listener.onServerStarted(); } diff --git a/app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt b/app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt index 7225fbf4f..74bd81bd5 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt +++ b/app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt @@ -18,6 +18,9 @@ package com.itsaky.androidide.utils import android.content.Context import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.actions.ActionItem.Location.EDITOR_FILE_TABS +import com.itsaky.androidide.actions.ActionItem.Location.EDITOR_TEXT_ACTIONS +import com.itsaky.androidide.actions.ActionItem.Location.EDITOR_TOOLBAR import com.itsaky.androidide.actions.ActionsRegistry import com.itsaky.androidide.actions.build.CancelBuildAction import com.itsaky.androidide.actions.build.ProjectSyncAction @@ -79,6 +82,10 @@ class EditorActivityActions { } @JvmStatic - fun clear() = ActionsRegistry.getInstance().clearActions(ActionItem.Location.EDITOR_TOOLBAR) + fun clear() { + val locations = arrayOf(EDITOR_TOOLBAR, EDITOR_FILE_TABS, EDITOR_TEXT_ACTIONS) + val registry = ActionsRegistry.getInstance() + locations.forEach(registry::clearActions) + } } } diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt index ad66c6c72..594228b69 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt @@ -30,6 +30,7 @@ class EditorViewModel : ViewModel() { internal val _isBuildInProgress = MutableLiveData(false) internal val _isInitializing = MutableLiveData(false) + internal val _isConfigurationChange = MutableLiveData(false) internal val _statusText = MutableLiveData>("" to CENTER) internal val _displayedFile = MutableLiveData(-1) internal val _fileTreeDrawerOpened = MutableLiveData(false) @@ -42,6 +43,12 @@ class EditorViewModel : ViewModel() { */ private val mCurrentFile = MutableLiveData?>(null) + var isConfigChange: Boolean + get() = _isConfigurationChange.value ?: false + set(value) { + _isConfigurationChange.value = value + } + var isBuildInProgress: Boolean get() = _isBuildInProgress.value ?: false set(value) { diff --git a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt index 772bb3b48..2c5f3ccb9 100644 --- a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt +++ b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt @@ -30,6 +30,7 @@ import com.itsaky.androidide.projects.api.Project import com.itsaky.androidide.projects.builder.BuildService import com.itsaky.androidide.projects.util.ProjectTransformer import com.itsaky.androidide.tooling.api.IProject +import com.itsaky.androidide.tooling.api.messages.result.InitializeResult import com.itsaky.androidide.utils.ILogger import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -52,6 +53,9 @@ object ProjectManager : EventReceiver { var rootProject: Project? = null var app: AndroidModule? = null + + var projectInitialized: Boolean = false + var cachedInitResult: InitializeResult? = null @JvmOverloads fun setupProject(project: IProject = Lookup.DEFAULT.lookup(BuildService.KEY_PROJECT_PROXY)!!) { @@ -61,7 +65,6 @@ object ProjectManager : EventReceiver { this.app = this.rootProject!!.findFirstAndroidAppModule() this.rootProject!!.subModules.filterIsInstance(ModuleProject::class.java).forEach { it.indexSourcesAndClasspaths() - if (it is AndroidModule) { it.readResources() } diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar index 32d973236..656b6cdf6 100644 Binary files a/tests/test-home/.androidide/init/model.jar and b/tests/test-home/.androidide/init/model.jar differ diff --git a/uidesigner/src/main/AndroidManifest.xml b/uidesigner/src/main/AndroidManifest.xml index f3d9fed2a..750227823 100644 --- a/uidesigner/src/main/AndroidManifest.xml +++ b/uidesigner/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@