Fix animated emojis in composable views
This commit is contained in:
parent
a26c078731
commit
81abedb89b
4 changed files with 451 additions and 276 deletions
|
@ -15,37 +15,47 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky.adapter
|
package com.keylesspalace.tusky.adapter
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.github.penfeizhou.animation.glide.AnimationDecoderOption
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.entity.Emoji
|
import com.keylesspalace.tusky.entity.Emoji
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class EmojiAdapter(emojiList: List<Emoji>, private val onEmojiSelectedListener: OnEmojiSelectedListener) : RecyclerView.Adapter<EmojiAdapter.EmojiHolder>() {
|
class EmojiAdapter(
|
||||||
private val emojiList : List<Emoji>
|
emojiList: List<Emoji>,
|
||||||
|
private val onEmojiSelectedListener: OnEmojiSelectedListener,
|
||||||
|
private val animateEmojis: Boolean
|
||||||
|
) : RecyclerView.Adapter<EmojiAdapter.EmojiHolder>() {
|
||||||
|
|
||||||
|
private val emojis: List<Emoji>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.emojiList = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
|
this.emojis =
|
||||||
.sortedBy { it.shortcode.toLowerCase(Locale.ROOT) }
|
emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
|
||||||
|
.sortedBy { it.shortcode.lowercase() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return emojiList.size
|
return emojis.size
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiHolder {
|
||||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_emoji_button, parent, false) as ImageView
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_emoji_button, parent, false) as ImageView
|
||||||
return EmojiHolder(view)
|
return EmojiHolder(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(viewHolder: EmojiHolder, position: Int) {
|
override fun onBindViewHolder(viewHolder: EmojiHolder, position: Int) {
|
||||||
val emoji = emojiList[position]
|
val emoji = emojis[position]
|
||||||
|
|
||||||
Glide.with(viewHolder.emojiImageView)
|
Glide.with(viewHolder.emojiImageView)
|
||||||
.load(emoji.url)
|
.load(emoji.url)
|
||||||
|
.set(AnimationDecoderOption.DISABLE_ANIMATION_GIF_DECODER, !animateEmojis)
|
||||||
|
.set(AnimationDecoderOption.DISABLE_ANIMATION_WEBP_DECODER, !animateEmojis)
|
||||||
|
.set(AnimationDecoderOption.DISABLE_ANIMATION_APNG_DECODER, !animateEmojis)
|
||||||
.into(viewHolder.emojiImageView)
|
.into(viewHolder.emojiImageView)
|
||||||
|
|
||||||
viewHolder.emojiImageView.setOnClickListener {
|
viewHolder.emojiImageView.setOnClickListener {
|
||||||
|
|
|
@ -55,6 +55,7 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
|
||||||
|
|
||||||
private val viewModel: AnnouncementsViewModel by viewModels { viewModelFactory }
|
private val viewModel: AnnouncementsViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
|
private lateinit var preferences: SharedPreferences
|
||||||
private lateinit var adapter: AnnouncementAdapter
|
private lateinit var adapter: AnnouncementAdapter
|
||||||
|
|
||||||
private val picker by lazy { EmojiPicker(this) }
|
private val picker by lazy { EmojiPicker(this) }
|
||||||
|
@ -89,7 +90,7 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
|
||||||
val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
val divider = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
||||||
announcementsList.addItemDecoration(divider)
|
announcementsList.addItemDecoration(divider)
|
||||||
|
|
||||||
val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
|
val wellbeingEnabled = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
|
||||||
|
|
||||||
adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled)
|
adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled)
|
||||||
|
@ -128,7 +129,11 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener,
|
||||||
|
|
||||||
viewModel.emojis.observe(this) {
|
viewModel.emojis.observe(this) {
|
||||||
it?.let { list ->
|
it?.let { list ->
|
||||||
picker.adapter = EmojiAdapter(list, this)
|
picker.adapter = EmojiAdapter(
|
||||||
|
list,
|
||||||
|
this,
|
||||||
|
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,13 @@ import android.app.Activity
|
||||||
import android.app.ProgressDialog
|
import android.app.ProgressDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -21,15 +21,6 @@ import android.widget.Toast
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
|
||||||
import com.keylesspalace.tusky.entity.Chat
|
|
||||||
import com.keylesspalace.tusky.entity.Emoji
|
|
||||||
import com.keylesspalace.tusky.interfaces.ChatActionListener
|
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
|
||||||
import com.keylesspalace.tusky.repository.ChatMesssageOrPlaceholder
|
|
||||||
import com.keylesspalace.tusky.repository.ChatRepository
|
|
||||||
import com.keylesspalace.tusky.viewdata.ChatMessageViewData
|
|
||||||
import androidx.arch.core.util.Function
|
import androidx.arch.core.util.Function
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
|
@ -41,28 +32,70 @@ import androidx.core.view.inputmethod.InputConnectionCompat
|
||||||
import androidx.core.view.inputmethod.InputContentInfoCompat
|
import androidx.core.view.inputmethod.InputContentInfoCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.*
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ListUpdateCallback
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.keylesspalace.tusky.*
|
import com.keylesspalace.tusky.BottomSheetActivity
|
||||||
import com.keylesspalace.tusky.adapter.*
|
import com.keylesspalace.tusky.BuildConfig
|
||||||
import com.keylesspalace.tusky.appstore.*
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.components.common.*
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
|
import com.keylesspalace.tusky.ViewTagActivity
|
||||||
|
import com.keylesspalace.tusky.adapter.ChatMessagesAdapter
|
||||||
|
import com.keylesspalace.tusky.adapter.ChatMessagesViewHolder
|
||||||
|
import com.keylesspalace.tusky.adapter.EmojiAdapter
|
||||||
|
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
|
||||||
|
import com.keylesspalace.tusky.adapter.TimelineAdapter
|
||||||
|
import com.keylesspalace.tusky.appstore.ChatMessageDeliveredEvent
|
||||||
|
import com.keylesspalace.tusky.appstore.ChatMessageReceivedEvent
|
||||||
|
import com.keylesspalace.tusky.appstore.Event
|
||||||
|
import com.keylesspalace.tusky.appstore.EventHub
|
||||||
|
import com.keylesspalace.tusky.components.common.AudioSizeException
|
||||||
|
import com.keylesspalace.tusky.components.common.DEFAULT_CHARACTER_LIMIT
|
||||||
|
import com.keylesspalace.tusky.components.common.MediaSizeException
|
||||||
|
import com.keylesspalace.tusky.components.common.VideoOrImageException
|
||||||
|
import com.keylesspalace.tusky.components.common.VideoSizeException
|
||||||
|
import com.keylesspalace.tusky.components.common.createNewImageFile
|
||||||
|
import com.keylesspalace.tusky.components.common.toFileName
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||||
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter
|
import com.keylesspalace.tusky.components.compose.ComposeAutoCompleteAdapter
|
||||||
|
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
||||||
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
|
import com.keylesspalace.tusky.entity.Chat
|
||||||
|
import com.keylesspalace.tusky.entity.Emoji
|
||||||
|
import com.keylesspalace.tusky.interfaces.ChatActionListener
|
||||||
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
|
import com.keylesspalace.tusky.repository.ChatMesssageOrPlaceholder
|
||||||
|
import com.keylesspalace.tusky.repository.ChatRepository
|
||||||
import com.keylesspalace.tusky.repository.Placeholder
|
import com.keylesspalace.tusky.repository.Placeholder
|
||||||
import com.keylesspalace.tusky.repository.TimelineRequestMode
|
import com.keylesspalace.tusky.repository.TimelineRequestMode
|
||||||
import com.keylesspalace.tusky.service.MessageToSend
|
import com.keylesspalace.tusky.service.MessageToSend
|
||||||
import com.keylesspalace.tusky.service.ServiceClient
|
import com.keylesspalace.tusky.service.ServiceClient
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
import com.keylesspalace.tusky.settings.PrefKeys
|
||||||
import com.keylesspalace.tusky.util.*
|
import com.keylesspalace.tusky.util.ComposeTokenizer
|
||||||
|
import com.keylesspalace.tusky.util.Either
|
||||||
|
import com.keylesspalace.tusky.util.PairedList
|
||||||
|
import com.keylesspalace.tusky.util.ThemeUtils
|
||||||
|
import com.keylesspalace.tusky.util.ViewDataUtils
|
||||||
|
import com.keylesspalace.tusky.util.afterTextChanged
|
||||||
|
import com.keylesspalace.tusky.util.dec
|
||||||
|
import com.keylesspalace.tusky.util.emojify
|
||||||
|
import com.keylesspalace.tusky.util.highlightSpans
|
||||||
|
import com.keylesspalace.tusky.util.inc
|
||||||
|
import com.keylesspalace.tusky.util.loadAvatar
|
||||||
|
import com.keylesspalace.tusky.util.withLifecycleContext
|
||||||
import com.keylesspalace.tusky.view.EmojiKeyboard
|
import com.keylesspalace.tusky.view.EmojiKeyboard
|
||||||
|
import com.keylesspalace.tusky.viewdata.ChatMessageViewData
|
||||||
import com.mikepenz.iconics.IconicsDrawable
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
|
@ -70,13 +103,32 @@ import com.mikepenz.iconics.utils.sizeDp
|
||||||
import com.uber.autodispose.android.lifecycle.autoDispose
|
import com.uber.autodispose.android.lifecycle.autoDispose
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.activity_chat.*
|
|
||||||
import kotlinx.android.synthetic.main.toolbar_basic.toolbar
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.lang.Exception
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.actionPhotoPick
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.actionPhotoTake
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.activityChat
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.addMediaBottomSheet
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.attachmentButton
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.attachmentLayout
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.chatAvatar
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.chatTitle
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.chatUsername
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.editText
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.emojiButton
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.emojiView
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.imageAttachment
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.messageView
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.progressBar
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.recycler
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.sendButton
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.stickerButton
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.stickerKeyboard
|
||||||
|
import kotlinx.android.synthetic.main.activity_chat.textAttachment
|
||||||
|
import kotlinx.android.synthetic.main.toolbar_basic.toolbar
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class ChatActivity : BottomSheetActivity(),
|
class ChatActivity : BottomSheetActivity(),
|
||||||
Injectable,
|
Injectable,
|
||||||
|
@ -85,30 +137,36 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
EmojiKeyboard.OnEmojiSelectedListener,
|
EmojiKeyboard.OnEmojiSelectedListener,
|
||||||
OnEmojiSelectedListener,
|
OnEmojiSelectedListener,
|
||||||
InputConnectionCompat.OnCommitContentListener {
|
InputConnectionCompat.OnCommitContentListener {
|
||||||
private val TAG = "ChatsActivity" // logging tag
|
|
||||||
private val LOAD_AT_ONCE = 30
|
private val LOAD_AT_ONCE = 30
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var eventHub: EventHub
|
lateinit var eventHub: EventHub
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var chatsRepo: ChatRepository
|
lateinit var chatsRepo: ChatRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var serviceClient: ServiceClient
|
lateinit var serviceClient: ServiceClient
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val viewModel: ChatViewModel by viewModels { viewModelFactory }
|
val viewModel: ChatViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT
|
var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT
|
||||||
|
|
||||||
lateinit var adapter: ChatMessagesAdapter
|
lateinit var adapter: ChatMessagesAdapter
|
||||||
|
|
||||||
private val msgs = PairedList<ChatMesssageOrPlaceholder, ChatMessageViewData?>(Function<ChatMesssageOrPlaceholder, ChatMessageViewData?> { input ->
|
private val msgs =
|
||||||
input.asRightOrNull()?.let(ViewDataUtils::chatMessageToViewData) ?:
|
PairedList<ChatMesssageOrPlaceholder, ChatMessageViewData?>(Function<ChatMesssageOrPlaceholder, ChatMessageViewData?> { input ->
|
||||||
ChatMessageViewData.Placeholder(input.asLeft().id, false)
|
input.asRightOrNull()?.let(ViewDataUtils::chatMessageToViewData)
|
||||||
|
?: ChatMessageViewData.Placeholder(input.asLeft().id, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
private var bottomLoading = false
|
private var bottomLoading = false
|
||||||
|
@ -130,7 +188,7 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
|
|
||||||
private val listUpdateCallback = object : ListUpdateCallback {
|
private val listUpdateCallback = object : ListUpdateCallback {
|
||||||
override fun onInserted(position: Int, count: Int) {
|
override fun onInserted(position: Int, count: Int) {
|
||||||
Log.d(TAG, "onInserted")
|
Timber.d("onInserted")
|
||||||
adapter.notifyItemRangeInserted(position, count)
|
adapter.notifyItemRangeInserted(position, count)
|
||||||
if(position == 0) {
|
if(position == 0) {
|
||||||
recycler.scrollToPosition(0)
|
recycler.scrollToPosition(0)
|
||||||
|
@ -138,31 +196,40 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRemoved(position: Int, count: Int) {
|
override fun onRemoved(position: Int, count: Int) {
|
||||||
Log.d(TAG, "onRemoved")
|
Timber.d("onRemoved")
|
||||||
adapter.notifyItemRangeRemoved(position, count)
|
adapter.notifyItemRangeRemoved(position, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||||
Log.d(TAG, "onMoved")
|
Timber.d("onMoved")
|
||||||
adapter.notifyItemMoved(fromPosition, toPosition)
|
adapter.notifyItemMoved(fromPosition, toPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||||
Log.d(TAG, "onChanged")
|
Timber.d("onChanged")
|
||||||
adapter.notifyItemRangeChanged(position, count, payload)
|
adapter.notifyItemRangeChanged(position, count, payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val diffCallback = object : DiffUtil.ItemCallback<ChatMessageViewData>() {
|
private val diffCallback = object : DiffUtil.ItemCallback<ChatMessageViewData>() {
|
||||||
override fun areItemsTheSame(oldItem: ChatMessageViewData, newItem: ChatMessageViewData): Boolean {
|
override fun areItemsTheSame(
|
||||||
|
oldItem: ChatMessageViewData,
|
||||||
|
newItem: ChatMessageViewData
|
||||||
|
): Boolean {
|
||||||
return oldItem.getViewDataId() == newItem.getViewDataId()
|
return oldItem.getViewDataId() == newItem.getViewDataId()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: ChatMessageViewData, newItem: ChatMessageViewData): Boolean {
|
override fun areContentsTheSame(
|
||||||
|
oldItem: ChatMessageViewData,
|
||||||
|
newItem: ChatMessageViewData
|
||||||
|
): Boolean {
|
||||||
return false // Items are different always. It allows to refresh timestamp on every view holder update
|
return false // Items are different always. It allows to refresh timestamp on every view holder update
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getChangePayload(oldItem: ChatMessageViewData, newItem: ChatMessageViewData): Any? {
|
override fun getChangePayload(
|
||||||
|
oldItem: ChatMessageViewData,
|
||||||
|
newItem: ChatMessageViewData
|
||||||
|
): Any? {
|
||||||
return if(oldItem.deepEquals(newItem)) {
|
return if(oldItem.deepEquals(newItem)) {
|
||||||
//If items are equal - update timestamp only
|
//If items are equal - update timestamp only
|
||||||
listOf(ChatMessagesViewHolder.Key.KEY_CREATED)
|
listOf(ChatMessagesViewHolder.Key.KEY_CREATED)
|
||||||
|
@ -171,8 +238,10 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val differ = AsyncListDiffer(listUpdateCallback,
|
private val differ = AsyncListDiffer(
|
||||||
AsyncDifferConfig.Builder(diffCallback).build())
|
listUpdateCallback,
|
||||||
|
AsyncDifferConfig.Builder(diffCallback).build()
|
||||||
|
)
|
||||||
|
|
||||||
private val dataSource = object : TimelineAdapter.AdapterDataSource<ChatMessageViewData> {
|
private val dataSource = object : TimelineAdapter.AdapterDataSource<ChatMessageViewData> {
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
|
@ -190,6 +259,8 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
private lateinit var username: String
|
private lateinit var username: String
|
||||||
private lateinit var emojis: ArrayList<Emoji>
|
private lateinit var emojis: ArrayList<Emoji>
|
||||||
|
|
||||||
|
private lateinit var preferences: SharedPreferences
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -197,18 +268,23 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
throw Exception("No active account!")
|
throw Exception("No active account!")
|
||||||
}
|
}
|
||||||
|
|
||||||
chatId = intent.getStringExtra(ID) ?: throw IllegalArgumentException("Can't open ChatActivity without chatId")
|
chatId = intent.getStringExtra(ID)
|
||||||
avatarUrl = intent.getStringExtra(AVATAR_URL) ?: throw IllegalArgumentException("Can't open ChatActivity without avatarUrl")
|
?: throw IllegalArgumentException("Can't open ChatActivity without chatId")
|
||||||
displayName = intent.getStringExtra(DISPLAY_NAME) ?: throw IllegalArgumentException("Can't open ChatActivity without displayName")
|
avatarUrl = intent.getStringExtra(AVATAR_URL)
|
||||||
username = intent.getStringExtra(USERNAME) ?: throw IllegalArgumentException("Can't open ChatActivity without username")
|
?: throw IllegalArgumentException("Can't open ChatActivity without avatarUrl")
|
||||||
emojis = intent.getParcelableArrayListExtra<Emoji>(EMOJIS) ?: throw IllegalArgumentException("Can't open ChatActivity without emojis")
|
displayName = intent.getStringExtra(DISPLAY_NAME)
|
||||||
|
?: throw IllegalArgumentException("Can't open ChatActivity without displayName")
|
||||||
|
username = intent.getStringExtra(USERNAME)
|
||||||
|
?: throw IllegalArgumentException("Can't open ChatActivity without username")
|
||||||
|
emojis = intent.getParcelableArrayListExtra<Emoji>(EMOJIS)
|
||||||
|
?: throw IllegalArgumentException("Can't open ChatActivity without emojis")
|
||||||
|
|
||||||
setContentView(R.layout.activity_chat)
|
setContentView(R.layout.activity_chat)
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
subscribeToUpdates()
|
subscribeToUpdates()
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
viewModel.tryFetchStickers = preferences.getBoolean(PrefKeys.STICKERS, false)
|
viewModel.tryFetchStickers = preferences.getBoolean(PrefKeys.STICKERS, false)
|
||||||
viewModel.anonymizeNames = preferences.getBoolean(PrefKeys.ANONYMIZE_FILENAMES, false)
|
viewModel.anonymizeNames = preferences.getBoolean(PrefKeys.ANONYMIZE_FILENAMES, false)
|
||||||
|
|
||||||
|
@ -254,7 +330,12 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
setDisplayHomeAsUpEnabled(true)
|
setDisplayHomeAsUpEnabled(true)
|
||||||
setDisplayShowHomeEnabled(true)
|
setDisplayShowHomeEnabled(true)
|
||||||
}
|
}
|
||||||
loadAvatar(avatarUrl, chatAvatar, resources.getDimensionPixelSize(R.dimen.avatar_radius_24dp),true)
|
loadAvatar(
|
||||||
|
avatarUrl,
|
||||||
|
chatAvatar,
|
||||||
|
resources.getDimensionPixelSize(R.dimen.avatar_radius_24dp),
|
||||||
|
true
|
||||||
|
)
|
||||||
chatTitle.text = displayName.emojify(emojis, chatTitle, true)
|
chatTitle.text = displayName.emojify(emojis, chatTitle, true)
|
||||||
chatUsername.text = username
|
chatUsername.text = username
|
||||||
}
|
}
|
||||||
|
@ -306,7 +387,8 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
editText.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) }
|
editText.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) }
|
||||||
|
|
||||||
editText.setAdapter(
|
editText.setAdapter(
|
||||||
ComposeAutoCompleteAdapter(this))
|
ComposeAutoCompleteAdapter(this)
|
||||||
|
)
|
||||||
editText.setTokenizer(ComposeTokenizer())
|
editText.setTokenizer(ComposeTokenizer())
|
||||||
|
|
||||||
editText.setText(startingText)
|
editText.setText(startingText)
|
||||||
|
@ -321,7 +403,8 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
|
|
||||||
// work around Android platform bug -> https://issuetracker.google.com/issues/67102093
|
// work around Android platform bug -> https://issuetracker.google.com/issues/67102093
|
||||||
if(Build.VERSION.SDK_INT == Build.VERSION_CODES.O
|
if(Build.VERSION.SDK_INT == Build.VERSION_CODES.O
|
||||||
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) {
|
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1
|
||||||
|
) {
|
||||||
editText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
editText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,17 +425,22 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This is for the fancy keyboards which can insert images and stuff. */
|
/** This is for the fancy keyboards which can insert images and stuff. */
|
||||||
override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle?): Boolean {
|
override fun onCommitContent(
|
||||||
|
inputContentInfo: InputContentInfoCompat,
|
||||||
|
flags: Int,
|
||||||
|
opts: Bundle?
|
||||||
|
): Boolean {
|
||||||
// Verify the returned content's type is of the correct MIME type
|
// Verify the returned content's type is of the correct MIME type
|
||||||
val supported = inputContentInfo.description.hasMimeType("image/*")
|
val supported = inputContentInfo.description.hasMimeType("image/*")
|
||||||
|
|
||||||
if(supported) {
|
if(supported) {
|
||||||
val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
|
val lacksPermission =
|
||||||
|
(flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
|
||||||
if(lacksPermission) {
|
if(lacksPermission) {
|
||||||
try {
|
try {
|
||||||
inputContentInfo.requestPermission()
|
inputContentInfo.requestPermission()
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
Log.e(TAG, "InputContentInfoCompat#requestPermission() failed." + e.message)
|
Timber.e("InputContentInfoCompat#requestPermission() failed: ${e.message}")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,7 +470,11 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
|
|
||||||
enableSendButton()
|
enableSendButton()
|
||||||
enableButton(attachmentButton, notHaveMedia, notHaveMedia)
|
enableButton(attachmentButton, notHaveMedia, notHaveMedia)
|
||||||
enableButton(stickerButton, haveStickers && notHaveMedia, haveStickers && notHaveMedia)
|
enableButton(
|
||||||
|
stickerButton,
|
||||||
|
haveStickers && notHaveMedia,
|
||||||
|
haveStickers && notHaveMedia
|
||||||
|
)
|
||||||
|
|
||||||
if(!notHaveMedia) {
|
if(!notHaveMedia) {
|
||||||
val media = it[0]
|
val media = it[0]
|
||||||
|
@ -432,7 +524,11 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
|
|
||||||
private fun setEmojiList(emojiList: List<Emoji>?) {
|
private fun setEmojiList(emojiList: List<Emoji>?) {
|
||||||
if(emojiList != null) {
|
if(emojiList != null) {
|
||||||
emojiView.adapter = EmojiAdapter(emojiList, this@ChatActivity)
|
emojiView.adapter = EmojiAdapter(
|
||||||
|
emojiList,
|
||||||
|
this@ChatActivity,
|
||||||
|
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||||
|
)
|
||||||
enableButton(emojiButton, true, emojiList.isNotEmpty())
|
enableButton(emojiButton, true, emojiList.isNotEmpty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -492,10 +588,19 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
|
|
||||||
val textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
val textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary)
|
||||||
|
|
||||||
val cameraIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_camera_alt).apply { colorInt = textColor; sizeDp = 18 }
|
val cameraIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_camera_alt).apply {
|
||||||
actionPhotoTake.setCompoundDrawablesRelativeWithIntrinsicBounds(cameraIcon, null, null, null)
|
colorInt = textColor; sizeDp = 18
|
||||||
|
}
|
||||||
|
actionPhotoTake.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||||
|
cameraIcon,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
val imageIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_image).apply { colorInt = textColor; sizeDp = 18 }
|
val imageIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_image).apply {
|
||||||
|
colorInt = textColor; sizeDp = 18
|
||||||
|
}
|
||||||
actionPhotoPick.setCompoundDrawablesRelativeWithIntrinsicBounds(imageIcon, null, null, null)
|
actionPhotoPick.setCompoundDrawablesRelativeWithIntrinsicBounds(imageIcon, null, null, null)
|
||||||
|
|
||||||
actionPhotoTake.setOnClickListener { initiateCameraApp() }
|
actionPhotoTake.setOnClickListener { initiateCameraApp() }
|
||||||
|
@ -505,14 +610,16 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
private fun onSendClicked() {
|
private fun onSendClicked() {
|
||||||
val media = viewModel.getSingleMedia()
|
val media = viewModel.getSingleMedia()
|
||||||
|
|
||||||
serviceClient.sendChatMessage(MessageToSend(
|
serviceClient.sendChatMessage(
|
||||||
|
MessageToSend(
|
||||||
editText.text.toString(),
|
editText.text.toString(),
|
||||||
media?.id,
|
media?.id,
|
||||||
media?.uri?.toString(),
|
media?.uri?.toString(),
|
||||||
accountManager.activeAccount!!.id,
|
accountManager.activeAccount!!.id,
|
||||||
this.chatId,
|
this.chatId,
|
||||||
0
|
0
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
sending = true
|
sending = true
|
||||||
editText.text.clear()
|
editText.text.clear()
|
||||||
|
@ -535,7 +642,10 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
private fun showEmojis() {
|
private fun showEmojis() {
|
||||||
emojiView.adapter?.let {
|
emojiView.adapter?.let {
|
||||||
if(it.itemCount == 0) {
|
if(it.itemCount == 0) {
|
||||||
val errorMessage = getString(R.string.error_no_custom_emojis, accountManager.activeAccount!!.domain)
|
val errorMessage = getString(
|
||||||
|
R.string.error_no_custom_emojis,
|
||||||
|
accountManager.activeAccount!!.domain
|
||||||
|
)
|
||||||
Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
if(emojiBehavior.state == BottomSheetBehavior.STATE_HIDDEN || emojiBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
|
if(emojiBehavior.state == BottomSheetBehavior.STATE_HIDDEN || emojiBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||||
|
@ -575,9 +685,11 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue only if the File was successfully created
|
// Continue only if the File was successfully created
|
||||||
photoUploadUri = FileProvider.getUriForFile(this,
|
photoUploadUri = FileProvider.getUriForFile(
|
||||||
|
this,
|
||||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||||
photoFile)
|
photoFile
|
||||||
|
)
|
||||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUploadUri)
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUploadUri)
|
||||||
startActivityForResult(intent, MEDIA_TAKE_PHOTO_RESULT)
|
startActivityForResult(intent, MEDIA_TAKE_PHOTO_RESULT)
|
||||||
}
|
}
|
||||||
|
@ -589,10 +701,16 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
//Wait until bottom sheet is not collapsed and show next screen after
|
//Wait until bottom sheet is not collapsed and show next screen after
|
||||||
if(newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
if(newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||||
addMediaBehavior.removeBottomSheetCallback(this)
|
addMediaBehavior.removeBottomSheetCallback(this)
|
||||||
if (ContextCompat.checkSelfPermission(this@ChatActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
if(ContextCompat.checkSelfPermission(
|
||||||
ActivityCompat.requestPermissions(this@ChatActivity,
|
this@ChatActivity,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
this@ChatActivity,
|
||||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
|
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE)
|
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
initiateMediaPicking()
|
initiateMediaPicking()
|
||||||
}
|
}
|
||||||
|
@ -604,19 +722,24 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
override fun onRequestPermissionsResult(
|
||||||
grantResults: IntArray) {
|
requestCode: Int, permissions: Array<String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
if(requestCode == PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
|
if(requestCode == PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
|
||||||
if(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
initiateMediaPicking()
|
initiateMediaPicking()
|
||||||
} else {
|
} else {
|
||||||
val bar = Snackbar.make(activityChat, R.string.error_media_upload_permission,
|
val bar = Snackbar.make(
|
||||||
Snackbar.LENGTH_SHORT).apply {
|
activityChat, R.string.error_media_upload_permission,
|
||||||
|
Snackbar.LENGTH_SHORT
|
||||||
|
).apply {
|
||||||
|
|
||||||
}
|
}
|
||||||
bar.setAction(R.string.action_retry) { onMediaPick() }
|
bar.setAction(R.string.action_retry) { onMediaPick() }
|
||||||
//necessary so snackbar is shown over everything
|
//necessary so snackbar is shown over everything
|
||||||
bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
bar.view.elevation =
|
||||||
|
resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
||||||
bar.show()
|
bar.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -641,14 +764,21 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
|
|
||||||
private fun enableButton(button: ImageButton, clickable: Boolean, colorActive: Boolean) {
|
private fun enableButton(button: ImageButton, clickable: Boolean, colorActive: Boolean) {
|
||||||
button.isEnabled = clickable
|
button.isEnabled = clickable
|
||||||
ThemeUtils.setDrawableTint(this, button.drawable,
|
ThemeUtils.setDrawableTint(
|
||||||
|
this, button.drawable,
|
||||||
if(colorActive) android.R.attr.textColorTertiary
|
if(colorActive) android.R.attr.textColorTertiary
|
||||||
else R.attr.textColorDisabled)
|
else R.attr.textColorDisabled
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pickMedia(uri: Uri, contentInfoCompat: InputContentInfoCompat? = null, filename: String? = null) {
|
private fun pickMedia(
|
||||||
|
uri: Uri,
|
||||||
|
contentInfoCompat: InputContentInfoCompat? = null,
|
||||||
|
filename: String? = null
|
||||||
|
) {
|
||||||
withLifecycleContext {
|
withLifecycleContext {
|
||||||
viewModel.pickMedia(uri, filename ?: uri.toFileName(contentResolver)).observe { exceptionOrItem ->
|
viewModel.pickMedia(uri, filename ?: uri.toFileName(contentResolver))
|
||||||
|
.observe { exceptionOrItem ->
|
||||||
|
|
||||||
contentInfoCompat?.releasePermission()
|
contentInfoCompat?.releasePermission()
|
||||||
|
|
||||||
|
@ -667,7 +797,7 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
R.string.error_media_upload_image_or_video
|
R.string.error_media_upload_image_or_video
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Log.d(TAG, "That file could not be opened", exception)
|
Timber.e("That file could not be opened", exception)
|
||||||
R.string.error_media_upload_opening
|
R.string.error_media_upload_opening
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -721,7 +851,14 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
val topId = msgs.first { it.isRight() }.asRight().id
|
val topId = msgs.first { it.isRight() }.asRight().id
|
||||||
chatsRepo.getChatMessages(chatId, topId, null, null, LOAD_AT_ONCE, TimelineRequestMode.NETWORK)
|
chatsRepo.getChatMessages(
|
||||||
|
chatId,
|
||||||
|
topId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
LOAD_AT_ONCE,
|
||||||
|
TimelineRequestMode.NETWORK
|
||||||
|
)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
||||||
.subscribe({ messages ->
|
.subscribe({ messages ->
|
||||||
|
@ -776,9 +913,11 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendFetchMessagesRequest(maxId: String?, sinceId: String?,
|
private fun sendFetchMessagesRequest(
|
||||||
|
maxId: String?, sinceId: String?,
|
||||||
sinceIdMinusOne: String?,
|
sinceIdMinusOne: String?,
|
||||||
fetchEnd: FetchEnd, pos: Int) {
|
fetchEnd: FetchEnd, pos: Int
|
||||||
|
) {
|
||||||
// allow getting old statuses/fallbacks for network only for for bottom loading
|
// allow getting old statuses/fallbacks for network only for for bottom loading
|
||||||
val mode = if(fetchEnd == FetchEnd.BOTTOM) {
|
val mode = if(fetchEnd == FetchEnd.BOTTOM) {
|
||||||
TimelineRequestMode.ANY
|
TimelineRequestMode.ANY
|
||||||
|
@ -793,11 +932,14 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateAdapter() {
|
private fun updateAdapter() {
|
||||||
Log.d(TAG, "updateAdapter")
|
Timber.d("updateAdapter")
|
||||||
differ.submitList(msgs.pairedCopy)
|
differ.submitList(msgs.pairedCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMessages(newMsgs: MutableList<ChatMesssageOrPlaceholder>, fullFetch: Boolean) {
|
private fun updateMessages(
|
||||||
|
newMsgs: MutableList<ChatMesssageOrPlaceholder>,
|
||||||
|
fullFetch: Boolean
|
||||||
|
) {
|
||||||
if(newMsgs.isEmpty()) {
|
if(newMsgs.isEmpty()) {
|
||||||
updateAdapter()
|
updateAdapter()
|
||||||
return
|
return
|
||||||
|
@ -836,8 +978,10 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replacePlaceholderWithMessages(newMsgs: MutableList<ChatMesssageOrPlaceholder>,
|
private fun replacePlaceholderWithMessages(
|
||||||
fullFetch: Boolean, pos: Int) {
|
newMsgs: MutableList<ChatMesssageOrPlaceholder>,
|
||||||
|
fullFetch: Boolean, pos: Int
|
||||||
|
) {
|
||||||
val placeholder = msgs[pos]
|
val placeholder = msgs[pos]
|
||||||
if(placeholder.isLeft()) {
|
if(placeholder.isLeft()) {
|
||||||
msgs.removeAt(pos)
|
msgs.removeAt(pos)
|
||||||
|
@ -869,8 +1013,10 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFetchTimelineSuccess(msgs: MutableList<ChatMesssageOrPlaceholder>,
|
private fun onFetchTimelineSuccess(
|
||||||
fetchEnd: FetchEnd, pos: Int) {
|
msgs: MutableList<ChatMesssageOrPlaceholder>,
|
||||||
|
fetchEnd: FetchEnd, pos: Int
|
||||||
|
) {
|
||||||
|
|
||||||
// We filled the hole (or reached the end) if the server returned less statuses than we
|
// We filled the hole (or reached the end) if the server returned less statuses than we
|
||||||
// we asked for.
|
// we asked for.
|
||||||
|
@ -886,9 +1032,9 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
Log.d(TAG, "Marked new messages as read up to ${msgs[last].asRight().id}")
|
Timber.d("Marked new messages as read up to ${msgs[last].asRight().id}")
|
||||||
}, {
|
}, {
|
||||||
Log.d(TAG, "Failed to mark messages as read", it)
|
Timber.e("Failed to mark messages as read", it)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
FetchEnd.MIDDLE -> {
|
FetchEnd.MIDDLE -> {
|
||||||
|
@ -964,7 +1110,7 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.e(TAG, "Fetch Failure: " + exception.message)
|
Timber.e("Fetch Failure: " + exception.message)
|
||||||
updateBottomLoadingState(fetchEnd)
|
updateBottomLoadingState(fetchEnd)
|
||||||
progressBar.visibility = View.GONE
|
progressBar.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
@ -981,25 +1127,28 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
val fromChat = msgs[position - 1].asRightOrNull()
|
val fromChat = msgs[position - 1].asRightOrNull()
|
||||||
val toChat = msgs[position + 1].asRightOrNull()
|
val toChat = msgs[position + 1].asRightOrNull()
|
||||||
if(fromChat == null || toChat == null) {
|
if(fromChat == null || toChat == null) {
|
||||||
Log.e(TAG, "Failed to load more at $position, wrong placeholder position")
|
Timber.e("Failed to load more at $position, wrong placeholder position")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val maxMinusOne = if (msgs.size > position + 1 && msgs[position + 2].isRight()) msgs[position + 1].asRight().id else null
|
val maxMinusOne =
|
||||||
sendFetchMessagesRequest(fromChat.id, toChat.id, maxMinusOne,
|
if(msgs.size > position + 1 && msgs[position + 2].isRight()) msgs[position + 1].asRight().id else null
|
||||||
FetchEnd.MIDDLE, position)
|
sendFetchMessagesRequest(
|
||||||
|
fromChat.id, toChat.id, maxMinusOne,
|
||||||
|
FetchEnd.MIDDLE, position
|
||||||
|
)
|
||||||
|
|
||||||
val (id) = msgs[position].asLeft()
|
val (id) = msgs[position].asLeft()
|
||||||
val newViewData = ChatMessageViewData.Placeholder(id, true)
|
val newViewData = ChatMessageViewData.Placeholder(id, true)
|
||||||
msgs.setPairedItem(position, newViewData)
|
msgs.setPairedItem(position, newViewData)
|
||||||
updateAdapter()
|
updateAdapter()
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "error loading more")
|
Timber.e("error loading more")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||||
Log.d(TAG, event.toString())
|
Timber.d(event.toString())
|
||||||
if(event.action == KeyEvent.ACTION_DOWN) {
|
if(event.action == KeyEvent.ACTION_DOWN) {
|
||||||
if(event.isCtrlPressed) {
|
if(event.isCtrlPressed) {
|
||||||
if(keyCode == KeyEvent.KEYCODE_ENTER) {
|
if(keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||||
|
@ -1022,7 +1171,8 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
// Acting like a teen: deliberately ignoring parent.
|
// Acting like a teen: deliberately ignoring parent.
|
||||||
if(addMediaBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
|
if(addMediaBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
|
||||||
emojiBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
|
emojiBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
|
||||||
stickerBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
|
stickerBehavior.state != BottomSheetBehavior.STATE_HIDDEN
|
||||||
|
) {
|
||||||
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
|
@ -1053,7 +1203,7 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
* Auto dispose observable on pause
|
* Auto dispose observable on pause
|
||||||
*/
|
*/
|
||||||
private fun startUpdateTimestamp() {
|
private fun startUpdateTimestamp() {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false)
|
val useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false)
|
||||||
if(!useAbsoluteTime) {
|
if(!useAbsoluteTime) {
|
||||||
Observable.interval(1, TimeUnit.MINUTES)
|
Observable.interval(1, TimeUnit.MINUTES)
|
||||||
|
@ -1086,7 +1236,8 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
if(view != null) {
|
if(view != null) {
|
||||||
val url = attachment.url
|
val url = attachment.url
|
||||||
ViewCompat.setTransitionName(view, url)
|
ViewCompat.setTransitionName(view, url)
|
||||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this, view, url)
|
val options =
|
||||||
|
ActivityOptionsCompat.makeSceneTransitionAnimation(this, view, url)
|
||||||
|
|
||||||
startActivity(intent, options.toBundle())
|
startActivity(intent, options.toBundle())
|
||||||
} else {
|
} else {
|
||||||
|
@ -1111,7 +1262,10 @@ class ChatActivity: BottomSheetActivity(),
|
||||||
intent.putExtra(ID, chat.id)
|
intent.putExtra(ID, chat.id)
|
||||||
intent.putExtra(AVATAR_URL, chat.account.avatar)
|
intent.putExtra(AVATAR_URL, chat.account.avatar)
|
||||||
intent.putExtra(DISPLAY_NAME, chat.account.displayName ?: chat.account.localUsername)
|
intent.putExtra(DISPLAY_NAME, chat.account.displayName ?: chat.account.localUsername)
|
||||||
intent.putParcelableArrayListExtra(EMOJIS, ArrayList(chat.account.emojis ?: emptyList<Emoji>()))
|
intent.putParcelableArrayListExtra(
|
||||||
|
EMOJIS,
|
||||||
|
ArrayList(chat.account.emojis ?: emptyList<Emoji>())
|
||||||
|
)
|
||||||
intent.putExtra(USERNAME, chat.account.username)
|
intent.putExtra(USERNAME, chat.account.username)
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,9 +167,11 @@ class ComposeActivity : BaseActivity(),
|
||||||
private val maxUploadMediaNumber = 4
|
private val maxUploadMediaNumber = 4
|
||||||
private var mediaCount = 0
|
private var mediaCount = 0
|
||||||
|
|
||||||
|
private lateinit var preferences: SharedPreferences
|
||||||
|
|
||||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
|
val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
|
||||||
if(theme == "black") {
|
if(theme == "black") {
|
||||||
setTheme(R.style.TuskyDialogActivityBlackTheme)
|
setTheme(R.style.TuskyDialogActivityBlackTheme)
|
||||||
|
@ -1447,7 +1449,11 @@ class ComposeActivity : BaseActivity(),
|
||||||
|
|
||||||
private fun setEmojiList(emojiList: List<Emoji>?) {
|
private fun setEmojiList(emojiList: List<Emoji>?) {
|
||||||
if(emojiList != null) {
|
if(emojiList != null) {
|
||||||
binding.emojiView.adapter = EmojiAdapter(emojiList, this@ComposeActivity)
|
binding.emojiView.adapter = EmojiAdapter(
|
||||||
|
emojiList,
|
||||||
|
this@ComposeActivity,
|
||||||
|
preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||||
|
)
|
||||||
enableButton(binding.composeEmojiButton, true, emojiList.isNotEmpty())
|
enableButton(binding.composeEmojiButton, true, emojiList.isNotEmpty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue