mirror of
https://github.com/nextcloud/talk-android
synced 2025-08-14 15:35:03 +01:00
Follow up improvements
- Added ComposePreviewUtils - Added ComposePreviewUtilsDao (both for previewing w/ dependencies) - Additional fixes Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
This commit is contained in:
parent
fdfa58dcdd
commit
fd7afccbc4
@ -33,7 +33,6 @@ import coil.decode.SvgDecoder
|
|||||||
import coil.memory.MemoryCache
|
import coil.memory.MemoryCache
|
||||||
import coil.util.DebugLogger
|
import coil.util.DebugLogger
|
||||||
import com.nextcloud.talk.BuildConfig
|
import com.nextcloud.talk.BuildConfig
|
||||||
import com.nextcloud.talk.filebrowser.webdav.DavUtils
|
|
||||||
import com.nextcloud.talk.dagger.modules.BusModule
|
import com.nextcloud.talk.dagger.modules.BusModule
|
||||||
import com.nextcloud.talk.dagger.modules.ContextModule
|
import com.nextcloud.talk.dagger.modules.ContextModule
|
||||||
import com.nextcloud.talk.dagger.modules.DaosModule
|
import com.nextcloud.talk.dagger.modules.DaosModule
|
||||||
@ -43,6 +42,7 @@ import com.nextcloud.talk.dagger.modules.RepositoryModule
|
|||||||
import com.nextcloud.talk.dagger.modules.RestModule
|
import com.nextcloud.talk.dagger.modules.RestModule
|
||||||
import com.nextcloud.talk.dagger.modules.UtilsModule
|
import com.nextcloud.talk.dagger.modules.UtilsModule
|
||||||
import com.nextcloud.talk.dagger.modules.ViewModelModule
|
import com.nextcloud.talk.dagger.modules.ViewModelModule
|
||||||
|
import com.nextcloud.talk.filebrowser.webdav.DavUtils
|
||||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||||
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
||||||
import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
||||||
|
@ -153,6 +153,7 @@ import com.nextcloud.talk.ui.PlaybackSpeed
|
|||||||
import com.nextcloud.talk.ui.PlaybackSpeedControl
|
import com.nextcloud.talk.ui.PlaybackSpeedControl
|
||||||
import com.nextcloud.talk.ui.StatusDrawable
|
import com.nextcloud.talk.ui.StatusDrawable
|
||||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
||||||
|
import com.nextcloud.talk.ui.dialog.ContextChatCompose
|
||||||
import com.nextcloud.talk.ui.dialog.DateTimeCompose
|
import com.nextcloud.talk.ui.dialog.DateTimeCompose
|
||||||
import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
|
import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
|
||||||
import com.nextcloud.talk.ui.dialog.MessageActionsDialog
|
import com.nextcloud.talk.ui.dialog.MessageActionsDialog
|
||||||
@ -208,6 +209,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
@ -296,7 +298,33 @@ class ChatActivity :
|
|||||||
private val startMessageSearchForResult =
|
private val startMessageSearchForResult =
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
executeIfResultOk(it) { intent ->
|
executeIfResultOk(it) { intent ->
|
||||||
onMessageSearchResult(intent)
|
runBlocking {
|
||||||
|
val id = intent?.getStringExtra(MessageSearchActivity.RESULT_KEY_MESSAGE_ID)
|
||||||
|
id?.let {
|
||||||
|
val long = id.toLong()
|
||||||
|
val isSaved = chatViewModel.isMessageSaved(id.toLong())
|
||||||
|
if (isSaved) {
|
||||||
|
onMessageSearchResult(intent)
|
||||||
|
} else {
|
||||||
|
binding.genericComposeView.apply {
|
||||||
|
val shouldDismiss = mutableStateOf(false)
|
||||||
|
setContent {
|
||||||
|
val bundle = bundleOf()
|
||||||
|
bundle.putString(BundleKeys.KEY_CREDENTIALS, credentials!!)
|
||||||
|
bundle.putString(BundleKeys.KEY_BASE_URL, conversationUser!!.baseUrl)
|
||||||
|
bundle.putString(KEY_ROOM_TOKEN, roomToken)
|
||||||
|
bundle.putString(BundleKeys.KEY_MESSAGE_ID, id)
|
||||||
|
bundle.putString(
|
||||||
|
BundleKeys.KEY_CONVERSATION_NAME,
|
||||||
|
currentConversation!!.displayName
|
||||||
|
)
|
||||||
|
ContextChatCompose(bundle).GetDialogView(shouldDismiss, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d("Julius", "Should open something else")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +76,8 @@ interface ChatMessageRepository : LifecycleAwareManager {
|
|||||||
*/
|
*/
|
||||||
suspend fun getMessage(messageId: Long, bundle: Bundle): Flow<ChatMessage>
|
suspend fun getMessage(messageId: Long, bundle: Bundle): Flow<ChatMessage>
|
||||||
|
|
||||||
|
suspend fun checkIfMessageIsSaved(messageId: Long): Boolean
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
suspend fun sendChatMessage(
|
suspend fun sendChatMessage(
|
||||||
credentials: String,
|
credentials: String,
|
||||||
|
@ -475,6 +475,15 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||||||
.map(ChatMessageEntity::asModel)
|
.map(ChatMessageEntity::asModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun checkIfMessageIsSaved(messageId: Long): Boolean {
|
||||||
|
try {
|
||||||
|
chatDao.getChatMessageForConversation(internalConversationId, messageId)
|
||||||
|
return true
|
||||||
|
} catch (_: Exception) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST", "MagicNumber", "Detekt.TooGenericExceptionCaught")
|
@Suppress("UNCHECKED_CAST", "MagicNumber", "Detekt.TooGenericExceptionCaught")
|
||||||
private fun getMessagesFromServer(bundle: Bundle): Pair<Int, List<ChatMessageJson>>? {
|
private fun getMessagesFromServer(bundle: Bundle): Pair<Int, List<ChatMessageJson>>? {
|
||||||
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
|
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
|
||||||
|
@ -281,6 +281,10 @@ class ChatViewModel @Inject constructor(
|
|||||||
conversationRepository.getRoom(token)
|
conversationRepository.getRoom(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun isMessageSaved(messageId: Long): Boolean {
|
||||||
|
return chatRepository.checkIfMessageIsSaved(messageId)
|
||||||
|
}
|
||||||
|
|
||||||
fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
|
fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
|
||||||
Log.d(TAG, "Remote server ${conversationModel.remoteServer}")
|
Log.d(TAG, "Remote server ${conversationModel.remoteServer}")
|
||||||
if (conversationModel.remoteServer.isNullOrEmpty()) {
|
if (conversationModel.remoteServer.isNullOrEmpty()) {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
package com.nextcloud.talk.ui
|
package com.nextcloud.talk.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View.TEXT_ALIGNMENT_VIEW_START
|
import android.view.View.TEXT_ALIGNMENT_VIEW_START
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
@ -23,6 +24,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -46,6 +48,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -68,15 +71,18 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.StrokeCap
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.colorResource
|
import androidx.compose.ui.res.colorResource
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import androidx.emoji2.widget.EmojiTextView
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.asFlow
|
import androidx.lifecycle.asFlow
|
||||||
import autodagger.AutoInjector
|
import autodagger.AutoInjector
|
||||||
@ -84,6 +90,8 @@ import coil.compose.AsyncImage
|
|||||||
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
import com.elyeproj.loaderviewlibrary.LoaderImageView
|
||||||
import com.elyeproj.loaderviewlibrary.LoaderTextView
|
import com.elyeproj.loaderviewlibrary.LoaderTextView
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.activities.MainActivity
|
||||||
|
import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder.Companion.KEY_MIMETYPE
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||||
import com.nextcloud.talk.chat.data.model.ChatMessage
|
import com.nextcloud.talk.chat.data.model.ChatMessage
|
||||||
@ -99,7 +107,9 @@ import com.nextcloud.talk.models.json.opengraph.Reference
|
|||||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||||
import com.nextcloud.talk.users.UserManager
|
import com.nextcloud.talk.users.UserManager
|
||||||
import com.nextcloud.talk.utils.DateUtils
|
import com.nextcloud.talk.utils.DateUtils
|
||||||
|
import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
|
||||||
import com.nextcloud.talk.utils.message.MessageUtils
|
import com.nextcloud.talk.utils.message.MessageUtils
|
||||||
|
import com.nextcloud.talk.utils.preview.ComposePreviewUtils
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.osmdroid.config.Configuration
|
import org.osmdroid.config.Configuration
|
||||||
@ -116,41 +126,54 @@ import kotlin.random.Random
|
|||||||
@Suppress("FunctionNaming", "TooManyFunctions", "LongMethod", "StaticFieldLeak", "LargeClass")
|
@Suppress("FunctionNaming", "TooManyFunctions", "LongMethod", "StaticFieldLeak", "LargeClass")
|
||||||
class ComposeChatAdapter(
|
class ComposeChatAdapter(
|
||||||
private var messagesJson: List<ChatMessageJson>? = null,
|
private var messagesJson: List<ChatMessageJson>? = null,
|
||||||
private var messageId: String? = null
|
private var messageId: String? = null,
|
||||||
|
private val utils: ComposePreviewUtils? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
interface PreviewAble {
|
||||||
|
val viewThemeUtils: ViewThemeUtils
|
||||||
|
val messageUtils: MessageUtils
|
||||||
|
val contactsViewModel: ContactsViewModel
|
||||||
|
val chatViewModel: ChatViewModel
|
||||||
|
val context: Context
|
||||||
|
val userManager: UserManager
|
||||||
|
}
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication::class)
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
inner class ComposeChatAdapterViewModel : ViewModel() {
|
inner class ComposeChatAdapterViewModel : ViewModel(), PreviewAble {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewThemeUtils: ViewThemeUtils
|
override lateinit var viewThemeUtils: ViewThemeUtils
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var messageUtils: MessageUtils
|
override lateinit var messageUtils: MessageUtils
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var contactsViewModel: ContactsViewModel
|
override lateinit var contactsViewModel: ContactsViewModel
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var chatViewModel: ChatViewModel
|
override lateinit var chatViewModel: ChatViewModel
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var context: Context
|
override lateinit var context: Context
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var userManager: UserManager
|
override lateinit var userManager: UserManager
|
||||||
|
|
||||||
val items = mutableStateListOf<ChatMessage>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
sharedApplication!!.componentApplication.inject(this)
|
sharedApplication?.componentApplication?.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentUser: User = userManager.currentUser.blockingGet()
|
|
||||||
val colorScheme = viewThemeUtils.getColorScheme(context)
|
|
||||||
val highEmphasisColorInt = context.resources.getColor(R.color.high_emphasis_text, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class ComposeChatAdapterPreviewViewModel(
|
||||||
|
override val viewThemeUtils: ViewThemeUtils,
|
||||||
|
override val messageUtils: MessageUtils,
|
||||||
|
override val contactsViewModel: ContactsViewModel,
|
||||||
|
override val chatViewModel: ChatViewModel,
|
||||||
|
override val context: Context,
|
||||||
|
override val userManager: UserManager
|
||||||
|
) : ViewModel(), PreviewAble
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG: String = ComposeChatAdapter::class.java.simpleName
|
val TAG: String = ComposeChatAdapter::class.java.simpleName
|
||||||
private val REGULAR_TEXT_SIZE = 16.sp
|
private val REGULAR_TEXT_SIZE = 16.sp
|
||||||
@ -173,21 +196,48 @@ class ComposeChatAdapter(
|
|||||||
|
|
||||||
private var incomingShape: RoundedCornerShape = RoundedCornerShape(2.dp, 20.dp, 20.dp, 20.dp)
|
private var incomingShape: RoundedCornerShape = RoundedCornerShape(2.dp, 20.dp, 20.dp, 20.dp)
|
||||||
private var outgoingShape: RoundedCornerShape = RoundedCornerShape(20.dp, 2.dp, 20.dp, 20.dp)
|
private var outgoingShape: RoundedCornerShape = RoundedCornerShape(20.dp, 2.dp, 20.dp, 20.dp)
|
||||||
private val viewModel = ComposeChatAdapterViewModel()
|
|
||||||
|
val viewModel: PreviewAble =
|
||||||
|
if (utils != null) {
|
||||||
|
ComposeChatAdapterPreviewViewModel(
|
||||||
|
utils.viewThemeUtils,
|
||||||
|
utils.messageUtils,
|
||||||
|
utils.contactsViewModel,
|
||||||
|
utils.chatViewModel,
|
||||||
|
utils.context,
|
||||||
|
utils.userManager
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ComposeChatAdapterViewModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
val items = mutableStateListOf<ChatMessage>()
|
||||||
|
val currentUser: User = viewModel.userManager.currentUser.blockingGet()
|
||||||
|
val colorScheme = viewModel.viewThemeUtils.getColorScheme(viewModel.context)
|
||||||
|
val highEmphasisColorInt = viewModel.context.resources.getColor(R.color.high_emphasis_text, null)
|
||||||
|
|
||||||
|
fun Context.findMainActivityOrNull(): MainActivity? {
|
||||||
|
var context = this
|
||||||
|
while (context is ContextWrapper) {
|
||||||
|
if (context is MainActivity) return context
|
||||||
|
context = context.baseContext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
fun addMessages(messages: MutableList<ChatMessage>, append: Boolean) {
|
fun addMessages(messages: MutableList<ChatMessage>, append: Boolean) {
|
||||||
if (messages.isEmpty()) return
|
if (messages.isEmpty()) return
|
||||||
|
|
||||||
val processedMessages = messages.toMutableList()
|
val processedMessages = messages.toMutableList()
|
||||||
if (viewModel.items.isNotEmpty()) {
|
if (items.isNotEmpty()) {
|
||||||
if (append) {
|
if (append) {
|
||||||
processedMessages.add(viewModel.items.first())
|
processedMessages.add(items.first())
|
||||||
} else {
|
} else {
|
||||||
processedMessages.add(viewModel.items.last())
|
processedMessages.add(items.last())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (append) viewModel.items.addAll(processedMessages) else viewModel.items.addAll(0, processedMessages)
|
if (append) items.addAll(processedMessages) else items.addAll(0, processedMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@ -202,7 +252,7 @@ class ComposeChatAdapter(
|
|||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
stickyHeader {
|
stickyHeader {
|
||||||
if (viewModel.items.size == 0) {
|
if (items.size == 0) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
@ -211,11 +261,11 @@ class ComposeChatAdapter(
|
|||||||
ShimmerGroup()
|
ShimmerGroup()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val timestamp = viewModel.items[listState.firstVisibleItemIndex].timestamp
|
val timestamp = items[listState.firstVisibleItemIndex].timestamp
|
||||||
val dateString = formatTime(timestamp * LONG_1000)
|
val dateString = formatTime(timestamp * LONG_1000)
|
||||||
val color = Color(viewModel.highEmphasisColorInt)
|
val color = Color(highEmphasisColorInt)
|
||||||
val backgroundColor =
|
val backgroundColor =
|
||||||
viewModel.context.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
|
LocalContext.current.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.Absolute.Center,
|
horizontalArrangement = Arrangement.Absolute.Center,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
@ -229,8 +279,8 @@ class ComposeChatAdapter(
|
|||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.shadow(
|
.shadow(
|
||||||
16.dp,
|
16.dp,
|
||||||
spotColor = viewModel.colorScheme.primary,
|
spotColor = colorScheme.primary,
|
||||||
ambientColor = viewModel.colorScheme.primary
|
ambientColor = colorScheme.primary
|
||||||
)
|
)
|
||||||
.background(color = Color(backgroundColor), shape = RoundedCornerShape(8.dp))
|
.background(color = Color(backgroundColor), shape = RoundedCornerShape(8.dp))
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
@ -240,8 +290,8 @@ class ComposeChatAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(viewModel.items) { message ->
|
items(items) { message ->
|
||||||
message.activeUser = viewModel.currentUser
|
message.activeUser = currentUser
|
||||||
when (val type = message.getCalculateMessageType()) {
|
when (val type = message.getCalculateMessageType()) {
|
||||||
ChatMessage.MessageType.SYSTEM_MESSAGE -> {
|
ChatMessage.MessageType.SYSTEM_MESSAGE -> {
|
||||||
if (!message.shouldFilter()) {
|
if (!message.shouldFilter()) {
|
||||||
@ -284,7 +334,7 @@ class ComposeChatAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageId != null && viewModel.items.size > 0) {
|
if (messageId != null && items.size > 0) {
|
||||||
LaunchedEffect(Dispatchers.Main) {
|
LaunchedEffect(Dispatchers.Main) {
|
||||||
delay(SCROLL_DELAY)
|
delay(SCROLL_DELAY)
|
||||||
val pos = searchMessages(messageId!!)
|
val pos = searchMessages(messageId!!)
|
||||||
@ -326,7 +376,7 @@ class ComposeChatAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun searchMessages(searchId: String): Int {
|
private fun searchMessages(searchId: String): Int {
|
||||||
viewModel.items.forEachIndexed { index, message ->
|
items.forEachIndexed { index, message ->
|
||||||
if (message.id == searchId) return index
|
if (message.id == searchId) return index
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
@ -380,18 +430,18 @@ class ComposeChatAdapter(
|
|||||||
@Composable
|
@Composable
|
||||||
(RowScope.() -> Unit)
|
(RowScope.() -> Unit)
|
||||||
) {
|
) {
|
||||||
val incoming = message.actorId != viewModel.currentUser.userId
|
val incoming = message.actorId != currentUser.userId
|
||||||
val color = if (incoming) {
|
val color = if (incoming) {
|
||||||
if (message.isDeleted) {
|
if (message.isDeleted) {
|
||||||
viewModel.context.resources.getColor(R.color.bg_message_list_incoming_bubble_deleted, null)
|
LocalContext.current.resources.getColor(R.color.bg_message_list_incoming_bubble_deleted, null)
|
||||||
} else {
|
} else {
|
||||||
viewModel.context.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
|
LocalContext.current.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (message.isDeleted) {
|
if (message.isDeleted) {
|
||||||
ColorUtils.setAlphaComponent(viewModel.colorScheme.surfaceVariant.toArgb(), HALF_OPACITY)
|
ColorUtils.setAlphaComponent(colorScheme.surfaceVariant.toArgb(), HALF_OPACITY)
|
||||||
} else {
|
} else {
|
||||||
viewModel.colorScheme.surfaceVariant.toArgb()
|
colorScheme.surfaceVariant.toArgb()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val shape = if (incoming) incomingShape else outgoingShape
|
val shape = if (incoming) incomingShape else outgoingShape
|
||||||
@ -405,7 +455,7 @@ class ComposeChatAdapter(
|
|||||||
if (incoming) {
|
if (incoming) {
|
||||||
val imageUri = message.actorId?.let { viewModel.contactsViewModel.getImageUri(it, true) }
|
val imageUri = message.actorId?.let { viewModel.contactsViewModel.getImageUri(it, true) }
|
||||||
val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
|
val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
|
||||||
val loadedImage = loadImage(imageUri, viewModel.context, errorPlaceholderImage)
|
val loadedImage = loadImage(imageUri, LocalContext.current, errorPlaceholderImage)
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = loadedImage,
|
model = loadedImage,
|
||||||
contentDescription = stringResource(R.string.user_avatar),
|
contentDescription = stringResource(R.string.user_avatar),
|
||||||
@ -427,13 +477,13 @@ class ComposeChatAdapter(
|
|||||||
color = Color(color),
|
color = Color(color),
|
||||||
shape = shape
|
shape = shape
|
||||||
) {
|
) {
|
||||||
val timeString = DateUtils(viewModel.context).getLocalTimeStringFromTimestamp(message.timestamp)
|
val timeString = DateUtils(LocalContext.current).getLocalTimeStringFromTimestamp(message.timestamp)
|
||||||
val modifier = if (includePadding) Modifier.padding(8.dp, 4.dp, 8.dp, 4.dp) else Modifier
|
val modifier = if (includePadding) Modifier.padding(8.dp, 4.dp, 8.dp, 4.dp) else Modifier
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
if (message.parentMessageId != null && !message.isDeleted && messagesJson != null) {
|
if (message.parentMessageId != null && !message.isDeleted && messagesJson != null) {
|
||||||
messagesJson!!
|
messagesJson!!
|
||||||
.find { it.parentMessage?.id == message.parentMessageId }
|
.find { it.parentMessage?.id == message.parentMessageId }
|
||||||
?.parentMessage!!.asModel().let { CommonMessageQuote(viewModel.context, it) }
|
?.parentMessage!!.asModel().let { CommonMessageQuote(LocalContext.current, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (incoming) {
|
if (incoming) {
|
||||||
@ -470,8 +520,8 @@ class ComposeChatAdapter(
|
|||||||
private fun Modifier.withCustomAnimation(incoming: Boolean): Modifier {
|
private fun Modifier.withCustomAnimation(incoming: Boolean): Modifier {
|
||||||
val infiniteTransition = rememberInfiniteTransition()
|
val infiniteTransition = rememberInfiniteTransition()
|
||||||
val borderColor by infiniteTransition.animateColor(
|
val borderColor by infiniteTransition.animateColor(
|
||||||
initialValue = viewModel.colorScheme.primary,
|
initialValue = colorScheme.primary,
|
||||||
targetValue = viewModel.colorScheme.background,
|
targetValue = colorScheme.background,
|
||||||
animationSpec = infiniteRepeatable(
|
animationSpec = infiniteRepeatable(
|
||||||
animation = tween(ANIMATED_BLINK, easing = LinearEasing),
|
animation = tween(ANIMATED_BLINK, easing = LinearEasing),
|
||||||
repeatMode = RepeatMode.Reverse
|
repeatMode = RepeatMode.Reverse
|
||||||
@ -542,7 +592,7 @@ class ComposeChatAdapter(
|
|||||||
LoaderTextView(ctx).apply {
|
LoaderTextView(ctx).apply {
|
||||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
||||||
val color = if (outgoing) {
|
val color = if (outgoing) {
|
||||||
viewModel.colorScheme.primary.toArgb()
|
colorScheme.primary.toArgb()
|
||||||
} else {
|
} else {
|
||||||
resources.getColor(R.color.nc_shimmer_default_color, null)
|
resources.getColor(R.color.nc_shimmer_default_color, null)
|
||||||
}
|
}
|
||||||
@ -562,7 +612,7 @@ class ComposeChatAdapter(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun EnrichedText(message: ChatMessage) {
|
private fun EnrichedText(message: ChatMessage) {
|
||||||
AndroidView(factory = { ctx ->
|
AndroidView(factory = { ctx ->
|
||||||
val incoming = message.actorId != viewModel.currentUser.userId
|
val incoming = message.actorId != currentUser.userId
|
||||||
var processedMessageText = viewModel.messageUtils.enrichChatMessageText(
|
var processedMessageText = viewModel.messageUtils.enrichChatMessageText(
|
||||||
ctx,
|
ctx,
|
||||||
message,
|
message,
|
||||||
@ -574,7 +624,7 @@ class ComposeChatAdapter(
|
|||||||
ctx, viewModel.viewThemeUtils, processedMessageText!!, message, null
|
ctx, viewModel.viewThemeUtils, processedMessageText!!, message, null
|
||||||
)
|
)
|
||||||
|
|
||||||
androidx.emoji2.widget.EmojiTextView(ctx).apply {
|
EmojiTextView(ctx).apply {
|
||||||
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
|
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
|
||||||
setLineSpacing(0F, LINE_SPACING)
|
setLineSpacing(0F, LINE_SPACING)
|
||||||
textAlignment = TEXT_ALIGNMENT_VIEW_START
|
textAlignment = TEXT_ALIGNMENT_VIEW_START
|
||||||
@ -592,14 +642,14 @@ class ComposeChatAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SystemMessage(message: ChatMessage) {
|
fun SystemMessage(message: ChatMessage) {
|
||||||
val similarMessages = sharedApplication!!.resources.getQuantityString(
|
val similarMessages = sharedApplication!!.resources.getQuantityString(
|
||||||
R.plurals.see_similar_system_messages,
|
R.plurals.see_similar_system_messages,
|
||||||
message.expandableChildrenAmount,
|
message.expandableChildrenAmount,
|
||||||
message.expandableChildrenAmount
|
message.expandableChildrenAmount
|
||||||
)
|
)
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
val timeString = DateUtils(viewModel.context).getLocalTimeStringFromTimestamp(message.timestamp)
|
val timeString = DateUtils(LocalContext.current).getLocalTimeStringFromTimestamp(message.timestamp)
|
||||||
Row(horizontalArrangement = Arrangement.Absolute.Center, verticalAlignment = Alignment.CenterVertically) {
|
Row(horizontalArrangement = Arrangement.Absolute.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text(
|
Text(
|
||||||
@ -632,7 +682,7 @@ class ComposeChatAdapter(
|
|||||||
Text(
|
Text(
|
||||||
text,
|
text,
|
||||||
fontSize = AUTHOR_TEXT_SIZE,
|
fontSize = AUTHOR_TEXT_SIZE,
|
||||||
color = Color(viewModel.highEmphasisColorInt)
|
color = Color(highEmphasisColorInt)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -640,14 +690,15 @@ class ComposeChatAdapter(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ImageMessage(message: ChatMessage, state: MutableState<Boolean>) {
|
private fun ImageMessage(message: ChatMessage, state: MutableState<Boolean>) {
|
||||||
val hasCaption = (message.message != "{file}")
|
val hasCaption = (message.message != "{file}")
|
||||||
val incoming = message.actorId != viewModel.currentUser.userId
|
val incoming = message.actorId != currentUser.userId
|
||||||
val timeString = DateUtils(viewModel.context).getLocalTimeStringFromTimestamp(message.timestamp)
|
val timeString = DateUtils(LocalContext.current).getLocalTimeStringFromTimestamp(message.timestamp)
|
||||||
CommonMessageBody(message, includePadding = false, playAnimation = state.value) {
|
CommonMessageBody(message, includePadding = false, playAnimation = state.value) {
|
||||||
Column {
|
Column {
|
||||||
message.activeUser = viewModel.currentUser
|
message.activeUser = currentUser
|
||||||
val imageUri = message.imageUrl
|
val imageUri = message.imageUrl
|
||||||
val errorPlaceholderImage: Int = R.drawable.ic_mimetype_image
|
val mimetype = message.selectedIndividualHashMap!![KEY_MIMETYPE]
|
||||||
val loadedImage = load(imageUri, viewModel.context, errorPlaceholderImage)
|
val drawableResourceId = getDrawableResourceIdForMimeType(mimetype)
|
||||||
|
val loadedImage = load(imageUri, LocalContext.current, drawableResourceId)
|
||||||
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = loadedImage,
|
model = loadedImage,
|
||||||
@ -717,8 +768,8 @@ class ComposeChatAdapter(
|
|||||||
WaveformSeekBar(ctx).apply {
|
WaveformSeekBar(ctx).apply {
|
||||||
setWaveData(FloatArray(DEFAULT_WAVE_SIZE) { Random.nextFloat() }) // READ ONLY for now
|
setWaveData(FloatArray(DEFAULT_WAVE_SIZE) { Random.nextFloat() }) // READ ONLY for now
|
||||||
setColors(
|
setColors(
|
||||||
viewModel.colorScheme.inversePrimary.toArgb(),
|
colorScheme.inversePrimary.toArgb(),
|
||||||
viewModel.colorScheme.onPrimaryContainer.toArgb()
|
colorScheme.onPrimaryContainer.toArgb()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -793,8 +844,8 @@ class ComposeChatAdapter(
|
|||||||
private fun LinkMessage(message: ChatMessage, state: MutableState<Boolean>) {
|
private fun LinkMessage(message: ChatMessage, state: MutableState<Boolean>) {
|
||||||
val color = colorResource(R.color.high_emphasis_text)
|
val color = colorResource(R.color.high_emphasis_text)
|
||||||
viewModel.chatViewModel.getOpenGraph(
|
viewModel.chatViewModel.getOpenGraph(
|
||||||
viewModel.currentUser.getCredentials(),
|
currentUser.getCredentials(),
|
||||||
viewModel.currentUser.baseUrl!!,
|
currentUser.baseUrl!!,
|
||||||
message.extractedUrlToPreview!!
|
message.extractedUrlToPreview!!
|
||||||
)
|
)
|
||||||
CommonMessageBody(message, playAnimation = state.value) {
|
CommonMessageBody(message, playAnimation = state.value) {
|
||||||
@ -828,7 +879,7 @@ class ComposeChatAdapter(
|
|||||||
it.link?.let { Text(it, fontSize = TIME_TEXT_SIZE) }
|
it.link?.let { Text(it, fontSize = TIME_TEXT_SIZE) }
|
||||||
it.thumb?.let {
|
it.thumb?.let {
|
||||||
val errorPlaceholderImage: Int = R.drawable.ic_mimetype_image
|
val errorPlaceholderImage: Int = R.drawable.ic_mimetype_image
|
||||||
val loadedImage = loadImage(it, viewModel.context, errorPlaceholderImage)
|
val loadedImage = loadImage(it, LocalContext.current, errorPlaceholderImage)
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = loadedImage,
|
model = loadedImage,
|
||||||
contentDescription = stringResource(R.string.nc_sent_an_image),
|
contentDescription = stringResource(R.string.nc_sent_an_image),
|
||||||
@ -882,7 +933,7 @@ class ComposeChatAdapter(
|
|||||||
|
|
||||||
if (cardName?.isNotEmpty() == true) {
|
if (cardName?.isNotEmpty() == true) {
|
||||||
val cardDescription = String.format(
|
val cardDescription = String.format(
|
||||||
viewModel.context.resources.getString(R.string.deck_card_description),
|
LocalContext.current.resources.getString(R.string.deck_card_description),
|
||||||
stackName,
|
stackName,
|
||||||
boardName
|
boardName
|
||||||
)
|
)
|
||||||
@ -899,3 +950,44 @@ class ComposeChatAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, widthDp = 380, heightDp = 800)
|
||||||
|
@Composable
|
||||||
|
fun AllMessageTypesPreview() {
|
||||||
|
val previewUtils = ComposePreviewUtils.getInstance(LocalContext.current)
|
||||||
|
val adapter = remember { ComposeChatAdapter(messagesJson = null, messageId = null, previewUtils) }
|
||||||
|
|
||||||
|
val sampleMessages = remember {
|
||||||
|
listOf(
|
||||||
|
// Text Messages
|
||||||
|
ChatMessage().apply {
|
||||||
|
jsonMessageId = 1
|
||||||
|
actorId = "user1"
|
||||||
|
message = "I love Nextcloud"
|
||||||
|
timestamp = System.currentTimeMillis()
|
||||||
|
actorDisplayName = "User1"
|
||||||
|
messageType = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE.name
|
||||||
|
},
|
||||||
|
ChatMessage().apply {
|
||||||
|
jsonMessageId = 2
|
||||||
|
actorId = "user1_id"
|
||||||
|
message = "I love Nextcloud"
|
||||||
|
timestamp = System.currentTimeMillis()
|
||||||
|
actorDisplayName = "User2"
|
||||||
|
messageType = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(sampleMessages) { // Use LaunchedEffect or similar to update state once
|
||||||
|
if (adapter.items.isEmpty()) { // Prevent adding multiple times on recomposition
|
||||||
|
adapter.addMessages(sampleMessages.toMutableList(), append = false) // Add messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(colorScheme = adapter.colorScheme) { // Use the (potentially faked) color scheme
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) { // Provide a container
|
||||||
|
adapter.GetView() // Call the main Composable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,7 +7,10 @@
|
|||||||
|
|
||||||
package com.nextcloud.talk.ui.dialog
|
package com.nextcloud.talk.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -94,6 +97,15 @@ class ContextChatCompose(val bundle: Bundle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Context.requireActivity(): Activity {
|
||||||
|
var context = this
|
||||||
|
while (context is ContextWrapper) {
|
||||||
|
if (context is Activity) return context
|
||||||
|
context = context.baseContext
|
||||||
|
}
|
||||||
|
throw IllegalStateException("No activity was present but it is required.")
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GetDialogView(
|
fun GetDialogView(
|
||||||
shouldDismiss: MutableState<Boolean>,
|
shouldDismiss: MutableState<Boolean>,
|
||||||
@ -101,9 +113,11 @@ class ContextChatCompose(val bundle: Bundle) {
|
|||||||
contextViewModel: ContextChatComposeViewModel = ContextChatComposeViewModel()
|
contextViewModel: ContextChatComposeViewModel = ContextChatComposeViewModel()
|
||||||
) {
|
) {
|
||||||
if (shouldDismiss.value) {
|
if (shouldDismiss.value) {
|
||||||
|
context.requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||||
val colorScheme = contextViewModel.viewThemeUtils.getColorScheme(context)
|
val colorScheme = contextViewModel.viewThemeUtils.getColorScheme(context)
|
||||||
MaterialTheme(colorScheme) {
|
MaterialTheme(colorScheme) {
|
||||||
Dialog(
|
Dialog(
|
||||||
|
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk - Android Client
|
||||||
|
*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.utils.preview
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.github.aurae.retrofit2.LoganSquareConverterFactory
|
||||||
|
import com.nextcloud.android.common.ui.color.ColorUtil
|
||||||
|
import com.nextcloud.android.common.ui.theme.MaterialSchemes
|
||||||
|
import com.nextcloud.android.common.ui.theme.utils.AndroidViewThemeUtils
|
||||||
|
import com.nextcloud.android.common.ui.theme.utils.AndroidXViewThemeUtils
|
||||||
|
import com.nextcloud.android.common.ui.theme.utils.DialogViewThemeUtils
|
||||||
|
import com.nextcloud.android.common.ui.theme.utils.MaterialViewThemeUtils
|
||||||
|
import com.nextcloud.talk.api.NcApi
|
||||||
|
import com.nextcloud.talk.api.NcApiCoroutines
|
||||||
|
import com.nextcloud.talk.chat.data.ChatMessageRepository
|
||||||
|
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
|
||||||
|
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
|
||||||
|
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
|
||||||
|
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
|
||||||
|
import com.nextcloud.talk.chat.data.network.RetrofitChatNetwork
|
||||||
|
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
|
||||||
|
import com.nextcloud.talk.contacts.ContactsRepository
|
||||||
|
import com.nextcloud.talk.contacts.ContactsRepositoryImpl
|
||||||
|
import com.nextcloud.talk.contacts.ContactsViewModel
|
||||||
|
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
|
||||||
|
import com.nextcloud.talk.conversationlist.data.network.ConversationsNetworkDataSource
|
||||||
|
import com.nextcloud.talk.conversationlist.data.network.OfflineFirstConversationsRepository
|
||||||
|
import com.nextcloud.talk.conversationlist.data.network.RetrofitConversationsNetwork
|
||||||
|
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
|
||||||
|
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
|
||||||
|
import com.nextcloud.talk.data.database.dao.ConversationsDao
|
||||||
|
import com.nextcloud.talk.data.network.NetworkMonitor
|
||||||
|
import com.nextcloud.talk.data.network.NetworkMonitorImpl
|
||||||
|
import com.nextcloud.talk.data.user.UsersDao
|
||||||
|
import com.nextcloud.talk.data.user.UsersRepository
|
||||||
|
import com.nextcloud.talk.data.user.UsersRepositoryImpl
|
||||||
|
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
|
||||||
|
import com.nextcloud.talk.repositories.reactions.ReactionsRepositoryImpl
|
||||||
|
import com.nextcloud.talk.ui.theme.MaterialSchemesProviderImpl
|
||||||
|
import com.nextcloud.talk.ui.theme.TalkSpecificViewThemeUtils
|
||||||
|
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||||
|
import com.nextcloud.talk.users.UserManager
|
||||||
|
import com.nextcloud.talk.utils.database.user.CurrentUserProviderImpl
|
||||||
|
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||||
|
import com.nextcloud.talk.utils.message.MessageUtils
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
|
import com.nextcloud.talk.utils.preferences.AppPreferencesImpl
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO - basically a reimplementation of common dependencies for use in Previewing Advanced Compose Views
|
||||||
|
* It's a hard coded Dependency Injector
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ComposePreviewUtils private constructor(context: Context) {
|
||||||
|
private val mContext = context
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getInstance(context: Context) = ComposePreviewUtils(context)
|
||||||
|
val TAG: String = ComposePreviewUtils::class.java.simpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val appPreferences: AppPreferences
|
||||||
|
get() = AppPreferencesImpl(mContext)
|
||||||
|
|
||||||
|
val context: Context = mContext
|
||||||
|
|
||||||
|
val userRepository: UsersRepository
|
||||||
|
get() = UsersRepositoryImpl(usersDao)
|
||||||
|
|
||||||
|
val userManager: UserManager
|
||||||
|
get() = UserManager(userRepository)
|
||||||
|
|
||||||
|
val userProvider: CurrentUserProviderNew
|
||||||
|
get() = CurrentUserProviderImpl(userManager)
|
||||||
|
|
||||||
|
val colorUtil: ColorUtil
|
||||||
|
get() = ColorUtil(mContext)
|
||||||
|
|
||||||
|
val materialScheme: MaterialSchemes
|
||||||
|
get() = MaterialSchemesProviderImpl(userProvider, colorUtil).getMaterialSchemesForCurrentUser()
|
||||||
|
|
||||||
|
val viewThemeUtils: ViewThemeUtils
|
||||||
|
get() {
|
||||||
|
val android = AndroidViewThemeUtils(materialScheme, colorUtil)
|
||||||
|
val material = MaterialViewThemeUtils(materialScheme, colorUtil)
|
||||||
|
val androidx = AndroidXViewThemeUtils(materialScheme, android)
|
||||||
|
val talk = TalkSpecificViewThemeUtils(materialScheme, androidx)
|
||||||
|
val dialog = DialogViewThemeUtils(materialScheme)
|
||||||
|
return ViewThemeUtils(materialScheme, android, material, androidx, talk, dialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
val messageUtils: MessageUtils
|
||||||
|
get() = MessageUtils(mContext)
|
||||||
|
|
||||||
|
val retrofit: Retrofit
|
||||||
|
get() {
|
||||||
|
val retrofitBuilder = Retrofit.Builder()
|
||||||
|
.client(OkHttpClient.Builder().build())
|
||||||
|
.baseUrl("https://nextcloud.com")
|
||||||
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
|
||||||
|
.addConverterFactory(LoganSquareConverterFactory.create())
|
||||||
|
|
||||||
|
return retrofitBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val ncApi: NcApi
|
||||||
|
get() = retrofit.create(NcApi::class.java)
|
||||||
|
|
||||||
|
val ncApiCoroutines: NcApiCoroutines
|
||||||
|
get() = retrofit.create(NcApiCoroutines::class.java)
|
||||||
|
|
||||||
|
val chatNetworkDataSource: ChatNetworkDataSource
|
||||||
|
get() = RetrofitChatNetwork(ncApi, ncApiCoroutines)
|
||||||
|
|
||||||
|
val usersDao: UsersDao
|
||||||
|
get() = DummyUserDaoImpl()
|
||||||
|
|
||||||
|
val chatMessagesDao: ChatMessagesDao
|
||||||
|
get() = DummyChatMessagesDaoImpl()
|
||||||
|
|
||||||
|
val chatBlocksDao: ChatBlocksDao
|
||||||
|
get() = DummyChatBlocksDaoImpl()
|
||||||
|
|
||||||
|
val conversationsDao: ConversationsDao
|
||||||
|
get() = DummyConversationDaoImpl()
|
||||||
|
|
||||||
|
val networkMonitor: NetworkMonitor
|
||||||
|
get() = NetworkMonitorImpl(mContext)
|
||||||
|
|
||||||
|
val chatRepository: ChatMessageRepository
|
||||||
|
get() = OfflineFirstChatRepository(
|
||||||
|
chatMessagesDao,
|
||||||
|
chatBlocksDao,
|
||||||
|
chatNetworkDataSource,
|
||||||
|
networkMonitor,
|
||||||
|
userProvider
|
||||||
|
)
|
||||||
|
|
||||||
|
val conversationNetworkDataSource: ConversationsNetworkDataSource
|
||||||
|
get() = RetrofitConversationsNetwork(ncApi)
|
||||||
|
|
||||||
|
val conversationRepository: OfflineConversationsRepository
|
||||||
|
get() = OfflineFirstConversationsRepository(
|
||||||
|
conversationsDao,
|
||||||
|
conversationNetworkDataSource,
|
||||||
|
chatNetworkDataSource,
|
||||||
|
networkMonitor,
|
||||||
|
userProvider
|
||||||
|
)
|
||||||
|
|
||||||
|
val reactionsRepository: ReactionsRepository
|
||||||
|
get() = ReactionsRepositoryImpl(ncApi, userProvider, chatMessagesDao)
|
||||||
|
|
||||||
|
val mediaRecorderManager: MediaRecorderManager
|
||||||
|
get() = MediaRecorderManager()
|
||||||
|
|
||||||
|
val audioFocusRequestManager: AudioFocusRequestManager
|
||||||
|
get() = AudioFocusRequestManager(mContext)
|
||||||
|
|
||||||
|
val chatViewModel: ChatViewModel
|
||||||
|
get() = ChatViewModel(
|
||||||
|
appPreferences,
|
||||||
|
chatNetworkDataSource,
|
||||||
|
chatRepository,
|
||||||
|
conversationRepository,
|
||||||
|
reactionsRepository,
|
||||||
|
mediaRecorderManager,
|
||||||
|
audioFocusRequestManager,
|
||||||
|
userProvider
|
||||||
|
)
|
||||||
|
|
||||||
|
val contactsRepository: ContactsRepository
|
||||||
|
get() = ContactsRepositoryImpl(ncApiCoroutines, userProvider)
|
||||||
|
|
||||||
|
val contactsViewModel: ContactsViewModel
|
||||||
|
get() = ContactsViewModel(contactsRepository)
|
||||||
|
}
|
@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* Nextcloud Talk - Android Client
|
||||||
|
*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.utils.preview
|
||||||
|
|
||||||
|
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
|
||||||
|
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
|
||||||
|
import com.nextcloud.talk.data.database.dao.ConversationsDao
|
||||||
|
import com.nextcloud.talk.data.database.model.ChatBlockEntity
|
||||||
|
import com.nextcloud.talk.data.database.model.ChatMessageEntity
|
||||||
|
import com.nextcloud.talk.data.database.model.ConversationEntity
|
||||||
|
import com.nextcloud.talk.data.user.UsersDao
|
||||||
|
import com.nextcloud.talk.data.user.model.UserEntity
|
||||||
|
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||||
|
import io.reactivex.Maybe
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
|
class DummyChatMessagesDaoImpl : ChatMessagesDao {
|
||||||
|
override fun getNewestMessageId(internalConversationId: String): Long = 0L
|
||||||
|
|
||||||
|
override fun getMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> = flowOf()
|
||||||
|
|
||||||
|
override fun getTempMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> =
|
||||||
|
flowOf()
|
||||||
|
|
||||||
|
override fun getTempMessageForConversation(
|
||||||
|
internalConversationId: String,
|
||||||
|
referenceId: String
|
||||||
|
): Flow<ChatMessageEntity> = flowOf()
|
||||||
|
|
||||||
|
override suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>) { /* */ }
|
||||||
|
|
||||||
|
override suspend fun upsertChatMessage(chatMessage: ChatMessageEntity) { /* */ }
|
||||||
|
|
||||||
|
override fun getChatMessageForConversation(
|
||||||
|
internalConversationId: String,
|
||||||
|
messageId: Long
|
||||||
|
): Flow<ChatMessageEntity> = flowOf()
|
||||||
|
|
||||||
|
override fun deleteChatMessages(internalIds: List<String>) { /* */ }
|
||||||
|
|
||||||
|
override fun deleteTempChatMessages(internalConversationId: String, referenceIds: List<String>) { /* */ }
|
||||||
|
|
||||||
|
override fun updateChatMessage(message: ChatMessageEntity) { /* */ }
|
||||||
|
|
||||||
|
override fun getMessagesFromIds(messageIds: List<Long>): Flow<List<ChatMessageEntity>> = flowOf()
|
||||||
|
|
||||||
|
override fun getMessagesForConversationSince(
|
||||||
|
internalConversationId: String,
|
||||||
|
messageId: Long
|
||||||
|
): Flow<List<ChatMessageEntity>> = flowOf()
|
||||||
|
|
||||||
|
override fun getMessagesForConversationBefore(
|
||||||
|
internalConversationId: String,
|
||||||
|
messageId: Long,
|
||||||
|
limit: Int
|
||||||
|
): Flow<List<ChatMessageEntity>> = flowOf()
|
||||||
|
|
||||||
|
override fun getMessagesForConversationBeforeAndEqual(
|
||||||
|
internalConversationId: String,
|
||||||
|
messageId: Long,
|
||||||
|
limit: Int
|
||||||
|
): Flow<List<ChatMessageEntity>> = flowOf()
|
||||||
|
|
||||||
|
override fun getCountBetweenMessageIds(
|
||||||
|
internalConversationId: String,
|
||||||
|
oldestMessageId: Long,
|
||||||
|
newestMessageId: Long
|
||||||
|
): Int = 0
|
||||||
|
|
||||||
|
override fun clearAllMessagesForUser(pattern: String) { /* */ }
|
||||||
|
|
||||||
|
override fun deleteMessagesOlderThan(internalConversationId: String, messageId: Long) { /* */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyUserDaoImpl : UsersDao() {
|
||||||
|
private val dummyUsers = mutableListOf(
|
||||||
|
UserEntity(1L, "user1_id", "user1", "server1", "1"),
|
||||||
|
UserEntity(2L, "user2_id", "user2", "server1", "2"),
|
||||||
|
UserEntity(0L, "user3_id", "user3", "server2", "3")
|
||||||
|
)
|
||||||
|
private var activeUserId: Long? = 1L
|
||||||
|
|
||||||
|
override fun getActiveUser(): Maybe<UserEntity> {
|
||||||
|
return Maybe.fromCallable { dummyUsers.find { it.id == activeUserId && !it.scheduledForDeletion } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActiveUserObservable(): Observable<UserEntity> {
|
||||||
|
return Observable.fromCallable { dummyUsers.find { it.id == activeUserId && !it.scheduledForDeletion } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActiveUserSynchronously(): UserEntity? {
|
||||||
|
return dummyUsers.find { it.id == activeUserId && !it.scheduledForDeletion }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteUser(user: UserEntity): Int {
|
||||||
|
val initialSize = dummyUsers.size
|
||||||
|
dummyUsers.removeIf { it.id == user.id }
|
||||||
|
return initialSize - dummyUsers.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateUser(user: UserEntity): Int {
|
||||||
|
val index = dummyUsers.indexOfFirst { it.id == user.id }
|
||||||
|
return if (index != -1) {
|
||||||
|
dummyUsers[index] = user
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveUser(user: UserEntity): Long {
|
||||||
|
val newUser = user.copy(id = dummyUsers.size + 1L)
|
||||||
|
dummyUsers.add(newUser)
|
||||||
|
return newUser.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveUsers(vararg users: UserEntity): List<Long> {
|
||||||
|
return users.map { saveUser(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUsers(): Single<List<UserEntity>> {
|
||||||
|
return Single.just(dummyUsers.filter { !it.scheduledForDeletion })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserWithId(id: Long): Maybe<UserEntity> {
|
||||||
|
return Maybe.fromCallable { dummyUsers.find { it.id == id } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserWithIdNotScheduledForDeletion(id: Long): Maybe<UserEntity> {
|
||||||
|
return Maybe.fromCallable { dummyUsers.find { it.id == id && !it.scheduledForDeletion } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserWithUserId(userId: String): Maybe<UserEntity> {
|
||||||
|
return Maybe.fromCallable { dummyUsers.find { it.userId == userId } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUsersScheduledForDeletion(): Single<List<UserEntity>> {
|
||||||
|
return Single.just(dummyUsers.filter { it.scheduledForDeletion })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUsersNotScheduledForDeletion(): Single<List<UserEntity>> {
|
||||||
|
return Single.just(dummyUsers.filter { !it.scheduledForDeletion })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserWithUsernameAndServer(username: String, server: String): Maybe<UserEntity> {
|
||||||
|
return Maybe.fromCallable { dummyUsers.find { it.username == username } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUserAsActiveWithId(id: Long): Int {
|
||||||
|
activeUserId = id
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updatePushState(id: Long, state: PushConfigurationState): Single<Int> {
|
||||||
|
val index = dummyUsers.indexOfFirst { it.id == id }
|
||||||
|
return if (index != -1) {
|
||||||
|
dummyUsers[index] = dummyUsers[index]
|
||||||
|
Single.just(1)
|
||||||
|
} else {
|
||||||
|
Single.just(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyConversationDaoImpl : ConversationsDao {
|
||||||
|
override fun getConversationsForUser(accountId: Long): Flow<List<ConversationEntity>> = flowOf()
|
||||||
|
|
||||||
|
override fun getConversationForUser(accountId: Long, token: String): Flow<ConversationEntity?> = flowOf()
|
||||||
|
|
||||||
|
override fun upsertConversations(conversationEntities: List<ConversationEntity>) { /* */ }
|
||||||
|
|
||||||
|
override fun deleteConversations(conversationIds: List<String>) { /* */ }
|
||||||
|
|
||||||
|
override fun updateConversation(conversationEntity: ConversationEntity) { /* */ }
|
||||||
|
|
||||||
|
override fun clearAllConversationsForUser(accountId: Long) { /* */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyChatBlocksDaoImpl : ChatBlocksDao {
|
||||||
|
override fun deleteChatBlocks(blocks: List<ChatBlockEntity>) { /* */ }
|
||||||
|
|
||||||
|
override fun getChatBlocks(internalConversationId: String): Flow<List<ChatBlockEntity>> = flowOf()
|
||||||
|
|
||||||
|
override fun getChatBlocksContainingMessageId(
|
||||||
|
internalConversationId: String,
|
||||||
|
messageId: Long
|
||||||
|
): Flow<List<ChatBlockEntity?>> = flowOf()
|
||||||
|
|
||||||
|
override fun getConnectedChatBlocks(
|
||||||
|
internalConversationId: String,
|
||||||
|
oldestMessageId: Long,
|
||||||
|
newestMessageId: Long
|
||||||
|
): Flow<List<ChatBlockEntity>> = flowOf()
|
||||||
|
|
||||||
|
override suspend fun upsertChatBlock(chatBlock: ChatBlockEntity) { /* */ }
|
||||||
|
|
||||||
|
override fun clearChatBlocksForUser(pattern: String) { /* */ }
|
||||||
|
|
||||||
|
override fun deleteChatBlocksOlderThan(internalConversationId: String, messageId: Long) { /* */ }
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user