use repository in MessageInputViewModel instead datasource

(as datasources should be only used in repositories)

use coroutines instead RxJava for api calls triggered by MessageInputViewModel

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2024-11-06 15:03:22 +01:00
parent c5c55e8c32
commit d335f4f371
No known key found for this signature in database
GPG key ID: C793F8B59F43CE7B
10 changed files with 171 additions and 149 deletions

View file

@ -388,12 +388,6 @@ public interface NcApi {
@Field("referenceId") String referenceId
);
@FormUrlEncoded
@PUT
Observable<ChatOverallSingleMessage> editChatMessage(@Header("Authorization") String authorization,
@Url String url,
@Field("message") String message);
@GET
Observable<Response<ChatShareOverall>> getSharedItems(
@Header("Authorization") String authorization,

View file

@ -8,6 +8,7 @@
package com.nextcloud.talk.api
import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.AddParticipantOverall
@ -116,4 +117,24 @@ interface NcApiCoroutines {
@DELETE
suspend fun unarchiveConversation(@Header("Authorization") authorization: String, @Url url: String): GenericOverall
@FormUrlEncoded
@POST
suspend fun sendChatMessage(
@Header("Authorization") authorization: String,
@Url url: String,
@Field("message") message: CharSequence,
@Field("actorDisplayName") actorDisplayName: String,
@Field("replyTo") replyTo: Int,
@Field("silent") sendWithoutNotification: Boolean,
@Field("referenceId") referenceId: String
): ChatOverallSingleMessage
@FormUrlEncoded
@PUT
suspend fun editChatMessage(
@Header("Authorization") authorization: String,
@Url url: String,
@Field("message") message: String
): ChatOverallSingleMessage
}

View file

@ -194,7 +194,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import retrofit2.HttpException
import java.io.File
import java.io.IOException
import java.net.HttpURLConnection
@ -762,18 +761,18 @@ class ChatActivity :
}
is MessageInputViewModel.SendChatMessageErrorState -> {
if (state.e is HttpException) {
val code = state.e.code()
if (code.toString().startsWith("2")) {
myFirstMessage = state.message
if (binding.unreadMessagesPopup.isShown) {
binding.unreadMessagesPopup.visibility = View.GONE
}
binding.messagesListView.smoothScrollToPosition(0)
}
}
// if (state.e is HttpException) {
// val code = state.e.code()
// if (code.toString().startsWith("2")) {
// myFirstMessage = state.message
//
// if (binding.unreadMessagesPopup.isShown) {
// binding.unreadMessagesPopup.visibility = View.GONE
// }
//
// binding.messagesListView.smoothScrollToPosition(0)
// }
// }
}
else -> {}

View file

@ -11,6 +11,7 @@ import android.os.Bundle
import com.nextcloud.talk.chat.data.io.LifecycleAwareManager
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
@ -69,4 +70,16 @@ interface ChatMessageRepository : LifecycleAwareManager {
* Destroys unused resources.
*/
fun handleChatOnBackPress()
suspend fun sendChatMessage(
credentials: String,
url: String,
message: CharSequence,
displayName: String,
replyTo: Int,
sendWithoutNotification: Boolean,
referenceId: String
): Flow<Result<ChatOverallSingleMessage>>
suspend fun editChatMessage(credentials: String, url: String, text: String): Flow<Result<ChatOverallSingleMessage>>
}

View file

@ -50,7 +50,7 @@ interface ChatNetworkDataSource {
): Observable<GenericOverall>
fun leaveRoom(credentials: String, url: String): Observable<GenericOverall>
fun sendChatMessage(
suspend fun sendChatMessage(
credentials: String,
url: String,
message: CharSequence,
@ -58,13 +58,15 @@ interface ChatNetworkDataSource {
replyTo: Int,
sendWithoutNotification: Boolean,
referenceId: String
): Observable<ChatOverallSingleMessage>
): ChatOverallSingleMessage
fun pullChatMessages(credentials: String, url: String, fieldMap: HashMap<String, Int>): Observable<Response<*>>
fun deleteChatMessage(credentials: String, url: String): Observable<ChatOverallSingleMessage>
fun createRoom(credentials: String, url: String, map: Map<String, String>): Observable<RoomOverall>
fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int): Observable<GenericOverall>
fun editChatMessage(credentials: String, url: String, text: String): Observable<ChatOverallSingleMessage>
suspend fun editChatMessage(credentials: String, url: String, text: String): ChatOverallSingleMessage
fun listBans(credentials: String, url: String): Observable<List<TalkBan>>
fun banActor(
credentials: String,

View file

@ -23,6 +23,7 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.preferences.AppPreferences
@ -36,6 +37,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -731,6 +733,50 @@ class OfflineFirstChatRepository @Inject constructor(
scope.cancel()
}
override suspend fun sendChatMessage(
credentials: String,
url: String,
message: CharSequence,
displayName: String,
replyTo: Int,
sendWithoutNotification: Boolean,
referenceId: String
): Flow<Result<ChatOverallSingleMessage>> =
flow {
try {
val response = network.sendChatMessage(
credentials,
url,
message,
displayName,
replyTo,
sendWithoutNotification,
referenceId
)
emit(Result.success(response))
} catch (e: Exception) {
emit(Result.failure(e))
}
}
override suspend fun editChatMessage(
credentials: String,
url: String,
text: String
): Flow<Result<ChatOverallSingleMessage>> =
flow {
try {
val response = network.editChatMessage(
credentials,
url,
text
)
emit(Result.success(response))
} catch (e: Exception) {
emit(Result.failure(e))
}
}
companion object {
val TAG = OfflineFirstChatRepository::class.simpleName
private const val HTTP_CODE_OK: Int = 200

View file

@ -7,6 +7,7 @@
package com.nextcloud.talk.chat.data.network
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
@ -21,7 +22,8 @@ import com.nextcloud.talk.utils.message.SendMessageUtils
import io.reactivex.Observable
import retrofit2.Response
class RetrofitChatNetwork(private val ncApi: NcApi) : ChatNetworkDataSource {
class RetrofitChatNetwork(private val ncApi: NcApi, private val ncApiCoroutines: NcApiCoroutines) :
ChatNetworkDataSource {
override fun getRoom(user: User, roomToken: String): Observable<ConversationModel> {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
@ -137,7 +139,7 @@ class RetrofitChatNetwork(private val ncApi: NcApi) : ChatNetworkDataSource {
it
}
override fun sendChatMessage(
override suspend fun sendChatMessage(
credentials: String,
url: String,
message: CharSequence,
@ -145,8 +147,8 @@ class RetrofitChatNetwork(private val ncApi: NcApi) : ChatNetworkDataSource {
replyTo: Int,
sendWithoutNotification: Boolean,
referenceId: String
): Observable<ChatOverallSingleMessage> =
ncApi.sendChatMessage(
): ChatOverallSingleMessage =
ncApiCoroutines.sendChatMessage(
credentials,
url,
message,
@ -154,9 +156,7 @@ class RetrofitChatNetwork(private val ncApi: NcApi) : ChatNetworkDataSource {
replyTo,
sendWithoutNotification,
referenceId
).map {
it
}
)
override fun pullChatMessages(
credentials: String,
@ -180,10 +180,8 @@ class RetrofitChatNetwork(private val ncApi: NcApi) : ChatNetworkDataSource {
previousMessageId: Int
): Observable<GenericOverall> = ncApi.setChatReadMarker(credentials, url, previousMessageId).map { it }
override fun editChatMessage(credentials: String, url: String, text: String): Observable<ChatOverallSingleMessage> =
ncApi.editChatMessage(credentials, url, text).map {
it
}
override suspend fun editChatMessage(credentials: String, url: String, text: String): ChatOverallSingleMessage =
ncApiCoroutines.editChatMessage(credentials, url, text)
override fun listBans(credentials: String, url: String): Observable<List<TalkBan>> =
ncApi.listBans(credentials, url).map {

View file

@ -15,25 +15,24 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
import com.nextcloud.talk.chat.data.io.AudioRecorderManager
import com.nextcloud.talk.chat.data.io.MediaPlayerManager
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.utils.message.SendMessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.commons.models.IMessage
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.lang.Thread.sleep
import javax.inject.Inject
class MessageInputViewModel @Inject constructor(
private val chatNetworkDataSource: ChatNetworkDataSource,
private val chatRepository: ChatMessageRepository,
private val audioRecorderManager: AudioRecorderManager,
private val mediaPlayerManager: MediaPlayerManager,
private val audioFocusRequestManager: AudioFocusRequestManager,
@ -106,7 +105,7 @@ class MessageInputViewModel @Inject constructor(
sealed interface ViewState
object SendChatMessageStartState : ViewState
class SendChatMessageSuccessState(val message: CharSequence) : ViewState
class SendChatMessageErrorState(val e: Throwable, val message: CharSequence) : ViewState
class SendChatMessageErrorState(val message: CharSequence) : ViewState
private val _sendChatMessageViewState: MutableLiveData<ViewState> = MutableLiveData(SendChatMessageStartState)
val sendChatMessageViewState: LiveData<ViewState>
get() = _sendChatMessageViewState
@ -156,7 +155,8 @@ class MessageInputViewModel @Inject constructor(
return
}
chatNetworkDataSource.sendChatMessage(
viewModelScope.launch {
chatRepository.sendChatMessage(
credentials,
url,
message,
@ -164,51 +164,32 @@ class MessageInputViewModel @Inject constructor(
replyTo,
sendWithoutNotification,
referenceId
).subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<ChatOverallSingleMessage> {
override fun onSubscribe(d: Disposable) {
disposableSet.add(d)
}
).collect { result ->
if (result.isSuccess) {
Log.d(TAG, "received ref id: " + (result.getOrNull()?.ocs?.data?.referenceId ?: "none"))
override fun onError(e: Throwable) {
_sendChatMessageViewState.value = SendChatMessageErrorState(e, message)
}
override fun onComplete() {
// unused atm
}
override fun onNext(t: ChatOverallSingleMessage) {
Log.d(TAG, "received ref id: " + (t.ocs?.data?.referenceId ?: "none"))
// TODO check ref id and replace temp message
_sendChatMessageViewState.value = SendChatMessageSuccessState(message)
} else {
_sendChatMessageViewState.value = SendChatMessageErrorState(message)
}
}
}
})
}
fun editChatMessage(credentials: String, url: String, text: String) {
chatNetworkDataSource.editChatMessage(credentials, url, text)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer<ChatOverallSingleMessage> {
override fun onSubscribe(d: Disposable) {
disposableSet.add(d)
}
override fun onError(e: Throwable) {
Log.e(TAG, "failed to edit message", e)
viewModelScope.launch {
chatRepository.editChatMessage(
credentials,
url,
text
).collect { result ->
if (result.isSuccess) {
_editMessageViewState.value = EditMessageSuccessState(result.getOrNull()!!)
} else {
_editMessageViewState.value = EditMessageErrorState
}
override fun onComplete() {
// unused atm
}
override fun onNext(messageEdited: ChatOverallSingleMessage) {
_editMessageViewState.value = EditMessageSuccessState(messageEdited)
}
})
}
fun reply(message: IMessage?) {

View file

@ -10,7 +10,6 @@ package com.nextcloud.talk.conversationlist.data.network
import android.util.Log
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
import com.nextcloud.talk.data.database.dao.ConversationsDao
import com.nextcloud.talk.data.database.mappers.asEntity
@ -106,7 +105,7 @@ class OfflineFirstConversationsRepository @Inject constructor(
var conversationsFromSync: List<ConversationEntity>? = null
if (!monitor.isOnline.first()) {
Log.d(OfflineFirstChatRepository.TAG, "Device is offline, can't load conversations from server")
Log.d(TAG, "Device is offline, can't load conversations from server")
return null
}

View file

@ -74,105 +74,79 @@ class RepositoryModule {
ncApi: NcApi,
ncApiCoroutines: NcApiCoroutines,
userProvider: CurrentUserProviderNew
): ConversationsRepository {
return ConversationsRepositoryImpl(ncApi, ncApiCoroutines, userProvider)
}
): ConversationsRepository = ConversationsRepositoryImpl(ncApi, ncApiCoroutines, userProvider)
@Provides
fun provideSharedItemsRepository(ncApi: NcApi, dateUtils: DateUtils): SharedItemsRepository {
return SharedItemsRepositoryImpl(ncApi, dateUtils)
}
fun provideSharedItemsRepository(ncApi: NcApi, dateUtils: DateUtils): SharedItemsRepository =
SharedItemsRepositoryImpl(ncApi, dateUtils)
@Provides
fun provideUnifiedSearchRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): UnifiedSearchRepository {
return UnifiedSearchRepositoryImpl(ncApi, userProvider)
}
fun provideUnifiedSearchRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): UnifiedSearchRepository =
UnifiedSearchRepositoryImpl(ncApi, userProvider)
@Provides
fun provideDialogPollRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): PollRepository {
return PollRepositoryImpl(ncApi, userProvider)
}
fun provideDialogPollRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): PollRepository =
PollRepositoryImpl(ncApi, userProvider)
@Provides
fun provideRemoteFileBrowserItemsRepository(
okHttpClient: OkHttpClient,
userProvider: CurrentUserProviderNew
): RemoteFileBrowserItemsRepository {
return RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider)
}
): RemoteFileBrowserItemsRepository = RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider)
@Provides
fun provideUsersRepository(database: TalkDatabase): UsersRepository {
return UsersRepositoryImpl(database.usersDao())
}
fun provideUsersRepository(database: TalkDatabase): UsersRepository = UsersRepositoryImpl(database.usersDao())
@Provides
fun provideArbitraryStoragesRepository(database: TalkDatabase): ArbitraryStoragesRepository {
return ArbitraryStoragesRepositoryImpl(database.arbitraryStoragesDao())
}
fun provideArbitraryStoragesRepository(database: TalkDatabase): ArbitraryStoragesRepository =
ArbitraryStoragesRepositoryImpl(database.arbitraryStoragesDao())
@Provides
fun provideReactionsRepository(
ncApi: NcApi,
userProvider: CurrentUserProviderNew,
dao: ChatMessagesDao
): ReactionsRepository {
return ReactionsRepositoryImpl(ncApi, userProvider, dao)
}
): ReactionsRepository = ReactionsRepositoryImpl(ncApi, userProvider, dao)
@Provides
fun provideCallRecordingRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): CallRecordingRepository {
return CallRecordingRepositoryImpl(ncApi, userProvider)
}
fun provideCallRecordingRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): CallRecordingRepository =
CallRecordingRepositoryImpl(ncApi, userProvider)
@Provides
fun provideRequestAssistanceRepository(
ncApi: NcApi,
userProvider: CurrentUserProviderNew
): RequestAssistanceRepository {
return RequestAssistanceRepositoryImpl(ncApi, userProvider)
}
): RequestAssistanceRepository = RequestAssistanceRepositoryImpl(ncApi, userProvider)
@Provides
fun provideOpenConversationsRepository(
ncApi: NcApi,
userProvider: CurrentUserProviderNew
): OpenConversationsRepository {
return OpenConversationsRepositoryImpl(ncApi, userProvider)
}
): OpenConversationsRepository = OpenConversationsRepositoryImpl(ncApi, userProvider)
@Provides
fun translateRepository(ncApi: NcApi): TranslateRepository {
return TranslateRepositoryImpl(ncApi)
}
fun translateRepository(ncApi: NcApi): TranslateRepository = TranslateRepositoryImpl(ncApi)
@Provides
fun provideChatNetworkDataSource(ncApi: NcApi): ChatNetworkDataSource {
return RetrofitChatNetwork(ncApi)
}
fun provideChatNetworkDataSource(ncApi: NcApi, ncApiCoroutines: NcApiCoroutines): ChatNetworkDataSource =
RetrofitChatNetwork(ncApi, ncApiCoroutines)
@Provides
fun provideConversationsNetworkDataSource(ncApi: NcApi): ConversationsNetworkDataSource {
return RetrofitConversationsNetwork(ncApi)
}
fun provideConversationsNetworkDataSource(ncApi: NcApi): ConversationsNetworkDataSource =
RetrofitConversationsNetwork(ncApi)
@Provides
fun provideConversationInfoEditRepository(
ncApi: NcApi,
userProvider: CurrentUserProviderNew
): ConversationInfoEditRepository {
return ConversationInfoEditRepositoryImpl(ncApi, userProvider)
}
): ConversationInfoEditRepository = ConversationInfoEditRepositoryImpl(ncApi, userProvider)
@Provides
fun provideConversationRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationRepository {
return ConversationRepositoryImpl(ncApi, userProvider)
}
fun provideConversationRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationRepository =
ConversationRepositoryImpl(ncApi, userProvider)
@Provides
fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository {
return InvitationsRepositoryImpl(ncApi)
}
fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository = InvitationsRepositoryImpl(ncApi)
@Provides
fun provideOfflineFirstChatRepository(
@ -182,8 +156,8 @@ class RepositoryModule {
appPreferences: AppPreferences,
networkMonitor: NetworkMonitor,
userProvider: CurrentUserProviderNew
): ChatMessageRepository {
return OfflineFirstChatRepository(
): ChatMessageRepository =
OfflineFirstChatRepository(
chatMessagesDao,
chatBlocksDao,
dataSource,
@ -191,7 +165,6 @@ class RepositoryModule {
networkMonitor,
userProvider
)
}
@Provides
fun provideOfflineFirstConversationsRepository(
@ -200,26 +173,22 @@ class RepositoryModule {
chatNetworkDataSource: ChatNetworkDataSource,
networkMonitor: NetworkMonitor,
currentUserProviderNew: CurrentUserProviderNew
): OfflineConversationsRepository {
return OfflineFirstConversationsRepository(
): OfflineConversationsRepository =
OfflineFirstConversationsRepository(
dao,
dataSource,
chatNetworkDataSource,
networkMonitor,
currentUserProviderNew
)
}
@Provides
fun provideContactsRepository(ncApiCoroutines: NcApiCoroutines, userManager: UserManager): ContactsRepository {
return ContactsRepositoryImpl(ncApiCoroutines, userManager)
}
fun provideContactsRepository(ncApiCoroutines: NcApiCoroutines, userManager: UserManager): ContactsRepository =
ContactsRepositoryImpl(ncApiCoroutines, userManager)
@Provides
fun provideConversationCreationRepository(
ncApiCoroutines: NcApiCoroutines,
userManager: UserManager
): ConversationCreationRepository {
return ConversationCreationRepositoryImpl(ncApiCoroutines, userManager)
}
): ConversationCreationRepository = ConversationCreationRepositoryImpl(ncApiCoroutines, userManager)
}