This commit is contained in:
Julius Linus 2025-06-18 07:54:22 +00:00 committed by GitHub
commit be8c4ecb0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 115 additions and 49 deletions

View File

@ -79,7 +79,6 @@ import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.text.Spans import com.nextcloud.talk.utils.text.Spans
import com.otaliastudios.autocomplete.Autocomplete import com.otaliastudios.autocomplete.Autocomplete
import com.stfalcon.chatkit.commons.models.IMessage
import com.vanniktech.emoji.EmojiPopup import com.vanniktech.emoji.EmojiPopup
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -87,7 +86,7 @@ import kotlinx.coroutines.launch
import java.util.Objects import java.util.Objects
import javax.inject.Inject import javax.inject.Inject
@Suppress("LongParameterList", "TooManyFunctions") @Suppress("LongParameterList", "TooManyFunctions", "LargeClass", "LongMethod")
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class MessageInputFragment : Fragment() { class MessageInputFragment : Fragment() {
@ -110,6 +109,10 @@ class MessageInputFragment : Fragment() {
private const val CONNECTION_ESTABLISHED_ANIM_DURATION: Long = 3000 private const val CONNECTION_ESTABLISHED_ANIM_DURATION: Long = 3000
private const val FULLY_OPAQUE: Float = 1.0f private const val FULLY_OPAQUE: Float = 1.0f
private const val FULLY_TRANSPARENT: Float = 0.0f private const val FULLY_TRANSPARENT: Float = 0.0f
const val QUOTED_MESSAGE_TEXT = "QUOTED_MESSAGE_TEXT"
const val QUOTED_MESSAGE_ID = "QUOTED_MESSAGE_ID"
const val QUOTED_MESSAGE_URL = "QUOTED_MESSAGE_URL"
const val QUOTED_MESSAGE_NAME = "QUOTED_MESSAGE_NAME"
} }
@Inject @Inject
@ -134,6 +137,10 @@ class MessageInputFragment : Fragment() {
private var xcounter = 0f private var xcounter = 0f
private var ycounter = 0f private var ycounter = 0f
private var collapsed = false private var collapsed = false
private var quotedMessageText: String? = ""
private var quotedActorDisplayName: String? = null
private var quotedImageUrl: String? = null
private var quotedJsonId: Int = -1
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -157,8 +164,8 @@ class MessageInputFragment : Fragment() {
} }
override fun onPause() { override fun onPause() {
super.onPause()
saveState() saveState()
super.onPause()
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -167,7 +174,10 @@ class MessageInputFragment : Fragment() {
mentionAutocomplete?.dismissPopup() mentionAutocomplete?.dismissPopup()
} }
clearEditUI() clearEditUI()
cancelReply() val isInReplyState = (quotedJsonId != -1 && quotedActorDisplayName != null && quotedMessageText != "")
if (!isInReplyState) {
cancelReply()
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -178,7 +188,13 @@ class MessageInputFragment : Fragment() {
private fun initObservers() { private fun initObservers() {
Log.d(TAG, "LifeCyclerOwner is: ${viewLifecycleOwner.lifecycle}") Log.d(TAG, "LifeCyclerOwner is: ${viewLifecycleOwner.lifecycle}")
chatActivity.messageInputViewModel.getReplyChatMessage.observe(viewLifecycleOwner) { message -> chatActivity.messageInputViewModel.getReplyChatMessage.observe(viewLifecycleOwner) { message ->
message?.let { replyToMessage(message) } (message as ChatMessage?)?.let {
quotedMessageText = message.text
quotedActorDisplayName = message.actorDisplayName
quotedImageUrl = message.imageUrl
quotedJsonId = message.jsonMessageId
replyToMessage(message.text, message.actorDisplayName, message.imageUrl, message.jsonMessageId)
}
} }
chatActivity.messageInputViewModel.getEditChatMessage.observe(viewLifecycleOwner) { message -> chatActivity.messageInputViewModel.getEditChatMessage.observe(viewLifecycleOwner) { message ->
@ -295,11 +311,20 @@ class MessageInputFragment : Fragment() {
private fun restoreState() { private fun restoreState() {
if (binding.fragmentMessageInputView.inputEditText.text.isEmpty()) { if (binding.fragmentMessageInputView.inputEditText.text.isEmpty()) {
Log.d("Julius", "State restored from: ${chatActivity.localClassName}")
requireContext().getSharedPreferences(chatActivity.localClassName, AppCompatActivity.MODE_PRIVATE).apply { requireContext().getSharedPreferences(chatActivity.localClassName, AppCompatActivity.MODE_PRIVATE).apply {
val text = getString(chatActivity.roomToken, "") val text = getString(chatActivity.roomToken, "")
val cursor = getInt(chatActivity.roomToken + CURSOR_KEY, 0) val cursor = getInt(chatActivity.roomToken + CURSOR_KEY, 0)
binding.fragmentMessageInputView.messageInput.setText(text) binding.fragmentMessageInputView.messageInput.setText(text)
binding.fragmentMessageInputView.messageInput.setSelection(cursor) binding.fragmentMessageInputView.messageInput.setSelection(cursor)
quotedJsonId = getInt(QUOTED_MESSAGE_ID, -1)
quotedImageUrl = getString(QUOTED_MESSAGE_URL, null)
quotedMessageText = getString(QUOTED_MESSAGE_TEXT, "")
quotedActorDisplayName = getString(QUOTED_MESSAGE_NAME, null)
val isInReplyState = (quotedJsonId != -1 && quotedActorDisplayName != null && quotedMessageText != "")
if (isInReplyState) {
replyToMessage(quotedMessageText, quotedActorDisplayName, quotedImageUrl, quotedJsonId)
}
} }
} }
} }
@ -313,15 +338,23 @@ class MessageInputFragment : Fragment() {
.MODE_PRIVATE .MODE_PRIVATE
).getString(chatActivity.roomToken, "null") ).getString(chatActivity.roomToken, "null")
if (text != previous) { val isInReplyState = (quotedJsonId != -1 && quotedActorDisplayName != null && quotedMessageText != "")
requireContext().getSharedPreferences( requireContext().getSharedPreferences(
chatActivity.localClassName, chatActivity.localClassName,
AppCompatActivity.MODE_PRIVATE AppCompatActivity.MODE_PRIVATE
).edit().apply { ).edit().apply {
if (text != previous) {
putString(chatActivity.roomToken, text) putString(chatActivity.roomToken, text)
putInt(chatActivity.roomToken + CURSOR_KEY, cursor) putInt(chatActivity.roomToken + CURSOR_KEY, cursor)
apply()
} }
if (isInReplyState) {
putInt(QUOTED_MESSAGE_ID, quotedJsonId)
putString(QUOTED_MESSAGE_NAME, quotedActorDisplayName)
putString(QUOTED_MESSAGE_TEXT, quotedMessageText)
putString(QUOTED_MESSAGE_URL, quotedImageUrl) // may be null
}
apply()
} }
} }
@ -598,7 +631,7 @@ class MessageInputFragment : Fragment() {
} }
} }
} }
v?.onTouchEvent(event) ?: true v?.onTouchEvent(event) != false
} }
} }
@ -700,52 +733,54 @@ class MessageInputFragment : Fragment() {
} }
} }
private fun replyToMessage(message: IMessage?) { private fun replyToMessage(
quotedMessageText: String?,
quotedActorDisplayName: String?,
quotedImageUrl: String?,
quotedJsonId: Int
) {
Log.d(TAG, "Reply") Log.d(TAG, "Reply")
val chatMessage = message as ChatMessage? val view = binding.fragmentMessageInputView
chatMessage?.let { view.findViewById<ImageButton>(R.id.attachmentButton)?.visibility =
val view = binding.fragmentMessageInputView View.GONE
view.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = view.findViewById<ImageButton>(R.id.cancelReplyButton)?.visibility =
View.GONE View.VISIBLE
view.findViewById<ImageButton>(R.id.cancelReplyButton)?.visibility =
View.VISIBLE
val quotedMessage = view.findViewById<EmojiTextView>(R.id.quotedMessage) val quotedMessage = view.findViewById<EmojiTextView>(R.id.quotedMessage)
quotedMessage?.maxLines = 2 quotedMessage?.maxLines = 2
quotedMessage?.ellipsize = TextUtils.TruncateAt.END quotedMessage?.ellipsize = TextUtils.TruncateAt.END
quotedMessage?.text = it.text quotedMessage?.text = quotedMessageText
view.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text = view.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text =
it.actorDisplayName ?: requireContext().getText(R.string.nc_nick_guest) quotedActorDisplayName ?: requireContext().getText(R.string.nc_nick_guest)
chatActivity.conversationUser?.let { chatActivity.conversationUser?.let {
val quotedMessageImage = view.findViewById<ImageView>(R.id.quotedMessageImage) val quotedMessageImage = view.findViewById<ImageView>(R.id.quotedMessageImage)
chatMessage.imageUrl?.let { previewImageUrl -> quotedImageUrl.let { previewImageUrl ->
quotedMessageImage?.visibility = View.VISIBLE quotedMessageImage?.visibility = View.VISIBLE
val px = TypedValue.applyDimension( val px = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, TypedValue.COMPLEX_UNIT_DIP,
QUOTED_MESSAGE_IMAGE_MAX_HEIGHT, QUOTED_MESSAGE_IMAGE_MAX_HEIGHT,
resources.displayMetrics resources.displayMetrics
) )
quotedMessageImage?.maxHeight = px.toInt() quotedMessageImage?.maxHeight = px.toInt()
val layoutParams = quotedMessageImage?.layoutParams as FlexboxLayout.LayoutParams val layoutParams = quotedMessageImage?.layoutParams as FlexboxLayout.LayoutParams
layoutParams.flexGrow = 0f layoutParams.flexGrow = 0f
quotedMessageImage.layoutParams = layoutParams quotedMessageImage.layoutParams = layoutParams
quotedMessageImage.load(previewImageUrl) { quotedMessageImage.load(previewImageUrl) {
addHeader("Authorization", chatActivity.credentials!!) addHeader("Authorization", chatActivity.credentials!!)
}
} ?: run {
view.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = View.GONE
} }
} ?: run {
view.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = View.GONE
} }
val quotedChatMessageView =
view.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
quotedChatMessageView?.tag = message?.jsonMessageId
quotedChatMessageView?.visibility = View.VISIBLE
} }
val quotedChatMessageView =
view.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
quotedChatMessageView?.tag = quotedJsonId
quotedChatMessageView?.visibility = View.VISIBLE
} }
fun updateOwnTypingStatus(typedText: CharSequence) { fun updateOwnTypingStatus(typedText: CharSequence) {
@ -1020,5 +1055,21 @@ class MessageInputFragment : Fragment() {
quote.tag = null quote.tag = null
binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.VISIBLE binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.VISIBLE
chatActivity.messageInputViewModel.reply(null) chatActivity.messageInputViewModel.reply(null)
quotedMessageText = ""
quotedActorDisplayName = null
quotedImageUrl = null
quotedJsonId = -1
requireContext().getSharedPreferences(
chatActivity.localClassName,
AppCompatActivity.MODE_PRIVATE
).edit().apply {
putInt(QUOTED_MESSAGE_ID, quotedJsonId)
putString(QUOTED_MESSAGE_NAME, quotedActorDisplayName)
putString(QUOTED_MESSAGE_TEXT, quotedMessageText)
putString(QUOTED_MESSAGE_URL, quotedImageUrl) // may be null
apply()
}
} }
} }

View File

@ -85,6 +85,10 @@ import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.chat.MessageInputFragment.Companion.QUOTED_MESSAGE_ID
import com.nextcloud.talk.chat.MessageInputFragment.Companion.QUOTED_MESSAGE_NAME
import com.nextcloud.talk.chat.MessageInputFragment.Companion.QUOTED_MESSAGE_TEXT
import com.nextcloud.talk.chat.MessageInputFragment.Companion.QUOTED_MESSAGE_URL
import com.nextcloud.talk.chat.viewmodels.ChatViewModel import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.contacts.ContactsActivity import com.nextcloud.talk.contacts.ContactsActivity
import com.nextcloud.talk.contacts.ContactsUiState import com.nextcloud.talk.contacts.ContactsUiState
@ -306,6 +310,16 @@ class ConversationsListActivity :
showNotificationWarning() showNotificationWarning()
showShareToScreen = hasActivityActionSendIntent() showShareToScreen = hasActivityActionSendIntent()
context.getSharedPreferences(
CHAT_ACTIVITY_LOCAL_NAME,
MODE_PRIVATE
).edit().apply {
putInt(QUOTED_MESSAGE_ID, -1)
putString(QUOTED_MESSAGE_NAME, null)
putString(QUOTED_MESSAGE_TEXT, "")
putString(QUOTED_MESSAGE_URL, null)
apply()
}
if (!eventBus.isRegistered(this)) { if (!eventBus.isRegistered(this)) {
eventBus.register(this) eventBus.register(this)
@ -2196,6 +2210,7 @@ class ConversationsListActivity :
const val UNREAD_BUBBLE_DELAY = 2500 const val UNREAD_BUBBLE_DELAY = 2500
const val BOTTOM_SHEET_DELAY: Long = 2500 const val BOTTOM_SHEET_DELAY: Long = 2500
private const val KEY_SEARCH_QUERY = "ConversationsListActivity.searchQuery" private const val KEY_SEARCH_QUERY = "ConversationsListActivity.searchQuery"
private const val CHAT_ACTIVITY_LOCAL_NAME = "com.nextcloud.talk.chat.ChatActivity"
const val SEARCH_DEBOUNCE_INTERVAL_MS = 300 const val SEARCH_DEBOUNCE_INTERVAL_MS = 300
const val SEARCH_MIN_CHARS = 1 const val SEARCH_MIN_CHARS = 1
const val HTTP_UNAUTHORIZED = 401 const val HTTP_UNAUTHORIZED = 401