Skip to content

Commit 5689e60

Browse files
authored
ADFA-1377 diagnostic tooltip (#323)
* refactor: use fixed tooltip tags for sidebar actions This commit introduces a `tooltipTag` property to `SidebarActionItem` and updates all sidebar actions to use fixed string values for their tooltips instead of dynamically generating them based on the label. This change simplifies the tooltip setup logic in `EditorSidebarActions` by removing the `tooltipTag()` extension function and directly using the new property. * ADFA-1381 | Refactor: Move tooltipTag to ActionItem Move the `tooltipTag` property from `SidebarActionItem` to the base `ActionItem` class. This makes the property available to all action item types. Update various action classes to initialize `tooltipTag`. In `EditorActionsMenu`, implement long-press listeners on action buttons to display tooltips using the `TooltipManager`. * Refactor: Use TooltipTag for sidebar actions This commit refactors the sidebar actions to use the newly introduced `TooltipTag` object for managing tooltip tags. The following sidebar actions were updated: - TerminalSidebarAction - HelpSideBarAction - FileTreeSidebarAction - PreferencesSidebarAction - CloseProjectSidebarAction - BuildVariantsSidebarAction A new file `idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt` was created to define the tooltip tags. * Use constants for tooltip categories This commit replaces hardcoded string values for tooltip categories with constants defined in the new `TooltipCategory` object. This improves code maintainability and reduces the risk of typos. The following files were modified to use these constants: - `app/src/main/java/com/itsaky/androidide/fragments/MainFragment.kt` - `app/src/main/java/com/itsaky/androidide/utils/EditorSidebarActions.kt` - `app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt` - `app/src/main/java/com/itsaky/androidide/actions/file/ShowTooltipAction.kt` * Refactor: Use TooltipTag for editor actions This commit refactors the editor actions to use the `TooltipTag` constants for tooltip tags. The following changes were made: - Added new `TooltipTag` constants for editor toolbar actions in `idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt`. - Updated editor actions in `app/src/main/java/com/itsaky/androidide/actions/editor/` to use the new `TooltipTag` constants. - Updated `actions/src/main/java/com/itsaky/androidide/actions/locations/CodeActionsMenu.kt` to use the new `TooltipTag` constant. - Added `idetooltips` project as a dependency to the `actions` module in `actions/build.gradle.kts`. * Refactor: Use TooltipCategory constants Updated `EditorCompletionWindow` and `EditorActionsMenu` to use constants from `TooltipCategory` for tooltip categories. Added `CATEGORY_XML` to `TooltipCategory`. * Refactor: Show tooltip for output tabs This commit refactors the tooltip display logic for the output tabs in `EditorBottomSheet.kt`. Instead of directly creating `IDETooltipItem` objects, it now: - Uses a `tooltipTag` to identify the relevant tooltip. - Fetches tooltip data using `TooltipManager.getTooltip`. - Displays the tooltip using the fetched data. - Adds error handling for tooltip display. - Leverages coroutines and `lifecycleScope` for asynchronous operations. * Refactor: Use TooltipTag constants for EditorBottomSheet tabs This commit refactors the `EditorBottomSheet` to use constants from `TooltipTag` for setting tooltip tags on its tabs. Specifically, the following changes were made: - Added new constants to `TooltipTag.kt` for project-specific tooltips: - `PROJECT_APP_LOGS` - `PROJECT_IDE_LOGS` - `PROJECT_SEARCH_RESULTS` - `PROJECT_DIAGNOSTICS` - Modified `EditorBottomSheet.kt` to: - Retrieve tooltip tags using `pagerAdapter.getTooltipTag(position)`. - Use `TooltipCategory.CATEGORY_IDE` when fetching tooltips. - Updated `EditorBottomSheetTabAdapter.java` to: - Add a `tooltipTag` field to the inner `Tab` class. - Initialize `tooltipTag` in the `Tab` constructor. - Add a `getTooltipTag(int position)` method to retrieve the tooltip tag for a given tab position. - Assign the new `TooltipTag` constants to the respective tabs. - Swapped tooltip tags for "Diagnostics" and "Search Results" tabs to match their functionality. * Refactor: Show IDE tooltips on long press - Add long press gesture recognizer to `RecyclerViewFragment` to show IDE tooltips. - Add tooltip tag for `SearchResultFragment`. - Remove unused Activity context from `EditorCompletionWindow`. - Enable test code for `TooltipManager.getTooltip`. * Merge conflcits * ADFA-1377 | Add tooltip tag to DiagnosticsListFragment The `fragmentTooltipTag` in `DiagnosticsListFragment.kt` was updated to use the `PROJECT_DIAGNOSTICS` constant from `TooltipTag`. * Merge conflicts
1 parent e044376 commit 5689e60

8 files changed

Lines changed: 158 additions & 77 deletions

File tree

‎app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.kt‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.itsaky.androidide.fragments.debug.DebuggerFragment
2626
import com.itsaky.androidide.fragments.output.AppLogFragment
2727
import com.itsaky.androidide.fragments.output.BuildOutputFragment
2828
import com.itsaky.androidide.fragments.output.IDELogFragment
29+
import com.itsaky.androidide.idetooltips.TooltipTag
2930
import com.itsaky.androidide.resources.R
3031
import org.slf4j.Logger
3132
import org.slf4j.LoggerFactory
@@ -41,7 +42,7 @@ class EditorBottomSheetTabAdapter(
4142
Tab(
4243
title = fragmentActivity.getString(R.string.build_output),
4344
fragmentClass = BuildOutputFragment::class.java,
44-
itemId = size.toLong(),
45+
itemId = size.toLong()
4546
),
4647
)
4748

@@ -50,6 +51,7 @@ class EditorBottomSheetTabAdapter(
5051
title = fragmentActivity.getString(R.string.app_logs),
5152
fragmentClass = AppLogFragment::class.java,
5253
itemId = size.toLong(),
54+
tooltipTag = TooltipTag.PROJECT_APP_LOGS
5355
),
5456
)
5557

@@ -58,6 +60,7 @@ class EditorBottomSheetTabAdapter(
5860
title = fragmentActivity.getString(R.string.ide_logs),
5961
fragmentClass = IDELogFragment::class.java,
6062
itemId = size.toLong(),
63+
tooltipTag = TooltipTag.PROJECT_IDE_LOGS
6164
),
6265
)
6366

@@ -66,6 +69,7 @@ class EditorBottomSheetTabAdapter(
6669
title = fragmentActivity.getString(R.string.view_diags),
6770
fragmentClass = DiagnosticsListFragment::class.java,
6871
itemId = size.toLong(),
72+
tooltipTag = TooltipTag.PROJECT_SEARCH_RESULTS
6973
),
7074
)
7175

@@ -74,14 +78,15 @@ class EditorBottomSheetTabAdapter(
7478
title = fragmentActivity.getString(R.string.view_search_results),
7579
fragmentClass = SearchResultFragment::class.java,
7680
itemId = size.toLong(),
81+
tooltipTag = TooltipTag.PROJECT_DIAGNOSTICS
7782
),
7883
)
7984

8085
add(
8186
Tab(
8287
title = fragmentActivity.getString(R.string.debugger_title),
8388
fragmentClass = DebuggerFragment::class.java,
84-
itemId = size.toLong(),
89+
itemId = size.toLong()
8590
),
8691
)
8792
}
@@ -223,10 +228,15 @@ class EditorBottomSheetTabAdapter(
223228
val title: String,
224229
val fragmentClass: Class<out Fragment>,
225230
val itemId: Long,
231+
val tooltipTag: String? = null
226232
)
227233

228234
companion object {
229235
private val logger: Logger =
230236
LoggerFactory.getLogger(EditorBottomSheetTabAdapter::class.java)
231237
}
238+
239+
fun getTooltipTag(position: Int): String? {
240+
return allTabs[position].tooltipTag
241+
}
232242
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import android.view.View
2121
import androidx.recyclerview.widget.RecyclerView
2222
import com.itsaky.androidide.R
2323
import com.itsaky.androidide.adapters.DiagnosticsAdapter
24+
import com.itsaky.androidide.idetooltips.TooltipTag
2425

2526
class DiagnosticsListFragment : RecyclerViewFragment<DiagnosticsAdapter>() {
26-
27+
override val fragmentTooltipTag: String? = TooltipTag.PROJECT_DIAGNOSTICS
2728
override fun onCreateAdapter(): RecyclerView.Adapter<*> {
2829
return DiagnosticsAdapter(ArrayList(), null)
2930
}

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,23 @@
1717

1818
package com.itsaky.androidide.fragments
1919

20+
import android.annotation.SuppressLint
2021
import android.os.Bundle
22+
import android.util.Log
23+
import android.view.GestureDetector
24+
import android.view.MotionEvent
2125
import android.view.View
26+
import androidx.lifecycle.lifecycleScope
2227
import androidx.recyclerview.widget.LinearLayoutManager
2328
import androidx.recyclerview.widget.RecyclerView
2429
import androidx.recyclerview.widget.RecyclerView.LayoutManager
2530
import com.itsaky.androidide.databinding.FragmentRecyclerviewBinding
31+
import com.itsaky.androidide.idetooltips.TooltipCategory
32+
import com.itsaky.androidide.idetooltips.TooltipManager
33+
import com.itsaky.androidide.utils.TooltipUtils
34+
import kotlinx.coroutines.Dispatchers
35+
import kotlinx.coroutines.launch
36+
import kotlinx.coroutines.withContext
2637

2738
/**
2839
* A fragment which shows a [RecyclerView].
@@ -31,9 +42,32 @@ import com.itsaky.androidide.databinding.FragmentRecyclerviewBinding
3142
*/
3243
abstract class RecyclerViewFragment<A : RecyclerView.Adapter<*>> :
3344
EmptyStateFragment<FragmentRecyclerviewBinding>(FragmentRecyclerviewBinding::inflate) {
45+
protected abstract val fragmentTooltipTag: String?
3446

3547
private var unsavedAdapter: A? = null
3648

49+
private lateinit var gestureDetector: GestureDetector
50+
51+
private val gestureListener =
52+
object : GestureDetector.SimpleOnGestureListener() {
53+
override fun onLongPress(e: MotionEvent) {
54+
showFragmentTooltip()
55+
}
56+
}
57+
58+
private val touchListener =
59+
object : RecyclerView.OnItemTouchListener {
60+
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
61+
// Pass the event to our gesture detector
62+
gestureDetector.onTouchEvent(e)
63+
// Always return false so we don't consume the event
64+
return false
65+
}
66+
67+
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {}
68+
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
69+
}
70+
3771
/**
3872
* Creates the adapter for the [RecyclerView].
3973
*/
@@ -56,10 +90,20 @@ abstract class RecyclerViewFragment<A : RecyclerView.Adapter<*>> :
5690
}
5791
}
5892

93+
@SuppressLint("ClickableViewAccessibility")
5994
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
6095
super.onViewCreated(view, savedInstanceState)
96+
97+
gestureDetector = GestureDetector(requireContext(), gestureListener)
98+
emptyStateBinding?.root?.setOnTouchListener { _, event ->
99+
gestureDetector.onTouchEvent(event)
100+
false
101+
}
102+
61103
onSetupRecyclerView()
62104

105+
binding.root.addOnItemTouchListener(touchListener)
106+
63107
unsavedAdapter = null
64108

65109
checkIsEmpty()
@@ -78,6 +122,29 @@ abstract class RecyclerViewFragment<A : RecyclerView.Adapter<*>> :
78122
checkIsEmpty()
79123
}
80124

125+
private fun showFragmentTooltip() {
126+
val workingContext = context ?: return
127+
val anchorView = this@RecyclerViewFragment.view ?: return
128+
val tooltipTag = fragmentTooltipTag ?: return
129+
viewLifecycleOwner.lifecycleScope.launch {
130+
try {
131+
val tooltipData = withContext(Dispatchers.IO) {
132+
TooltipManager.getTooltip(workingContext, TooltipCategory.CATEGORY_IDE, tooltipTag)
133+
}
134+
tooltipData?.let {
135+
TooltipUtils.showIDETooltip(
136+
context = workingContext,
137+
level = 0,
138+
tooltipItem = tooltipData,
139+
anchorView = anchorView,
140+
)
141+
}
142+
} catch (e: Exception) {
143+
Log.e("Tooltip", "Error showing tooltip for $tooltipTag", e)
144+
}
145+
}
146+
}
147+
81148
private fun checkIsEmpty() {
82149
emptyStateViewModel.isEmpty.value = _binding?.root?.adapter?.itemCount == 0
83150
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package com.itsaky.androidide.fragments
1818

1919
import androidx.recyclerview.widget.RecyclerView
2020
import com.itsaky.androidide.adapters.SearchListAdapter
21+
import com.itsaky.androidide.idetooltips.TooltipTag
2122

2223
class SearchResultFragment : RecyclerViewFragment<SearchListAdapter>() {
24+
override val fragmentTooltipTag: String? = TooltipTag.PROJECT_SEARCH_RESULTS
2325

2426
override fun onCreateAdapter(): RecyclerView.Adapter<*> {
2527
val noOp: (Any) -> Unit = {}

‎app/src/main/java/com/itsaky/androidide/fragments/debug/CallStackFragment.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import kotlinx.coroutines.withContext
2222
* @author Akash Yadav
2323
*/
2424
class CallStackFragment : RecyclerViewFragment<CallStackAdapter>() {
25-
25+
override val fragmentTooltipTag: String? = null// Tooltip pending to be defined
2626
private val viewHolder by activityViewModels<DebuggerViewModel>()
2727

2828
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

‎app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt‎

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.app.Activity
2121
import android.content.Context
2222
import android.text.TextUtils
2323
import android.util.AttributeSet
24+
import android.util.Log
2425
import android.view.LayoutInflater
2526
import android.view.View
2627
import android.view.ViewGroup
@@ -35,6 +36,8 @@ import androidx.core.view.updateLayoutParams
3536
import androidx.core.view.updatePadding
3637
import androidx.core.view.updatePaddingRelative
3738
import androidx.fragment.app.FragmentActivity
39+
import androidx.lifecycle.LifecycleOwner
40+
import androidx.lifecycle.lifecycleScope
3841
import androidx.transition.TransitionManager
3942
import com.blankj.utilcode.util.KeyboardUtils
4043
import com.blankj.utilcode.util.SizeUtils
@@ -50,8 +53,8 @@ import com.itsaky.androidide.adapters.EditorBottomSheetTabAdapter
5053
import com.itsaky.androidide.adapters.SearchListAdapter
5154
import com.itsaky.androidide.databinding.LayoutEditorBottomSheetBinding
5255
import com.itsaky.androidide.fragments.output.ShareableOutputFragment
53-
import com.itsaky.androidide.idetooltips.IDETooltipItem
5456
import com.itsaky.androidide.idetooltips.TooltipCategory
57+
import com.itsaky.androidide.idetooltips.TooltipManager
5558
import com.itsaky.androidide.models.LogLine
5659
import com.itsaky.androidide.resources.R.string
5760
import com.itsaky.androidide.tasks.TaskExecutor.CallbackWithError
@@ -61,6 +64,9 @@ import com.itsaky.androidide.utils.IntentUtils.shareFile
6164
import com.itsaky.androidide.utils.Symbols.forFile
6265
import com.itsaky.androidide.utils.TooltipUtils
6366
import com.itsaky.androidide.utils.flashError
67+
import kotlinx.coroutines.Dispatchers
68+
import kotlinx.coroutines.launch
69+
import kotlinx.coroutines.withContext
6470
import org.slf4j.LoggerFactory
6571
import java.io.File
6672
import java.io.IOException
@@ -115,49 +121,45 @@ class EditorBottomSheet
115121
const val CHILD_ACTION = 2
116122
}
117123

118-
private fun initialize(context: FragmentActivity) {
119-
val mediator =
120-
TabLayoutMediator(binding.tabs, binding.pager, true, true) { tab, position ->
121-
tab.text = pagerAdapter.getTitle(position)
122-
}
124+
private fun initialize(context: FragmentActivity) {
125+
126+
val mediator =
127+
TabLayoutMediator(binding.tabs, binding.pager, true, true) { tab, position ->
128+
tab.text = pagerAdapter.getTitle(position)
129+
tab.view.setOnLongClickListener { view ->
130+
val tooltipTag =
131+
pagerAdapter.getTooltipTag(position) ?: return@setOnLongClickListener true
132+
val lifecycleOwner = context as? LifecycleOwner ?: return@setOnLongClickListener true
133+
lifecycleOwner.lifecycleScope.launch {
134+
try {
135+
val tooltipData = withContext(Dispatchers.IO) {
136+
TooltipManager.getTooltip(context, TooltipCategory.CATEGORY_IDE, tooltipTag)
137+
}
138+
tooltipData?.let {
139+
TooltipUtils.showIDETooltip(
140+
context = context,
141+
level = 0,
142+
tooltipItem = tooltipData,
143+
anchorView = view
144+
)
145+
}
146+
} catch (e: Exception) {
147+
Log.e("Tooltip", "Error showing tooltip for $tooltipTag", e)
148+
}
149+
}
150+
151+
true
152+
}
153+
}
123154

124155
mediator.attach()
125-
binding.pager.isUserInputEnabled = false
126-
binding.pager.offscreenPageLimit = pagerAdapter.itemCount - 1 // Do not remove any views
127-
128-
for (i in 0 until binding.tabs.tabCount) {
129-
val tab = binding.tabs.getTabAt(i) ?: continue
130-
val tabView = tab.view
131-
132-
tabView.setOnLongClickListener {
133-
val title = pagerAdapter.getTitle(i)
134-
val tooltipMessage =
135-
when (title) {
136-
context.getString(R.string.app_logs) -> context.getString(R.string.app_logs)
137-
context.getString(R.string.ide_logs) -> context.getString(R.string.ide_logs)
138-
else -> context.getString(R.string.default_tooltip)
139-
}
140-
TooltipUtils.showIDETooltip(
141-
context,
142-
tabView,
143-
0,
144-
IDETooltipItem(
145-
tooltipCategory = TooltipCategory.CATEGORY_IDE,
146-
tooltipTag = tooltipMessage,
147-
detail = tooltipMessage,
148-
summary = context.getString(R.string.more_information_about, title),
149-
buttons = arrayListOf(Pair(context.getString(R.string.learn_more), "~/help_top.html")),
150-
),
151-
)
152-
153-
true
154-
}
155-
}
156+
binding.pager.isUserInputEnabled = false
157+
binding.pager.offscreenPageLimit = pagerAdapter.itemCount - 1
156158

157-
binding.tabs.addOnTabSelectedListener(
158-
object : OnTabSelectedListener {
159-
override fun onTabSelected(tab: Tab) {
160-
val fragment = pagerAdapter.getFragmentAtIndex(tab.position)
159+
binding.tabs.addOnTabSelectedListener(
160+
object : OnTabSelectedListener {
161+
override fun onTabSelected(tab: Tab) {
162+
val fragment = pagerAdapter.getFragmentAtIndex(tab.position)
161163
if (fragment is ShareableOutputFragment) {
162164
binding.clearFab.show()
163165
binding.shareOutputFab.show()

0 commit comments

Comments
 (0)