hardening chat controller by null-checking any UI access due to asynchronous UI accesses

Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
This commit is contained in:
Andy Scherzinger 2021-05-26 18:08:44 +02:00
parent 8d6b526576
commit d372335d57
No known key found for this signature in database
GPG Key ID: 6CADC7E3523C308B

View File

@ -159,7 +159,7 @@ class ChatController(args: Bundle) :
MessagesListAdapter.Formatter<Date>, MessagesListAdapter.Formatter<Date>,
MessagesListAdapter.OnMessageViewLongClickListener<IMessage>, MessagesListAdapter.OnMessageViewLongClickListener<IMessage>,
MessageHolders.ContentChecker<IMessage> { MessageHolders.ContentChecker<IMessage> {
private val binding: ControllerChatBinding by viewBinding(ControllerChatBinding::bind) private val binding: ControllerChatBinding? by viewBinding(ControllerChatBinding::bind)
@Inject @Inject
@JvmField @JvmField
@ -370,7 +370,7 @@ class ChatController(args: Bundle) :
var adapterWasNull = false var adapterWasNull = false
if (adapter == null) { if (adapter == null) {
binding.progressBar.visibility = View.VISIBLE binding?.progressBar?.visibility = View.VISIBLE
adapterWasNull = true adapterWasNull = true
@ -431,19 +431,19 @@ class ChatController(args: Bundle) :
} }
) )
} else { } else {
binding.messagesListView.visibility = View.VISIBLE binding?.messagesListView?.visibility = View.VISIBLE
} }
binding.messagesListView.setAdapter(adapter) binding?.messagesListView?.setAdapter(adapter)
adapter?.setLoadMoreListener(this) adapter?.setLoadMoreListener(this)
adapter?.setDateHeadersFormatter { format(it) } adapter?.setDateHeadersFormatter { format(it) }
adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) } adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) }
layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager? layoutManager = binding?.messagesListView?.layoutManager as LinearLayoutManager?
binding.popupBubbleView.setRecyclerView(binding.messagesListView) binding?.popupBubbleView?.setRecyclerView(binding?.messagesListView)
binding.popupBubbleView.setPopupBubbleListener { context -> binding?.popupBubbleView?.setPopupBubbleListener { context ->
if (newMessagesCount != 0) { if (newMessagesCount != 0) {
val scrollPosition: Int val scrollPosition: Int
if (newMessagesCount - 1 < 0) { if (newMessagesCount - 1 < 0) {
@ -451,20 +451,20 @@ class ChatController(args: Bundle) :
} else { } else {
scrollPosition = newMessagesCount - 1 scrollPosition = newMessagesCount - 1
} }
Handler().postDelayed({ binding.messagesListView.smoothScrollToPosition(scrollPosition) }, 200) Handler().postDelayed({ binding?.messagesListView?.smoothScrollToPosition(scrollPosition) }, 200)
} }
} }
if (args.containsKey("showToggleChat") && args.getBoolean("showToggleChat")) { if (args.containsKey("showToggleChat") && args.getBoolean("showToggleChat")) {
binding.callControlToggleChat.visibility = View.VISIBLE binding?.callControlToggleChat?.visibility = View.VISIBLE
wasDetached = true wasDetached = true
} }
binding.callControlToggleChat.setOnClickListener { binding?.callControlToggleChat?.setOnClickListener {
(activity as MagicCallActivity).showCall() (activity as MagicCallActivity).showCall()
} }
binding.messagesListView.addOnScrollListener(object : RecyclerView.OnScrollListener() { binding?.messagesListView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState) super.onScrollStateChanged(recyclerView, newState)
@ -473,8 +473,8 @@ class ChatController(args: Bundle) :
if (layoutManager!!.findFirstCompletelyVisibleItemPosition() < newMessagesCount) { if (layoutManager!!.findFirstCompletelyVisibleItemPosition() < newMessagesCount) {
newMessagesCount = 0 newMessagesCount = 0
if (binding.popupBubbleView.isShown) { if (binding?.popupBubbleView?.isShown == true) {
binding.popupBubbleView.hide() binding?.popupBubbleView?.hide()
} }
} }
} }
@ -486,26 +486,26 @@ class ChatController(args: Bundle) :
val lengthFilter = CapabilitiesUtil.getMessageMaxLength(conversationUser) ?: 1000 val lengthFilter = CapabilitiesUtil.getMessageMaxLength(conversationUser) ?: 1000
filters[0] = InputFilter.LengthFilter(lengthFilter) filters[0] = InputFilter.LengthFilter(lengthFilter)
binding.messageInputView.inputEditText?.filters = filters binding?.messageInputView?.inputEditText?.filters = filters
binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { binding?.messageInputView?.inputEditText?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
} }
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (s.length >= lengthFilter) { if (s.length >= lengthFilter) {
binding.messageInputView.inputEditText?.error = String.format( binding?.messageInputView?.inputEditText?.error = String.format(
Objects.requireNonNull<Resources>(resources).getString(R.string.nc_limit_hit), Objects.requireNonNull<Resources>(resources).getString(R.string.nc_limit_hit),
Integer.toString(lengthFilter) Integer.toString(lengthFilter)
) )
} else { } else {
binding.messageInputView.inputEditText?.error = null binding?.messageInputView?.inputEditText?.error = null
} }
val editable = binding.messageInputView.inputEditText?.editableText val editable = binding?.messageInputView?.inputEditText?.editableText
if (editable != null && binding.messageInputView.inputEditText != null) { if (editable != null && binding?.messageInputView?.inputEditText != null) {
val mentionSpans = editable.getSpans( val mentionSpans = editable.getSpans(
0, binding.messageInputView.inputEditText!!.length(), 0, binding?.messageInputView?.inputEditText!!.length(),
Spans.MentionChipSpan::class.java Spans.MentionChipSpan::class.java
) )
var mentionSpan: Spans.MentionChipSpan var mentionSpan: Spans.MentionChipSpan
@ -528,14 +528,14 @@ class ChatController(args: Bundle) :
} }
}) })
binding.messageInput.setText(sharedText) binding?.messageInputView?.inputEditText?.setText(sharedText)
binding.messageInputView.setAttachmentsListener { binding?.messageInputView?.setAttachmentsListener {
activity?.let { AttachmentDialog(it, this).show() } activity?.let { AttachmentDialog(it, this).show() }
} }
binding.messageInputView.button?.setOnClickListener { v -> submitMessage() } binding?.messageInputView?.button?.setOnClickListener { v -> submitMessage() }
binding.messageInputView.button?.contentDescription = resources?.getString( binding?.messageInputView?.button?.contentDescription = resources?.getString(
R.string R.string
.nc_description_send_message_button .nc_description_send_message_button
) )
@ -566,7 +566,7 @@ class ChatController(args: Bundle) :
conversationVoiceCallMenuItem?.icon?.alpha = 99 conversationVoiceCallMenuItem?.icon?.alpha = 99
conversationVideoMenuItem?.icon?.alpha = 99 conversationVideoMenuItem?.icon?.alpha = 99
binding.messageInputView.visibility = View.GONE binding?.messageInputView?.visibility = View.GONE
} else { } else {
if (conversationVoiceCallMenuItem != null) { if (conversationVoiceCallMenuItem != null) {
conversationVoiceCallMenuItem?.icon?.alpha = 255 conversationVoiceCallMenuItem?.icon?.alpha = 255
@ -578,9 +578,9 @@ class ChatController(args: Bundle) :
if (currentConversation != null && currentConversation!!.shouldShowLobby(conversationUser) if (currentConversation != null && currentConversation!!.shouldShowLobby(conversationUser)
) { ) {
binding.messageInputView.visibility = View.GONE binding?.messageInputView?.visibility = View.GONE
} else { } else {
binding.messageInputView.visibility = View.VISIBLE binding?.messageInputView?.visibility = View.VISIBLE
} }
} }
} }
@ -594,15 +594,15 @@ class ChatController(args: Bundle) :
} }
if (currentConversation?.shouldShowLobby(conversationUser) ?: false) { if (currentConversation?.shouldShowLobby(conversationUser) ?: false) {
binding.lobby.lobbyView.visibility = View.VISIBLE binding?.lobby?.lobbyView?.visibility = View.VISIBLE
binding.messagesListView.visibility = View.GONE binding?.messagesListView?.visibility = View.GONE
binding.messageInputView.visibility = View.GONE binding?.messageInputView?.visibility = View.GONE
binding.progressBar?.visibility = View.GONE binding?.progressBar?.visibility = View.GONE
if (currentConversation?.lobbyTimer != null && currentConversation?.lobbyTimer != if (currentConversation?.lobbyTimer != null && currentConversation?.lobbyTimer !=
0L 0L
) { ) {
binding.lobby.lobbyTextView.text = String.format( binding?.lobby?.lobbyTextView?.text = String.format(
resources!!.getString(R.string.nc_lobby_waiting_with_date), resources!!.getString(R.string.nc_lobby_waiting_with_date),
DateUtils.getLocalDateStringFromTimestampForLobby( DateUtils.getLocalDateStringFromTimestampForLobby(
currentConversation?.lobbyTimer currentConversation?.lobbyTimer
@ -610,12 +610,12 @@ class ChatController(args: Bundle) :
) )
) )
} else { } else {
binding.lobby.lobbyTextView.setText(R.string.nc_lobby_waiting) binding?.lobby?.lobbyTextView?.setText(R.string.nc_lobby_waiting)
} }
} else { } else {
binding.lobby.lobbyView.visibility = View.GONE binding?.lobby?.lobbyView?.visibility = View.GONE
binding.messagesListView.visibility = View.VISIBLE binding?.messagesListView?.visibility = View.VISIBLE
binding.messageInputView.inputEditText?.visibility = View.VISIBLE binding?.messageInputView?.inputEditText?.visibility = View.VISIBLE
if (isFirstMessagesProcessing && pastPreconditionFailed) { if (isFirstMessagesProcessing && pastPreconditionFailed) {
pastPreconditionFailed = false pastPreconditionFailed = false
pullChatMessages(0) pullChatMessages(0)
@ -625,9 +625,9 @@ class ChatController(args: Bundle) :
} }
} }
} else { } else {
binding.lobby.lobbyView.visibility = View.GONE binding?.lobby?.lobbyView?.visibility = View.GONE
binding.messagesListView.visibility = View.VISIBLE binding?.messagesListView?.visibility = View.VISIBLE
binding.messageInputView.inputEditText?.visibility = View.VISIBLE binding?.messageInputView?.inputEditText?.visibility = View.VISIBLE
} }
} }
@ -775,11 +775,11 @@ class ChatController(args: Bundle) :
val callback = MentionAutocompleteCallback( val callback = MentionAutocompleteCallback(
activity, activity,
conversationUser, conversationUser,
binding.messageInputView.inputEditText binding?.messageInputView?.inputEditText
) )
if (mentionAutocomplete == null && binding.messageInputView.inputEditText != null) { if (mentionAutocomplete == null && binding?.messageInputView?.inputEditText != null) {
mentionAutocomplete = Autocomplete.on<Mention>(binding.messageInputView.inputEditText) mentionAutocomplete = Autocomplete.on<Mention>(binding?.messageInputView?.inputEditText)
.with(elevation) .with(elevation)
.with(backgroundDrawable) .with(backgroundDrawable)
.with(MagicCharPolicy('@')) .with(MagicCharPolicy('@'))
@ -808,9 +808,9 @@ class ChatController(args: Bundle) :
isLinkPreviewAllowed = appPreferences?.areLinkPreviewsAllowed ?: false isLinkPreviewAllowed = appPreferences?.areLinkPreviewsAllowed ?: false
val smileyButton = binding.messageInputView.findViewById<ImageButton>(R.id.smileyButton) val smileyButton = binding?.messageInputView?.findViewById<ImageButton>(R.id.smileyButton)
emojiPopup = binding.messageInputView.inputEditText?.let { emojiPopup = binding?.messageInputView?.inputEditText?.let {
EmojiPopup.Builder.fromRootView(view).setOnEmojiPopupShownListener { EmojiPopup.Builder.fromRootView(view).setOnEmojiPopupShownListener {
if (resources != null) { if (resources != null) {
smileyButton?.setColorFilter( smileyButton?.setColorFilter(
@ -825,15 +825,15 @@ class ChatController(args: Bundle) :
) )
}.setOnEmojiClickListener { emoji, }.setOnEmojiClickListener { emoji,
imageView -> imageView ->
binding.messageInputView.inputEditText?.editableText?.append(" ") binding?.messageInputView?.inputEditText?.editableText?.append(" ")
}.build(it) }.build(it)
} }
smileyButton.setOnClickListener { smileyButton?.setOnClickListener {
emojiPopup?.toggle() emojiPopup?.toggle()
} }
binding.messageInputView.findViewById<ImageButton>(R.id.cancelReplyButton).setOnClickListener { binding?.messageInputView?.findViewById<ImageButton>(R.id.cancelReplyButton)?.setOnClickListener {
cancelReply() cancelReply()
} }
@ -853,9 +853,9 @@ class ChatController(args: Bundle) :
} }
private fun cancelReply() { private fun cancelReply() {
binding.messageInputView.findViewById<RelativeLayout>(R.id.quotedChatMessageView).visibility = View.GONE binding?.messageInputView?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.visibility = View.GONE
binding.messageInputView.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.VISIBLE binding?.messageInputView?.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.VISIBLE
binding.messageInputView.findViewById<Space>(R.id.attachmentButtonSpace)?.visibility = View.VISIBLE binding?.messageInputView?.findViewById<Space>(R.id.attachmentButtonSpace)?.visibility = View.VISIBLE
} }
private fun cancelNotificationsForCurrentConversation() { private fun cancelNotificationsForCurrentConversation() {
@ -1053,8 +1053,8 @@ class ChatController(args: Bundle) :
} }
private fun submitMessage() { private fun submitMessage() {
if (binding.messageInputView.inputEditText != null) { if (binding?.messageInputView?.inputEditText != null) {
val editable = binding.messageInputView.inputEditText!!.editableText val editable = binding?.messageInputView?.inputEditText!!.editableText
val mentionSpans = editable.getSpans( val mentionSpans = editable.getSpans(
0, editable.length, 0, editable.length,
Spans.MentionChipSpan::class.java Spans.MentionChipSpan::class.java
@ -1069,7 +1069,7 @@ class ChatController(args: Bundle) :
editable.replace(editable.getSpanStart(mentionSpan), editable.getSpanEnd(mentionSpan), "@$mentionId") editable.replace(editable.getSpanStart(mentionSpan), editable.getSpanEnd(mentionSpan), "@$mentionId")
} }
binding.messageInputView.inputEditText?.setText("") binding?.messageInputView?.inputEditText?.setText("")
val replyMessageId: Int? = view?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.tag as Int? val replyMessageId: Int? = view?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.tag as Int?
sendMessage( sendMessage(
editable, editable,
@ -1100,11 +1100,11 @@ class ChatController(args: Bundle) :
override fun onNext(genericOverall: GenericOverall) { override fun onNext(genericOverall: GenericOverall) {
myFirstMessage = message myFirstMessage = message
if (binding.popupBubbleView.isShown) { if (binding?.popupBubbleView?.isShown == true) {
binding.popupBubbleView.hide() binding?.popupBubbleView?.hide()
} }
binding.messagesListView.smoothScrollToPosition(0) binding?.messagesListView?.smoothScrollToPosition(0)
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
@ -1113,11 +1113,11 @@ class ChatController(args: Bundle) :
if (Integer.toString(code).startsWith("2")) { if (Integer.toString(code).startsWith("2")) {
myFirstMessage = message myFirstMessage = message
if (binding.popupBubbleView.isShown) { if (binding?.popupBubbleView?.isShown == true) {
binding.popupBubbleView.hide() binding?.popupBubbleView?.hide()
} }
binding.messagesListView.smoothScrollToPosition(0) binding?.messagesListView?.smoothScrollToPosition(0)
} }
} }
} }
@ -1282,9 +1282,9 @@ class ChatController(args: Bundle) :
cancelNotificationsForCurrentConversation() cancelNotificationsForCurrentConversation()
isFirstMessagesProcessing = false isFirstMessagesProcessing = false
binding.progressBar.visibility = View.GONE binding?.progressBar?.visibility = View.GONE
binding.messagesListView.visibility = View.VISIBLE binding?.messagesListView?.visibility = View.VISIBLE
} }
var countGroupedMessages = 0 var countGroupedMessages = 0
@ -1349,10 +1349,10 @@ class ChatController(args: Bundle) :
adapter?.itemCount == 0 adapter?.itemCount == 0
if (!shouldAddNewMessagesNotice && !shouldScroll) { if (!shouldAddNewMessagesNotice && !shouldScroll) {
if (!binding.popupBubbleView.isShown) { if (!binding?.popupBubbleView?.isShown!!) {
newMessagesCount = 1 newMessagesCount = 1
binding.popupBubbleView.show() binding?.popupBubbleView?.show()
} else if (binding.popupBubbleView.isShown) { } else if (binding?.popupBubbleView?.isShown == true) {
newMessagesCount++ newMessagesCount++
} }
} else { } else {
@ -1375,7 +1375,7 @@ class ChatController(args: Bundle) :
if (shouldAddNewMessagesNotice && adapter != null) { if (shouldAddNewMessagesNotice && adapter != null) {
layoutManager?.scrollToPositionWithOffset( layoutManager?.scrollToPositionWithOffset(
adapter!!.getMessagePositionByIdInReverse("-1"), adapter!!.getMessagePositionByIdInReverse("-1"),
binding.messagesListView.height / 2 binding!!.messagesListView.height / 2
) )
} }
} }
@ -1404,7 +1404,7 @@ class ChatController(args: Bundle) :
cancelNotificationsForCurrentConversation() cancelNotificationsForCurrentConversation()
isFirstMessagesProcessing = false isFirstMessagesProcessing = false
binding.progressBar.visibility = View.GONE binding?.progressBar?.visibility = View.GONE
} }
historyRead = true historyRead = true
@ -1561,21 +1561,21 @@ class ChatController(args: Bundle) :
R.id.action_reply_to_message -> { R.id.action_reply_to_message -> {
val chatMessage = message as ChatMessage? val chatMessage = message as ChatMessage?
chatMessage?.let { chatMessage?.let {
binding.messageInputView.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.GONE binding?.messageInputView?.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.GONE
binding.messageInputView.findViewById<Space>(R.id.attachmentButtonSpace)?.visibility = View.GONE binding?.messageInputView?.findViewById<Space>(R.id.attachmentButtonSpace)?.visibility = View.GONE
binding.messageInputView.findViewById<ImageButton>(R.id.cancelReplyButton)?.visibility = binding?.messageInputView?.findViewById<ImageButton>(R.id.cancelReplyButton)?.visibility =
View.VISIBLE View.VISIBLE
binding.messageInputView.findViewById<EmojiTextView>(R.id.quotedMessage)?.maxLines = 2 binding?.messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.maxLines = 2
binding.messageInputView.findViewById<EmojiTextView>(R.id.quotedMessage)?.ellipsize = binding?.messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.ellipsize =
TextUtils.TruncateAt.END TextUtils.TruncateAt.END
binding.messageInputView.findViewById<EmojiTextView>(R.id.quotedMessage)?.text = it.text binding?.messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.text = it.text
binding.messageInputView.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text = binding?.messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text =
it.actorDisplayName ?: context!!.getText(R.string.nc_nick_guest) it.actorDisplayName ?: context!!.getText(R.string.nc_nick_guest)
conversationUser?.let { currentUser -> conversationUser?.let { currentUser ->
chatMessage.imageUrl?.let { previewImageUrl -> chatMessage.imageUrl?.let { previewImageUrl ->
binding.messageInputView.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = binding?.messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility =
View.VISIBLE View.VISIBLE
val px = TypedValue.applyDimension( val px = TypedValue.applyDimension(
@ -1583,26 +1583,26 @@ class ChatController(args: Bundle) :
96f, 96f,
resources?.displayMetrics resources?.displayMetrics
) )
binding.messageInputView.findViewById<ImageView>(R.id.quotedMessageImage)?.maxHeight = binding?.messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.maxHeight =
px.toInt() px.toInt()
val layoutParams = val layoutParams =
binding.messageInputView.findViewById<ImageView>(R.id.quotedMessageImage)?.layoutParams as FlexboxLayout.LayoutParams binding?.messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.layoutParams as FlexboxLayout.LayoutParams
layoutParams.flexGrow = 0f layoutParams.flexGrow = 0f
binding.messageInputView.findViewById<ImageView>(R.id.quotedMessageImage)?.layoutParams = binding?.messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.layoutParams =
layoutParams layoutParams
binding.messageInputView.findViewById<ImageView>(R.id.quotedMessageImage) binding?.messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)
?.load(previewImageUrl) { ?.load(previewImageUrl) {
addHeader("Authorization", credentials!!) addHeader("Authorization", credentials!!)
} }
} ?: run { } ?: run {
binding.messageInputView.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = binding?.messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility =
View.GONE View.GONE
} }
} }
val quotedChatMessageView = binding val quotedChatMessageView = binding
.messageInputView ?.messageInputView
.findViewById<RelativeLayout>(R.id.quotedChatMessageView) ?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
quotedChatMessageView?.tag = message?.jsonMessageId quotedChatMessageView?.tag = message?.jsonMessageId
quotedChatMessageView?.visibility = View.VISIBLE quotedChatMessageView?.visibility = View.VISIBLE
} }