From 2d89eb82ff067c086a7b0885851c8e8208af14e1 Mon Sep 17 00:00:00 2001 From: Felipe Teixeira Date: Wed, 11 Dec 2024 16:05:22 -0300 Subject: [PATCH] refactor: show a Flashbar to ask if you want to open application --- .../activities/editor/BaseEditorActivity.kt | 387 +++++++++++------- .../activities/editor/EditorActivityKt.kt | 6 +- .../editor/EditorHandlerActivity.kt | 164 +++++--- .../activities/editor/IDELogcatReader.kt | 56 ++- .../editor/ProjectHandlerActivity.kt | 242 +++++++---- 5 files changed, 527 insertions(+), 328 deletions(-) diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt index 3d089148e2..25a6622ed3 100644 --- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt +++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt @@ -57,7 +57,6 @@ import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.formatter.IAxisValueFormatter import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback -import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.Tab import com.itsaky.androidide.R @@ -70,6 +69,7 @@ import com.itsaky.androidide.databinding.ActivityEditorBinding import com.itsaky.androidide.databinding.ContentEditorBinding import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding import com.itsaky.androidide.events.InstallationResultEvent +import com.itsaky.androidide.flashbar.Flashbar import com.itsaky.androidide.fragments.SearchResultFragment import com.itsaky.androidide.fragments.sidebar.EditorSidebarFragment import com.itsaky.androidide.fragments.sidebar.FileTreeFragment @@ -84,7 +84,6 @@ import com.itsaky.androidide.models.Range import com.itsaky.androidide.models.SearchResult import com.itsaky.androidide.preferences.internal.BuildPreferences import com.itsaky.androidide.projects.IProjectManager -import com.itsaky.androidide.tasks.cancelIfActive import com.itsaky.androidide.ui.CodeEditorView import com.itsaky.androidide.ui.ContentTranslatingDrawerLayout import com.itsaky.androidide.ui.SwipeRevealLayout @@ -95,21 +94,21 @@ import com.itsaky.androidide.utils.DialogUtils.newMaterialDialogBuilder import com.itsaky.androidide.utils.InstallationResultHandler.onResult import com.itsaky.androidide.utils.IntentUtils import com.itsaky.androidide.utils.MemoryUsageWatcher -import com.itsaky.androidide.utils.flashError +import com.itsaky.androidide.utils.flashbarBuilder +import com.itsaky.androidide.utils.infoIcon import com.itsaky.androidide.utils.resolveAttr +import com.itsaky.androidide.utils.showOnUiThread import com.itsaky.androidide.viewmodel.EditorViewModel import com.itsaky.androidide.xml.resources.ResourceTableRegistry import com.itsaky.androidide.xml.versions.ApiVersionsRegistry import com.itsaky.androidide.xml.widgets.WidgetTableRegistry -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers +import java.io.File +import kotlin.math.roundToInt +import kotlin.math.roundToLong import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode.MAIN import org.slf4j.Logger import org.slf4j.LoggerFactory -import java.io.File -import kotlin.math.roundToInt -import kotlin.math.roundToLong /** * Base class for EditorActivity which handles most of the view related things. @@ -117,7 +116,9 @@ import kotlin.math.roundToLong * @author Akash Yadav */ @Suppress("MemberVisibilityCanBePrivate") -abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSelectedListener, +abstract class BaseEditorActivity : + EdgeToEdgeIDEActivity(), + TabLayout.OnTabSelectedListener, DiagnosticClickListener { protected val mLifecycleObserver = EditorActivityLifecyclerObserver() @@ -127,14 +128,12 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele protected val memoryUsageWatcher = MemoryUsageWatcher() protected val pidToDatasetIdxMap = MutableIntIntMap(initialCapacity = 3) + protected var syncNotificationFlashbar: Flashbar? = null + protected var tempFlashbar: Flashbar? = null + var isDestroying = false protected set - /** - * Editor activity's [CoroutineScope] for executing tasks in the background. - */ - protected val editorActivityScope = CoroutineScope(Dispatchers.Default) - internal var installationCallback: ApkInstallationSessionCallback? = null var uiDesignerResultLauncher: ActivityResultLauncher? = null @@ -143,47 +142,61 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele internal var _binding: ActivityEditorBinding? = null val binding: ActivityEditorBinding get() = checkNotNull(_binding) { "Activity has been destroyed" } + val content: ContentEditorBinding get() = binding.content override val subscribeToEvents: Boolean get() = true - private val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - if (binding.root.isDrawerOpen(GravityCompat.START)) { - binding.root.closeDrawer(GravityCompat.START) - } else if (editorBottomSheet?.state != BottomSheetBehavior.STATE_COLLAPSED) { - editorBottomSheet?.setState(BottomSheetBehavior.STATE_COLLAPSED) - } else if (binding.swipeReveal.isOpen) { - binding.swipeReveal.close() - } else { - doConfirmProjectClose() + private val onBackPressedCallback: OnBackPressedCallback = + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (binding.root.isDrawerOpen(GravityCompat.START)) { + binding.root.closeDrawer(GravityCompat.START) + } else if ( + editorBottomSheet?.state != BottomSheetBehavior.STATE_COLLAPSED + ) { + editorBottomSheet?.setState(BottomSheetBehavior.STATE_COLLAPSED) + } else if (binding.swipeReveal.isOpen) { + binding.swipeReveal.close() + } else { + doConfirmProjectClose() + } } } - } - private val memoryUsageListener = MemoryUsageWatcher.MemoryUsageListener { memoryUsage -> - memoryUsage.forEachValue { proc -> - _binding?.memUsageView?.chart?.apply { - val dataset = (data.getDataSetByIndex(pidToDatasetIdxMap[proc.pid]) as LineDataSet?) - ?: run { - log.error("No dataset found for process: {}: {}", proc.pid, proc.pname) - return@forEachValue + private val memoryUsageListener = + MemoryUsageWatcher.MemoryUsageListener { memoryUsage -> + memoryUsage.forEachValue { proc -> + _binding?.memUsageView?.chart?.apply { + val dataset = + (data.getDataSetByIndex(pidToDatasetIdxMap[proc.pid]) + as LineDataSet?) + ?: run { + log.error( + "No dataset found for process: {}: {}", + proc.pid, + proc.pname, + ) + return@forEachValue + } + + dataset.entries.mapIndexed { index, entry -> + entry.y = + byte2MemorySize(proc.usageHistory[index], MemoryConstants.MB) + .toFloat() } - dataset.entries.mapIndexed { index, entry -> - entry.y = byte2MemorySize(proc.usageHistory[index], MemoryConstants.MB).toFloat() + dataset.label = + "%s - %.2fMB".format(proc.pname, dataset.entries.last().y) + dataset.notifyDataSetChanged() + data.notifyDataChanged() + notifyDataSetChanged() + invalidate() } - - dataset.label = "%s - %.2fMB".format(proc.pname, dataset.entries.last().y) - dataset.notifyDataSetChanged() - data.notifyDataChanged() - notifyDataSetChanged() - invalidate() } } - } private var isImeVisible = false private val editorSurfaceContainerBackground by lazy { @@ -197,17 +210,15 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele companion object { - @JvmStatic - protected val PROC_IDE = "IDE" + @JvmStatic protected val PROC_IDE = "IDE" - @JvmStatic - protected val PROC_GRADLE_TOOLING = "Gradle Tooling" + @JvmStatic protected val PROC_GRADLE_TOOLING = "Gradle Tooling" - @JvmStatic - protected val PROC_GRADLE_DAEMON = "Gradle Daemon" + @JvmStatic protected val PROC_GRADLE_DAEMON = "Gradle Daemon" @JvmStatic - protected val log: Logger = LoggerFactory.getLogger(BaseEditorActivity::class.java) + protected val log: Logger = + LoggerFactory.getLogger(BaseEditorActivity::class.java) private const val OPTIONS_MENU_INVALIDATION_DELAY = 150L @@ -240,10 +251,15 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele installationCallback?.destroy() installationCallback = null + syncNotificationFlashbar?.dismiss() + syncNotificationFlashbar = null + + tempFlashbar?.dismiss() + tempFlashbar = null + if (isDestroying) { memoryUsageWatcher.stopWatching(true) memoryUsageWatcher.listener = null - editorActivityScope.cancelIfActive("Activity is being destroyed") } } @@ -282,16 +298,15 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele override fun onApplySystemBarInsets(insets: Insets) { super.onApplySystemBarInsets(insets) this._binding?.apply { - drawerSidebar.getFragment() + drawerSidebar + .getFragment() .onApplyWindowInsets(insets) content.apply { - editorAppBarLayout.updatePadding( - top = insets.top - ) + editorAppBarLayout.updatePadding(top = insets.top) editorToolbar.updatePaddingRelative( start = editorToolbar.paddingStart + insets.left, - end = editorToolbar.paddingEnd + insets.right + end = editorToolbar.paddingEnd + insets.right, ) } } @@ -311,8 +326,7 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele return } - Snackbar.make(content.realContainer, string.msg_action_open_application, Snackbar.LENGTH_LONG) - .setAction(string.yes) { IntentUtils.launchApp(this, packageName) }.show() + notifyOpenApplication(packageName) } override fun onCreate(savedInstanceState: Bundle?) { @@ -322,7 +336,10 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele registerLanguageServers() - if (savedInstanceState != null && savedInstanceState.containsKey(KEY_PROJECT_PATH)) { + if ( + savedInstanceState != null && + savedInstanceState.containsKey(KEY_PROJECT_PATH) + ) { IProjectManager.getInstance() .openProject(savedInstanceState.getString(KEY_PROJECT_PATH)!!) } @@ -340,10 +357,11 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele setupContainers() setupDiagnosticInfo() - uiDesignerResultLauncher = registerForActivityResult( - StartActivityForResult(), - this::handleUiDesignerResult - ) + uiDesignerResultLauncher = + registerForActivityResult( + StartActivityForResult(), + this::handleUiDesignerResult, + ) setupMemUsageChart() watchMemory() @@ -377,12 +395,15 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele setScaleEnabled(true) axisLeft.isEnabled = false - axisRight.valueFormatter = object : - IAxisValueFormatter { - override fun getFormattedValue(value: Float, axis: AxisBase?): String { - return "%dMB".format(value.roundToLong()) + axisRight.valueFormatter = + object : IAxisValueFormatter { + override fun getFormattedValue( + value: Float, + axis: AxisBase?, + ): String { + return "%dMB".format(value.roundToLong()) + } } - } } } @@ -394,12 +415,15 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele protected fun resetMemUsageChart() { val processes = memoryUsageWatcher.getMemoryUsages() - val datasets = Array(processes.size) { index -> - LineDataSet( - List(MemoryUsageWatcher.MAX_USAGE_ENTRIES) { Entry(it.toFloat(), 0f) }, - processes[index].pname - ) - } + val datasets = + Array(processes.size) { index -> + LineDataSet( + List(MemoryUsageWatcher.MAX_USAGE_ENTRIES) { + Entry(it.toFloat(), 0f) + }, + processes[index].pname, + ) + } val bgColor = editorSurfaceContainerBackground val textColor = resolveAttr(R.attr.colorOnSurface) @@ -433,7 +457,9 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele } } - private fun getMemUsageLineColorFor(proc: MemoryUsageWatcher.ProcessMemoryInfo): Int { + private fun getMemUsageLineColorFor( + proc: MemoryUsageWatcher.ProcessMemoryInfo + ): Int { return when (proc.pname) { PROC_IDE -> Color.BLUE PROC_GRADLE_TOOLING -> Color.RED @@ -471,7 +497,10 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele } override fun onSaveInstanceState(outState: Bundle) { - outState.putString(KEY_PROJECT_PATH, IProjectManager.getInstance().projectDirPath) + outState.putString( + KEY_PROJECT_PATH, + IProjectManager.getInstance().projectDirPath, + ) super.onSaveInstanceState(outState) } @@ -515,13 +544,18 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele open fun handleSearchResults(map: Map>?) { val results = map ?: emptyMap() - setSearchResultAdapter(SearchListAdapter(results, { file -> - doOpenFile(file, null) - hideBottomSheet() - }) { match -> - doOpenFile(match.file, match) - hideBottomSheet() - }) + setSearchResultAdapter( + SearchListAdapter( + results, + { file -> + doOpenFile(file, null) + hideBottomSheet() + }, + ) { match -> + doOpenFile(match.file, match) + hideBottomSheet() + } + ) showSearchResults() doDismissSearchProgress() @@ -546,9 +580,10 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele editorBottomSheet?.state = BottomSheetBehavior.STATE_EXPANDED } - val index = content.bottomSheet.pagerAdapter.findIndexOfFragmentByClass( - SearchResultFragment::class.java - ) + val index = + content.bottomSheet.pagerAdapter.findIndexOfFragmentByClass( + SearchResultFragment::class.java + ) if (index >= 0 && index < content.bottomSheet.binding.tabs.tabCount) { content.bottomSheet.binding.tabs.getTabAt(index)?.select() @@ -564,9 +599,13 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele } open fun showFirstBuildNotice() { - newMaterialDialogBuilder(this).setPositiveButton(android.R.string.ok, null) - .setTitle(string.title_first_build).setMessage(string.msg_first_build).setCancelable(false) - .create().show() + newMaterialDialogBuilder(this) + .setPositiveButton(android.R.string.ok, null) + .setTitle(string.title_first_build) + .setMessage(string.msg_first_build) + .setCancelable(false) + .create() + .show() } fun doSetStatus(text: CharSequence, @GravityInt gravity: Int) { @@ -591,38 +630,48 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele private fun handleUiDesignerResult(result: ActivityResult) { if (result.resultCode != RESULT_OK || result.data == null) { log.warn( - "UI Designer returned invalid result: resultCode={}, data={}", result.resultCode, - result.data + "UI Designer returned invalid result: resultCode={}, data={}", + result.resultCode, + result.data, ) return } - val generated = result.data!!.getStringExtra(UIDesignerActivity.RESULT_GENERATED_XML) + val generated = + result.data!!.getStringExtra(UIDesignerActivity.RESULT_GENERATED_XML) if (TextUtils.isEmpty(generated)) { log.warn("UI Designer returned blank generated XML code") return } val view = provideCurrentEditor() - val text = view?.editor?.text ?: run { - log.warn("No file opened to append UI designer result") - return - } + val text = + view?.editor?.text + ?: run { + log.warn("No file opened to append UI designer result") + return + } val endLine = text.lineCount - 1 text.replace(0, 0, endLine, text.getColumnCount(endLine), generated) } private fun setupDrawers() { - val toggle = ActionBarDrawerToggle( - this, binding.editorDrawerLayout, content.editorToolbar, - string.app_name, string.app_name - ) + val toggle = + ActionBarDrawerToggle( + this, + binding.editorDrawerLayout, + content.editorToolbar, + string.app_name, + string.app_name, + ) binding.editorDrawerLayout.addDrawerListener(toggle) toggle.syncState() binding.apply { editorDrawerLayout.apply { childId = contentCard.id - translationBehaviorStart = ContentTranslatingDrawerLayout.TranslationBehavior.FULL - translationBehaviorEnd = ContentTranslatingDrawerLayout.TranslationBehavior.FULL + translationBehaviorStart = + ContentTranslatingDrawerLayout.TranslationBehavior.FULL + translationBehaviorEnd = + ContentTranslatingDrawerLayout.TranslationBehavior.FULL setScrimColor(Color.TRANSPARENT) } } @@ -632,15 +681,19 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele log.debug( "onBuildStatusChanged: isInitializing: ${editorViewModel.isInitializing}, isBuildInProgress: ${editorViewModel.isBuildInProgress}" ) - val visible = editorViewModel.isBuildInProgress || editorViewModel.isInitializing - content.progressIndicator.visibility = if (visible) View.VISIBLE else View.GONE + val visible = + editorViewModel.isBuildInProgress || editorViewModel.isInitializing + content.progressIndicator.visibility = + if (visible) View.VISIBLE else View.GONE invalidateOptionsMenu() } private fun setupViews() { editorViewModel._isBuildInProgress.observe(this) { onBuildStatusChanged() } editorViewModel._isInitializing.observe(this) { onBuildStatusChanged() } - editorViewModel._statusText.observe(this) { content.bottomSheet.setStatus(it.first, it.second) } + editorViewModel._statusText.observe(this) { + content.bottomSheet.setStatus(it.first, it.second) + } editorViewModel.observeFiles(this) { files -> content.apply { @@ -659,38 +712,51 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele setupNoEditorView() setupBottomSheet() - if (!app.prefManager.getBoolean( - KEY_BOTTOM_SHEET_SHOWN - ) && editorBottomSheet?.state != BottomSheetBehavior.STATE_EXPANDED + if ( + !app.prefManager.getBoolean(KEY_BOTTOM_SHEET_SHOWN) && + editorBottomSheet?.state != BottomSheetBehavior.STATE_EXPANDED ) { editorBottomSheet?.state = BottomSheetBehavior.STATE_EXPANDED - ThreadUtils.runOnUiThreadDelayed({ - editorBottomSheet?.state = BottomSheetBehavior.STATE_COLLAPSED - app.prefManager.putBoolean(KEY_BOTTOM_SHEET_SHOWN, true) - }, 1500) + ThreadUtils.runOnUiThreadDelayed( + { + editorBottomSheet?.state = BottomSheetBehavior.STATE_COLLAPSED + app.prefManager.putBoolean(KEY_BOTTOM_SHEET_SHOWN, true) + }, + 1500, + ) } binding.contentCard.progress = 0f - binding.swipeReveal.dragListener = object : SwipeRevealLayout.OnDragListener { - override fun onDragStateChanged(swipeRevealLayout: SwipeRevealLayout, state: Int) {} - override fun onDragProgress(swipeRevealLayout: SwipeRevealLayout, progress: Float) { - onSwipeRevealDragProgress(progress) + binding.swipeReveal.dragListener = + object : SwipeRevealLayout.OnDragListener { + override fun onDragStateChanged( + swipeRevealLayout: SwipeRevealLayout, + state: Int, + ) {} + + override fun onDragProgress( + swipeRevealLayout: SwipeRevealLayout, + progress: Float, + ) { + onSwipeRevealDragProgress(progress) + } } - } } private fun setupNoEditorView() { content.noEditorSummary.movementMethod = LinkMovementMethod() - val filesSpan: ClickableSpan = object : ClickableSpan() { - override fun onClick(widget: View) { - binding.root.openDrawer(GravityCompat.START) + val filesSpan: ClickableSpan = + object : ClickableSpan() { + override fun onClick(widget: View) { + binding.root.openDrawer(GravityCompat.START) + } } - } - val bottomSheetSpan: ClickableSpan = object : ClickableSpan() { - override fun onClick(widget: View) { - editorBottomSheet?.state = BottomSheetBehavior.STATE_EXPANDED + val bottomSheetSpan: ClickableSpan = + object : ClickableSpan() { + override fun onClick(widget: View) { + editorBottomSheet?.state = BottomSheetBehavior.STATE_EXPANDED + } } - } val sb = SpannableStringBuilder() appendClickableSpan(sb, string.msg_drawer_for_files, filesSpan) appendClickableSpan(sb, string.msg_swipe_for_output, bottomSheetSpan) @@ -718,35 +784,39 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele private fun setupBottomSheet() { editorBottomSheet = BottomSheetBehavior.from(content.bottomSheet) - editorBottomSheet?.addBottomSheetCallback(object : BottomSheetCallback() { - override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState == BottomSheetBehavior.STATE_EXPANDED) { - val editor = provideCurrentEditor() - editor?.editor?.ensureWindowsDismissed() + editorBottomSheet?.addBottomSheetCallback( + object : BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == BottomSheetBehavior.STATE_EXPANDED) { + val editor = provideCurrentEditor() + editor?.editor?.ensureWindowsDismissed() + } } - } - override fun onSlide(bottomSheet: View, slideOffset: Float) { - content.apply { - val editorScale = 1 - slideOffset * (1 - EDITOR_CONTAINER_SCALE_FACTOR) - this.bottomSheet.onSlide(slideOffset) - this.viewContainer.scaleX = editorScale - this.viewContainer.scaleY = editorScale + override fun onSlide(bottomSheet: View, slideOffset: Float) { + content.apply { + val editorScale = + 1 - slideOffset * (1 - EDITOR_CONTAINER_SCALE_FACTOR) + this.bottomSheet.onSlide(slideOffset) + this.viewContainer.scaleX = editorScale + this.viewContainer.scaleY = editorScale + } } } - }) - - val observer: OnGlobalLayoutListener = object : OnGlobalLayoutListener { - override fun onGlobalLayout() { - content.also { - it.realContainer.pivotX = it.realContainer.width.toFloat() / 2f - it.realContainer.pivotY = - (it.realContainer.height.toFloat() / 2f) + (systemBarInsets?.run { bottom - top } - ?: 0) - it.viewContainer.viewTreeObserver.removeOnGlobalLayoutListener(this) + ) + + val observer: OnGlobalLayoutListener = + object : OnGlobalLayoutListener { + override fun onGlobalLayout() { + content.also { + it.realContainer.pivotX = it.realContainer.width.toFloat() / 2f + it.realContainer.pivotY = + (it.realContainer.height.toFloat() / 2f) + + (systemBarInsets?.run { bottom - top } ?: 0) + it.viewContainer.viewTreeObserver.removeOnGlobalLayoutListener(this) + } } } - } content.apply { viewContainer.viewTreeObserver.addOnGlobalLayoutListener(observer) @@ -784,7 +854,30 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele builder.create().show() } + private fun notifyOpenApplication(packageName: String) { + this.syncNotificationFlashbar?.dismiss() + this.syncNotificationFlashbar = null + + this.tempFlashbar?.dismiss() + + this.tempFlashbar = + flashbarBuilder() + .infoIcon() + .message(string.msg_action_open_application) + .positiveActionText(string.yes) + .positiveActionTapListener { + IntentUtils.launchApp(this, packageName) + it.dismiss() + } + .negativeActionText(string.cancel) + .negativeActionTapListener(Flashbar::dismiss) + .build() + this.tempFlashbar?.showOnUiThread() + } + open fun installationSessionCallback(): SessionCallback { - return ApkInstallationSessionCallback(this).also { installationCallback = it } + return ApkInstallationSessionCallback(this).also { + installationCallback = it + } } } diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorActivityKt.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorActivityKt.kt index 9e2845b589..445a816632 100644 --- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorActivityKt.kt +++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorActivityKt.kt @@ -17,9 +17,7 @@ package com.itsaky.androidide.activities.editor -/** - * @author Akash Yadav - */ +/** @author Akash Yadav */ class EditorActivityKt : EditorHandlerActivity() -// TODO: Should we adjust to the display cutout insets? \ No newline at end of file +// TODO: Should we adjust to the display cutout insets? diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt index 58dd1ff8fe..c4d6da2371 100644 --- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt +++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt @@ -29,6 +29,7 @@ import androidx.appcompat.view.menu.MenuBuilder import androidx.collection.MutableIntObjectMap import androidx.core.content.res.ResourcesCompat import androidx.core.view.GravityCompat +import androidx.lifecycle.lifecycleScope import com.blankj.utilcode.util.ImageUtils import com.itsaky.androidide.R.string import com.itsaky.androidide.actions.ActionData @@ -58,14 +59,14 @@ import com.itsaky.androidide.utils.DialogUtils.newYesNoDialog import com.itsaky.androidide.utils.IntentUtils.openImage import com.itsaky.androidide.utils.UniqueNameBuilder import com.itsaky.androidide.utils.flashSuccess +import java.io.File +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.collections.set import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import java.io.File -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.collections.set /** * Base class for EditorActivity. Handles logic for working with file editors. @@ -102,11 +103,13 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { mBuildEventListener.setActivity(this) super.onCreate(savedInstanceState) - editorViewModel._displayedFile.observe( - this) { this.content.editorContainer.displayedChild = it } + editorViewModel._displayedFile.observe(this) { + this.content.editorContainer.displayedChild = it + } editorViewModel._startDrawerOpened.observe(this) { opened -> this.binding.editorDrawerLayout.apply { - if (opened) openDrawer(GravityCompat.START) else closeDrawer(GravityCompat.START) + if (opened) openDrawer(GravityCompat.START) + else closeDrawer(GravityCompat.START) } } @@ -130,12 +133,30 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } executeAsync { - TSLanguageRegistry.instance.register(JavaLanguage.TS_TYPE, JavaLanguage.FACTORY) - TSLanguageRegistry.instance.register(KotlinLanguage.TS_TYPE_KT, KotlinLanguage.FACTORY) - TSLanguageRegistry.instance.register(KotlinLanguage.TS_TYPE_KTS, KotlinLanguage.FACTORY) - TSLanguageRegistry.instance.register(LogLanguage.TS_TYPE, LogLanguage.FACTORY) - TSLanguageRegistry.instance.register(JsonLanguage.TS_TYPE, JsonLanguage.FACTORY) - TSLanguageRegistry.instance.register(XMLLanguage.TS_TYPE, XMLLanguage.FACTORY) + TSLanguageRegistry.instance.register( + JavaLanguage.TS_TYPE, + JavaLanguage.FACTORY, + ) + TSLanguageRegistry.instance.register( + KotlinLanguage.TS_TYPE_KT, + KotlinLanguage.FACTORY, + ) + TSLanguageRegistry.instance.register( + KotlinLanguage.TS_TYPE_KTS, + KotlinLanguage.FACTORY, + ) + TSLanguageRegistry.instance.register( + LogLanguage.TS_TYPE, + LogLanguage.FACTORY, + ) + TSLanguageRegistry.instance.register( + JsonLanguage.TS_TYPE, + JsonLanguage.FACTORY, + ) + TSLanguageRegistry.instance.register( + XMLLanguage.TS_TYPE, + XMLLanguage.FACTORY, + ) IDEColorSchemeProvider.initIfNeeded() } } @@ -159,7 +180,10 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { writeOpenedFilesCache(getOpenedFiles(), getCurrentEditor()?.editor?.file) } - private fun writeOpenedFilesCache(openedFiles: List, selectedFile: File?) { + private fun writeOpenedFilesCache( + openedFiles: List, + selectedFile: File?, + ) { if (selectedFile == null || openedFiles.isEmpty()) { editorViewModel.writeOpenedFiles(null) editorViewModel.openedFilesCache = null @@ -168,11 +192,18 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { return } - val cache = OpenedFilesCache(selectedFile = selectedFile.absolutePath, allFiles = openedFiles) + val cache = + OpenedFilesCache( + selectedFile = selectedFile.absolutePath, + allFiles = openedFiles, + ) editorViewModel.writeOpenedFiles(cache) editorViewModel.openedFilesCache = if (!isDestroying) cache else null - log.debug("[onPause] Opened files cache reset to {}", editorViewModel.openedFilesCache) + log.debug( + "[onPause] Opened files cache reset to {}", + editorViewModel.openedFilesCache, + ) isOpenedFilesSaved.set(true) } @@ -222,18 +253,20 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { item.isEnabled = action.enabled item.title = action.label - item.icon = action.icon?.apply { - colorFilter = action.createColorFilter(data) - alpha = if (action.enabled) 255 else 76 - } + item.icon = + action.icon?.apply { + colorFilter = action.createColorFilter(data) + alpha = if (action.enabled) 255 else 76 + } var showAsAction = action.getShowAsActionFlags(data) if (showAsAction == -1) { - showAsAction = if (action.icon != null) { - MenuItem.SHOW_AS_ACTION_IF_ROOM - } else { - MenuItem.SHOW_AS_ACTION_NEVER - } + showAsAction = + if (action.icon != null) { + MenuItem.SHOW_AS_ACTION_IF_ROOM + } else { + MenuItem.SHOW_AS_ACTION_NEVER + } } if (!action.enabled) { @@ -269,7 +302,8 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } override fun getEditorAtIndex(index: Int): CodeEditorView? { - return _binding?.content?.editorContainer?.getChildAt(index) as CodeEditorView? + return _binding?.content?.editorContainer?.getChildAt(index) + as CodeEditorView? } override fun openFileAndSelect(file: File, selection: Range?) { @@ -307,7 +341,11 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { return try { getEditorAtIndex(index) } catch (th: Throwable) { - log.error("Unable to get editor fragment at opened file index {}", index, th) + log.error( + "Unable to get editor fragment at opened file index {}", + index, + th, + ) null } } @@ -327,7 +365,8 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { log.info("Opening file at index {} file:{}", position, file) val editor = CodeEditorView(this, file, selection!!) - editor.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + editor.layoutParams = + LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) content.editorContainer.addView(editor) content.tabs.addTab(content.tabs.newTab()) @@ -369,9 +408,9 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { requestSync: Boolean, processResources: Boolean, progressConsumer: ((Int, Int) -> Unit)?, - runAfter: (() -> Unit)? + runAfter: (() -> Unit)?, ) { - editorActivityScope.launch { + lifecycleScope.launch { saveAll(notify, requestSync, processResources, progressConsumer) runAfter?.invoke() } @@ -381,7 +420,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { notify: Boolean, requestSync: Boolean, processResources: Boolean, - progressConsumer: ((Int, Int) -> Unit)? + progressConsumer: ((Int, Int) -> Unit)?, ): Boolean { val result = saveAllResult(progressConsumer) @@ -405,7 +444,9 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { return result.gradleSaved } - override suspend fun saveAllResult(progressConsumer: ((Int, Int) -> Unit)?): SaveResult { + override suspend fun saveAllResult( + progressConsumer: ((Int, Int) -> Unit)? + ): SaveResult { return performFileSave { val result = SaveResult() for (i in 0 until editorViewModel.getOpenedFileCount()) { @@ -418,15 +459,13 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } override suspend fun saveResult(index: Int, result: SaveResult) { - performFileSave { - saveResultInternal(index, result) - } + performFileSave { saveResultInternal(index, result) } } private suspend fun saveResultInternal( index: Int, - result: SaveResult - ) : Boolean { + result: SaveResult, + ): Boolean { if (index < 0 || index >= editorViewModel.getOpenedFileCount()) { return false } @@ -442,7 +481,8 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { return false } - val isGradle = fileName.endsWith(".gradle") || fileName.endsWith(".gradle.kts") + val isGradle = + fileName.endsWith(".gradle") || fileName.endsWith(".gradle.kts") val isXml: Boolean = fileName.endsWith(".xml") if (!result.gradleSaved) { result.gradleSaved = modified && isGradle @@ -456,7 +496,6 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { val hasUnsaved = hasUnsavedFiles() withContext(Dispatchers.Main) { - editorViewModel.areFilesModified = hasUnsaved // set tab as unmodified @@ -469,11 +508,14 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { return true } - private fun hasUnsavedFiles() = editorViewModel.getOpenedFiles().any { file -> - getEditorForFile(file)?.isModified == true - } + private fun hasUnsavedFiles() = + editorViewModel.getOpenedFiles().any { file -> + getEditorForFile(file)?.isModified == true + } - private suspend inline fun performFileSave(crossinline action: suspend () -> T) : T { + private suspend inline fun performFileSave( + crossinline action: suspend () -> T + ): T { setFilesSaving(true) try { return action() @@ -508,15 +550,14 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { val editor = getEditorAtIndex(index) if (editor?.isModified == true) { log.info("File has been modified: {}", opened) - notifyFilesUnsaved(listOf(editor)) { - closeFile(index, runAfter) - } + notifyFilesUnsaved(listOf(editor)) { closeFile(index, runAfter) } return } - editor?.close() ?: run { - log.error("Cannot save file before close. Editor instance is null") - } + editor?.close() + ?: run { + log.error("Cannot save file before close. Editor instance is null") + } editorViewModel.removeFile(index) content.apply { @@ -536,8 +577,9 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } val unsavedFiles = - editorViewModel.getOpenedFiles().map(::getEditorForFile) - .filter { it != null && it.isModified } + editorViewModel.getOpenedFiles().map(::getEditorForFile).filter { + it != null && it.isModified + } if (unsavedFiles.isNotEmpty()) { notifyFilesUnsaved(unsavedFiles) { closeOthers() } @@ -570,8 +612,9 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { override fun closeAll(runAfter: () -> Unit) { val count = editorViewModel.getOpenedFileCount() val unsavedFiles = - editorViewModel.getOpenedFiles().map(this::getEditorForFile) - .filter { it != null && it.isModified } + editorViewModel.getOpenedFiles().map(this::getEditorForFile).filter { + it != null && it.isModified + } if (unsavedFiles.isNotEmpty()) { // There are unsaved files @@ -581,9 +624,8 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { // Files were already saved, close all files one by one for (i in 0 until count) { - getEditorAtIndex(i)?.close() ?: run { - log.error("Unable to close file at index {}", i) - } + getEditorAtIndex(i)?.close() + ?: run { log.error("Unable to close file at index {}", i) } } editorViewModel.removeAllFiles() @@ -602,7 +644,10 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { OpenedFile(it.absolutePath, editor.cursorLSPRange) } - private fun notifyFilesUnsaved(unsavedEditors: List, invokeAfter: Runnable) { + private fun notifyFilesUnsaved( + unsavedEditors: List, + invokeAfter: Runnable, + ) { if (isDestroying) { // Do not show unsaved files dialog if the activity is being destroyed // TODO Use a service to save files and to avoid file content loss @@ -618,11 +663,12 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { newYesNoDialog( context = this, title = getString(string.title_files_unsaved), - message = getString(string.msg_files_unsaved, TextUtils.join("\n", mapped)), + message = + getString(string.msg_files_unsaved, TextUtils.join("\n", mapped)), positiveClickListener = { dialog, _ -> dialog.dismiss() saveAllAsync(notify = true, runAfter = { runOnUiThread(invokeAfter) }) - } + }, ) { dialog, _ -> dialog.dismiss() // Mark all the files as saved, then try to close them all @@ -668,7 +714,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } private fun updateTabs() { - editorActivityScope.launch { + lifecycleScope.launch { val files = editorViewModel.getOpenedFiles() val dupliCount = mutableMapOf() val names = MutableIntObjectMap>() diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/IDELogcatReader.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/IDELogcatReader.kt index 0013e00023..c71d4767ce 100644 --- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/IDELogcatReader.kt +++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/IDELogcatReader.kt @@ -19,19 +19,20 @@ package com.itsaky.androidide.activities.editor import android.os.Process import com.itsaky.androidide.utils.Environment import com.itsaky.androidide.utils.transferToStream +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import org.slf4j.LoggerFactory -import java.io.File -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale /** - * Reads the logs from AndroidIDE and saves it to a file in the projects directory. + * Reads the logs from AndroidIDE and saves it to a file in the projects + * directory. * * @author Akash Yadav */ @@ -45,24 +46,16 @@ class IDELogcatReader { private val log = LoggerFactory.getLogger(IDELogcatReader::class.java) } - /** - * Start reading the logs. - */ + /** Start reading the logs. */ fun start() { shouldRun = true - check(job == null) { - "Logcat reader is already running" - } + check(job == null) { "Logcat reader is already running" } - job = CoroutineScope(Dispatchers.IO).launch { - run() - } + job = CoroutineScope(Dispatchers.IO).launch { run() } } - /** - * Stop the log reader. - */ + /** Stop the log reader. */ fun stop() { shouldRun = false job?.cancel("User requested cancellation") @@ -72,8 +65,11 @@ class IDELogcatReader { private fun run() { val date = Date() val dateFormat = SimpleDateFormat("yyyy-MM-dd_HH:mm:ss.SSS", Locale.US) - val outputFile = File(Environment.ANDROIDIDE_HOME, - "logs/AndroidIDE-LOG-${dateFormat.format(date)}.txt") + val outputFile = + File( + Environment.ANDROIDIDE_HOME, + "logs/AndroidIDE-LOG-${dateFormat.format(date)}.txt", + ) log.debug("Creating output file: {}", outputFile) @@ -87,15 +83,17 @@ class IDELogcatReader { outputFile.outputStream().buffered().use { writer -> try { - val process = ProcessBuilder( - "logcat", - "--pid=${Process.myPid()}", - "-v", - "threadtime" - ).let { builder -> - builder.redirectErrorStream(true) - builder.start() - } + val process = + ProcessBuilder( + "logcat", + "--pid=${Process.myPid()}", + "-v", + "threadtime", + ) + .let { builder -> + builder.redirectErrorStream(true) + builder.start() + } process.inputStream.transferToStream(writer) writer.flush() @@ -106,4 +104,4 @@ class IDELogcatReader { } } } -} \ No newline at end of file +} diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt index 5569d59ce7..64b6176853 100644 --- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt +++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt @@ -65,12 +65,12 @@ import com.itsaky.androidide.utils.resolveAttr import com.itsaky.androidide.utils.showOnUiThread import com.itsaky.androidide.utils.withIcon import com.itsaky.androidide.viewmodel.BuildVariantsViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import java.io.File import java.util.concurrent.CompletableFuture import java.util.regex.Pattern import java.util.stream.Collectors +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch /** @author Akash Yadav */ @Suppress("MemberVisibilityCanBePrivate") @@ -80,12 +80,12 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { protected var mSearchingProgress: ProgressSheet? = null protected var mFindInProjectDialog: AlertDialog? = null - protected var syncNotificationFlashbar: Flashbar? = null protected var isFromSavedInstance = false protected var shouldInitialize = false - protected var initializingFuture: CompletableFuture? = null + protected var initializingFuture: CompletableFuture? = + null val findInProjectDialog: AlertDialog get() { @@ -124,7 +124,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { savedInstanceState?.let { this.shouldInitialize = it.getBoolean(STATE_KEY_SHOULD_INITIALIZE, true) - this.isFromSavedInstance = it.getBoolean(STATE_KEY_FROM_SAVED_INSTANACE, false) + this.isFromSavedInstance = + it.getBoolean(STATE_KEY_FROM_SAVED_INSTANACE, false) } ?: run { this.shouldInitialize = true @@ -161,7 +162,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { super.onPause() if (isDestroying) { // reset these values here - // sometimes, when the IDE closed and reopened instantly, these values prevent initialization + // sometimes, when the IDE closed and reopened instantly, these values + // prevent initialization // of the project ProjectManagerImpl.getInstance().destroy() @@ -171,10 +173,6 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { } override fun preDestroy() { - - syncNotificationFlashbar?.dismiss() - syncNotificationFlashbar = null - if (isDestroying) { releaseServerListener() this.initializingFuture?.cancel(true) @@ -204,7 +202,6 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { log.error("Unable to unbind service") } finally { Lookup.getDefault().apply { - (lookup(BuildService.KEY_BUILD_SERVICE) as? GradleBuildService?) ?.setEventListener(null) @@ -234,33 +231,47 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { } private fun notifySyncNeeded(onConfirm: () -> Unit) { - val buildService = Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) - if (buildService == null || editorViewModel.isInitializing || buildService.isBuildInProgress) return + val buildService = + Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) + if ( + buildService == null || + editorViewModel.isInitializing || + buildService.isBuildInProgress + ) + return + + this.tempFlashbar?.dismiss() + this.tempFlashbar = null this.syncNotificationFlashbar?.dismiss() - this.syncNotificationFlashbar = flashbarBuilder( - duration = DURATION_INDEFINITE, - backgroundColor = resolveAttr(R.attr.colorSecondaryContainer), - messageColor = resolveAttr(R.attr.colorOnSecondaryContainer) - ) - .withIcon(R.drawable.ic_sync, colorFilter = resolveAttr(R.attr.colorOnSecondaryContainer)) - .message(string.msg_sync_needed) - .positiveActionText(string.btn_sync) - .positiveActionTapListener { - onConfirm() - it.dismiss() - } - .negativeActionText(string.btn_ignore_changes) - .negativeActionTapListener(Flashbar::dismiss) - .build() + this.syncNotificationFlashbar = + flashbarBuilder( + duration = DURATION_INDEFINITE, + backgroundColor = resolveAttr(R.attr.colorSecondaryContainer), + messageColor = resolveAttr(R.attr.colorOnSecondaryContainer), + ) + .withIcon( + R.drawable.ic_sync, + colorFilter = resolveAttr(R.attr.colorOnSecondaryContainer), + ) + .message(string.msg_sync_needed) + .positiveActionText(string.btn_sync) + .positiveActionTapListener { + onConfirm() + it.dismiss() + } + .negativeActionText(string.btn_ignore_changes) + .negativeActionTapListener(Flashbar::dismiss) + .build() this.syncNotificationFlashbar?.showOnUiThread() - } fun startServices() { - val service = Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) as GradleBuildService? + val service = + Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) + as GradleBuildService? if (editorViewModel.isBoundToBuildSerice && service != null) { log.info("Reusing already started Gradle build service") onGradleBuildServiceConnected(service) @@ -275,12 +286,14 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { bindService( Intent(this, GradleBuildService::class.java), buildServiceConnection, - BIND_AUTO_CREATE or BIND_IMPORTANT + BIND_AUTO_CREATE or BIND_IMPORTANT, ) ) { log.info("Bind request for Gradle build service was successful...") } else { - log.error("Gradle build service doesn't exist or the IDE is not allowed to access it.") + log.error( + "Gradle build service doesn't exist or the IDE is not allowed to access it." + ) } initLspClient() @@ -289,15 +302,14 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { /** * Initialize (sync) the project. * - * @param buildVariantsProvider A function which returns the map of project paths to the selected build variants. - * This function is called asynchronously. + * @param buildVariantsProvider A function which returns the map of project + * paths to the selected build variants. This function is called + * asynchronously. */ fun initializeProject(buildVariantsProvider: () -> Map) { executeWithProgress { progress -> executeAsyncProvideError(buildVariantsProvider::invoke) { result, error -> - com.itsaky.androidide.tasks.runOnUiThread { - progress.dismiss() - } + com.itsaky.androidide.tasks.runOnUiThread { progress.dismiss() } if (result == null || error != null) { val msg = getString(string.msg_build_variants_fetch_failed) @@ -306,9 +318,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { return@executeAsyncProvideError } - com.itsaky.androidide.tasks.runOnUiThread { - initializeProject(result) - } + com.itsaky.androidide.tasks.runOnUiThread { initializeProject(result) } } } } @@ -334,17 +344,23 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { newSelections.putAll(buildVariantsViewModel.updatedBuildVariants) initializeProject { newSelections.mapToSelectedVariants().also { - log.debug("Initializing project with new build variant selections: {}", it) + log.debug( + "Initializing project with new build variant selections: {}", + it, + ) } } return } - // variant selection information is available but no variant selections have been updated + // variant selection information is available but no variant selections have + // been updated // the user might be trying to sync the project from options menu // initialize the project with the existing selected variants initializeProject { - log.debug("Re-initializing project with existing build variant selections") + log.debug( + "Re-initializing project with existing build variant selections" + ) currentVariants.mapToSelectedVariants() } } @@ -358,13 +374,17 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { val manager = ProjectManagerImpl.getInstance() val projectDir = manager.projectDir if (!projectDir.exists()) { - log.error("GradleProject directory does not exist. Cannot initialize project") + log.error( + "GradleProject directory does not exist. Cannot initialize project" + ) return } - val initialized = manager.projectInitialized && manager.cachedInitResult != null + val initialized = + manager.projectInitialized && manager.cachedInitResult != null log.debug("Is project initialized: {}", initialized) - // When returning after a configuration change between the initialization process, + // When returning after a configuration change between the initialization + // process, // we do not want to start another project initialization if (isFromSavedInstance && initialized && !shouldInitialize) { log.debug("Skipping init process because initialized && !wasInitializing") @@ -374,7 +394,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { //noinspection ConstantConditions ThreadUtils.runOnUiThread { preProjectInit() } - val buildService = Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) + val buildService = + Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) if (buildService == null) { log.error("No build service found. Cannot initialize project.") return @@ -388,13 +409,20 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { this.initializingFuture = if (shouldInitialize || (!isFromSavedInstance && !initialized)) { log.debug("Sending init request to tooling server..") - buildService.initializeProject(createProjectInitParams(projectDir, buildVariants)) + buildService.initializeProject( + createProjectInitParams(projectDir, buildVariants) + ) } else { - // The project initialization was in progress before the configuration change + // The project initialization was in progress before the configuration + // change // In this case, we should not start another project initialization - log.debug("Using cached initialize result as the project is already initialized") + log.debug( + "Using cached initialize result as the project is already initialized" + ) CompletableFuture.supplyAsync { - log.warn("GradleProject has already been initialized. Skipping initialization process.") + log.warn( + "GradleProject has already been initialized. Skipping initialization process." + ) manager.cachedInitResult } } @@ -404,12 +432,13 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { if (result == null || !result.isSuccessful || error != null) { if (!CancelChecker.isCancelled(error)) { - log.error("An error occurred initializing the project with Tooling API", error) + log.error( + "An error occurred initializing the project with Tooling API", + error, + ) } - ThreadUtils.runOnUiThread { - postProjectInit(false, result?.failure) - } + ThreadUtils.runOnUiThread { postProjectInit(false, result?.failure) } return@whenCompleteAsync } @@ -419,16 +448,18 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { private fun createProjectInitParams( projectDir: File, - buildVariants: Map + buildVariants: Map, ): InitializeProjectParams { return InitializeProjectParams( projectDir.absolutePath, gradleDistributionParams, - createAndroidParams(buildVariants) + createAndroidParams(buildVariants), ) } - private fun createAndroidParams(buildVariants: Map): AndroidInitializationParams { + private fun createAndroidParams( + buildVariants: Map + ): AndroidInitializationParams { if (buildVariants.isEmpty()) { return AndroidInitializationParams.DEFAULT } @@ -438,7 +469,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { private fun releaseServerListener() { // Release reference to server listener in order to prevent memory leak - (Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) as? GradleBuildService?) + (Lookup.getDefault().lookup(BuildService.KEY_BUILD_SERVICE) + as? GradleBuildService?) ?.setServerListener(null) } @@ -446,7 +478,10 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { try { destroyLanguageServers(isChangingConfigurations) } catch (err: Throwable) { - log.error("Unable to stop editor services. Please report this issue.", err) + log.error( + "Unable to stop editor services. Please report this issue.", + err, + ) } } @@ -472,7 +507,8 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { if (pid != metadata.pid) { log.warn( "Tooling server pid mismatch. Expected: {}, Actual: {}. Replacing memory watcher...", - pid, metadata.pid + pid, + metadata.pid, ) memoryUsageWatcher.watchProcess(metadata.pid, PROC_GRADLE_TOOLING) resetMemUsageChart() @@ -488,20 +524,24 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { protected open fun onProjectInitialized(result: InitializeResult) { val manager = ProjectManagerImpl.getInstance() - if (isFromSavedInstance && manager.projectInitialized && result == manager.cachedInitResult) { + if ( + isFromSavedInstance && + manager.projectInitialized && + result == manager.cachedInitResult + ) { log.debug("Not setting up project as this a configuration change") return } manager.cachedInitResult = result - editorActivityScope.launch(Dispatchers.IO) { + lifecycleScope.launch(Dispatchers.IO) { manager.setupProject() manager.notifyProjectUpdate() - updateBuildVariants(manager.requireWorkspace().getAndroidVariantSelections()) + updateBuildVariants( + manager.requireWorkspace().getAndroidVariantSelections() + ) - com.itsaky.androidide.tasks.runOnUiThread { - postProjectInit(true, null) - } + com.itsaky.androidide.tasks.runOnUiThread { postProjectInit(true, null) } } } @@ -510,20 +550,22 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { editorViewModel.isInitializing = true } - protected open fun postProjectInit(isSuccessful: Boolean, failure: TaskExecutionResult.Failure?) { + protected open fun postProjectInit( + isSuccessful: Boolean, + failure: TaskExecutionResult.Failure?, + ) { val manager = ProjectManagerImpl.getInstance() if (!isSuccessful) { val initFailed = getString(string.msg_project_initialization_failed) setStatus(initFailed) - val msg = when (failure) { - PROJECT_DIRECTORY_INACCESSIBLE -> string.msg_project_dir_inaccessible - PROJECT_NOT_DIRECTORY -> string.msg_file_is_not_dir - PROJECT_NOT_FOUND -> string.msg_project_dir_doesnt_exist - else -> null - }?.let { - "$initFailed: ${getString(it)}" - } + val msg = + when (failure) { + PROJECT_DIRECTORY_INACCESSIBLE -> string.msg_project_dir_inaccessible + PROJECT_NOT_DIRECTORY -> string.msg_file_is_not_dir + PROJECT_NOT_FOUND -> string.msg_project_dir_doesnt_exist + else -> null + }?.let { "$initFailed: ${getString(it)}" } flashError(msg ?: initFailed) @@ -544,7 +586,9 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { mFindInProjectDialog = null // Create the dialog again if needed } - private fun updateBuildVariants(buildVariants: Map) { + private fun updateBuildVariants( + buildVariants: Map + ) { // avoid using the 'runOnUiThread' method defined in the activity com.itsaky.androidide.tasks.runOnUiThread { buildVariantsViewModel.buildVariants = buildVariants @@ -562,7 +606,11 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { val moduleDirs = try { - manager.getWorkspace()!!.getSubProjects().stream().map(GradleProject::projectDir) + manager + .getWorkspace()!! + .getSubProjects() + .stream() + .map(GradleProject::projectDir) .collect(Collectors.toList()) } catch (e: Throwable) { flashError(getString(string.msg_no_modules)) @@ -572,7 +620,9 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { return createFindInProjectDialog(moduleDirs) } - protected open fun createFindInProjectDialog(moduleDirs: List): AlertDialog? { + protected open fun createFindInProjectDialog( + moduleDirs: List + ): AlertDialog? { val srcDirs = mutableListOf() val binding = LayoutSearchProjectBinding.inflate(layoutInflater) binding.modulesContainer.removeAllViews() @@ -581,7 +631,12 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { val module = moduleDirs[i] val src = File(module, "src") - if (!module.exists() || !module.isDirectory || !src.exists() || !src.isDirectory) { + if ( + !module.exists() || + !module.isDirectory || + !src.exists() || + !src.isDirectory + ) { continue } @@ -619,10 +674,10 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { if (extensions.isNotEmpty()) { if (extensions.contains("|")) { for (str in - extensions - .split(Pattern.quote("|").toRegex()) - .dropLastWhile { it.isEmpty() } - .toTypedArray()) { + extensions + .split(Pattern.quote("|").toRegex()) + .dropLastWhile { it.isEmpty() } + .toTypedArray()) { if (str.trim().isEmpty()) { continue } @@ -642,13 +697,19 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { show(supportFragmentManager, "search_in_project_progress") } - RecursiveFileSearcher.searchRecursiveAsync(text, extensionList, searchDirs) { results -> + RecursiveFileSearcher.searchRecursiveAsync( + text, + extensionList, + searchDirs, + ) { results -> handleSearchResults(results) } } } - builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() } + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.dismiss() + } mFindInProjectDialog = builder.create() return mFindInProjectDialog } @@ -678,12 +739,15 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { if (manualFinish) { // if the user is manually closing the project, // save the opened files cache - // this is needed because in this case, the opened files cache will be empty + // this is needed because in this case, the opened files cache will be + // empty // when onPause will be called. saveOpenedFiles() - // reset the lastOpenedProject if the user explicitly chose to close the project - GeneralPreferences.lastOpenedProject = GeneralPreferences.NO_OPENED_PROJECT + // reset the lastOpenedProject if the user explicitly chose to close the + // project + GeneralPreferences.lastOpenedProject = + GeneralPreferences.NO_OPENED_PROJECT } // Make sure we close files