diff --git a/.jules/bolt.md b/.jules/bolt.md index 12742c31b..54a6a3b49 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -57,3 +57,6 @@ ## 2025-10-29 - [Stream Allocation and O(N^2) trap in HomeTimelineFragment] **Learning:** `HomeTimelineFragment.loadNewPosts` used a Java Stream to collect `existingIds` into a `List`, and then called `toAdd.removeIf(s -> existingIds.contains(s.getID()))`. Since `List.contains` is O(N), this created an O(N * M) operation in a hot path, causing both allocation overhead from Streams and quadratic performance degradation for duplicate checking. **Action:** Replaced the Stream with a simple `for` loop that populates a `HashSet`. This eliminates the Stream allocations and reduces the time complexity of the duplicate check from O(N * M) to O(N + M) because `HashSet.contains` is O(1). Additionally, replaced other `Optional` and `Stream` usages in `loadNewPosts` and `onGapClick` with simple loops to further reduce GC pressure. +## 2025-02-12 - [Dynamic TooltipText Parity for Icon-only Buttons] +**Learning:** When dynamically updating the `contentDescription` of icon-only buttons (like play/pause states) in Java code, the `tooltipText` attribute must also be updated to ensure continued accessibility support for hover and long-press interactions. +**Action:** Always mirror `setContentDescription()` calls with `setTooltipText()` (wrapped in `Build.VERSION.SDK_INT >= Build.VERSION_CODES.O`) for icon-only interactive elements. diff --git a/mastodon/src/main/java/de/icod/techidon/ui/displayitems/AudioStatusDisplayItem.java b/mastodon/src/main/java/de/icod/techidon/ui/displayitems/AudioStatusDisplayItem.java index aa971d09d..b540ea27a 100644 --- a/mastodon/src/main/java/de/icod/techidon/ui/displayitems/AudioStatusDisplayItem.java +++ b/mastodon/src/main/java/de/icod/techidon/ui/displayitems/AudioStatusDisplayItem.java @@ -216,7 +216,11 @@ private void updateColors(int mainColor){ private void setPlayButtonPlaying(boolean playing, boolean animated){ playPauseBtn.setImageResource(playing ? R.drawable.ic_fluent_pause_48_regular : R.drawable.ic_fluent_play_48_regular); - playPauseBtn.setContentDescription(item.parentFragment.getString(playing ? R.string.pause : R.string.play)); + String desc = item.parentFragment.getString(playing ? R.string.pause : R.string.play); + playPauseBtn.setContentDescription(desc); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + playPauseBtn.setTooltipText(desc); + } if(playing) bgDrawable.startAnimation(); else diff --git a/mastodon/src/main/java/de/icod/techidon/ui/photoviewer/PhotoViewer.java b/mastodon/src/main/java/de/icod/techidon/ui/photoviewer/PhotoViewer.java index f4d740f08..766e5a13a 100644 --- a/mastodon/src/main/java/de/icod/techidon/ui/photoviewer/PhotoViewer.java +++ b/mastodon/src/main/java/de/icod/techidon/ui/photoviewer/PhotoViewer.java @@ -692,7 +692,11 @@ private void pauseVideo(){ return; holder.player.pause(); videoPlayPauseButton.setImageResource(R.drawable.ic_fluent_play_24_filled); - videoPlayPauseButton.setContentDescription(activity.getString(R.string.play)); + String desc = activity.getString(R.string.play); + videoPlayPauseButton.setContentDescription(desc); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + videoPlayPauseButton.setTooltipText(desc); + } stopUpdatingVideoPosition(); windowView.removeCallbacks(uiAutoHider); // Some MediaPlayer implementations clear the texture when the app goes into background. @@ -706,7 +710,11 @@ private void resumeVideo(){ return; player.start(); videoPlayPauseButton.setImageResource(R.drawable.ic_fluent_pause_24_filled); - videoPlayPauseButton.setContentDescription(activity.getString(R.string.pause)); + String desc = activity.getString(R.string.pause); + videoPlayPauseButton.setContentDescription(desc); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + videoPlayPauseButton.setTooltipText(desc); + } startUpdatingVideoPosition(player); } @@ -1161,7 +1169,11 @@ public void onSeekComplete(MediaPlayer mp){ @Override public void onCompletion(MediaPlayer mp){ videoPlayPauseButton.setImageResource(R.drawable.ic_fluent_play_24_filled); - videoPlayPauseButton.setContentDescription(activity.getString(R.string.play)); + String desc = activity.getString(R.string.play); + videoPlayPauseButton.setContentDescription(desc); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + videoPlayPauseButton.setTooltipText(desc); + } stopUpdatingVideoPosition(); if(!uiVisible) toggleUI(); diff --git a/mastodon/src/main/res/layout/display_item_audio.xml b/mastodon/src/main/res/layout/display_item_audio.xml index 65fae2e16..e71cb8204 100644 --- a/mastodon/src/main/res/layout/display_item_audio.xml +++ b/mastodon/src/main/res/layout/display_item_audio.xml @@ -38,6 +38,7 @@ android:layout_height="96dp" android:background="@drawable/bg_audio_play_button" android:contentDescription="@string/play" + android:tooltipText="@string/play" android:layout_gravity="center" android:tint="?colorM3DarkOnSurface" android:src="@drawable/ic_fluent_play_48_regular"/> @@ -45,6 +46,7 @@