Skip to content

Commit 7788b79

Browse files
committed
Merge remote-tracking branch 'origin/stage' into feat/ADFA-4419-remote-peer-editor-decoration
2 parents 78ee0cb + c14733b commit 7788b79

42 files changed

Lines changed: 2221 additions & 236 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt‎

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import android.content.Intent
2222
import android.content.ServiceConnection
2323
import android.content.res.Configuration
2424
import android.graphics.Color
25+
import android.graphics.Rect
2526
import android.graphics.drawable.GradientDrawable
2627
import android.os.Build
2728
import android.os.Bundle
@@ -82,6 +83,7 @@ import com.google.android.material.tabs.TabLayout.Tab
8283
import com.itsaky.androidide.FeedbackButtonManager
8384
import com.itsaky.androidide.R
8485
import com.itsaky.androidide.R.string
86+
import com.itsaky.androidide.activities.MainActivity
8587
import com.itsaky.androidide.actions.build.DebugAction
8688
import com.itsaky.androidide.adapters.DiagnosticsAdapter
8789
import com.itsaky.androidide.adapters.SearchListAdapter
@@ -111,6 +113,7 @@ import com.itsaky.androidide.models.Range
111113
import com.itsaky.androidide.models.SearchResult
112114
import com.itsaky.androidide.plugins.manager.ui.PluginEditorTabManager
113115
import com.itsaky.androidide.preferences.internal.BuildPreferences
116+
import com.itsaky.androidide.preferences.internal.GeneralPreferences
114117
import com.itsaky.androidide.projects.IProjectManager
115118
import com.itsaky.androidide.projects.ProjectManagerImpl
116119
import com.itsaky.androidide.services.debug.DebuggerService
@@ -631,11 +634,35 @@ abstract class BaseEditorActivity :
631634
builder.show()
632635
}
633636

637+
/**
638+
* Restores the project path on recreation (saved state, launch intent, or last opened
639+
* project) and routes back to MainActivity if none is available, rather than crashing while
640+
* building the editor UI.
641+
*/
634642
override fun onCreate(savedInstanceState: Bundle?) {
635-
savedInstanceState?.getString(KEY_PROJECT_PATH)
636-
?.let(ProjectManagerImpl.getInstance()::projectPath::set)
643+
// The OS can recreate EditorActivity after process death without routing through
644+
// MainActivity, leaving the ProjectManagerImpl singleton's lateinit projectPath unset.
645+
// Restore it from the saved state, the launch intent, or the last opened project.
646+
val restoredProjectPath =
647+
savedInstanceState?.getString(KEY_PROJECT_PATH)?.takeIf { it.isNotBlank() }
648+
?: intent?.getStringExtra("PROJECT_PATH")?.takeIf { it.isNotBlank() }
649+
?: GeneralPreferences.lastOpenedProject
650+
.takeIf { it.isNotBlank() && it != GeneralPreferences.NO_OPENED_PROJECT }
651+
if (restoredProjectPath != null) {
652+
ProjectManagerImpl.getInstance().projectPath = restoredProjectPath
653+
}
637654
super.onCreate(savedInstanceState)
638655

656+
// If we still have no project path after every fallback, we cannot safely build the
657+
// editor UI (setupToolbar -> getProjectName dereferences the project path). Route the
658+
// user back to MainActivity instead of crashing.
659+
if (ProjectManagerImpl.getInstance().projectDirPath.isBlank()) {
660+
log.warn("No project path available in EditorActivity.onCreate(); returning to MainActivity")
661+
startActivity(Intent(this, MainActivity::class.java))
662+
finish()
663+
return
664+
}
665+
639666
editorViewModel.isBuildInProgress = false
640667
editorViewModel.isInitializing = false
641668

@@ -1626,7 +1653,9 @@ abstract class BaseEditorActivity :
16261653
}
16271654

16281655
// Filter out diagonal flings so only an intentional right swipe opens the drawer.
1629-
if (isDrawerOpenFling) {
1656+
// A horizontal fling that started on the bottom-sheet tab strip is the user
1657+
// scrolling tabs, not asking for the drawer.
1658+
if (isDrawerOpenFling && !isTouchOnBottomSheetTabs(e1)) {
16301659
binding.editorDrawerLayout.openDrawer(GravityCompat.START)
16311660
return true
16321661
}
@@ -1646,6 +1675,13 @@ abstract class BaseEditorActivity :
16461675
return super.dispatchTouchEvent(ev)
16471676
}
16481677

1678+
private fun isTouchOnBottomSheetTabs(ev: MotionEvent): Boolean {
1679+
val tabs = contentOrNull?.bottomSheet?.binding?.tabs ?: return false
1680+
val rect = Rect()
1681+
if (!tabs.getGlobalVisibleRect(rect)) return false
1682+
return rect.contains(ev.rawX.toInt(), ev.rawY.toInt())
1683+
}
1684+
16491685
private fun showTooltip(tag: String) {
16501686
TooltipManager.showIdeCategoryTooltip(
16511687
context = this,

‎app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt‎

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -420,23 +420,35 @@ open class EditorHandlerActivity :
420420
syncPluginUiFontSize()
421421
}
422422

423+
/**
424+
* Restores the plugin tabs cached from the previous session, running the
425+
* SharedPreferences IO and Gson decode off the main thread to avoid a startup UI stall.
426+
*/
423427
private fun restoreOpenedPluginTabs() {
424-
try {
425-
val prefs = (application as BaseApplication).prefManager
426-
val json = prefs.getString(PREF_KEY_OPEN_PLUGIN_TABS, null) ?: return
428+
lifecycleScope.launch {
429+
try {
430+
val prefs = (application as BaseApplication).prefManager
431+
val json = withContext(Dispatchers.IO) {
432+
prefs.getString(PREF_KEY_OPEN_PLUGIN_TABS, null)
433+
} ?: return@launch
427434

428-
val tabIds = Gson().fromJson(json, Array<String>::class.java)?.toList() ?: return
429-
Log.d("EditorHandlerActivity", "Restoring plugin tabs: $tabIds")
435+
// Decoding the cached JSON off the main thread avoids a UI stall on startup.
436+
val tabIds = withContext(Dispatchers.Default) {
437+
Gson().fromJson(json, Array<String>::class.java)?.toList()
438+
} ?: return@launch
439+
Log.d("EditorHandlerActivity", "Restoring plugin tabs: $tabIds")
430440

431-
tabIds.forEach { tabId ->
432-
if (!pluginTabIndices.containsKey(tabId)) {
433-
selectPluginTabById(tabId)
441+
// Tab selection touches UI state, so keep it on the main thread.
442+
tabIds.forEach { tabId ->
443+
if (!pluginTabIndices.containsKey(tabId)) {
444+
selectPluginTabById(tabId)
445+
}
434446
}
435-
}
436447

437-
prefs.putString(PREF_KEY_OPEN_PLUGIN_TABS, null)
438-
} catch (e: Exception) {
439-
Log.e("EditorHandlerActivity", "Failed to restore plugin tabs", e)
448+
withContext(Dispatchers.IO) { prefs.putString(PREF_KEY_OPEN_PLUGIN_TABS, null) }
449+
} catch (e: Exception) {
450+
Log.e("EditorHandlerActivity", "Failed to restore plugin tabs", e)
451+
}
440452
}
441453
}
442454

‎app/src/main/java/com/itsaky/androidide/agent/fragments/AiSettingsFragment.kt‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import androidx.core.net.toUri
1616
import androidx.fragment.app.Fragment
1717
import androidx.fragment.app.viewModels
1818
import androidx.navigation.fragment.findNavController
19+
import com.google.android.material.bottomsheet.BottomSheetBehavior
1920
import com.google.android.material.textfield.TextInputEditText
2021
import com.google.android.material.textfield.TextInputLayout
2122
import com.google.android.material.materialswitch.MaterialSwitch
2223
import com.itsaky.androidide.R
24+
import com.itsaky.androidide.activities.editor.BaseEditorActivity
2325
import com.itsaky.androidide.agent.repository.AiBackend
2426
import com.itsaky.androidide.agent.repository.Util.getCurrentBackend
2527
import com.itsaky.androidide.agent.viewmodel.AiSettingsViewModel
@@ -28,6 +30,7 @@ import com.itsaky.androidide.agent.viewmodel.ModelLoadingState
2830
import com.itsaky.androidide.databinding.FragmentAiSettingsBinding
2931
import com.itsaky.androidide.utils.flashInfo
3032
import com.itsaky.androidide.utils.getFileName
33+
import com.itsaky.androidide.viewmodel.BottomSheetViewModel
3134
import java.text.SimpleDateFormat
3235
import java.util.Date
3336
import java.util.Locale
@@ -50,6 +53,13 @@ class AiSettingsFragment : Fragment(R.layout.fragment_ai_settings) {
5053
val uriString = it.toString()
5154
viewModel.loadModelFromUri(uriString, requireContext())
5255
flashInfo("Attempting to load selected model...")
56+
57+
view?.postDelayed({
58+
(activity as? BaseEditorActivity)?.bottomSheetViewModel?.setSheetState(
59+
sheetState = BottomSheetBehavior.STATE_EXPANDED,
60+
currentTab = BottomSheetViewModel.TAB_AGENT
61+
)
62+
}, 100)
5363
}
5464
}
5565

@@ -111,7 +121,7 @@ class AiSettingsFragment : Fragment(R.layout.fragment_ai_settings) {
111121
val browseButton = view.findViewById<Button>(R.id.btn_browse_model)
112122
val loadSavedButton = view.findViewById<Button>(R.id.loadSavedButton)
113123
val modelStatusTextView = view.findViewById<TextView>(R.id.model_status_text_view)
114-
val engineStatusTextView = view.findViewById<TextView>(R.id.engine_status_text) // <-- NEW: Get reference to the new TextView
124+
val engineStatusTextView = view.findViewById<TextView>(R.id.engine_status_text)
115125
val simplePromptSwitch = view.findViewById<MaterialSwitch>(R.id.switch_simple_local_prompt)
116126
val shaInput = view.findViewById<TextInputEditText>(R.id.local_model_sha_input)
117127

@@ -204,6 +214,13 @@ class AiSettingsFragment : Fragment(R.layout.fragment_ai_settings) {
204214
}
205215
if (hasPermission) {
206216
viewModel.loadModelFromUri(savedUri, requireContext())
217+
218+
view?.postDelayed({
219+
(activity as? BaseEditorActivity)?.bottomSheetViewModel?.setSheetState(
220+
sheetState = BottomSheetBehavior.STATE_EXPANDED,
221+
currentTab = BottomSheetViewModel.TAB_AGENT
222+
)
223+
}, 100)
207224
} else {
208225
requireActivity().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
209226
remove(SAVED_MODEL_URI_KEY)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.itsaky.androidide.agent.model
2+
3+
sealed interface ModelLoadResult {
4+
data class Loaded(
5+
val modelName: String
6+
) : ModelLoadResult
7+
8+
data class Rejected(
9+
val message: String
10+
) : ModelLoadResult
11+
12+
data class Failed(
13+
val message: String,
14+
val cause: Throwable? = null
15+
) : ModelLoadResult
16+
}

0 commit comments

Comments
 (0)