Skip to content

Commit 95fcc26

Browse files
authored
Merge branch 'stage' into feat/ADFA-3290-smart-boundary-detection-experimental
2 parents cb36d75 + 140716a commit 95fcc26

File tree

12 files changed

+372
-70
lines changed

12 files changed

+372
-70
lines changed

app/src/main/java/com/itsaky/androidide/actions/build/DebugAction.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.content.ActivityNotFoundException
77
import android.content.Context
88
import android.content.Intent
99
import android.graphics.Color
10+
import android.net.Uri
1011
import android.os.Build
1112
import android.provider.Settings
1213
import android.text.SpannableStringBuilder
@@ -17,6 +18,7 @@ import androidx.core.content.ContextCompat.startForegroundService
1718
import androidx.core.view.setPadding
1819
import com.google.android.material.textview.MaterialTextView
1920
import com.itsaky.androidide.actions.ActionData
21+
import com.itsaky.androidide.activities.editor.EditorHandlerActivity
2022
import com.itsaky.androidide.activities.editor.HelpActivity
2123
import com.itsaky.androidide.idetooltips.TooltipTag
2224
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
@@ -26,6 +28,7 @@ import com.itsaky.androidide.projects.IProjectManager
2628
import com.itsaky.androidide.projects.isPluginProject
2729
import com.itsaky.androidide.resources.R
2830
import com.itsaky.androidide.utils.DialogUtils
31+
import com.itsaky.androidide.utils.PermissionsHelper
2932
import com.itsaky.androidide.utils.appendHtmlWithLinks
3033
import com.itsaky.androidide.utils.appendOrderedList
3134
import com.itsaky.androidide.utils.flashError
@@ -99,6 +102,15 @@ class DebugAction(
99102
return false
100103
}
101104

105+
val overlayState = withContext(Dispatchers.Main.immediate) {
106+
PermissionsHelper.getOverlayPermissionState(activity)
107+
}
108+
109+
if (overlayState != PermissionsHelper.OverlayPermissionState.GRANTED) {
110+
handleMissingOverlayPermission(activity, overlayState)
111+
return false
112+
}
113+
102114
if (!Shizuku.pingBinder()) {
103115
log.error("Shizuku service is not running")
104116
withContext(Dispatchers.Main.immediate) {
@@ -110,6 +122,32 @@ class DebugAction(
110122
return Shizuku.pingBinder()
111123
}
112124

125+
private suspend fun handleMissingOverlayPermission(
126+
activity: EditorHandlerActivity,
127+
state: PermissionsHelper.OverlayPermissionState
128+
) {
129+
withContext(Dispatchers.Main.immediate) {
130+
when (state) {
131+
PermissionsHelper.OverlayPermissionState.UNSUPPORTED -> {
132+
activity.flashError(activity.getString(R.string.permission_overlay_unsupported_hint))
133+
}
134+
PermissionsHelper.OverlayPermissionState.REQUESTABLE -> {
135+
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
136+
putExtra(Settings.EXTRA_APP_PACKAGE, activity.packageName)
137+
setData(Uri.fromParts("package", activity.packageName, null))
138+
}
139+
try {
140+
activity.startActivity(intent)
141+
} catch (e: Exception) {
142+
log.error("Failed to launch overlay settings", e)
143+
activity.flashError(activity.getString(R.string.err_no_activity_to_handle_action, Settings.ACTION_MANAGE_OVERLAY_PERMISSION))
144+
}
145+
}
146+
else -> {}
147+
}
148+
}
149+
}
150+
113151
@RequiresApi(Build.VERSION_CODES.R)
114152
private fun showPairingDialog(context: Context): AlertDialog? {
115153
val launchHelp = { url: String ->

app/src/main/java/com/itsaky/androidide/actions/file/ShowTooltipAction.kt

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import com.itsaky.androidide.R
2323
import com.itsaky.androidide.actions.ActionData
2424
import com.itsaky.androidide.actions.ActionItem
2525
import com.itsaky.androidide.actions.BaseEditorAction
26+
import com.itsaky.androidide.editor.utils.isJavaOperatorToken
27+
import com.itsaky.androidide.editor.utils.isKotlinOperatorToken
2628
import com.itsaky.androidide.editor.utils.isXmlAttribute
2729
import com.itsaky.androidide.idetooltips.TooltipCategory
2830
import com.itsaky.androidide.idetooltips.TooltipManager
@@ -58,29 +60,19 @@ class ShowTooltipAction(private val context: Context, override val order: Int) :
5860
val anchorView = target.getAnchorView() ?: return false
5961
val editor = getEditor(data)
6062

61-
val category: String
62-
val tag: String
63-
64-
if (editor != null) {
65-
val selectedText = target.getSelectedText()
66-
category = when (editor.file?.extension) {
67-
"java" -> TooltipCategory.CATEGORY_JAVA
68-
"kt" -> TooltipCategory.CATEGORY_KOTLIN
69-
"xml" -> TooltipCategory.CATEGORY_XML
70-
else -> TooltipCategory.CATEGORY_IDE
71-
}
72-
73-
val useEditorTag = editor.tag != null
74-
val textToUse = selectedText ?: ""
75-
tag = when {
76-
useEditorTag -> editor.tag.toString()
77-
category == TooltipCategory.CATEGORY_XML && editor.isXmlAttribute() -> textToUse.substringAfterLast(":")
78-
else -> textToUse
63+
val categoryAndTag =
64+
if (editor != null) {
65+
val category = tooltipCategoryForExtension(editor.file?.extension)
66+
resolveTooltipTag(
67+
category = category,
68+
selectedText = target.getSelectedText(),
69+
editorTag = editor.tag?.toString(),
70+
isXmlAttribute = category == TooltipCategory.CATEGORY_XML && editor.isXmlAttribute(),
71+
).let { tag -> category to tag }
72+
} else {
73+
TooltipCategory.CATEGORY_IDE to TooltipTag.DIALOG_FIND_IN_PROJECT
7974
}
80-
} else {
81-
category = TooltipCategory.CATEGORY_IDE
82-
tag = TooltipTag.DIALOG_FIND_IN_PROJECT
83-
}
75+
val (category, tag) = categoryAndTag
8476

8577
if (tag.isEmpty()) return false
8678

@@ -95,4 +87,28 @@ class ShowTooltipAction(private val context: Context, override val order: Int) :
9587
}
9688

9789
override fun retrieveTooltipTag(isAlternateContext: Boolean) = TooltipTag.EDITOR_TOOLBAR_HELP
90+
}
91+
92+
internal fun tooltipCategoryForExtension(extension: String?): String =
93+
when (extension) {
94+
"java" -> TooltipCategory.CATEGORY_JAVA
95+
"kt" -> TooltipCategory.CATEGORY_KOTLIN
96+
"xml" -> TooltipCategory.CATEGORY_XML
97+
else -> TooltipCategory.CATEGORY_IDE
98+
}
99+
100+
internal fun resolveTooltipTag(
101+
category: String,
102+
selectedText: String?,
103+
editorTag: String?,
104+
isXmlAttribute: Boolean,
105+
): String {
106+
val textToUse = selectedText ?: ""
107+
return when {
108+
!editorTag.isNullOrEmpty() -> editorTag
109+
category == TooltipCategory.CATEGORY_XML && isXmlAttribute -> textToUse.substringAfterLast(":")
110+
category == TooltipCategory.CATEGORY_KOTLIN && isKotlinOperatorToken(textToUse) -> "kotlin.operator.$textToUse"
111+
category == TooltipCategory.CATEGORY_JAVA && isJavaOperatorToken(textToUse) -> "java.operator.$textToUse"
112+
else -> textToUse
113+
}
98114
}

app/src/main/java/com/itsaky/androidide/adapters/onboarding/OnboardingPermissionsAdapter.kt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ import android.content.res.ColorStateList
2121
import android.view.LayoutInflater
2222
import android.view.ViewGroup
2323
import androidx.core.content.ContextCompat
24+
import androidx.core.graphics.ColorUtils
2425
import androidx.recyclerview.widget.RecyclerView
2526
import com.blankj.utilcode.util.SizeUtils
2627
import com.google.android.material.button.MaterialButton
28+
import com.google.android.material.color.MaterialColors
2729
import com.itsaky.androidide.R
2830
import com.itsaky.androidide.databinding.LayoutOnboardingPermissionItemBinding
2931
import com.itsaky.androidide.models.OnboardingPermissionItem
@@ -36,7 +38,12 @@ class OnboardingPermissionsAdapter(private val permissions: List<OnboardingPermi
3638
RecyclerView.Adapter<OnboardingPermissionsAdapter.ViewHolder>() {
3739

3840
class ViewHolder(val binding: LayoutOnboardingPermissionItemBinding) :
39-
RecyclerView.ViewHolder(binding.root)
41+
RecyclerView.ViewHolder(binding.root) {
42+
val titleColor: Int = MaterialColors.getColor(binding.root, R.attr.colorOnSurface)
43+
val descriptionColor: Int = MaterialColors.getColor(binding.root, R.attr.colorOnSurfaceVariant)
44+
val disabledTitleColor: Int = ColorUtils.setAlphaComponent(titleColor, (255 * 0.38f).toInt())
45+
val disabledDescriptionColor: Int = ColorUtils.setAlphaComponent(descriptionColor, (255 * 0.38f).toInt())
46+
}
4047

4148
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
4249
return ViewHolder(
@@ -47,10 +54,23 @@ class OnboardingPermissionsAdapter(private val permissions: List<OnboardingPermi
4754
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
4855
val binding = holder.binding
4956
val permission = permissions[position]
57+
val context = binding.root.context
5058

5159
binding.infoContent.apply {
5260
title.setText(permission.title)
5361
description.setText(permission.description)
62+
title.setTextColor(if (permission.isSupportedOnDevice) holder.titleColor else holder.disabledTitleColor)
63+
description.setTextColor(if (permission.isSupportedOnDevice) holder.descriptionColor else holder.disabledDescriptionColor)
64+
}
65+
66+
binding.grantButton.apply {
67+
isEnabled = permission.isSupportedOnDevice
68+
text = context.getString(R.string.title_grant)
69+
icon = null
70+
iconTint = null
71+
iconGravity = MaterialButton.ICON_GRAVITY_TEXT_START
72+
iconPadding = 0
73+
iconSize = 0
5474
}
5575

5676
binding.grantButton.setOnClickListener {
@@ -61,9 +81,9 @@ class OnboardingPermissionsAdapter(private val permissions: List<OnboardingPermi
6181
binding.grantButton.apply {
6282
isEnabled = false
6383
text = ""
64-
icon = ContextCompat.getDrawable(binding.root.context, R.drawable.ic_ok)
84+
icon = ContextCompat.getDrawable(context, R.drawable.ic_ok)
6585
iconTint = ColorStateList.valueOf(
66-
ContextCompat.getColor(binding.root.context, R.color.green_500))
86+
ContextCompat.getColor(context, R.color.green_500))
6787
iconGravity = MaterialButton.ICON_GRAVITY_TEXT_TOP
6888
iconPadding = 0
6989
iconSize = SizeUtils.dp2px(28f)
@@ -74,4 +94,4 @@ class OnboardingPermissionsAdapter(private val permissions: List<OnboardingPermi
7494
override fun getItemCount(): Int {
7595
return permissions.size
7696
}
77-
}
97+
}

app/src/main/java/com/itsaky/androidide/fragments/EmptyStateFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ abstract class EmptyStateFragment<T : ViewBinding> : FragmentWithBinding<T> {
5454

5555
open fun onFragmentLongPressed() {
5656
val currentEditor = currentEditor ?: return
57-
currentEditor.selectCurrentWord()
57+
currentEditor.selectWordOrOperatorAtCursor()
5858
}
5959

6060
private val gestureListener =

app/src/main/java/com/itsaky/androidide/fragments/onboarding/PermissionsFragment.kt

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class PermissionsFragment :
114114
permissionsBinding?.let { b ->
115115
recyclerView = b.onboardingItems
116116
finishButton = b.finishInstallationButton
117-
pulseAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.pulse_animation)
117+
pulseAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.pulse_animation)
118118

119119
b.onboardingItems.adapter = createAdapter()
120120

@@ -217,17 +217,19 @@ class PermissionsFragment :
217217
viewModel.onPermissionsUpdated(allGranted)
218218
}
219219

220-
private fun handlePostOverlayPermissionState() {
221-
if (!awaitingOverlayGrantResult) {
222-
return
223-
}
224-
awaitingOverlayGrantResult = false
225-
if (PermissionsHelper.canDrawOverlays(requireContext())) {
226-
return
227-
}
228-
flashError(getString(R.string.permission_overlay_restricted_settings_hint))
229-
openAppInfoSettings()
230-
}
220+
private fun handlePostOverlayPermissionState() {
221+
if (!awaitingOverlayGrantResult) {
222+
return
223+
}
224+
awaitingOverlayGrantResult = false
225+
226+
if (PermissionsHelper.canDrawOverlays(requireContext())) {
227+
return
228+
}
229+
230+
flashError(getString(R.string.permission_overlay_restricted_settings_hint))
231+
requestSettingsTogglePermission(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
232+
}
231233

232234
private fun startIdeSetup() {
233235
val shouldProceed = viewModel.checkStorageAndNotify(requireContext())
@@ -294,13 +296,19 @@ class PermissionsFragment :
294296
}
295297
}
296298

297-
private fun requestOverlayPermission() {
298-
awaitingOverlayGrantResult = requestSettingsTogglePermission(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
299-
}
300-
301-
private fun openAppInfoSettings() {
302-
requestSettingsTogglePermission(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
303-
}
299+
private fun requestOverlayPermission() {
300+
val state = PermissionsHelper.getOverlayPermissionState(requireContext())
301+
302+
when (state) {
303+
PermissionsHelper.OverlayPermissionState.UNSUPPORTED -> {
304+
flashError(getString(R.string.permission_overlay_unsupported_hint))
305+
}
306+
PermissionsHelper.OverlayPermissionState.REQUESTABLE -> {
307+
awaitingOverlayGrantResult = requestSettingsTogglePermission(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
308+
}
309+
PermissionsHelper.OverlayPermissionState.GRANTED -> {}
310+
}
311+
}
304312

305313
private fun requestStoragePermission() {
306314
if (isAtLeastR()) {

app/src/main/java/com/itsaky/androidide/models/OnboardingPermissionItem.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ data class OnboardingPermissionItem(
3232
val description: Int,
3333
var isGranted: Boolean,
3434

35-
var isOptional: Boolean = false
36-
)
35+
var isOptional: Boolean = false,
36+
var isSupportedOnDevice: Boolean = true
37+
)

app/src/main/java/com/itsaky/androidide/services/debug/DebugOverlayManager.kt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
88
import android.view.MotionEvent
99
import android.view.ViewConfiguration
1010
import android.view.WindowManager
11+
import android.widget.Toast
1112
import android.provider.Settings
1213
import androidx.core.content.ContextCompat
1314
import com.itsaky.androidide.R
@@ -16,7 +17,7 @@ import com.itsaky.androidide.actions.ActionsRegistry
1617
import com.itsaky.androidide.databinding.DebuggerActionsWindowBinding
1718
import com.itsaky.androidide.idetooltips.TooltipManager
1819
import com.itsaky.androidide.idetooltips.TooltipTag
19-
import com.itsaky.androidide.utils.flashError
20+
import com.itsaky.androidide.utils.PermissionsHelper
2021
import org.slf4j.LoggerFactory
2122
import kotlin.math.abs
2223

@@ -111,9 +112,18 @@ class DebugOverlayManager private constructor(
111112
return
112113
}
113114

114-
if (!Settings.canDrawOverlays(binding.root.context)) {
115+
val ctx = binding.root.context
116+
117+
if (!Settings.canDrawOverlays(ctx)) {
115118
logger.warn("Overlay permission denied. Skipping debugger overlay window.")
116-
flashError(binding.root.context.getString(R.string.permission_overlay_restricted_settings_hint))
119+
120+
val state = PermissionsHelper.getOverlayPermissionState(ctx)
121+
val message = if (state == PermissionsHelper.OverlayPermissionState.UNSUPPORTED) {
122+
ctx.getString(R.string.permission_overlay_unsupported_hint)
123+
} else {
124+
ctx.getString(R.string.permission_overlay_restricted_settings_hint)
125+
}
126+
Toast.makeText(ctx, message, Toast.LENGTH_LONG).show()
117127
return
118128
}
119129

@@ -139,10 +149,10 @@ class DebugOverlayManager private constructor(
139149
}
140150
}
141151

142-
fun refreshActions() {
143-
// noinspection NotifyDataSetChanged
144-
binding.actions.adapter?.notifyDataSetChanged()
145-
}
152+
fun refreshActions() {
153+
// noinspection NotifyDataSetChanged
154+
binding.actions.adapter?.notifyDataSetChanged()
155+
}
146156

147157
companion object {
148158

@@ -172,9 +182,9 @@ class DebugOverlayManager private constructor(
172182
layout.actions.adapter = adapter
173183

174184
return DebugOverlayManager(
175-
windowManager = windowManager,
176-
binding = layout,
185+
windowManager = windowManager,
186+
binding = layout,
177187
)
178188
}
179189
}
180-
}
190+
}

0 commit comments

Comments
 (0)