commit dbb902b19840dee58727f99a568028670259999d
Author: JabJab <noreply@xmpp.tel>
Date: Wed Sep 23 16:00:53 2026 +0300
Fixed mini-player position
---
build.gradle | 2 +-
.../tel/xmpp/jabjab/ui/ConversationsActivity.java | 9 +
.../xmpp/jabjab/ui/widget/AudioMiniPlayerView.java | 26 ++-
src/main/res/layout/fragment_conversation.xml | 241 +++++++++++----------
4 files changed, 164 insertions(+), 114 deletions(-)
diff --git a/build.gradle b/build.gradle
index 461311e..68f685b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -105,7 +105,7 @@ android {
defaultConfig {
minSdk = 23
- versionCode = 42315
+ versionCode = 42316
versionName = '1.0.8'
applicationId = 'tel.xmpp.jabjab'
resValue('string', 'applicationId', applicationId)
diff --git a/src/main/java/tel/xmpp/jabjab/ui/ConversationsActivity.java b/src/main/java/tel/xmpp/jabjab/ui/ConversationsActivity.java
index 6d78951..cf4ccc3 100644
--- a/src/main/java/tel/xmpp/jabjab/ui/ConversationsActivity.java
+++ b/src/main/java/tel/xmpp/jabjab/ui/ConversationsActivity.java
@@ -406,6 +406,10 @@ public class ConversationsActivity extends QrCodeProcessingActivity
private void updateBottomNavVisibility() {
if (binding.secondaryFragment != null) {
binding.bottomNav.setVisibility(android.view.View.VISIBLE);
+ // Tablet dual-pane: the secondary pane is a conversation, which has its own
+ // embedded mini-player above its input box — suppress this Activity-level one so
+ // they don't both show.
+ binding.audioMiniPlayer.setSuppressed(true);
return;
}
final var fm = getSupportFragmentManager();
@@ -413,6 +417,11 @@ public class ConversationsActivity extends QrCodeProcessingActivity
binding.bottomNav.setVisibility(inConversation
? android.view.View.GONE
: android.view.View.VISIBLE);
+ // Same reasoning as above: an open conversation has its own mini-player positioned
+ // above its message input box (see fragment_conversation.xml) — showing this
+ // Activity-level one too would duplicate it, and worse, overlap the input box, since
+ // this bar doesn't know about the fragment's own layout.
+ binding.audioMiniPlayer.setSuppressed(inConversation);
}
public void clearPendingViewIntent() {
diff --git a/src/main/java/tel/xmpp/jabjab/ui/widget/AudioMiniPlayerView.java b/src/main/java/tel/xmpp/jabjab/ui/widget/AudioMiniPlayerView.java
index d01bed3..1e3de5e 100644
--- a/src/main/java/tel/xmpp/jabjab/ui/widget/AudioMiniPlayerView.java
+++ b/src/main/java/tel/xmpp/jabjab/ui/widget/AudioMiniPlayerView.java
@@ -39,6 +39,12 @@ public class AudioMiniPlayerView extends LinearLayout implements AudioPlayer.Pla
private final ImageButton closeButton;
private boolean userIsSeeking = false;
+ // Set by the host Activity when another AudioMiniPlayerView already covers the same
+ // playback (e.g. ConversationsActivity's own Activity-level bar, once the open
+ // conversation's embedded copy — positioned above the message input box — takes over) so
+ // the two don't both show at once. Independent of whether something is actually playing;
+ // combined with that in refreshVisibility().
+ private boolean suppressed = false;
public AudioMiniPlayerView(final Context context) {
this(context, null);
@@ -115,7 +121,7 @@ public class AudioMiniPlayerView extends LinearLayout implements AudioPlayer.Pla
onProgress(message, AudioPlayer.getCurrentPositionMs(), AudioPlayer.getDurationMs());
speedToggleButton.setText(AudioPlayer.getSpeedLabel());
} else {
- setVisibility(GONE);
+ refreshVisibility();
}
}
@@ -125,8 +131,22 @@ public class AudioMiniPlayerView extends LinearLayout implements AudioPlayer.Pla
super.onDetachedFromWindow();
}
+ /** Hides this bar regardless of playback state — for a host Activity that has its own,
+ * more specific copy of this view active elsewhere (e.g. ConversationsActivity suppressing
+ * its Activity-level bar while the open conversation's own embedded one, positioned above
+ * the message input box, is showing instead). */
+ public void setSuppressed(final boolean suppressed) {
+ this.suppressed = suppressed;
+ refreshVisibility();
+ }
+
+ private void refreshVisibility() {
+ setVisibility(
+ !suppressed && AudioPlayer.getCurrentlyPlayingMessage() != null ? VISIBLE : GONE);
+ }
+
private void bind(final Message message) {
- setVisibility(VISIBLE);
+ refreshVisibility();
final var conversation = message.getConversation();
titleView.setText(conversation instanceof Conversation c ? c.getName() : "");
}
@@ -177,6 +197,6 @@ public class AudioMiniPlayerView extends LinearLayout implements AudioPlayer.Pla
@Override
public void onPlaybackStopped() {
- post(() -> setVisibility(GONE));
+ post(this::refreshVisibility);
}
}
diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml
index 71f88f8..cbd40be 100644
--- a/src/main/res/layout/fragment_conversation.xml
+++ b/src/main/res/layout/fragment_conversation.xml
@@ -268,140 +268,161 @@
android:id="@+id/sticker_suggestion_strip"
android:layout_width="match_parent"
android:layout_height="72dp"
- android:layout_above="@+id/message_input_box"
+ android:layout_above="@+id/message_input_area"
android:background="?colorSurfaceContainer"
android:orientation="horizontal"
android:paddingVertical="4dp"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
- <RelativeLayout
- android:id="@+id/message_input_box"
+ <!-- Groups the mini-player with the input box (rather than chaining
+ message_input_box's own layout_above through the mini-player's id) so a
+ RelativeLayout child can't end up anchored through a GONE custom view with no
+ other constraints of its own — this whole block just stacks vertically and
+ collapses to message_input_box's height alone when the mini-player is GONE,
+ same proven pattern as activity_conversations.xml's bottom-docked bar. The
+ Activity-level AudioMiniPlayerView there is suppressed while this fragment is on
+ screen (see ConversationsActivity.updateBottomNavVisibility) so only this one
+ shows. -->
+ <LinearLayout
+ android:id="@+id/message_input_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true"
android:layout_above="@+id/attachment_choices"
- android:paddingHorizontal="8dp"
- android:paddingVertical="6dp">
+ android:orientation="vertical">
+
+ <tel.xmpp.jabjab.ui.widget.AudioMiniPlayerView
+ android:id="@+id/audio_mini_player"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
<RelativeLayout
- android:id="@+id/input_layout"
- android:layout_width="wrap_content"
+ android:id="@+id/message_input_box"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:layout_toStartOf="@+id/textSendButton"
- android:background="@drawable/background_message_bubble"
- android:backgroundTint="?colorTertiaryContainer"
- android:orientation="vertical"
- android:paddingHorizontal="4dp">
-
- <LinearLayout
- android:layout_width="match_parent"
+ android:paddingHorizontal="8dp"
+ android:paddingVertical="6dp">
+
+ <RelativeLayout
+ android:id="@+id/input_layout"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
- android:layout_marginVertical="8sp"
- android:layout_marginHorizontal="12dp"
- android:layout_toStartOf="@+id/compose_bar_icons"
- android:orientation="vertical">
-
- <TextView
- android:id="@+id/text_input_hint"
- android:layout_width="wrap_content"
+ android:layout_toStartOf="@+id/textSendButton"
+ android:background="@drawable/background_message_bubble"
+ android:backgroundTint="?colorTertiaryContainer"
+ android:orientation="vertical"
+ android:paddingHorizontal="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="8sp"
- android:maxLines="1"
- android:textAppearance="?textAppearanceBodySmall"
- android:textColor="?colorOnTertiaryContainer"
- android:visibility="visible"
- tools:text="@string/private_message_to" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/media_preview"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:layout_marginVertical="8sp"
+ android:layout_marginHorizontal="12dp"
+ android:layout_toStartOf="@+id/compose_bar_icons"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text_input_hint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8sp"
+ android:maxLines="1"
+ android:textAppearance="?textAppearanceBodySmall"
+ android:textColor="?colorOnTertiaryContainer"
+ android:visibility="visible"
+ tools:text="@string/private_message_to" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/media_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:requiresFadingEdge="horizontal"
+ android:visibility="gone"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ tools:listitem="@layout/item_media_preview">
+
+ </androidx.recyclerview.widget.RecyclerView>
+
+ <tel.xmpp.jabjab.ui.widget.EditMessage
+
+ android:id="@+id/text_input"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:ems="10"
+ android:imeOptions="flagNoExtractUi|actionSend"
+ android:inputType="textShortMessage|textMultiLine|textCapSentences"
+ android:maxLines="8"
+ android:minHeight="32dp"
+ android:minLines="1"
+ android:textColor="?colorOnTertiaryContainer"
+ android:textColorHint="@color/hint_on_tertiary_container"
+ android:textCursorDrawable="@drawable/cursor_on_tertiary_container">
+
+ <requestFocus />
+ </tel.xmpp.jabjab.ui.widget.EditMessage>
+
+ </LinearLayout>
+
+ <!-- Grouped in their own LinearLayout rather than two RelativeLayout
+ siblings chained via layout_toStartOf: a GONE sibling referenced by
+ toStartOf/toEndOf resolves unreliably in RelativeLayout (can collapse
+ to a negative-width remainder), which is exactly what attach_button
+ going GONE while typing would trigger. LinearLayout collapses GONE
+ children cleanly instead. -->
+ <LinearLayout
+ android:id="@+id/compose_bar_icons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:requiresFadingEdge="horizontal"
- android:visibility="gone"
- app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- tools:listitem="@layout/item_media_preview">
-
- </androidx.recyclerview.widget.RecyclerView>
-
- <tel.xmpp.jabjab.ui.widget.EditMessage
-
- android:id="@+id/text_input"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@android:color/transparent"
- android:ems="10"
- android:imeOptions="flagNoExtractUi|actionSend"
- android:inputType="textShortMessage|textMultiLine|textCapSentences"
- android:maxLines="8"
- android:minHeight="32dp"
- android:minLines="1"
- android:textColor="?colorOnTertiaryContainer"
- android:textColorHint="@color/hint_on_tertiary_container"
- android:textCursorDrawable="@drawable/cursor_on_tertiary_container">
-
- <requestFocus />
- </tel.xmpp.jabjab.ui.widget.EditMessage>
-
- </LinearLayout>
-
- <!-- Grouped in their own LinearLayout rather than two RelativeLayout
- siblings chained via layout_toStartOf: a GONE sibling referenced by
- toStartOf/toEndOf resolves unreliably in RelativeLayout (can collapse
- to a negative-width remainder), which is exactly what attach_button
- going GONE while typing would trigger. LinearLayout collapses GONE
- children cleanly instead. -->
- <LinearLayout
- android:id="@+id/compose_bar_icons"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:orientation="horizontal">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/translate_button"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/translate_message"
+ app:icon="@drawable/ic_translate_24dp"
+ app:iconSize="24dp"
+ app:iconTint="?colorOnTertiaryContainer" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/attach_button"
+ style="?attr/materialIconButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checkable="true"
+ android:checked="false"
+ app:icon="@drawable/ic_attach_file_24dp"
+ app:iconSize="24dp"
+ app:iconTint="?colorOnTertiaryContainer" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/textSendButton"
+ style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
- android:orientation="horizontal">
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/translate_button"
- style="?attr/materialIconButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:contentDescription="@string/translate_message"
- app:icon="@drawable/ic_translate_24dp"
- app:iconSize="24dp"
- app:iconTint="?colorOnTertiaryContainer" />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/attach_button"
- style="?attr/materialIconButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:checkable="true"
- android:checked="false"
- app:icon="@drawable/ic_attach_file_24dp"
- app:iconSize="24dp"
- app:iconTint="?colorOnTertiaryContainer" />
-
- </LinearLayout>
-
+ android:layout_marginStart="8dp"
+ android:backgroundTint="?colorSurfaceContainerHigh"
+ android:contentDescription="@string/send_message"
+ app:icon="@drawable/ic_send_24dp"
+ app:iconSize="32dp" />
</RelativeLayout>
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/textSendButton"
- style="?attr/materialIconButtonFilledTonalStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- android:layout_marginStart="8dp"
- android:backgroundTint="?colorSurfaceContainerHigh"
- android:contentDescription="@string/send_message"
- app:icon="@drawable/ic_send_24dp"
- app:iconSize="32dp" />
- </RelativeLayout>
+ </LinearLayout>
<RelativeLayout
android:id="@+id/snackbar"
Built with passion for open-source software, XMPP, privacy, and security —
the belief that people deserve communication tools that serve them, not surveil them.
Dedicated to every brave man and woman who stands up against tyranny and oppression across the world.
And in memory of those who were silenced before they could speak freely.