mirror of
https://github.com/SimpleMobileTools/Simple-SMS-Messenger.git
synced 2025-02-17 04:00:35 +01:00
Merge pull request #488 from Naveen3Singh/recyclerview_improvements
Recyclerview related improvements
This commit is contained in:
commit
322ccbd76a
@ -63,7 +63,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.github.SimpleMobileTools:Simple-Commons:af11ea2e30'
|
implementation 'com.github.SimpleMobileTools:Simple-Commons:8a01bdee8c'
|
||||||
implementation 'org.greenrobot:eventbus:3.3.1'
|
implementation 'org.greenrobot:eventbus:3.3.1'
|
||||||
implementation 'com.github.tibbi:IndicatorFastScroll:4524cd0b61'
|
implementation 'com.github.tibbi:IndicatorFastScroll:4524cd0b61'
|
||||||
implementation 'com.github.tibbi:android-smsmms:3581774c39'
|
implementation 'com.github.tibbi:android-smsmms:3581774c39'
|
||||||
|
@ -257,7 +257,7 @@ class MainActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cachedConversations.forEach { cachedConv ->
|
cachedConversations.forEach { cachedConv ->
|
||||||
val conv = conversations.find { it.threadId == cachedConv.threadId && !cachedConv.areContentsTheSame(it) }
|
val conv = conversations.find { it.threadId == cachedConv.threadId && !Conversation.areContentsTheSame(cachedConv, it) }
|
||||||
if (conv != null) {
|
if (conv != null) {
|
||||||
val conversation = conv.copy(date = maxOf(cachedConv.date, conv.date))
|
val conversation = conv.copy(date = maxOf(cachedConv.date, conv.date))
|
||||||
conversationsDB.insertOrUpdate(conversation)
|
conversationsDB.insertOrUpdate(conversation)
|
||||||
@ -299,7 +299,7 @@ class MainActivity : SimpleActivity() {
|
|||||||
val currAdapter = conversations_list.adapter
|
val currAdapter = conversations_list.adapter
|
||||||
if (currAdapter == null) {
|
if (currAdapter == null) {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
ConversationsAdapter(this, sortedConversations, conversations_list) {
|
ConversationsAdapter(this, conversations_list) {
|
||||||
Intent(this, ThreadActivity::class.java).apply {
|
Intent(this, ThreadActivity::class.java).apply {
|
||||||
val conversation = it as Conversation
|
val conversation = it as Conversation
|
||||||
putExtra(THREAD_ID, conversation.threadId)
|
putExtra(THREAD_ID, conversation.threadId)
|
||||||
@ -308,6 +308,7 @@ class MainActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
conversations_list.adapter = this
|
conversations_list.adapter = this
|
||||||
|
updateConversations(sortedConversations)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (areSystemAnimationsEnabled) {
|
if (areSystemAnimationsEnabled) {
|
||||||
@ -315,12 +316,13 @@ class MainActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
(currAdapter as ConversationsAdapter).updateConversations(sortedConversations)
|
(currAdapter as ConversationsAdapter).updateConversations(sortedConversations) {
|
||||||
if (currAdapter.conversations.isEmpty()) {
|
if (currAdapter.currentList.isEmpty()) {
|
||||||
conversations_fastscroller.beGone()
|
conversations_fastscroller.beGone()
|
||||||
no_conversations_placeholder.text = getString(R.string.no_conversations_found)
|
no_conversations_placeholder.text = getString(R.string.no_conversations_found)
|
||||||
no_conversations_placeholder.beVisible()
|
no_conversations_placeholder.beVisible()
|
||||||
no_conversations_placeholder_2.beVisible()
|
no_conversations_placeholder_2.beVisible()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog
|
|||||||
import com.simplemobiletools.smsmessenger.extensions.*
|
import com.simplemobiletools.smsmessenger.extensions.*
|
||||||
import com.simplemobiletools.smsmessenger.helpers.*
|
import com.simplemobiletools.smsmessenger.helpers.*
|
||||||
import com.simplemobiletools.smsmessenger.models.*
|
import com.simplemobiletools.smsmessenger.models.*
|
||||||
|
import com.simplemobiletools.smsmessenger.models.ThreadItem.*
|
||||||
import kotlinx.android.synthetic.main.activity_thread.*
|
import kotlinx.android.synthetic.main.activity_thread.*
|
||||||
import kotlinx.android.synthetic.main.item_selected_contact.view.*
|
import kotlinx.android.synthetic.main.item_selected_contact.view.*
|
||||||
import kotlinx.android.synthetic.main.layout_attachment_picker.*
|
import kotlinx.android.synthetic.main.layout_attachment_picker.*
|
||||||
@ -116,8 +117,8 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
|
|
||||||
bus = EventBus.getDefault()
|
bus = EventBus.getDefault()
|
||||||
bus!!.register(this)
|
bus!!.register(this)
|
||||||
handlePermission(PERMISSION_READ_PHONE_STATE) {
|
handlePermission(PERMISSION_READ_PHONE_STATE) { granted ->
|
||||||
if (it) {
|
if (granted) {
|
||||||
setupButtons()
|
setupButtons()
|
||||||
setupCachedMessages {
|
setupCachedMessages {
|
||||||
val searchedMessageId = intent.getLongExtra(SEARCHED_MESSAGE_ID, -1L)
|
val searchedMessageId = intent.getLongExtra(SEARCHED_MESSAGE_ID, -1L)
|
||||||
@ -263,7 +264,7 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupThread() {
|
private fun setupThread() {
|
||||||
val privateCursor = getMyContactsCursor(false, true)
|
val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
|
privateContacts = MyContactsContentProvider.getSimpleContacts(this, privateCursor)
|
||||||
|
|
||||||
@ -329,33 +330,40 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getOrCreateThreadAdapter(): ThreadAdapter {
|
||||||
|
var currAdapter = thread_messages_list.adapter
|
||||||
|
if (currAdapter == null) {
|
||||||
|
currAdapter = ThreadAdapter(
|
||||||
|
activity = this,
|
||||||
|
recyclerView = thread_messages_list,
|
||||||
|
itemClick = { handleItemClick(it) },
|
||||||
|
deleteMessages = { deleteMessages(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
thread_messages_list.adapter = currAdapter
|
||||||
|
thread_messages_list.endlessScrollListener = object : MyRecyclerView.EndlessScrollListener {
|
||||||
|
override fun updateBottom() {}
|
||||||
|
|
||||||
|
override fun updateTop() {
|
||||||
|
fetchNextMessages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currAdapter as ThreadAdapter
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupAdapter() {
|
private fun setupAdapter() {
|
||||||
threadItems = getThreadItems()
|
threadItems = getThreadItems()
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
refreshMenuItems()
|
refreshMenuItems()
|
||||||
|
getOrCreateThreadAdapter().apply {
|
||||||
val currAdapter = thread_messages_list.adapter
|
val scrollPosition = if (currentList.lastOrNull() != threadItems.lastOrNull()) {
|
||||||
if (currAdapter == null) {
|
threadItems.lastIndex
|
||||||
ThreadAdapter(
|
} else {
|
||||||
activity = this,
|
-1
|
||||||
messages = threadItems,
|
|
||||||
recyclerView = thread_messages_list,
|
|
||||||
itemClick = { handleItemClick(it) },
|
|
||||||
onThreadIdUpdate = { threadId = it }
|
|
||||||
).apply {
|
|
||||||
thread_messages_list.adapter = this
|
|
||||||
}
|
}
|
||||||
|
updateMessages(threadItems, scrollPosition)
|
||||||
thread_messages_list.endlessScrollListener = object : MyRecyclerView.EndlessScrollListener {
|
|
||||||
override fun updateBottom() {}
|
|
||||||
|
|
||||||
override fun updateTop() {
|
|
||||||
fetchNextMessages()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(currAdapter as ThreadAdapter).updateMessages(threadItems)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,6 +395,13 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun scrollToBottom() {
|
||||||
|
val position = getOrCreateThreadAdapter().currentList.lastIndex
|
||||||
|
if (position >= 0) {
|
||||||
|
thread_messages_list.smoothScrollToPosition(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleItemClick(any: Any) {
|
private fun handleItemClick(any: Any) {
|
||||||
when {
|
when {
|
||||||
any is Message && any.isScheduled -> showScheduledMessageInfo(any)
|
any is Message && any.isScheduled -> showScheduledMessageInfo(any)
|
||||||
@ -394,8 +409,53 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deleteMessages(messagesToRemove: List<Message>) {
|
||||||
|
val deletePosition = threadItems.indexOf(messagesToRemove.first())
|
||||||
|
messages.removeAll(messagesToRemove.toSet())
|
||||||
|
threadItems = getThreadItems()
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
if (messages.isEmpty()) {
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
getOrCreateThreadAdapter().apply {
|
||||||
|
updateMessages(threadItems, scrollPosition = deletePosition)
|
||||||
|
finishActMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messagesToRemove.forEach { message ->
|
||||||
|
val messageId = message.id
|
||||||
|
if (message.isScheduled) {
|
||||||
|
deleteScheduledMessage(messageId)
|
||||||
|
cancelScheduleSendPendingIntent(messageId)
|
||||||
|
} else {
|
||||||
|
deleteMessage(messageId, message.isMMS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateLastConversationMessage(threadId)
|
||||||
|
|
||||||
|
// move all scheduled messages to a temporary thread when there are no real messages left
|
||||||
|
if (messages.isNotEmpty() && messages.all { it.isScheduled }) {
|
||||||
|
val scheduledMessage = messages.last()
|
||||||
|
val fakeThreadId = generateRandomId()
|
||||||
|
createTemporaryThread(scheduledMessage, fakeThreadId)
|
||||||
|
updateScheduledMessagesThreadId(messages, fakeThreadId)
|
||||||
|
threadId = fakeThreadId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun fetchNextMessages() {
|
private fun fetchNextMessages() {
|
||||||
if (messages.isEmpty() || allMessagesFetched || loadingOlderMessages) {
|
if (messages.isEmpty() || allMessagesFetched || loadingOlderMessages) {
|
||||||
|
if (allMessagesFetched) {
|
||||||
|
getOrCreateThreadAdapter().apply {
|
||||||
|
val newList = currentList.toMutableList().apply {
|
||||||
|
removeAll { it is ThreadLoading }
|
||||||
|
}
|
||||||
|
updateMessages(newMessages = newList as ArrayList<ThreadItem>, scrollPosition = 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,16 +474,13 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
.filter { message -> !messages.contains(message) }
|
.filter { message -> !messages.contains(message) }
|
||||||
|
|
||||||
messages.addAll(0, olderMessages)
|
messages.addAll(0, olderMessages)
|
||||||
threadItems = getThreadItems()
|
|
||||||
|
|
||||||
allMessagesFetched = olderMessages.size < MESSAGES_LIMIT || olderMessages.isEmpty()
|
allMessagesFetched = olderMessages.size < MESSAGES_LIMIT || olderMessages.isEmpty()
|
||||||
|
threadItems = getThreadItems()
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
loadingOlderMessages = false
|
loadingOlderMessages = false
|
||||||
val itemAtRefreshIndex = threadItems.indexOfFirst { it == firstItem }
|
val itemAtRefreshIndex = threadItems.indexOfFirst { it == firstItem }
|
||||||
(thread_messages_list.adapter as ThreadAdapter).apply {
|
getOrCreateThreadAdapter().updateMessages(threadItems, itemAtRefreshIndex)
|
||||||
updateMessages(threadItems, itemAtRefreshIndex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,7 +490,9 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
val textColor = getProperTextColor()
|
val textColor = getProperTextColor()
|
||||||
thread_send_message.apply {
|
thread_send_message.apply {
|
||||||
setTextColor(textColor)
|
setTextColor(textColor)
|
||||||
compoundDrawables.forEach { it?.applyColorFilter(textColor) }
|
compoundDrawables.forEach {
|
||||||
|
it?.applyColorFilter(textColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
confirm_manage_contacts.applyColorFilter(textColor)
|
confirm_manage_contacts.applyColorFilter(textColor)
|
||||||
@ -460,7 +519,11 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
thread_send_message.isClickable = false
|
thread_send_message.isClickable = false
|
||||||
thread_type_message.onTextChangeListener {
|
thread_type_message.onTextChangeListener {
|
||||||
checkSendMessageAvailability()
|
checkSendMessageAvailability()
|
||||||
val messageString = if (config.useSimpleCharacters) it.normalizeString() else it
|
val messageString = if (config.useSimpleCharacters) {
|
||||||
|
it.normalizeString()
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
val messageLength = SmsMessage.calculateLength(messageString, false)
|
val messageLength = SmsMessage.calculateLength(messageString, false)
|
||||||
thread_character_counter.text = "${messageLength[2]}/${messageLength[0]}"
|
thread_character_counter.text = "${messageLength[2]}/${messageLength[0]}"
|
||||||
}
|
}
|
||||||
@ -819,6 +882,11 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
bus?.post(Events.RefreshMessages())
|
bus?.post(Events.RefreshMessages())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!allMessagesFetched && messages.size >= MESSAGES_LIMIT) {
|
||||||
|
val threadLoading = ThreadLoading(generateRandomId())
|
||||||
|
items.add(0, threadLoading)
|
||||||
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -988,6 +1056,7 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
showErrorToast(getString(R.string.unknown_error_occurred))
|
showErrorToast(getString(R.string.unknown_error_occurred))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
scrollToBottom()
|
||||||
|
|
||||||
text = removeDiacriticsIfNeeded(text)
|
text = removeDiacriticsIfNeeded(text)
|
||||||
|
|
||||||
@ -1017,22 +1086,18 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
threadId = message.threadId
|
threadId = message.threadId
|
||||||
createTemporaryThread(message, message.threadId)
|
createTemporaryThread(message, message.threadId)
|
||||||
}
|
}
|
||||||
messagesDB.insertOrUpdate(message)
|
|
||||||
val conversation = conversationsDB.getConversationWithThreadId(threadId)
|
val conversation = conversationsDB.getConversationWithThreadId(threadId)
|
||||||
if (conversation != null) {
|
if (conversation != null) {
|
||||||
val nowSeconds = (System.currentTimeMillis() / 1000).toInt()
|
val nowSeconds = (System.currentTimeMillis() / 1000).toInt()
|
||||||
conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds))
|
conversationsDB.insertOrUpdate(conversation.copy(date = nowSeconds, snippet = message.body))
|
||||||
}
|
}
|
||||||
scheduleMessage(message)
|
scheduleMessage(message)
|
||||||
|
insertOrUpdateMessage(message)
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
clearCurrentMessage()
|
clearCurrentMessage()
|
||||||
hideScheduleSendUi()
|
hideScheduleSendUi()
|
||||||
scheduledMessage = null
|
scheduledMessage = null
|
||||||
|
|
||||||
if (!refreshedSinceSent) {
|
|
||||||
refreshMessages()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -1047,11 +1112,16 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
try {
|
try {
|
||||||
refreshedSinceSent = false
|
refreshedSinceSent = false
|
||||||
sendMessage(text, addresses, subscriptionId, attachments)
|
sendMessage(text, addresses, subscriptionId, attachments)
|
||||||
|
ensureBackgroundThread {
|
||||||
|
val messageIds = messages.map { it.id }
|
||||||
|
val message = getMessages(threadId, getImageResolutions = true, limit = 1).firstOrNull { it.id !in messageIds }
|
||||||
|
if (message != null) {
|
||||||
|
maybeUpdateMessageSubId(message)
|
||||||
|
insertOrUpdateMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
clearCurrentMessage()
|
clearCurrentMessage()
|
||||||
|
|
||||||
if (!refreshedSinceSent) {
|
|
||||||
refreshMessages()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showErrorToast(e)
|
showErrorToast(e)
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
@ -1065,6 +1135,24 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
checkSendMessageAvailability()
|
checkSendMessageAvailability()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun insertOrUpdateMessage(message: Message) {
|
||||||
|
if (messages.map { it.id }.contains(message.id)) {
|
||||||
|
val messageToReplace = messages.find { it.id == message.id }
|
||||||
|
messages[messages.indexOf(messageToReplace)] = message
|
||||||
|
} else {
|
||||||
|
messages.add(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
val newItems = getThreadItems()
|
||||||
|
runOnUiThread {
|
||||||
|
getOrCreateThreadAdapter().updateMessages(newItems)
|
||||||
|
if (!refreshedSinceSent) {
|
||||||
|
refreshMessages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messagesDB.insertOrUpdate(message)
|
||||||
|
}
|
||||||
|
|
||||||
// show selected contacts, properly split to new lines when appropriate
|
// show selected contacts, properly split to new lines when appropriate
|
||||||
// based on https://stackoverflow.com/a/13505029/1967672
|
// based on https://stackoverflow.com/a/13505029/1967672
|
||||||
private fun showSelectedContact(views: ArrayList<View>) {
|
private fun showSelectedContact(views: ArrayList<View>) {
|
||||||
@ -1191,7 +1279,6 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
@Subscribe(threadMode = ThreadMode.ASYNC)
|
@Subscribe(threadMode = ThreadMode.ASYNC)
|
||||||
fun refreshMessages(event: Events.RefreshMessages) {
|
fun refreshMessages(event: Events.RefreshMessages) {
|
||||||
refreshedSinceSent = true
|
refreshedSinceSent = true
|
||||||
@ -1202,29 +1289,24 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
notificationManager.cancel(threadId.hashCode())
|
notificationManager.cancel(threadId.hashCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val lastMaxId = messages.filterNot { it.isScheduled }.maxByOrNull { it.id }?.id ?: 0L
|
||||||
val newThreadId = getThreadId(participants.getAddresses().toSet())
|
val newThreadId = getThreadId(participants.getAddresses().toSet())
|
||||||
val newMessages = getMessages(newThreadId, false)
|
val newMessages = getMessages(newThreadId, getImageResolutions = true, includeScheduledMessages = false)
|
||||||
messages = if (messages.all { it.isScheduled } && newMessages.isNotEmpty()) {
|
|
||||||
threadId = newThreadId
|
if (messages.isNotEmpty() && messages.all { it.isScheduled } && newMessages.isNotEmpty()) {
|
||||||
// update scheduled messages with real thread id
|
// update scheduled messages with real thread id
|
||||||
updateScheduledMessagesThreadId(messages, newThreadId)
|
threadId = newThreadId
|
||||||
getMessages(newThreadId, true)
|
updateScheduledMessagesThreadId(messages = messages.filter { it.threadId != threadId }, threadId)
|
||||||
} else {
|
|
||||||
getMessages(threadId, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastMaxId = messages.filterNot { it.isScheduled }.maxByOrNull { it.id }?.id ?: 0L
|
messages = newMessages.apply {
|
||||||
|
val scheduledMessages = messagesDB.getScheduledThreadMessages(threadId)
|
||||||
messages.filter { !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage ->
|
.filterNot { it.isScheduled && it.millis() < System.currentTimeMillis() }
|
||||||
// subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually
|
addAll(scheduledMessages)
|
||||||
if ((subscriptionManagerCompat().activeSubscriptionInfoList?.size ?: 0) > 1) {
|
}
|
||||||
val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId
|
|
||||||
if (subscriptionId != null) {
|
|
||||||
updateMessageSubscriptionId(latestMessage.id, subscriptionId)
|
|
||||||
latestMessage.subscriptionId = subscriptionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
messages.filter { !it.isScheduled && !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage ->
|
||||||
|
maybeUpdateMessageSubId(latestMessage)
|
||||||
messagesDB.insertOrIgnore(latestMessage)
|
messagesDB.insertOrIgnore(latestMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1234,6 +1316,18 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private fun maybeUpdateMessageSubId(message: Message) {
|
||||||
|
// subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually
|
||||||
|
if ((subscriptionManagerCompat().activeSubscriptionInfoList?.size ?: 0) > 1) {
|
||||||
|
val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId
|
||||||
|
if (subscriptionId != null) {
|
||||||
|
updateMessageSubscriptionId(message.id, subscriptionId)
|
||||||
|
message.subscriptionId = subscriptionId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun isMmsMessage(text: String): Boolean {
|
private fun isMmsMessage(text: String): Boolean {
|
||||||
val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS
|
val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS
|
||||||
val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS
|
val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS
|
||||||
@ -1256,11 +1350,12 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
RadioItem(TYPE_SEND, getString(R.string.send_now)),
|
RadioItem(TYPE_SEND, getString(R.string.send_now)),
|
||||||
RadioItem(TYPE_DELETE, getString(R.string.delete))
|
RadioItem(TYPE_DELETE, getString(R.string.delete))
|
||||||
)
|
)
|
||||||
RadioGroupDialog(activity = this, items = items, titleId = R.string.scheduled_message) {
|
RadioGroupDialog(activity = this, items = items, titleId = R.string.scheduled_message) { any ->
|
||||||
when (it as Int) {
|
when (any as Int) {
|
||||||
TYPE_DELETE -> cancelScheduledMessageAndRefresh(message.id)
|
TYPE_DELETE -> cancelScheduledMessageAndRefresh(message.id)
|
||||||
TYPE_EDIT -> editScheduledMessage(message)
|
TYPE_EDIT -> editScheduledMessage(message)
|
||||||
TYPE_SEND -> {
|
TYPE_SEND -> {
|
||||||
|
messages.removeAll { message.id == it.id }
|
||||||
extractAttachments(message)
|
extractAttachments(message)
|
||||||
sendNormalMessage(message.body, message.subscriptionId)
|
sendNormalMessage(message.body, message.subscriptionId)
|
||||||
cancelScheduledMessageAndRefresh(message.id)
|
cancelScheduledMessageAndRefresh(message.id)
|
||||||
|
@ -2,15 +2,18 @@ package com.simplemobiletools.smsmessenger.adapters
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.os.Parcelable
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||||
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
|
import com.simplemobiletools.commons.adapters.MyRecyclerViewListAdapter
|
||||||
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
|
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
|
||||||
import com.simplemobiletools.commons.dialogs.FeatureLockedDialog
|
import com.simplemobiletools.commons.dialogs.FeatureLockedDialog
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
@ -27,14 +30,23 @@ import com.simplemobiletools.smsmessenger.models.Conversation
|
|||||||
import kotlinx.android.synthetic.main.item_conversation.view.*
|
import kotlinx.android.synthetic.main.item_conversation.view.*
|
||||||
|
|
||||||
class ConversationsAdapter(
|
class ConversationsAdapter(
|
||||||
activity: SimpleActivity, var conversations: ArrayList<Conversation>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit
|
activity: SimpleActivity, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit
|
||||||
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate {
|
) : MyRecyclerViewListAdapter<Conversation>(activity, recyclerView, ConversationDiffCallback(), itemClick), RecyclerViewFastScroller.OnPopupTextUpdate {
|
||||||
private var fontSize = activity.getTextSize()
|
private var fontSize = activity.getTextSize()
|
||||||
private var drafts = HashMap<Long, String?>()
|
private var drafts = HashMap<Long, String?>()
|
||||||
|
|
||||||
|
private var recyclerViewState: Parcelable? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setupDragListener(true)
|
setupDragListener(true)
|
||||||
fetchDrafts(drafts)
|
fetchDrafts(drafts)
|
||||||
|
setHasStableIds(true)
|
||||||
|
|
||||||
|
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onChanged() = restoreRecyclerViewState()
|
||||||
|
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) = restoreRecyclerViewState()
|
||||||
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) = restoreRecyclerViewState()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getActionMenuId() = R.menu.cab_conversations
|
override fun getActionMenuId() = R.menu.cab_conversations
|
||||||
@ -73,13 +85,13 @@ class ConversationsAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSelectableItemCount() = conversations.size
|
override fun getSelectableItemCount() = itemCount
|
||||||
|
|
||||||
override fun getIsItemSelectable(position: Int) = true
|
override fun getIsItemSelectable(position: Int) = true
|
||||||
|
|
||||||
override fun getItemSelectionKey(position: Int) = conversations.getOrNull(position)?.hashCode()
|
override fun getItemSelectionKey(position: Int) = currentList.getOrNull(position)?.hashCode()
|
||||||
|
|
||||||
override fun getItemKeyPosition(key: Int) = conversations.indexOfFirst { it.hashCode() == key }
|
override fun getItemKeyPosition(key: Int) = currentList.indexOfFirst { it.hashCode() == key }
|
||||||
|
|
||||||
override fun onActionModeCreated() {}
|
override fun onActionModeCreated() {}
|
||||||
|
|
||||||
@ -88,14 +100,14 @@ class ConversationsAdapter(
|
|||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_conversation, parent)
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_conversation, parent)
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val conversation = conversations[position]
|
val conversation = getItem(position)
|
||||||
holder.bindView(conversation, true, true) { itemView, layoutPosition ->
|
holder.bindView(conversation, allowSingleClick = true, allowLongClick = true) { itemView, _ ->
|
||||||
setupView(itemView, conversation)
|
setupView(itemView, conversation)
|
||||||
}
|
}
|
||||||
bindViewHolder(holder)
|
bindViewHolder(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = conversations.size
|
override fun getItemId(position: Int) = getItem(position).threadId
|
||||||
|
|
||||||
override fun onViewRecycled(holder: ViewHolder) {
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
super.onViewRecycled(holder)
|
super.onViewRecycled(holder)
|
||||||
@ -128,8 +140,7 @@ class ConversationsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val numbersToBlock = getSelectedItems()
|
val numbersToBlock = getSelectedItems()
|
||||||
val positions = getSelectedItemPositions()
|
val newList = currentList.toMutableList().apply { removeAll(numbersToBlock) }
|
||||||
conversations.removeAll(numbersToBlock)
|
|
||||||
|
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
numbersToBlock.map { it.phoneNumber }.forEach { number ->
|
numbersToBlock.map { it.phoneNumber }.forEach { number ->
|
||||||
@ -137,7 +148,7 @@ class ConversationsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
removeSelectedItems(positions)
|
submitList(newList)
|
||||||
finishActMode()
|
finishActMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,25 +186,25 @@ class ConversationsAdapter(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val conversationsToRemove = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
|
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
|
||||||
val positions = getSelectedItemPositions()
|
|
||||||
conversationsToRemove.forEach {
|
conversationsToRemove.forEach {
|
||||||
activity.deleteConversation(it.threadId)
|
activity.deleteConversation(it.threadId)
|
||||||
activity.notificationManager.cancel(it.hashCode())
|
activity.notificationManager.cancel(it.hashCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
val newList = try {
|
||||||
conversations.removeAll(conversationsToRemove.toSet())
|
currentList.toMutableList().apply { removeAll(conversationsToRemove) }
|
||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
|
currentList.toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
if (conversationsToRemove.isEmpty()) {
|
if (newList.none { selectedKeys.contains(it.hashCode()) }) {
|
||||||
refreshMessages()
|
refreshMessages()
|
||||||
finishActMode()
|
finishActMode()
|
||||||
} else {
|
} else {
|
||||||
removeSelectedItems(positions)
|
submitList(newList)
|
||||||
if (conversations.isEmpty()) {
|
if (newList.isEmpty()) {
|
||||||
refreshMessages()
|
refreshMessages()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,7 +216,7 @@ class ConversationsAdapter(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val conversationsMarkedAsRead = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
|
val conversationsMarkedAsRead = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
conversationsMarkedAsRead.filter { conversation -> !conversation.read }.forEach {
|
conversationsMarkedAsRead.filter { conversation -> !conversation.read }.forEach {
|
||||||
activity.markThreadMessagesRead(it.threadId)
|
activity.markThreadMessagesRead(it.threadId)
|
||||||
@ -223,7 +234,7 @@ class ConversationsAdapter(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val conversationsMarkedAsUnread = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
|
val conversationsMarkedAsUnread = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
conversationsMarkedAsUnread.filter { conversation -> conversation.read }.forEach {
|
conversationsMarkedAsUnread.filter { conversation -> conversation.read }.forEach {
|
||||||
activity.markThreadMessagesUnread(it.threadId)
|
activity.markThreadMessagesUnread(it.threadId)
|
||||||
@ -246,7 +257,7 @@ class ConversationsAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSelectedItems() = conversations.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
|
private fun getSelectedItems() = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
|
||||||
|
|
||||||
private fun pinConversation(pin: Boolean) {
|
private fun pinConversation(pin: Boolean) {
|
||||||
val conversations = getSelectedItems()
|
val conversations = getSelectedItems()
|
||||||
@ -285,14 +296,9 @@ class ConversationsAdapter(
|
|||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateConversations(newConversations: ArrayList<Conversation>) {
|
fun updateConversations(newConversations: ArrayList<Conversation>, commitCallback: (() -> Unit)? = null) {
|
||||||
val latestConversations = newConversations.clone() as ArrayList<Conversation>
|
saveRecyclerViewState()
|
||||||
val oldHashCode = conversations.hashCode()
|
submitList(newConversations.toList(), commitCallback)
|
||||||
val newHashCode = latestConversations.hashCode()
|
|
||||||
if (newHashCode != oldHashCode) {
|
|
||||||
conversations = latestConversations
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDrafts() {
|
fun updateDrafts() {
|
||||||
@ -356,5 +362,23 @@ class ConversationsAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChange(position: Int) = conversations.getOrNull(position)?.title ?: ""
|
override fun onChange(position: Int) = currentList.getOrNull(position)?.title ?: ""
|
||||||
|
|
||||||
|
private fun saveRecyclerViewState() {
|
||||||
|
recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreRecyclerViewState() {
|
||||||
|
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConversationDiffCallback : DiffUtil.ItemCallback<Conversation>() {
|
||||||
|
override fun areItemsTheSame(oldItem: Conversation, newItem: Conversation): Boolean {
|
||||||
|
return Conversation.areItemsTheSame(oldItem, newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Conversation, newItem: Conversation): Boolean {
|
||||||
|
return Conversation.areContentsTheSame(oldItem, newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import android.util.TypedValue
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DataSource
|
import com.bumptech.glide.load.DataSource
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
@ -21,7 +22,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|||||||
import com.bumptech.glide.request.RequestListener
|
import com.bumptech.glide.request.RequestListener
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
|
import com.simplemobiletools.commons.adapters.MyRecyclerViewListAdapter
|
||||||
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
|
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
|
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
|
||||||
@ -35,7 +36,10 @@ import com.simplemobiletools.smsmessenger.activities.VCardViewerActivity
|
|||||||
import com.simplemobiletools.smsmessenger.dialogs.SelectTextDialog
|
import com.simplemobiletools.smsmessenger.dialogs.SelectTextDialog
|
||||||
import com.simplemobiletools.smsmessenger.extensions.*
|
import com.simplemobiletools.smsmessenger.extensions.*
|
||||||
import com.simplemobiletools.smsmessenger.helpers.*
|
import com.simplemobiletools.smsmessenger.helpers.*
|
||||||
import com.simplemobiletools.smsmessenger.models.*
|
import com.simplemobiletools.smsmessenger.models.Attachment
|
||||||
|
import com.simplemobiletools.smsmessenger.models.Message
|
||||||
|
import com.simplemobiletools.smsmessenger.models.ThreadItem
|
||||||
|
import com.simplemobiletools.smsmessenger.models.ThreadItem.*
|
||||||
import kotlinx.android.synthetic.main.item_attachment_image.view.*
|
import kotlinx.android.synthetic.main.item_attachment_image.view.*
|
||||||
import kotlinx.android.synthetic.main.item_received_message.view.*
|
import kotlinx.android.synthetic.main.item_received_message.view.*
|
||||||
import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder
|
import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder
|
||||||
@ -45,12 +49,16 @@ import kotlinx.android.synthetic.main.item_received_message.view.thread_message_
|
|||||||
import kotlinx.android.synthetic.main.item_sent_message.view.*
|
import kotlinx.android.synthetic.main.item_sent_message.view.*
|
||||||
import kotlinx.android.synthetic.main.item_thread_date_time.view.*
|
import kotlinx.android.synthetic.main.item_thread_date_time.view.*
|
||||||
import kotlinx.android.synthetic.main.item_thread_error.view.*
|
import kotlinx.android.synthetic.main.item_thread_error.view.*
|
||||||
|
import kotlinx.android.synthetic.main.item_thread_loading.view.*
|
||||||
import kotlinx.android.synthetic.main.item_thread_sending.view.*
|
import kotlinx.android.synthetic.main.item_thread_sending.view.*
|
||||||
import kotlinx.android.synthetic.main.item_thread_success.view.*
|
import kotlinx.android.synthetic.main.item_thread_success.view.*
|
||||||
|
|
||||||
class ThreadAdapter(
|
class ThreadAdapter(
|
||||||
activity: SimpleActivity, var messages: ArrayList<ThreadItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, val onThreadIdUpdate: (Long) -> Unit
|
activity: SimpleActivity,
|
||||||
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
|
recyclerView: MyRecyclerView,
|
||||||
|
itemClick: (Any) -> Unit,
|
||||||
|
val deleteMessages: (messages: List<Message>) -> Unit
|
||||||
|
) : MyRecyclerViewListAdapter<ThreadItem>(activity, recyclerView, ThreadItemDiffCallback(), itemClick) {
|
||||||
private var fontSize = activity.getTextSize()
|
private var fontSize = activity.getTextSize()
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@ -59,6 +67,7 @@ class ThreadAdapter(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
setupDragListener(true)
|
setupDragListener(true)
|
||||||
|
setHasStableIds(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getActionMenuId() = R.menu.cab_thread
|
override fun getActionMenuId() = R.menu.cab_thread
|
||||||
@ -92,13 +101,13 @@ class ThreadAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSelectableItemCount() = messages.filter { it is Message }.size
|
override fun getSelectableItemCount() = currentList.filterIsInstance<Message>().size
|
||||||
|
|
||||||
override fun getIsItemSelectable(position: Int) = !isThreadDateTime(position)
|
override fun getIsItemSelectable(position: Int) = !isThreadDateTime(position)
|
||||||
|
|
||||||
override fun getItemSelectionKey(position: Int) = (messages.getOrNull(position) as? Message)?.hashCode()
|
override fun getItemSelectionKey(position: Int) = (currentList.getOrNull(position) as? Message)?.hashCode()
|
||||||
|
|
||||||
override fun getItemKeyPosition(key: Int) = messages.indexOfFirst { (it as? Message)?.hashCode() == key }
|
override fun getItemKeyPosition(key: Int) = currentList.indexOfFirst { (it as? Message)?.hashCode() == key }
|
||||||
|
|
||||||
override fun onActionModeCreated() {}
|
override fun onActionModeCreated() {}
|
||||||
|
|
||||||
@ -106,6 +115,7 @@ class ThreadAdapter(
|
|||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val layout = when (viewType) {
|
val layout = when (viewType) {
|
||||||
|
THREAD_LOADING -> R.layout.item_thread_loading
|
||||||
THREAD_DATE_TIME -> R.layout.item_thread_date_time
|
THREAD_DATE_TIME -> R.layout.item_thread_date_time
|
||||||
THREAD_RECEIVED_MESSAGE -> R.layout.item_received_message
|
THREAD_RECEIVED_MESSAGE -> R.layout.item_received_message
|
||||||
THREAD_SENT_MESSAGE_ERROR -> R.layout.item_thread_error
|
THREAD_SENT_MESSAGE_ERROR -> R.layout.item_thread_error
|
||||||
@ -117,32 +127,37 @@ class ThreadAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = messages[position]
|
val item = getItem(position)
|
||||||
val isClickable = item is ThreadError || item is Message
|
val isClickable = item is ThreadError || item is Message
|
||||||
val isLongClickable = item is Message
|
val isLongClickable = item is Message
|
||||||
holder.bindView(item, isClickable, isLongClickable) { itemView, layoutPosition ->
|
holder.bindView(item, isClickable, isLongClickable) { itemView, _ ->
|
||||||
when (item) {
|
when (item) {
|
||||||
|
is ThreadLoading -> setupThreadLoading(itemView)
|
||||||
is ThreadDateTime -> setupDateTime(itemView, item)
|
is ThreadDateTime -> setupDateTime(itemView, item)
|
||||||
is ThreadSent -> setupThreadSuccess(itemView, item.delivered)
|
|
||||||
is ThreadError -> setupThreadError(itemView)
|
is ThreadError -> setupThreadError(itemView)
|
||||||
|
is ThreadSent -> setupThreadSuccess(itemView, item.delivered)
|
||||||
is ThreadSending -> setupThreadSending(itemView)
|
is ThreadSending -> setupThreadSending(itemView)
|
||||||
else -> setupView(holder, itemView, item as Message)
|
is Message -> setupView(holder, itemView, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bindViewHolder(holder)
|
bindViewHolder(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = messages.size
|
override fun getItemId(position: Int): Long {
|
||||||
|
return when (val item = getItem(position)) {
|
||||||
|
is Message -> Message.getStableId(item)
|
||||||
|
else -> item.hashCode().toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
val item = messages[position]
|
return when (val item = getItem(position)) {
|
||||||
return when {
|
is ThreadLoading -> THREAD_LOADING
|
||||||
item is ThreadDateTime -> THREAD_DATE_TIME
|
is ThreadDateTime -> THREAD_DATE_TIME
|
||||||
(messages[position] as? Message)?.isReceivedMessage() == true -> THREAD_RECEIVED_MESSAGE
|
is ThreadError -> THREAD_SENT_MESSAGE_ERROR
|
||||||
item is ThreadError -> THREAD_SENT_MESSAGE_ERROR
|
is ThreadSent -> THREAD_SENT_MESSAGE_SENT
|
||||||
item is ThreadSent -> THREAD_SENT_MESSAGE_SENT
|
is ThreadSending -> THREAD_SENT_MESSAGE_SENDING
|
||||||
item is ThreadSending -> THREAD_SENT_MESSAGE_SENDING
|
is Message -> if (item.isReceivedMessage()) THREAD_RECEIVED_MESSAGE else THREAD_SENT_MESSAGE
|
||||||
else -> THREAD_SENT_MESSAGE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,45 +200,14 @@ class ThreadAdapter(
|
|||||||
|
|
||||||
ConfirmationDialog(activity, question) {
|
ConfirmationDialog(activity, question) {
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
deleteMessages()
|
val messagesToRemove = getSelectedItems()
|
||||||
|
if (messagesToRemove.isNotEmpty()) {
|
||||||
|
deleteMessages(messagesToRemove.filterIsInstance<Message>())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteMessages() {
|
|
||||||
val messagesToRemove = getSelectedItems()
|
|
||||||
if (messagesToRemove.isEmpty()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val positions = getSelectedItemPositions()
|
|
||||||
val threadId = (messagesToRemove.firstOrNull() as? Message)?.threadId ?: return
|
|
||||||
messagesToRemove.forEach {
|
|
||||||
activity.deleteMessage((it as Message).id, it.isMMS)
|
|
||||||
}
|
|
||||||
messages.removeAll(messagesToRemove.toSet())
|
|
||||||
activity.updateLastConversationMessage(threadId)
|
|
||||||
|
|
||||||
val messages = messages.filterIsInstance<Message>()
|
|
||||||
if (messages.isNotEmpty() && messages.all { it.isScheduled }) {
|
|
||||||
// move all scheduled messages to a temporary thread as there are no real messages left
|
|
||||||
val message = messages.last()
|
|
||||||
val newThreadId = generateRandomId()
|
|
||||||
activity.createTemporaryThread(message, newThreadId)
|
|
||||||
activity.updateScheduledMessagesThreadId(messages, newThreadId)
|
|
||||||
onThreadIdUpdate(newThreadId)
|
|
||||||
}
|
|
||||||
|
|
||||||
activity.runOnUiThread {
|
|
||||||
if (messages.isEmpty()) {
|
|
||||||
activity.finish()
|
|
||||||
} else {
|
|
||||||
removeSelectedItems(positions)
|
|
||||||
}
|
|
||||||
refreshMessages()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun forwardMessage() {
|
private fun forwardMessage() {
|
||||||
val message = getSelectedItems().firstOrNull() as? Message ?: return
|
val message = getSelectedItems().firstOrNull() as? Message ?: return
|
||||||
val attachment = message.attachment?.attachments?.firstOrNull()
|
val attachment = message.attachment?.attachments?.firstOrNull()
|
||||||
@ -239,18 +223,16 @@ class ThreadAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSelectedItems() = messages.filter { selectedKeys.contains((it as? Message)?.hashCode() ?: 0) } as ArrayList<ThreadItem>
|
private fun getSelectedItems() = currentList.filter { selectedKeys.contains((it as? Message)?.hashCode() ?: 0) } as ArrayList<ThreadItem>
|
||||||
|
|
||||||
private fun isThreadDateTime(position: Int) = messages.getOrNull(position) is ThreadDateTime
|
private fun isThreadDateTime(position: Int) = currentList.getOrNull(position) is ThreadDateTime
|
||||||
|
|
||||||
fun updateMessages(newMessages: ArrayList<ThreadItem>, scrollPosition: Int = newMessages.size - 1) {
|
fun updateMessages(newMessages: ArrayList<ThreadItem>, scrollPosition: Int = newMessages.lastIndex) {
|
||||||
val latestMessages = newMessages.clone() as ArrayList<ThreadItem>
|
val latestMessages = newMessages.toMutableList()
|
||||||
val oldHashCode = messages.hashCode()
|
submitList(latestMessages) {
|
||||||
val newHashCode = latestMessages.hashCode()
|
if (scrollPosition != -1) {
|
||||||
if (newHashCode != oldHashCode) {
|
recyclerView.scrollToPosition(scrollPosition)
|
||||||
messages = latestMessages
|
}
|
||||||
notifyDataSetChanged()
|
|
||||||
recyclerView.scrollToPosition(scrollPosition)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,6 +466,8 @@ class ThreadAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupThreadLoading(view: View) = view.thread_loading.setIndicatorColor(properPrimaryColor)
|
||||||
|
|
||||||
override fun onViewRecycled(holder: ViewHolder) {
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
super.onViewRecycled(holder)
|
super.onViewRecycled(holder)
|
||||||
if (!activity.isDestroyed && !activity.isFinishing && holder.itemView.thread_message_sender_photo != null) {
|
if (!activity.isDestroyed && !activity.isFinishing && holder.itemView.thread_message_sender_photo != null) {
|
||||||
@ -491,3 +475,29 @@ class ThreadAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ThreadItemDiffCallback : DiffUtil.ItemCallback<ThreadItem>() {
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItem: ThreadItem, newItem: ThreadItem): Boolean {
|
||||||
|
if (oldItem::class.java != newItem::class.java) return false
|
||||||
|
return when (oldItem) {
|
||||||
|
is ThreadLoading -> oldItem.id == (newItem as ThreadLoading).id
|
||||||
|
is ThreadDateTime -> oldItem.date == (newItem as ThreadDateTime).date
|
||||||
|
is ThreadError -> oldItem.messageId == (newItem as ThreadError).messageId
|
||||||
|
is ThreadSent -> oldItem.messageId == (newItem as ThreadSent).messageId
|
||||||
|
is ThreadSending -> oldItem.messageId == (newItem as ThreadSending).messageId
|
||||||
|
is Message -> Message.areItemsTheSame(oldItem, newItem as Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: ThreadItem, newItem: ThreadItem): Boolean {
|
||||||
|
if (oldItem::class.java != newItem::class.java) return false
|
||||||
|
return when (oldItem) {
|
||||||
|
is ThreadLoading, is ThreadSending -> true
|
||||||
|
is ThreadDateTime -> oldItem.simID == (newItem as ThreadDateTime).simID
|
||||||
|
is ThreadError -> oldItem.messageText == (newItem as ThreadError).messageText
|
||||||
|
is ThreadSent -> oldItem.delivered == (newItem as ThreadSent).delivered
|
||||||
|
is Message -> Message.areContentsTheSame(oldItem, newItem as Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -58,7 +58,13 @@ val Context.messageAttachmentsDB: MessageAttachmentsDao get() = getMessagesDB().
|
|||||||
|
|
||||||
val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao()
|
val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao()
|
||||||
|
|
||||||
fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: Int = -1, includeScheduledMessages: Boolean = true): ArrayList<Message> {
|
fun Context.getMessages(
|
||||||
|
threadId: Long,
|
||||||
|
getImageResolutions: Boolean,
|
||||||
|
dateFrom: Int = -1,
|
||||||
|
includeScheduledMessages: Boolean = true,
|
||||||
|
limit: Int = MESSAGES_LIMIT
|
||||||
|
): ArrayList<Message> {
|
||||||
val uri = Sms.CONTENT_URI
|
val uri = Sms.CONTENT_URI
|
||||||
val projection = arrayOf(
|
val projection = arrayOf(
|
||||||
Sms._ID,
|
Sms._ID,
|
||||||
@ -75,7 +81,7 @@ fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom:
|
|||||||
val rangeQuery = if (dateFrom == -1) "" else "AND ${Sms.DATE} < ${dateFrom.toLong() * 1000}"
|
val rangeQuery = if (dateFrom == -1) "" else "AND ${Sms.DATE} < ${dateFrom.toLong() * 1000}"
|
||||||
val selection = "${Sms.THREAD_ID} = ? $rangeQuery"
|
val selection = "${Sms.THREAD_ID} = ? $rangeQuery"
|
||||||
val selectionArgs = arrayOf(threadId.toString())
|
val selectionArgs = arrayOf(threadId.toString())
|
||||||
val sortOrder = "${Sms.DATE} DESC LIMIT $MESSAGES_LIMIT"
|
val sortOrder = "${Sms.DATE} DESC LIMIT $limit"
|
||||||
|
|
||||||
val blockStatus = HashMap<String, Boolean>()
|
val blockStatus = HashMap<String, Boolean>()
|
||||||
val blockedNumbers = getBlockedNumbers()
|
val blockedNumbers = getBlockedNumbers()
|
||||||
|
@ -5,6 +5,7 @@ import android.net.Uri
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
|
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
|
||||||
|
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||||
import com.simplemobiletools.smsmessenger.R
|
import com.simplemobiletools.smsmessenger.R
|
||||||
import com.simplemobiletools.smsmessenger.extensions.*
|
import com.simplemobiletools.smsmessenger.extensions.*
|
||||||
import kotlinx.android.synthetic.main.item_attachment_document.view.*
|
import kotlinx.android.synthetic.main.item_attachment_document.view.*
|
||||||
@ -25,12 +26,18 @@ fun View.setupDocumentPreview(
|
|||||||
filename.text = title
|
filename.text = title
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
ensureBackgroundThread {
|
||||||
val size = context.getFileSizeFromUri(uri)
|
try {
|
||||||
file_size.beVisible()
|
val size = context.getFileSizeFromUri(uri)
|
||||||
file_size.text = size.formatSize()
|
post {
|
||||||
} catch (e: Exception) {
|
file_size.beVisible()
|
||||||
file_size.beGone()
|
file_size.text = size.formatSize()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
post {
|
||||||
|
file_size.beGone()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val textColor = context.getProperTextColor()
|
val textColor = context.getProperTextColor()
|
||||||
|
@ -46,6 +46,7 @@ const val THREAD_SENT_MESSAGE = 3
|
|||||||
const val THREAD_SENT_MESSAGE_ERROR = 4
|
const val THREAD_SENT_MESSAGE_ERROR = 4
|
||||||
const val THREAD_SENT_MESSAGE_SENT = 5
|
const val THREAD_SENT_MESSAGE_SENT = 5
|
||||||
const val THREAD_SENT_MESSAGE_SENDING = 6
|
const val THREAD_SENT_MESSAGE_SENDING = 6
|
||||||
|
const val THREAD_LOADING = 7
|
||||||
|
|
||||||
// view types for attachment list
|
// view types for attachment list
|
||||||
const val ATTACHMENT_DOCUMENT = 7
|
const val ATTACHMENT_DOCUMENT = 7
|
||||||
|
@ -4,8 +4,6 @@ import android.app.AlarmManager
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import androidx.core.app.AlarmManagerCompat
|
import androidx.core.app.AlarmManagerCompat
|
||||||
import com.klinker.android.send_message.Settings
|
import com.klinker.android.send_message.Settings
|
||||||
import com.klinker.android.send_message.Transaction
|
import com.klinker.android.send_message.Transaction
|
||||||
@ -71,12 +69,10 @@ fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: I
|
|||||||
|
|
||||||
transaction.setExplicitBroadcastForSentSms(smsSentIntent)
|
transaction.setExplicitBroadcastForSentSms(smsSentIntent)
|
||||||
transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent)
|
transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent)
|
||||||
Handler(Looper.getMainLooper()).post {
|
try {
|
||||||
try {
|
transaction.sendNewMessage(message)
|
||||||
transaction.sendNewMessage(message)
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
showErrorToast(e)
|
||||||
showErrorToast(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,13 +18,19 @@ data class Conversation(
|
|||||||
@ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false
|
@ColumnInfo(name = "is_scheduled") var isScheduled: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun areContentsTheSame(other: Conversation): Boolean {
|
companion object {
|
||||||
return snippet == other.snippet &&
|
fun areItemsTheSame(old: Conversation, new: Conversation): Boolean {
|
||||||
date == other.date &&
|
return old.threadId == new.threadId
|
||||||
read == other.read &&
|
}
|
||||||
title == other.title &&
|
|
||||||
photoUri == other.photoUri &&
|
fun areContentsTheSame(old: Conversation, new: Conversation): Boolean {
|
||||||
isGroupConversation == other.isGroupConversation &&
|
return old.snippet == new.snippet &&
|
||||||
phoneNumber == other.phoneNumber
|
old.date == new.date &&
|
||||||
|
old.read == new.read &&
|
||||||
|
old.title == new.title &&
|
||||||
|
old.photoUri == new.photoUri &&
|
||||||
|
old.isGroupConversation == new.isGroupConversation &&
|
||||||
|
old.phoneNumber == new.phoneNumber
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,35 @@ data class Message(
|
|||||||
fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX
|
fun isReceivedMessage() = type == Telephony.Sms.MESSAGE_TYPE_INBOX
|
||||||
|
|
||||||
fun millis() = date * 1000L
|
fun millis() = date * 1000L
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun getStableId(message: Message): Long {
|
||||||
|
var result = message.id.hashCode()
|
||||||
|
result = 31 * result + message.body.hashCode()
|
||||||
|
result = 31 * result + message.date.hashCode()
|
||||||
|
result = 31 * result + message.threadId.hashCode()
|
||||||
|
result = 31 * result + message.isMMS.hashCode()
|
||||||
|
result = 31 * result + (message.attachment?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + message.senderName.hashCode()
|
||||||
|
result = 31 * result + message.senderPhotoUri.hashCode()
|
||||||
|
result = 31 * result + message.isScheduled.hashCode()
|
||||||
|
return result.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun areItemsTheSame(old: Message, new: Message): Boolean {
|
||||||
|
return old.id == new.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun areContentsTheSame(old: Message, new: Message): Boolean {
|
||||||
|
return old.body == new.body &&
|
||||||
|
old.threadId == new.threadId &&
|
||||||
|
old.date == new.date &&
|
||||||
|
old.isMMS == new.isMMS &&
|
||||||
|
old.attachment == new.attachment &&
|
||||||
|
old.senderName == new.senderName &&
|
||||||
|
old.senderPhotoUri == new.senderPhotoUri &&
|
||||||
|
old.isScheduled == new.isScheduled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package com.simplemobiletools.smsmessenger.models
|
|
||||||
|
|
||||||
data class ThreadDateTime(val date: Int, val simID: String) : ThreadItem()
|
|
@ -1,3 +0,0 @@
|
|||||||
package com.simplemobiletools.smsmessenger.models
|
|
||||||
|
|
||||||
data class ThreadError(val messageId: Long, val messageText: String) : ThreadItem()
|
|
@ -1,3 +0,0 @@
|
|||||||
package com.simplemobiletools.smsmessenger.models
|
|
||||||
|
|
||||||
open class ThreadItem
|
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread item representations for the main thread recyclerview. [Message] is also a [ThreadItem]
|
||||||
|
*/
|
||||||
|
sealed class ThreadItem {
|
||||||
|
data class ThreadLoading(val id: Long) : ThreadItem()
|
||||||
|
data class ThreadDateTime(val date: Int, val simID: String) : ThreadItem()
|
||||||
|
data class ThreadError(val messageId: Long, val messageText: String) : ThreadItem()
|
||||||
|
data class ThreadSent(val messageId: Long, val delivered: Boolean) : ThreadItem()
|
||||||
|
data class ThreadSending(val messageId: Long) : ThreadItem()
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
package com.simplemobiletools.smsmessenger.models
|
|
||||||
|
|
||||||
data class ThreadSending(val messageId: Long) : ThreadItem()
|
|
@ -1,5 +0,0 @@
|
|||||||
package com.simplemobiletools.smsmessenger.models
|
|
||||||
|
|
||||||
// show a check after the latest message, if it is a sent one and succeeded,
|
|
||||||
// show a double check if it is delivered
|
|
||||||
data class ThreadSent(val messageID: Long, val delivered: Boolean) : ThreadItem()
|
|
@ -3,6 +3,8 @@ package com.simplemobiletools.smsmessenger.receivers
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import com.simplemobiletools.commons.extensions.showErrorToast
|
import com.simplemobiletools.commons.extensions.showErrorToast
|
||||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||||
@ -43,7 +45,9 @@ class ScheduledMessageReceiver : BroadcastReceiver() {
|
|||||||
val attachments = message.attachment?.attachments ?: emptyList()
|
val attachments = message.attachment?.attachments ?: emptyList()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.sendMessage(message.body, addresses, message.subscriptionId, attachments)
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
context.sendMessage(message.body, addresses, message.subscriptionId, attachments)
|
||||||
|
}
|
||||||
|
|
||||||
// delete temporary conversation and message as it's already persisted to the telephony db now
|
// delete temporary conversation and message as it's already persisted to the telephony db now
|
||||||
context.deleteScheduledMessage(messageId)
|
context.deleteScheduledMessage(messageId)
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
android:bottomRightRadius="@dimen/normal_margin"
|
android:bottomLeftRadius="@dimen/small_margin"
|
||||||
android:topLeftRadius="@dimen/normal_margin"
|
android:bottomRightRadius="@dimen/big_margin"
|
||||||
android:topRightRadius="@dimen/normal_margin" />
|
android:topLeftRadius="@dimen/big_margin"
|
||||||
|
android:topRightRadius="@dimen/big_margin" />
|
||||||
|
|
||||||
<solid android:color="@color/activated_item_foreground" />
|
<solid android:color="@color/activated_item_foreground" />
|
||||||
|
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
android:shape="rectangle">
|
android:shape="rectangle">
|
||||||
|
|
||||||
<corners
|
<corners
|
||||||
android:bottomLeftRadius="@dimen/normal_margin"
|
android:bottomLeftRadius="@dimen/big_margin"
|
||||||
android:topLeftRadius="@dimen/normal_margin"
|
android:bottomRightRadius="@dimen/small_margin"
|
||||||
android:topRightRadius="@dimen/normal_margin" />
|
android:topLeftRadius="@dimen/big_margin"
|
||||||
|
android:topRightRadius="@dimen/big_margin" />
|
||||||
|
|
||||||
<solid android:color="@color/color_primary" />
|
<solid android:color="@color/color_primary" />
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
android:layout_marginStart="@dimen/medium_margin"
|
android:layout_marginStart="@dimen/medium_margin"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
@ -48,6 +49,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/normal_text_size"
|
android:textSize="@dimen/normal_text_size"
|
||||||
|
android:visibility="gone"
|
||||||
tools:text="2.18 KB" />
|
tools:text="2.18 KB" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
18
app/src/main/res/layout/item_thread_loading.xml
Normal file
18
app/src/main/res/layout/item_thread_loading.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="@dimen/normal_margin">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:id="@+id/thread_loading"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="@dimen/small_margin"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:indicatorSize="@dimen/big_margin"
|
||||||
|
app:trackCornerRadius="@dimen/normal_margin" />
|
||||||
|
</LinearLayout>
|
Loading…
x
Reference in New Issue
Block a user