Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ import com.github.mikephil.charting.data.LineData
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.STATE_COLLAPSED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.Tab
import com.itsaky.androidide.FeedbackButtonManager
Expand Down Expand Up @@ -234,8 +236,8 @@ abstract class BaseEditorActivity :
binding.editorDrawerLayout.closeDrawer(GravityCompat.START)
}

bottomSheetViewModel.sheetBehaviorState != BottomSheetBehavior.STATE_COLLAPSED -> {
bottomSheetViewModel.setSheetState(sheetState = BottomSheetBehavior.STATE_COLLAPSED)
bottomSheetViewModel.sheetBehaviorState != STATE_COLLAPSED -> {
bottomSheetViewModel.setSheetState(sheetState = STATE_COLLAPSED)
}

binding.swipeReveal.isOpen -> {
Expand Down Expand Up @@ -517,7 +519,7 @@ abstract class BaseEditorActivity :

applyImmersiveModeInsets(systemBars)

handleKeyboardInsets(imeInsets)
handleKeyboardInsets(imeInsets, systemBars)
}

private fun applyStandardInsets(systemBars: Insets) {
Expand All @@ -530,7 +532,7 @@ abstract class BaseEditorActivity :
_binding?.content?.applyImmersiveModeInsets(systemBars)
}

private fun handleKeyboardInsets(imeInsets: Insets) {
private fun handleKeyboardInsets(imeInsets: Insets, systemBars: Insets) {
val isImeVisible = imeInsets.bottom > 0
_binding?.content?.bottomSheet?.setImeVisible(isImeVisible)

Expand All @@ -539,7 +541,8 @@ abstract class BaseEditorActivity :
isImeVisible -> {
contentCardRealHeight?.let { baseHeight ->
updateLayoutParams<ViewGroup.LayoutParams> {
height = (baseHeight - imeInsets.bottom).coerceAtLeast(0)
val diff = (imeInsets.bottom - systemBars.bottom).coerceAtLeast(0)
height = (baseHeight - diff).coerceAtLeast(0)
}
}
}
Expand All @@ -555,6 +558,14 @@ abstract class BaseEditorActivity :

if (this.isImeVisible != isImeVisible) {
this.isImeVisible = isImeVisible

if (editorViewModel.isFullscreen) {
// Hide the bottom sheet if in fullscreen mode, but only collapse it if keyboard
// is open so the symbol input view is visible
val targetState = if (isImeVisible) STATE_COLLAPSED else STATE_HIDDEN
editorBottomSheet?.state = targetState
}

onSoftInputChanged()
}
}
Expand Down Expand Up @@ -1053,7 +1064,7 @@ abstract class BaseEditorActivity :
}

open fun hideBottomSheet() {
bottomSheetViewModel.setSheetState(sheetState = BottomSheetBehavior.STATE_COLLAPSED)
bottomSheetViewModel.setSheetState(sheetState = STATE_COLLAPSED)
}

private fun updateBottomSheetState(state: BottomSheetViewModel.SheetState = BottomSheetViewModel.SheetState.EMPTY) {
Expand Down Expand Up @@ -1332,7 +1343,7 @@ abstract class BaseEditorActivity :
) {
bottomSheetViewModel.setSheetState(BottomSheetBehavior.STATE_EXPANDED)
ThreadUtils.runOnUiThreadDelayed({
bottomSheetViewModel.setSheetState(BottomSheetBehavior.STATE_COLLAPSED)
bottomSheetViewModel.setSheetState(STATE_COLLAPSED)
app.prefManager.putBoolean(KEY_BOTTOM_SHEET_SHOWN, true)
}, 1500)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.itsaky.androidide.activities.editor

import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
import com.blankj.utilcode.util.KeyboardUtils
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.itsaky.androidide.R
Expand Down Expand Up @@ -225,8 +227,12 @@ class FullscreenManager(
appBarContent.alpha = 0f

bottomSheetBehavior.isHideable = true
if (bottomSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN

val isKeyboardOpen = KeyboardUtils.isSoftInputVisible(contentBinding.root.context as Activity)
val targetState = if (isKeyboardOpen) BottomSheetBehavior.STATE_COLLAPSED else BottomSheetBehavior.STATE_HIDDEN
Comment on lines +231 to +232
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find how ContentEditorBinding / content root is inflated and what context backs it
rg -nP --type=kotlin -C3 '\bContentEditorBinding\b'
rg -nP --type=kotlin -C3 'isSoftInputVisible'

Repository: appdevforall/CodeOnTheGo

Length of output: 9661


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show surrounding code in FullscreenManager.kt
nl -ba app/src/main/java/com/itsaky/androidide/activities/editor/FullscreenManager.kt | sed -n '180,270p'

# 2) Find where FullscreenManager is instantiated
rg -n --type=kotlin "FullscreenManager\s*\(" app/src/main/java/com/itsaky/androidide | head -n 50

# 3) Inspect BaseEditorActivity binding initialization (where ActivityEditorBinding / content binding is inflated)
rg -n --type=kotlin "_binding\s*=\s*" app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
nl -ba app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt | sed -n '150,280p'

# 4) Locate where ActivityEditorBinding.inflate is called (more precise)
rg -n --type=kotlin "ActivityEditorBinding\.inflate|inflate\(.*ActivityEditorBinding" app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt app/src/main/java/com/itsaky/androidide/activities/editor/*.kt

Repository: appdevforall/CodeOnTheGo

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Surrounding code in FullscreenManager.kt (no nl; use cat -n)
sed -n '190,260p' app/src/main/java/com/itsaky/androidide/activities/editor/FullscreenManager.kt
echo "----"
cat -n app/src/main/java/com/itsaky/androidide/activities/editor/FullscreenManager.kt | sed -n '210,245p'

# 2) Find FullscreenManager instantiation call sites
rg -n --type=kotlin "FullscreenManager\s*\(" app/src/main/java/com/itsaky/androidide | head -n 50

# 3) Inspect BaseEditorActivity binding initialization area
cat -n app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt | sed -n '120,260p'

# 4) Look specifically for ActivityEditorBinding.inflate / setContentView usage
rg -n --type=kotlin "ActivityEditorBinding\.(inflate|bind)\(|inflate\(.*ActivityEditorBinding|setContentView\(.*ActivityEditorBinding" app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt

Repository: appdevforall/CodeOnTheGo

Length of output: 10983


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# BaseEditorActivity: binding inflate region and where fullscreenManager is created
sed -n '480,560p' app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
echo "----"
sed -n '620,720p' app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt

# KeyboardUtils: locate and show isSoftInputVisible implementation/signature
rg -n --type=kotlin "object\s+KeyboardUtils|class\s+KeyboardUtils|fun\s+isSoftInputVisible" app/src/main/java/com/itsaky/androidide | head -n 50

Repository: appdevforall/CodeOnTheGo

Length of output: 5010


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find KeyboardUtils source file
rg -n --type=kotlin "isSoftInputVisible\s*\(" app/src/main/java/com/itsaky/androidide | head -n 50

# 2) Show definition region (try common file locations by name)
fd -a -t f "KeyboardUtils.*" app/src/main/java/com/itsaky/androidide 2>/dev/null || true
fd -a -t f "KeyboardUtils.*" app/src/main/java 2>/dev/null || true

# 3) If we found the file, print it around isSoftInputVisible
# Use rg to get file path, then print a window around the definition line.
DEF_FILE=$(rg -n --type=kotlin "fun\s+isSoftInputVisible" app/src/main/java/com/itsaky/androidide -S | head -n 1 | cut -d: -f1 || true)
echo "DEF_FILE=$DEF_FILE"
if [ -n "$DEF_FILE" ]; then
  rg -n --type=kotlin "fun\s+isSoftInputVisible" "$DEF_FILE"
  sed -n '1,200p' "$DEF_FILE" | head -n 200
fi

Repository: appdevforall/CodeOnTheGo

Length of output: 690


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate any definition/implementation of isSoftInputVisible and KeyboardUtils
rg -n --hidden --no-ignore --smart-case "isSoftInputVisible" . | head -n 50

echo "----"

rg -n --hidden --no-ignore --smart-case "object\s+KeyboardUtils|class\s+KeyboardUtils|KeyboardUtils" app/src/main/java | head -n 80

Repository: appdevforall/CodeOnTheGo

Length of output: 1787


🌐 Web query:

com.blankj.utilcode.util.KeyboardUtils.isSoftInputVisible Activity signature ContextThemeWrapper ClassCastException

💡 Result:

There is no evidence (in the sources I found) that Blankj utilcode’s KeyboardUtils.isSoftInputVisible(Activity) has an “Activity signature ContextThemeWrapper ClassCastException”. Instead, the isSoftInputVisible implementation is type-safe in that it only accepts an Activity parameter and uses activity.getWindow/getDecorView—it does not cast a Context to Activity inside the method. 1) What Blankj’s code actually does - KeyboardUtils.isSoftInputVisible(@NonNull Activity activity) returns getDecorViewInvisibleHeight(activity.getWindow) > 0. [1] - getDecorViewInvisibleHeight(Window window) uses window.getDecorView, then decorView.getWindowVisibleDisplayFrame(outRect) and computes a delta from the decor view vs the visible frame. [1] So, within this implementation, there is no cast from ContextThemeWrapper (or any Context wrapper) to Activity. [1] 2) Why you might see ClassCastException involving ContextThemeWrapper + keyboard code - A ClassCastException like “android.view.ContextThemeWrapper cannot be cast to android.app.Activity” is a known pattern when code mistakenly does (Activity) getContext, but getContext is actually a ContextThemeWrapper rather than an Activity. - In React Native, keyboard visibility detection code in ReactRootView previously cast getContext directly to Activity, and the fix unwrapped the context using ContextWrapper.getBaseContext before casting. [2][3] This shows the general cause of the “ContextThemeWrapper -> Activity cast” failure is incorrect casting of an arbitrary Context; it is not an intrinsic issue with Blankj’s isSoftInputVisible(Activity) implementation. [2][3] 3) Android-soft-keyboard visibility detection guidance - Android’s official docs for keyboard/input-method visibility note that keyboard visibility behavior depends on window/view focus and windowSoftInputMode / flags, and recommends using InputMethodManager when you need to show it reliably. [4] This is consistent with the fact that any keyboard-visibility helper may produce incorrect results if it’s invoked with the wrong window/view state. [4] What to do if you are crashing - If you call KeyboardUtils.isSoftInputVisible(...) with something that is not actually an Activity (for example, passing view.getContext instead of the Activity), you can get crashes elsewhere in your stack. Ensure you pass a real Activity instance. - If you are using a framework view (e.g., React Native) that contains its own keyboard detection, check for code that casts getContext to Activity; apply the same pattern used in React Native’s fix: unwrap via ContextWrapper.getBaseContext (or use a safer way to obtain the owning Activity) before casting. [2][3] Top relevant sources: [1] https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/src/main/java/com/blankj/utilcode/util/KeyboardUtils.java [2] facebook/react-native@f7e35d4 [3] facebook/react-native#36121 [4] https://developer.android.com/develop/ui/views/touch-and-input/keyboard-input/visibility

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --type=kotlin "class\s+EdgeToEdgeIDEActivity" app/src/main/java | head -n 20

Repository: appdevforall/CodeOnTheGo

Length of output: 195


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --type=kotlin "open\s+class\s+IDEActivity|abstract\s+class\s+IDEActivity|class\s+IDEActivity" app/src/main/java/com/itsaky/androidide/app app/src/main/java/com/itsaky/androidide | head -n 20

Repository: appdevforall/CodeOnTheGo

Length of output: 290


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,120p' app/src/main/java/com/itsaky/androidide/app/IDEActivity.kt
echo "----"
rg -n --type=kotlin "open\s+class\s+BaseIDEActivity|abstract\s+class\s+BaseIDEActivity|class\s+BaseIDEActivity" app/src/main/java/com/itsaky/androidide/app app/src/main/java/com/itsaky/androidide | head -n 20
# Print the BaseIDEActivity file once found
BASE_FILE=$(rg -n --type=kotlin "class\s+BaseIDEActivity" app/src/main/java/com/itsaky/androidide/app app/src/main/java/com/itsaky/androidide | head -n 1 | cut -d: -f1 || true)
echo "BASE_FILE=$BASE_FILE"
if [ -n "$BASE_FILE" ]; then
  sed -n '1,140p' "$BASE_FILE"
fi

Repository: appdevforall/CodeOnTheGo

Length of output: 928


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find BaseIDEActivity file
rg -n --hidden --no-ignore --smart-case "class\s+BaseIDEActivity" app/src/main/java | head -n 20

BASE_FILE=$(rg -n --hidden --no-ignore --smart-case "class\s+BaseIDEActivity" app/src/main/java | head -n 1 | cut -d: -f1 || true)
echo "BASE_FILE=$BASE_FILE"
if [ -n "$BASE_FILE" ]; then
  sed -n '1,160p' "$BASE_FILE"
fi

# Also search for layoutInflater usage in BaseIDEActivity
rg -n --type=kotlin "layoutInflater" "$BASE_FILE" || true

Repository: appdevforall/CodeOnTheGo

Length of output: 50


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- rg BaseIDEActivity"
rg -n --hidden --no-ignore --smart-case "BaseIDEActivity" app/src/main/java | head -n 50 || true

echo "---- fd BaseIDEActivity"
fd -a -t f "BaseIDEActivity*" app/src/main/java 2>/dev/null || true

echo "---- fd *IDEActivity*"
fd -a -t f "*IDEActivity*" app/src/main/java/com/itsaky/androidide/app 2>/dev/null || true

Repository: appdevforall/CodeOnTheGo

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --hidden --no-ignore --smart-case "BaseIDEActivity" . | head -n 50 || true
echo "----"
fd -a -t f "*BaseIDE*Activity*" . 2>/dev/null | head -n 50 || true

Repository: appdevforall/CodeOnTheGo

Length of output: 1920


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,140p' common/src/main/java/com/itsaky/androidide/app/BaseIDEActivity.kt
echo "----"
sed -n '140,260p' common/src/main/java/com/itsaky/androidide/app/BaseIDEActivity.kt

Repository: appdevforall/CodeOnTheGo

Length of output: 3134


Avoid the unsafe contentBinding.root.context as Activity cast in FullscreenManager.applyFullscreen.

FullscreenManager.applyFullscreen casts the view context (contentBinding.root.context) to Activity to call KeyboardUtils.isSoftInputVisible(...). Since the view is inflated via BaseIDEActivity (an AppCompatActivity) and ActivityEditorBinding.inflate(layoutInflater), root.context can be a ContextThemeWrapper, making this cast a potential ClassCastException. Unwrap the context chain (or pass the owning activity) before calling KeyboardUtils.

🛡️ Proposed fix to safely resolve the Activity
-        val isKeyboardOpen = KeyboardUtils.isSoftInputVisible(contentBinding.root.context as Activity)
-        val targetState = if (isKeyboardOpen) BottomSheetBehavior.STATE_COLLAPSED else BottomSheetBehavior.STATE_HIDDEN
+        val activity = contentBinding.root.context.let { ctx ->
+            generateSequence(ctx) { (it as? ContextWrapper)?.baseContext }
+                .filterIsInstance<Activity>()
+                .firstOrNull()
+        }
+        val isKeyboardOpen = activity?.let { KeyboardUtils.isSoftInputVisible(it) } ?: false
+        val targetState = if (isKeyboardOpen) BottomSheetBehavior.STATE_COLLAPSED else BottomSheetBehavior.STATE_HIDDEN

Add import android.content.ContextWrapper if you adopt this.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val isKeyboardOpen = KeyboardUtils.isSoftInputVisible(contentBinding.root.context as Activity)
val targetState = if (isKeyboardOpen) BottomSheetBehavior.STATE_COLLAPSED else BottomSheetBehavior.STATE_HIDDEN
val activity = contentBinding.root.context.let { ctx ->
generateSequence(ctx) { (it as? ContextWrapper)?.baseContext }
.filterIsInstance<Activity>()
.firstOrNull()
}
val isKeyboardOpen = activity?.let { KeyboardUtils.isSoftInputVisible(it) } ?: false
val targetState = if (isKeyboardOpen) BottomSheetBehavior.STATE_COLLAPSED else BottomSheetBehavior.STATE_HIDDEN
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/itsaky/androidide/activities/editor/FullscreenManager.kt`
around lines 231 - 232, FullscreenManager.applyFullscreen currently unsafely
casts contentBinding.root.context to Activity for
KeyboardUtils.isSoftInputVisible; instead unwrap the view context by walking
ContextWrapper (while ctx is ContextWrapper set ctx = ctx.baseContext) until you
find an Activity, or accept/pass the owning Activity into FullscreenManager and
use that; then call KeyboardUtils.isSoftInputVisible(activity). Reference:
FullscreenManager.applyFullscreen, contentBinding.root.context, and
KeyboardUtils.isSoftInputVisible; add android.content.ContextWrapper import if
unwrapping.


if (bottomSheetBehavior.state != targetState) {
bottomSheetBehavior.state = targetState
}

editorContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ constructor(
}

private fun applyPeekHeight() {
behavior.peekHeight = if (isImeVisible || isSearchModeActive) 0 else collapsedHeight.toInt()
behavior.peekHeight = if (isSearchModeActive) 0 else collapsedHeight.toInt()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use roundToInt() for consistent and accurate float-to-int conversion.

The code uses toInt() here, which truncates the decimal part, while other locations in this file (lines 367, 375, 386, 392, 409) use roundToInt() for the same collapsedHeight property. Truncation can introduce off-by-one pixel errors in peek height calculations.

🔧 Proposed fix
-    behavior.peekHeight = if (isSearchModeActive) 0 else collapsedHeight.toInt()
+    behavior.peekHeight = if (isSearchModeActive) 0 else collapsedHeight.roundToInt()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
behavior.peekHeight = if (isSearchModeActive) 0 else collapsedHeight.toInt()
behavior.peekHeight = if (isSearchModeActive) 0 else collapsedHeight.roundToInt()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt` at line 357,
Replace the float-to-int conversion for the bottom sheet peek height to use
rounding instead of truncation: locate the assignment to behavior.peekHeight in
EditorBottomSheet (the line using isSearchModeActive and collapsedHeight) and
change the conversion of collapsedHeight to use roundToInt() so it matches other
uses in this file and avoids off-by-one pixel errors.

}

fun setOffsetAnchor(view: View) {
Expand Down
Loading