Skip to content

Commit 40d0a66

Browse files
committed
migrate call started banner to compose (+remove caller name)
As we only rely on hasCall now, for simplicity the chip with the caller was removed. It can be reimplemented in the future, but as the deriving from chat messages was quite buggy, we should keep it simple for now. Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
1 parent 7a659c9 commit 40d0a66

7 files changed

Lines changed: 157 additions & 139 deletions

File tree

app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import android.widget.LinearLayout
3535
import android.widget.PopupMenu
3636
import android.widget.RelativeLayout
3737
import android.widget.SeekBar
38-
import androidx.appcompat.content.res.AppCompatResources
3938
import androidx.appcompat.view.ContextThemeWrapper
39+
import androidx.compose.material3.MaterialTheme
4040
import androidx.core.graphics.drawable.toDrawable
4141
import androidx.core.view.isVisible
4242
import androidx.core.widget.doAfterTextChanged
@@ -67,6 +67,7 @@ import com.nextcloud.talk.models.json.chat.ChatUtils
6767
import com.nextcloud.talk.models.json.mention.Mention
6868
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
6969
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
70+
import com.nextcloud.talk.ui.CallStartedBanner
7071
import com.nextcloud.talk.ui.MicInputCloud
7172
import com.nextcloud.talk.ui.dialog.AttachmentDialog
7273
import com.nextcloud.talk.ui.theme.ViewThemeUtils
@@ -119,7 +120,6 @@ class MessageInputFragment : Fragment() {
119120
private var mentionAutocomplete: Autocomplete<*>? = null
120121
private var xcounter = 0f
121122
private var ycounter = 0f
122-
private var collapsed = false
123123
private var hasScheduledMessages = false
124124
private lateinit var spreedCapabilities: SpreedCapability
125125
private var hasSharedText = false
@@ -285,9 +285,19 @@ class MessageInputFragment : Fragment() {
285285
}.collect()
286286
}
287287

288+
binding.fragmentCallStarted.setContent {
289+
MaterialTheme(colorScheme = viewThemeUtils.getColorScheme(requireContext())) {
290+
CallStartedBanner(
291+
viewThemeUtils = viewThemeUtils,
292+
onJoinVideoCall = { chatActivity.joinVideoCall() },
293+
onJoinAudioCall = { chatActivity.joinAudioCall() }
294+
)
295+
}
296+
}
297+
288298
viewLifecycleOwner.lifecycleScope.launch {
289-
chatActivity.chatViewModel.lastCallSystemMessage.collect { hasCall ->
290-
binding.fragmentCallStarted.root.visibility = if (hasCall) View.VISIBLE else View.GONE
299+
chatActivity.chatViewModel.hasCall.collect { hasCall ->
300+
binding.fragmentCallStarted.visibility = if (hasCall) View.VISIBLE else View.GONE
291301
}
292302
}
293303
}
@@ -518,36 +528,11 @@ class MessageInputFragment : Fragment() {
518528
binding.fragmentMessageInputView.button?.contentDescription =
519529
resources.getString(R.string.nc_description_send_message_button)
520530

521-
binding.fragmentCallStarted.joinAudioCall.setOnClickListener {
522-
chatActivity.joinAudioCall()
523-
}
524-
525-
binding.fragmentCallStarted.joinVideoCall.setOnClickListener {
526-
chatActivity.joinVideoCall()
527-
}
528-
529-
binding.fragmentCallStarted.callStartedCloseBtn.setOnClickListener {
530-
collapsed = !collapsed
531-
binding.fragmentCallStarted.callBtnLayout.visibility = if (collapsed) View.GONE else View.VISIBLE
532-
setDropDown(collapsed)
533-
}
534-
535531
binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.cancelReplyButton)?.setOnClickListener {
536532
cancelReply()
537533
}
538534
}
539535

540-
private fun setDropDown(collapsed: Boolean) {
541-
val drawable = if (collapsed) {
542-
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_keyboard_arrow_up)
543-
} else {
544-
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_keyboard_arrow_down)
545-
}
546-
547-
binding.fragmentCallStarted.callStartedCloseBtn.setImageDrawable(drawable)
548-
viewThemeUtils.platform.colorImageView(binding.fragmentCallStarted.callStartedCloseBtn, ColorRole.PRIMARY)
549-
}
550-
551536
@Suppress("ClickableViewAccessibility", "CyclomaticComplexMethod", "LongMethod")
552537
private fun initVoiceRecordButton() {
553538
handleButtonsVisibility()
@@ -1144,14 +1129,6 @@ class MessageInputFragment : Fragment() {
11441129
viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY)
11451130
}
11461131

1147-
binding.fragmentCallStarted.callStartedBackground.apply {
1148-
viewThemeUtils.talk.themeOutgoingMessageBubble(this, grouped = true, false)
1149-
}
1150-
1151-
binding.fragmentCallStarted.callStartedCloseBtn.apply {
1152-
viewThemeUtils.platform.colorImageView(this, ColorRole.PRIMARY)
1153-
}
1154-
11551132
binding.fragmentMessageInputView.submitThreadButton.apply {
11561133
viewThemeUtils.platform.colorImageView(this, ColorRole.SECONDARY)
11571134
}

app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ class ChatViewModel @AssistedInject constructor(
469469
private val nonNullUserFlow = currentUserFlow.filterNotNull()
470470

471471
// Unlike conversationFlow below, this is not deduped by lastReadMessage/lastCommonReadMessage,
472-
// so it also reacts to fields those two ignore, e.g. hasCall (see lastCallSystemMessage).
472+
// so it also reacts to fields those two ignore, e.g. hasCall (see [hasCall]).
473473
private val rawConversationFlow: Flow<ConversationModel> =
474474
nonNullUserFlow
475475
.flatMapLatest { user ->
@@ -1029,7 +1029,7 @@ class ChatViewModel @AssistedInject constructor(
10291029
// active" — unlike a scan over whatever chat message window happens to be loaded (which used to
10301030
// make the call-started banner reappear for calls that had long since ended, since a loaded
10311031
// window's contents don't necessarily reflect what's actually happening right now).
1032-
val lastCallSystemMessage: Flow<Boolean> = rawConversationFlow.map { it.hasCall }.distinctUntilChanged()
1032+
val hasCall: Flow<Boolean> = rawConversationFlow.map { it.hasCall }.distinctUntilChanged()
10331033

10341034
private val _callEndedSystemMessage = MutableSharedFlow<ChatMessage.SystemMessageType>(extraBufferCapacity = 1)
10351035
val callEndedSystemMessage: SharedFlow<ChatMessage.SystemMessageType>
@@ -1377,7 +1377,7 @@ class ChatViewModel @AssistedInject constructor(
13771377
val chatMessageIterator = chatMessageMap.iterator()
13781378

13791379
// Only drives the "conversation will be deleted after the call" warning (callEndedSystemMessage)
1380-
// — the call-started banner itself comes from lastCallSystemMessage / conversation.hasCall now.
1380+
// — the call-started banner itself comes from hasCall / conversation.hasCall now.
13811381
chatMessageList.lastOrNull {
13821382
it.systemMessageType in
13831383
listOf(
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: GPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.talk.ui
9+
10+
import androidx.compose.foundation.BorderStroke
11+
import androidx.compose.foundation.layout.Arrangement
12+
import androidx.compose.foundation.layout.Column
13+
import androidx.compose.foundation.layout.Row
14+
import androidx.compose.foundation.layout.Spacer
15+
import androidx.compose.foundation.layout.fillMaxWidth
16+
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.foundation.layout.size
18+
import androidx.compose.foundation.shape.RoundedCornerShape
19+
import androidx.compose.material3.Button
20+
import androidx.compose.material3.ButtonDefaults
21+
import androidx.compose.material3.Card
22+
import androidx.compose.material3.CardDefaults
23+
import androidx.compose.material3.Icon
24+
import androidx.compose.material3.MaterialTheme
25+
import androidx.compose.material3.Text
26+
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.remember
28+
import androidx.compose.ui.Alignment
29+
import androidx.compose.ui.Modifier
30+
import androidx.compose.ui.graphics.Color
31+
import androidx.compose.ui.graphics.vector.ImageVector
32+
import androidx.compose.ui.platform.LocalContext
33+
import androidx.compose.ui.res.colorResource
34+
import androidx.compose.ui.res.stringResource
35+
import androidx.compose.ui.res.vectorResource
36+
import androidx.compose.ui.tooling.preview.Preview
37+
import androidx.compose.ui.unit.dp
38+
import com.nextcloud.talk.R
39+
import com.nextcloud.talk.ui.theme.ViewThemeUtils
40+
import com.nextcloud.talk.utils.preview.ComposePreviewUtils
41+
42+
@Composable
43+
fun CallStartedBanner(viewThemeUtils: ViewThemeUtils, onJoinVideoCall: () -> Unit, onJoinAudioCall: () -> Unit) {
44+
val context = LocalContext.current
45+
val colorScheme = remember { viewThemeUtils.getColorScheme(context) }
46+
47+
Card(
48+
modifier = Modifier.fillMaxWidth(),
49+
shape = RoundedCornerShape(4.dp),
50+
colors = CardDefaults.cardColors(containerColor = colorScheme.surface),
51+
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
52+
) {
53+
Column(modifier = Modifier.padding(8.dp)) {
54+
Row(
55+
modifier = Modifier.fillMaxWidth(),
56+
horizontalArrangement = Arrangement.Center,
57+
verticalAlignment = Alignment.CenterVertically
58+
) {
59+
Icon(
60+
imageVector = ImageVector.vectorResource(R.drawable.ic_phone),
61+
contentDescription = null,
62+
modifier = Modifier.size(24.dp)
63+
)
64+
Spacer(modifier = Modifier.size(8.dp))
65+
Text(
66+
text = stringResource(R.string.call_in_progress),
67+
style = MaterialTheme.typography.bodyMedium
68+
)
69+
}
70+
71+
Row(
72+
modifier = Modifier
73+
.fillMaxWidth()
74+
.padding(top = 8.dp),
75+
horizontalArrangement = Arrangement.Center
76+
) {
77+
CallStartedActionButton(
78+
icon = R.drawable.ic_videocam_grey_600_24dp,
79+
text = stringResource(R.string.video_call),
80+
onClick = onJoinVideoCall,
81+
modifier = Modifier.padding(end = 8.dp)
82+
)
83+
CallStartedActionButton(
84+
icon = R.drawable.ic_phone,
85+
text = stringResource(R.string.audio_call),
86+
onClick = onJoinAudioCall
87+
)
88+
}
89+
}
90+
}
91+
}
92+
93+
@Composable
94+
private fun CallStartedActionButton(icon: Int, text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
95+
Button(
96+
onClick = onClick,
97+
modifier = modifier,
98+
colors = ButtonDefaults.buttonColors(containerColor = colorResource(R.color.nc_darkGreen))
99+
) {
100+
Icon(
101+
imageVector = ImageVector.vectorResource(icon),
102+
contentDescription = null,
103+
tint = Color.White,
104+
modifier = Modifier.size(18.dp)
105+
)
106+
Spacer(modifier = Modifier.size(4.dp))
107+
Text(text = text, color = Color.White)
108+
}
109+
}
110+
111+
@Preview(name = "Light Mode")
112+
@Composable
113+
fun CallStartedBannerPreview() {
114+
val context = LocalContext.current
115+
val previewUtils = ComposePreviewUtils.getInstance(context)
116+
val viewThemeUtils = previewUtils.viewThemeUtils
117+
val colorScheme = viewThemeUtils.getColorScheme(context)
118+
119+
MaterialTheme(colorScheme = colorScheme) {
120+
CallStartedBanner(
121+
viewThemeUtils = viewThemeUtils,
122+
onJoinVideoCall = {},
123+
onJoinAudioCall = {}
124+
)
125+
}
126+
}
127+
128+
@Preview(name = "Dark Mode", uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES)
129+
@Composable
130+
fun CallStartedBannerPreviewDark() {
131+
CallStartedBannerPreview()
132+
}
133+
134+
@Preview(name = "R-t-L", locale = "ar")
135+
@Composable
136+
fun CallStartedBannerPreviewRtl() {
137+
CallStartedBannerPreview()
138+
}

app/src/main/res/layout/call_started_message.xml

Lines changed: 0 additions & 95 deletions
This file was deleted.

app/src/main/res/layout/fragment_message_input.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
android:layout_height="wrap_content"
1414
android:orientation="vertical">
1515

16-
<include
16+
<androidx.compose.ui.platform.ComposeView
1717
android:id="@+id/fragment_call_started"
18-
layout="@layout/call_started_message"
1918
android:layout_width="match_parent"
2019
android:layout_height="wrap_content"
2120
android:layout_marginHorizontal="@dimen/standard_half_margin"

app/src/main/res/values-night/colors.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
<color name="fg_default">#FFFFFF</color>
3232
<color name="fg_inverse">#121212</color>
3333

34-
<color name="nc_darkGreen">#00AA00</color>
35-
3634
<color name="conversation_unread_bubble">#373737</color>
3735
<color name="conversation_unread_bubble_text">#D8D8D8</color>
3836

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,7 @@ How to translate with transifex:
950950
<string name="video_call">Video call</string>
951951
<string name="audio_call">Audio call</string>
952952
<string name="started_a_call">started a call</string>
953+
<string name="call_in_progress">A call is in progress</string>
953954
<string name="nc_settings_phone_book_integration_phone_number_dialog_429">Error 429 Too Many Requests</string>
954955
<string name="nc_caption">Caption</string>
955956
<string name="nc_compress_images">Compress images and videos</string>

0 commit comments

Comments
 (0)