Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ import one.mixin.android.vo.ConversationMinimal
import one.mixin.android.vo.ConversationStatus
import one.mixin.android.vo.ConversationStorageUsage
import one.mixin.android.vo.GroupInfo
import one.mixin.android.vo.isAppCard
import one.mixin.android.vo.Job
import one.mixin.android.vo.Message
import one.mixin.android.vo.MessageItem
import one.mixin.android.vo.isAppCard
import one.mixin.android.vo.isImage
import one.mixin.android.vo.isVideo
import one.mixin.android.vo.MessageMention
import one.mixin.android.vo.MessageMinimal
import one.mixin.android.vo.Participant
Expand Down Expand Up @@ -196,37 +198,25 @@ class ConversationRepository
messageId: String,
excludeLive: Boolean,
): Int {
val list = if (excludeLive) {
messageDao.getMediaMessagesExcludeLiveList(conversationId)
val item = messageDao.getMediaMessage(conversationId, messageId) ?: return -1
if (excludeLive && !item.isImage() && !item.isVideo()) return -1
if (item.isAppCard() && !item.isAppCardWithCover()) return -1
return if (excludeLive) {
messageDao.indexMediaMessagesExcludeLive(conversationId, messageId)
} else {
messageDao.getMediaMessagesList(conversationId)
messageDao.indexMediaMessages(conversationId, messageId)
}
val filteredList = list.filter { !it.isAppCard() || it.isAppCardWithCover() }
return filteredList.indexOfFirst { it.messageId == messageId }.coerceAtLeast(0)
}

suspend fun countIndexMediaMessages(
conversationId: String,
excludeLive: Boolean,
): Int {
val list = if (excludeLive) {
messageDao.getMediaMessagesExcludeLiveList(conversationId)
} else {
messageDao.getMediaMessagesList(conversationId)
}
return list.count { !it.isAppCard() || it.isAppCardWithCover() }
}

fun getMediaMessagesDataSource(
conversationId: String,
excludeLive: Boolean,
): DataSource.Factory<Int, MessageItem> {
return if (excludeLive) {
messageDao.getMediaMessagesExcludeLive(conversationId)
): Int =
if (excludeLive) {
messageDao.countIndexMediaMessagesExcludeLive(conversationId)
} else {
messageDao.getMediaMessages(conversationId)
messageDao.countIndexMediaMessages(conversationId)
}
}

fun getMediaMessages(
conversationId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ private fun PerpsAddContent(
val defaultLiquidationPrice = position.liquidationPrice
?.takeIf { showLiquidationPrice && it.isNotBlank() }
val displayLiquidationPrice = remoteLiquidationPrice ?: defaultLiquidationPrice
val visibleLiquidationPrice = displayLiquidationPrice?.takeIf { it != "-" }
val entryPriceText = position.entryPrice
.takeIf { it.isNotBlank() }
?.let { formatPerpsPrice(it, priceScale) }
Expand Down Expand Up @@ -582,12 +583,8 @@ private fun PerpsAddContent(
Spacer(modifier = Modifier.height(16.dp))
PerpsAddInfoRow(
title = stringResource(R.string.Liquidation_Price),
value = when (displayLiquidationPrice) {
"-" -> "-"
null -> "-"
else -> formatPerpsPrice(displayLiquidationPrice, priceScale)
},
isLoading = isLiquidationLoading && displayLiquidationPrice != "-",
value = visibleLiquidationPrice?.let { formatPerpsPrice(it, priceScale) } ?: "-",
isLoading = isLiquidationLoading && visibleLiquidationPrice != null,
onTipClick = {
showPerpsGuide(PerpetualGuideBottomSheetDialogFragment.TAB_LIQUIDATION)
},
Expand Down
122 changes: 20 additions & 102 deletions app/src/main/java/one/mixin/android/ui/media/SharedMediaViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
package one.mixin.android.ui.media

import android.net.Uri
import android.annotation.SuppressLint
import androidx.annotation.OptIn
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.media3.common.util.UnstableApi
import androidx.paging.DataSource
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import androidx.paging.PositionalDataSource
import androidx.arch.core.executor.ArchTaskExecutor
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import one.mixin.android.Constants.PAGE_SIZE
import one.mixin.android.job.AttachmentDownloadJob
import one.mixin.android.job.ConvertVideoJob
Expand All @@ -32,10 +24,8 @@ import one.mixin.android.vo.MessageCategory
import one.mixin.android.vo.MessageItem
import one.mixin.android.vo.isEncrypted
import one.mixin.android.vo.isImage
import one.mixin.android.vo.isAppCard
import one.mixin.android.vo.isSignal
import one.mixin.android.vo.isVideo
import org.threeten.bp.ZonedDateTime
import javax.inject.Inject

@HiltViewModel
Expand All @@ -46,61 +36,27 @@ class SharedMediaViewModel
private val jobManager: MixinJobManager,
) : ViewModel() {

fun getMediaMessagesExcludeLive(conversationId: String): LiveData<PagedList<MessageItem>> {
val liveData = MutableLiveData<PagedList<MessageItem>>()
viewModelScope.launch(Dispatchers.IO) {
val list = conversationRepository.getMediaMessagesExcludeLiveList(conversationId)
val filteredList = list.filter { !it.isAppCard() || it.isAppCardWithCover() }
val pagedList = createPagedList(filteredList, 0)
liveData.postValue(pagedList)
}
return liveData
}

@SuppressLint("RestrictedApi")
private fun createPagedList(list: List<MessageItem>, initialKey: Int): PagedList<MessageItem> {
val config = PagedList.Config.Builder()
.setPageSize(MediaFragment.PAGE_SIZE)
.setEnablePlaceholders(false)
.build()
return PagedList.Builder(ListDataSource(list), config)
.setNotifyExecutor(ArchTaskExecutor.getMainThreadExecutor())
.setFetchExecutor(ArchTaskExecutor.getIOThreadExecutor())
.setInitialKey(initialKey)
fun getMediaMessagesExcludeLive(conversationId: String): LiveData<PagedList<MessageItem>> =
LivePagedListBuilder(
conversationRepository.getMediaMessagesExcludeLive(conversationId),
PagedList.Config.Builder()
.setPrefetchDistance(MediaFragment.PAGE_SIZE * 2)
.setPageSize(MediaFragment.PAGE_SIZE)
.setEnablePlaceholders(true)
.build(),
)
.build()
}

fun getAudioMessages(conversationId: String): LiveData<PagedList<MessageItem>> {
val liveData = MutableLiveData<PagedList<MessageItem>>()
viewModelScope.launch(Dispatchers.IO) {
val list = conversationRepository.getAudioMessagesList(conversationId)
val sortedList = list.sortedWith(
Comparator<MessageItem> { o1, o2 ->
if (o1 == null || o2 == null) return@Comparator 0

val time1 = ZonedDateTime.parse(o1.createdAt)
val time2 = ZonedDateTime.parse(o2.createdAt)
val year1 = time1.year
val year2 = time2.year
val day1 = time1.dayOfYear
val day2 = time2.dayOfYear
if (year1 == year2) {
if (day1 == day2) {
return@Comparator time1.toOffsetDateTime()
.compareTo(time2.toOffsetDateTime())
} else {
return@Comparator day2 - day1
}
} else {
return@Comparator year2 - year1
}
},
)
val pagedList = createPagedList(sortedList, 0)
liveData.postValue(pagedList)
}
return liveData
}
fun getAudioMessages(conversationId: String): LiveData<PagedList<MessageItem>> =
LivePagedListBuilder(
conversationRepository.getAudioMessages(conversationId),
PagedList.Config.Builder()
.setPrefetchDistance(PAGE_SIZE * 2)
.setPageSize(PAGE_SIZE)
.setEnablePlaceholders(true)
.build(),
)
.build()

fun getPostMessages(conversationId: String): LiveData<PagedList<MessageItem>> {
return LivePagedListBuilder(
Expand Down Expand Up @@ -226,25 +182,11 @@ class SharedMediaViewModel
excludeLive: Boolean,
) = conversationRepository.countIndexMediaMessages(conversationId, excludeLive)

@OptIn(UnstableApi::class)
fun getMediaMessages(
conversationId: String,
index: Int,
excludeLive: Boolean,
): LiveData<PagedList<MessageItem>> {
val liveData = MutableLiveData<PagedList<MessageItem>>()
viewModelScope.launch(Dispatchers.IO) {
val list = if (excludeLive) {
conversationRepository.getMediaMessagesExcludeLiveList(conversationId)
} else {
conversationRepository.getMediaMessagesList(conversationId)
}
val filteredList = list.filter { !it.isAppCard() || it.isAppCardWithCover() }
val pagedList = createPagedList(filteredList, index)
liveData.postValue(pagedList)
}
return liveData
}
): LiveData<PagedList<MessageItem>> = conversationRepository.getMediaMessages(conversationId, index, excludeLive)

suspend fun getMediaMessage(
conversationId: String,
Expand All @@ -259,27 +201,3 @@ class SharedMediaViewModel
}
}
}

class ListDataSource<T : Any>(private val items: List<T>) : PositionalDataSource<T>() {
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
val totalCount = items.size
if (totalCount == 0) {
callback.onResult(emptyList(), 0, 0)
return
}
val position = computeInitialLoadPosition(params, totalCount)
val loadSize = computeInitialLoadSize(params, position, totalCount)
callback.onResult(items.subList(position, minOf(position + loadSize, totalCount)), position, totalCount)
}

override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
val start = params.startPosition
val end = minOf(start + params.loadSize, items.size)
if (start < items.size && start < end) {
callback.onResult(items.subList(start, end))
} else {
callback.onResult(emptyList())
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,12 @@ class MediaPagerActivity : BaseActivity(), DismissFrameLayout.OnDismissListener,
private fun observeAllDataSource() =
lifecycleScope.launch {
val excludeLive = mediaSource == MediaSource.SharedMedia
initialIndex = viewModel.indexMediaMessages(conversationId, messageId, excludeLive)
val index = viewModel.indexMediaMessages(conversationId, messageId, excludeLive)
if (index < 0) {
reportEvent("Media message not found,conversationId: $conversationId,messageId: $messageId")
return@launch
}
initialIndex = index
viewModel.getMediaMessages(conversationId, initialIndex, excludeLive)
.observe(
this@MediaPagerActivity,
Expand All @@ -326,9 +331,7 @@ class MediaPagerActivity : BaseActivity(), DismissFrameLayout.OnDismissListener,
runCatching {
adapter.initialPos = initialIndex
it.loadAround(initialIndex)
if (excludeLive) {
binding.viewPager.setCurrentItem(initialIndex, false)
} else if (it.getOrNull(initialIndex)?.messageId == messageId) { // Only change when data is same
if (it.getOrNull(initialIndex)?.messageId == messageId) {
binding.viewPager.setCurrentItem(initialIndex, false)
} else {
lifecycleScope.launch {
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/one/mixin/android/vo/AppCardData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ data class AppCardData(
}
}

val hashCover: Boolean
@IgnoredOnParcel
val hasCover: Boolean
get() {
if (oldVersion) return false
if (coverUrl.isNullOrBlank()) return false
return true
return !coverUrl.isNullOrBlank() || !cover?.url.isNullOrBlank()
}

val hasValidCoverSize: Boolean
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/one/mixin/android/vo/MessageItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ data class MessageItem(

fun isAppCardWithCover(): Boolean {
if (!isAppCard()) return false
return appCardData?.hashCover == true
return appCardData?.hasCover == true
}

private fun unfinishedAttachment(): Boolean = !mediaDownloaded(this.mediaStatus) && (isData() || isImage() || isVideo() || isAudio())
Expand Down
36 changes: 36 additions & 0 deletions app/src/test/java/one/mixin/android/vo/AppCardDataTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package one.mixin.android.vo

import one.mixin.android.util.GsonHelper
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class AppCardDataTest {
@Test
fun nestedCoverUrlCountsAsCover() {
val coverUrl = "https://example.com/cover.png"
val appCardData = AppCardData(
appId = "app-id",
iconUrl = null,
coverUrl = null,
cover = Cover(
height = 100,
width = 100,
mimeType = "image/png",
url = coverUrl,
thumbnail = null,
),
title = "title",
description = "description",
action = null,
updatedAt = null,
shareable = null,
)
val message = create(MessageCategory.APP_CARD.name).copy(
content = GsonHelper.customGson.toJson(appCardData),
)

assertTrue(appCardData.hasCover)
assertEquals(coverUrl, message.appCardCoverUrl())
}
}