Skip to content

Commit d6b229a

Browse files
authored
Merge pull request #5283 from nextcloud/feature/3074/addNewCreateThreadWorkflow
Feature/3074/add new create thread workflow
2 parents c1a2cfa + 7e58bbf commit d6b229a

16 files changed

Lines changed: 274 additions & 53 deletions

app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ interface NcApiCoroutines {
149149
@Field("actorDisplayName") actorDisplayName: String,
150150
@Field("replyTo") replyTo: Int,
151151
@Field("silent") sendWithoutNotification: Boolean,
152-
@Field("referenceId") referenceId: String
152+
@Field("referenceId") referenceId: String,
153+
@Field("threadTitle") threadTitle: String?
153154
): ChatOverallSingleMessage
154155

155156
@FormUrlEncoded

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,14 +1176,16 @@ class ChatActivity :
11761176

11771177
chatMenu?.removeItem(R.id.conversation_event)
11781178
}
1179+
11791180
is ChatViewModel.UnbindRoomUiState.Error -> {
11801181
Snackbar.make(
11811182
binding.root,
11821183
context.getString(R.string.nc_common_error_sorry),
11831184
Snackbar.LENGTH_LONG
11841185
).show()
11851186
}
1186-
else -> { }
1187+
1188+
else -> {}
11871189
}
11881190
}
11891191

@@ -3209,7 +3211,8 @@ class ChatActivity :
32093211
conversationInfoItem.isVisible = !isChatThread()
32103212

32113213
val showThreadsItem = menu.findItem(R.id.show_threads)
3212-
showThreadsItem.isVisible = !isChatThread()
3214+
showThreadsItem.isVisible = !isChatThread() &&
3215+
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.THREADS)
32133216

32143217
if (CapabilitiesUtil.isAbleToCall(spreedCapabilities) && !isChatThread()) {
32153218
conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
@@ -4250,6 +4253,10 @@ class ChatActivity :
42504253
pollVoteDialog.show(supportFragmentManager, TAG)
42514254
}
42524255

4256+
fun createThread() {
4257+
messageInputViewModel.startThreadCreation()
4258+
}
4259+
42534260
fun jumpToQuotedMessage(parentMessage: ChatMessage) {
42544261
var foundMessage = false
42554262
for (position in 0 until (adapter!!.items.size)) {
@@ -4369,6 +4376,10 @@ class ChatActivity :
43694376
chatViewModel.messageDraft.quotedJsonId = null
43704377
}
43714378

4379+
fun cancelCreateThread() {
4380+
chatViewModel.clearThreadTitle()
4381+
}
4382+
43724383
companion object {
43734384
val TAG = ChatActivity::class.simpleName
43744385
private const val CONTENT_TYPE_CALL_STARTED: Byte = 1

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

Lines changed: 84 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA
6060
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
6161
import com.nextcloud.talk.chat.data.model.ChatMessage
6262
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
63+
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
6364
import com.nextcloud.talk.data.network.NetworkMonitor
6465
import com.nextcloud.talk.databinding.FragmentMessageInputBinding
6566
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
@@ -74,6 +75,7 @@ import com.nextcloud.talk.users.UserManager
7475
import com.nextcloud.talk.utils.ApiUtils
7576
import com.nextcloud.talk.utils.CapabilitiesUtil
7677
import com.nextcloud.talk.utils.CharPolicy
78+
import com.nextcloud.talk.utils.EmojiTextInputEditText
7779
import com.nextcloud.talk.utils.ImageEmojiEditText
7880
import com.nextcloud.talk.utils.SpreedFeatures
7981
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
@@ -172,6 +174,14 @@ class MessageInputFragment : Fragment() {
172174
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
173175
super.onViewCreated(view, savedInstanceState)
174176
initObservers()
177+
178+
binding.fragmentCreateThreadView.createThreadView.findViewById<EmojiTextInputEditText>(
179+
R.id
180+
.createThread
181+
).doAfterTextChanged { text ->
182+
val threadTitle = text.toString()
183+
chatActivity.chatViewModel.messageDraft.threadTitle = threadTitle
184+
}
175185
}
176186

177187
private fun initObservers() {
@@ -194,6 +204,24 @@ class MessageInputFragment : Fragment() {
194204
message?.let { setEditUI(it as ChatMessage) }
195205
}
196206

207+
chatActivity.messageInputViewModel.createThreadViewState.observe(viewLifecycleOwner) { state ->
208+
when (state) {
209+
is MessageInputViewModel.CreateThreadStartState ->
210+
binding.fragmentCreateThreadView.createThreadView.visibility = View.GONE
211+
212+
is MessageInputViewModel.CreateThreadEditState -> {
213+
binding.fragmentCreateThreadView.createThreadView.visibility = View.VISIBLE
214+
binding.fragmentCreateThreadView.createThreadView
215+
.findViewById<EmojiTextInputEditText>(R.id.createThread)?.setText(
216+
chatActivity.chatViewModel.messageDraft.threadTitle
217+
)
218+
}
219+
220+
else -> {}
221+
}
222+
initVoiceRecordButton()
223+
}
224+
197225
chatActivity.chatViewModel.leaveRoomViewState.observe(viewLifecycleOwner) { state ->
198226
when (state) {
199227
is ChatViewModel.LeaveRoomSuccessState -> sendStopTypingMessage()
@@ -310,6 +338,11 @@ class MessageInputFragment : Fragment() {
310338
val draft = chatActivity.chatViewModel.messageDraft
311339
binding.fragmentMessageInputView.messageInput.setText(draft.messageText)
312340
binding.fragmentMessageInputView.messageInput.setSelection(draft.messageCursor)
341+
342+
if (draft.threadTitle?.isNotEmpty() == true) {
343+
chatActivity.messageInputViewModel.startThreadCreation()
344+
}
345+
313346
if (draft.messageText != "") {
314347
binding.fragmentMessageInputView.messageInput.requestFocus()
315348
}
@@ -444,6 +477,9 @@ class MessageInputFragment : Fragment() {
444477
binding.fragmentEditView.clearEdit.setOnClickListener {
445478
clearEditUI()
446479
}
480+
binding.fragmentCreateThreadView.abortCreateThread.setOnClickListener {
481+
cancelCreateThread()
482+
}
447483

448484
if (CapabilitiesUtil.hasSpreedFeatureCapability(chatActivity.spreedCapabilities, SpreedFeatures.SILENT_SEND)) {
449485
binding.fragmentMessageInputView.button?.setOnLongClickListener {
@@ -489,31 +525,10 @@ class MessageInputFragment : Fragment() {
489525

490526
@Suppress("ClickableViewAccessibility", "CyclomaticComplexMethod", "LongMethod")
491527
private fun initVoiceRecordButton() {
492-
if (binding.fragmentMessageInputView.messageInput.text.isNullOrBlank()) {
493-
binding.fragmentMessageInputView.messageSendButton.visibility = View.GONE
494-
binding.fragmentMessageInputView.recordAudioButton.visibility = View.VISIBLE
495-
} else {
496-
binding.fragmentMessageInputView.messageSendButton.visibility = View.VISIBLE
497-
binding.fragmentMessageInputView.recordAudioButton.visibility = View.GONE
498-
}
499-
binding.fragmentMessageInputView.inputEditText.doAfterTextChanged {
500-
binding.fragmentMessageInputView.recordAudioButton.visibility =
501-
if (binding.fragmentMessageInputView.inputEditText.text.isEmpty() &&
502-
chatActivity.messageInputViewModel.getEditChatMessage.value == null
503-
) {
504-
View.VISIBLE
505-
} else {
506-
View.GONE
507-
}
528+
handleButtonsVisibility()
508529

509-
binding.fragmentMessageInputView.messageSendButton.visibility =
510-
if (binding.fragmentMessageInputView.inputEditText.text.isEmpty() ||
511-
binding.fragmentEditView.editMessageView.isVisible
512-
) {
513-
View.GONE
514-
} else {
515-
View.VISIBLE
516-
}
530+
binding.fragmentMessageInputView.inputEditText.doAfterTextChanged {
531+
handleButtonsVisibility()
517532
}
518533

519534
var prevDx = 0f
@@ -628,6 +643,33 @@ class MessageInputFragment : Fragment() {
628643
}
629644
}
630645

646+
private fun handleButtonsVisibility() {
647+
fun View.setVisible(isVisible: Boolean) {
648+
visibility = if (isVisible) View.VISIBLE else View.GONE
649+
}
650+
651+
val isEditModeActive = binding.fragmentEditView.editMessageView.isVisible
652+
val isThreadCreateModeActive = binding.fragmentCreateThreadView.createThreadView.isVisible
653+
val inputContainsText = binding.fragmentMessageInputView.messageInput.text.isNotEmpty()
654+
655+
binding.fragmentMessageInputView.apply {
656+
when {
657+
isEditModeActive -> {
658+
messageSendButton.setVisible(false)
659+
recordAudioButton.setVisible(false)
660+
}
661+
inputContainsText || isThreadCreateModeActive -> {
662+
messageSendButton.setVisible(true)
663+
recordAudioButton.setVisible(false)
664+
}
665+
else -> {
666+
messageSendButton.setVisible(false)
667+
recordAudioButton.setVisible(true)
668+
}
669+
}
670+
}
671+
}
672+
631673
private fun resetSlider() {
632674
binding.fragmentMessageInputView.audioRecordDuration.stop()
633675
binding.fragmentMessageInputView.audioRecordDuration.clearAnimation()
@@ -837,27 +879,28 @@ class MessageInputFragment : Fragment() {
837879
replaceMentionChipSpans(editable)
838880
binding.fragmentMessageInputView.inputEditText?.setText("")
839881
sendStopTypingMessage()
840-
841882
sendMessage(
842883
editable.toString(),
843884
sendWithoutNotification
844885
)
845886
cancelReply()
887+
cancelCreateThread()
846888
}
847889
}
848890

849891
private fun sendMessage(message: String, sendWithoutNotification: Boolean) {
850892
chatActivity.messageInputViewModel.sendChatMessage(
851-
chatActivity.conversationUser!!.getCredentials(),
852-
ApiUtils.getUrlForChat(
893+
credentials = chatActivity.conversationUser!!.getCredentials(),
894+
url = ApiUtils.getUrlForChat(
853895
chatActivity.chatApiVersion,
854896
chatActivity.conversationUser!!.baseUrl!!,
855897
chatActivity.roomToken
856898
),
857-
message,
858-
chatActivity.conversationUser!!.displayName ?: "",
859-
chatActivity.getReplyToMessageId(),
860-
sendWithoutNotification
899+
message = message,
900+
displayName = chatActivity.conversationUser!!.displayName ?: "",
901+
replyTo = chatActivity.getReplyToMessageId(),
902+
sendWithoutNotification = sendWithoutNotification,
903+
threadTitle = chatActivity.chatViewModel.messageDraft.threadTitle
861904
)
862905
}
863906

@@ -952,6 +995,7 @@ class MessageInputFragment : Fragment() {
952995
binding.fragmentEditView.editMessageView.visibility = View.GONE
953996
binding.fragmentMessageInputView.attachmentButton.visibility = View.VISIBLE
954997
chatActivity.messageInputViewModel.edit(null)
998+
handleButtonsVisibility()
955999
}
9561000

9571001
private fun themeMessageInputView() {
@@ -994,6 +1038,9 @@ class MessageInputFragment : Fragment() {
9941038
binding.fragmentEditView.clearEdit.let {
9951039
viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY)
9961040
}
1041+
binding.fragmentCreateThreadView.abortCreateThread.let {
1042+
viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY)
1043+
}
9971044

9981045
binding.fragmentCallStarted.callStartedBackground.apply {
9991046
viewThemeUtils.talk.themeOutgoingMessageBubble(this, grouped = true, false)
@@ -1012,6 +1059,12 @@ class MessageInputFragment : Fragment() {
10121059
}
10131060
}
10141061

1062+
private fun cancelCreateThread() {
1063+
chatActivity.cancelCreateThread()
1064+
chatActivity.messageInputViewModel.stopThreadCreation()
1065+
binding.fragmentCreateThreadView.createThreadView.visibility = View.GONE
1066+
}
1067+
10151068
private fun cancelReply() {
10161069
chatActivity.cancelReply()
10171070
clearReplyUi()

app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ interface ChatMessageRepository : LifecycleAwareManager {
8484
displayName: String,
8585
replyTo: Int,
8686
sendWithoutNotification: Boolean,
87-
referenceId: String
87+
referenceId: String,
88+
threadTitle: String?
8889
): Flow<Result<ChatMessage?>>
8990

9091
@Suppress("LongParameterList")

app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ interface ChatNetworkDataSource {
6060
displayName: String,
6161
replyTo: Int,
6262
sendWithoutNotification: Boolean,
63-
referenceId: String
63+
referenceId: String,
64+
threadTitle: String?
6465
): ChatOverallSingleMessage
6566

6667
fun pullChatMessages(credentials: String, url: String, fieldMap: HashMap<String, Int>): Observable<Response<*>>

app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,8 @@ class OfflineFirstChatRepository @Inject constructor(
865865
displayName: String,
866866
replyTo: Int,
867867
sendWithoutNotification: Boolean,
868-
referenceId: String
868+
referenceId: String,
869+
threadTitle: String?
869870
): Flow<Result<ChatMessage?>> {
870871
if (!networkMonitor.isOnline.value) {
871872
return flow {
@@ -881,7 +882,8 @@ class OfflineFirstChatRepository @Inject constructor(
881882
displayName,
882883
replyTo,
883884
sendWithoutNotification,
884-
referenceId
885+
referenceId,
886+
threadTitle
885887
)
886888

887889
val chatMessageModel = response.ocs?.data?.asModel()
@@ -942,13 +944,14 @@ class OfflineFirstChatRepository @Inject constructor(
942944
_updateMessageFlow.emit(messageToResendModel)
943945

944946
sendChatMessage(
945-
credentials,
946-
url,
947-
message,
948-
displayName,
949-
replyTo,
950-
sendWithoutNotification,
951-
referenceId
947+
credentials = credentials,
948+
url = url,
949+
message = message,
950+
displayName = displayName,
951+
replyTo = replyTo,
952+
sendWithoutNotification = sendWithoutNotification,
953+
referenceId = referenceId,
954+
threadTitle = null
952955
)
953956
} else {
954957
flow {
@@ -1005,7 +1008,8 @@ class OfflineFirstChatRepository @Inject constructor(
10051008
it.actorDisplayName,
10061009
it.parentMessageId?.toIntOrZero() ?: 0,
10071010
it.silent,
1008-
it.referenceId.orEmpty()
1011+
it.referenceId.orEmpty(),
1012+
null
10091013
).collect { result ->
10101014
if (result.isSuccess) {
10111015
Log.d(TAG, "Sent temp message")

app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ class RetrofitChatNetwork(private val ncApi: NcApi, private val ncApiCoroutines:
145145
displayName: String,
146146
replyTo: Int,
147147
sendWithoutNotification: Boolean,
148-
referenceId: String
148+
referenceId: String,
149+
threadTitle: String?
149150
): ChatOverallSingleMessage =
150151
ncApiCoroutines.sendChatMessage(
151152
credentials,
@@ -154,7 +155,8 @@ class RetrofitChatNetwork(private val ncApi: NcApi, private val ncApiCoroutines:
154155
displayName,
155156
replyTo,
156157
sendWithoutNotification,
157-
referenceId
158+
referenceId,
159+
threadTitle
158160
)
159161

160162
override fun pullChatMessages(

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ class ChatViewModel @Inject constructor(
115115
chatRepository.handleOnPause()
116116
mediaPlayerManager.handleOnPause()
117117

118+
saveMessageDraft()
119+
}
120+
121+
private fun saveMessageDraft() {
118122
CoroutineScope(Dispatchers.IO).launch {
119123
val model = conversationRepository.getLocallyStoredConversation(chatRoomToken)
120124
model?.let {
@@ -957,6 +961,11 @@ class ChatViewModel @Inject constructor(
957961
}
958962
}
959963

964+
fun clearThreadTitle() {
965+
messageDraft.threadTitle = ""
966+
saveMessageDraft()
967+
}
968+
960969
companion object {
961970
private val TAG = ChatViewModel::class.simpleName
962971
const val JOIN_ROOM_RETRY_COUNT: Long = 3

0 commit comments

Comments
 (0)