Upstream changes (#23)

This commit is contained in:
2024-07-11 02:12:32 +03:00
committed by GitHub
parent 8a6378f509
commit 3503ecffab
906 changed files with 23577 additions and 24115 deletions
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,354 @@
package com.meloda.app.fast.data
import android.util.Log
import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.common.extensions.asInt
import com.meloda.app.fast.common.extensions.listenValue
import com.meloda.app.fast.common.extensions.toList
import com.meloda.app.fast.data.api.messages.MessagesUseCase
import com.meloda.app.fast.model.ApiEvent
import com.meloda.app.fast.model.InteractionType
import com.meloda.app.fast.model.LongPollEvent
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class LongPollUpdatesParser(
private val messagesUseCase: MessagesUseCase
) {
private val job = SupervisorJob()
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Log.d("LongPollUpdatesParser", "error: $throwable")
throwable.printStackTrace()
}
private val coroutineContext: CoroutineContext
get() = Dispatchers.Default + job + exceptionHandler
private val coroutineScope = CoroutineScope(coroutineContext)
private val listenersMap: MutableMap<ApiEvent, MutableCollection<VkEventCallback<*>>> =
mutableMapOf()
fun parseNextUpdate(event: List<Any>) {
val eventId = event.first().asInt()
val eventType: ApiEvent = try {
ApiEvent.parse(eventId)
} catch (e: Exception) {
e.printStackTrace()
Log.d("LongPollUpdatesParser", "parseNextUpdate: unknownEvent: $event")
return
}
when (eventType) {
ApiEvent.MESSAGE_SET_FLAGS -> parseMessageSetFlags(eventType, event)
ApiEvent.MESSAGE_CLEAR_FLAGS -> parseMessageClearFlags(eventType, event)
ApiEvent.MESSAGE_NEW -> parseMessageNew(eventType, event)
ApiEvent.MESSAGE_EDIT -> parseMessageEdit(eventType, event)
ApiEvent.MESSAGE_READ_INCOMING -> parseMessageReadIncoming(eventType, event)
ApiEvent.MESSAGE_READ_OUTGOING -> parseMessageReadOutgoing(eventType, event)
ApiEvent.MESSAGES_DELETED -> parseMessagesDeleted(eventType, event)
ApiEvent.PIN_UNPIN_CONVERSATION -> parseConversationPinStateChanged(eventType, event)
ApiEvent.TYPING,
ApiEvent.AUDIO_MESSAGE_RECORDING,
ApiEvent.PHOTO_UPLOADING,
ApiEvent.VIDEO_UPLOADING,
ApiEvent.FILE_UPLOADING -> parseInteraction(eventType, event)
ApiEvent.UNREAD_COUNT_UPDATE -> onNewEvent(eventType, event)
}
}
private fun onNewEvent(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "newEvent: $eventType: $event")
}
private fun parseInteraction(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val interactionType = when (eventType) {
ApiEvent.TYPING -> InteractionType.Typing
ApiEvent.AUDIO_MESSAGE_RECORDING -> InteractionType.VoiceMessage
ApiEvent.PHOTO_UPLOADING -> InteractionType.Photo
ApiEvent.VIDEO_UPLOADING -> InteractionType.Video
ApiEvent.FILE_UPLOADING -> InteractionType.File
else -> return
}
val peerId = event[1].asInt()
val userIds = event[2].toList(Any::asInt).filter { it != UserConfig.userId }
val totalCount = event[3].asInt()
val timestamp = event[4].asInt()
// if userIds contains only account's id, then we don't need to show our status
if (userIds.isEmpty()) return
coroutineScope.launch {
listenersMap[eventType]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.Interaction>)
.onEvent(
LongPollEvent.Interaction(
interactionType = interactionType,
peerId = peerId,
userIds = userIds,
totalCount = totalCount,
timestamp = timestamp
)
)
}
}
}
}
private fun parseConversationPinStateChanged(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asInt()
val majorId = event[2].asInt()
coroutineScope.launch {
listenersMap[ApiEvent.PIN_UNPIN_CONVERSATION]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkConversationPinStateChangedEvent>)
.onEvent(
LongPollEvent.VkConversationPinStateChangedEvent(
peerId = peerId,
majorId = majorId
)
)
}
}
}
}
private fun parseMessageSetFlags(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
}
private fun parseMessageClearFlags(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
}
private fun parseMessageNew(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val messageId = event[1].asInt()
coroutineScope.launch(Dispatchers.IO) {
val newMessageEvent: LongPollEvent.VkMessageNewEvent? =
loadNormalMessage(
eventType,
messageId
)
newMessageEvent?.let { event ->
listenersMap[ApiEvent.MESSAGE_NEW]?.let {
it.map { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageNewEvent>)
.onEvent(event)
}
}
}
}
}
private fun parseMessageEdit(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val messageId = event[1].asInt()
coroutineScope.launch {
val editedMessageEvent: LongPollEvent.VkMessageEditEvent? =
loadNormalMessage(
eventType,
messageId
)
editedMessageEvent?.let { event ->
listenersMap[ApiEvent.MESSAGE_EDIT]?.let {
it.map { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageEditEvent>)
.onEvent(event)
}
}
}
}
}
private fun parseMessageReadIncoming(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asInt()
val messageId = event[2].asInt()
val unreadCount = event[3].asInt()
coroutineScope.launch {
listenersMap[ApiEvent.MESSAGE_READ_INCOMING]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageReadIncomingEvent>)
.onEvent(
LongPollEvent.VkMessageReadIncomingEvent(
peerId = peerId,
messageId = messageId,
unreadCount = unreadCount
)
)
}
}
}
}
private fun parseMessageReadOutgoing(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asInt()
val messageId = event[2].asInt()
val unreadCount = event[3].asInt()
coroutineScope.launch {
listenersMap[ApiEvent.MESSAGE_READ_OUTGOING]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageReadOutgoingEvent>)
.onEvent(
LongPollEvent.VkMessageReadOutgoingEvent(
peerId = peerId,
messageId = messageId,
unreadCount = unreadCount
)
)
}
}
}
}
private fun parseMessagesDeleted(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
}
private suspend fun <T : LongPollEvent> loadNormalMessage(
eventType: ApiEvent,
messageId: Int
): T? = suspendCoroutine {
coroutineScope.launch(Dispatchers.IO) {
messagesUseCase.getById(
messageId = messageId,
extended = true,
fields = VkConstants.ALL_FIELDS
).listenValue(this) { state ->
state.processState(
error = { error ->
Log.e("LongPollUpdatesParser", "loadNormalMessage: error: $error")
},
success = { response ->
response?.let { message ->
VkMemoryCache[message.id] = message
messagesUseCase.storeMessage(message)
val resumeValue: LongPollEvent? = when (eventType) {
ApiEvent.MESSAGE_NEW -> LongPollEvent.VkMessageNewEvent(message)
ApiEvent.MESSAGE_EDIT -> LongPollEvent.VkMessageEditEvent(message)
else -> null
}
resumeValue?.let { value -> it.resume(value as T) }
} ?: it.resume(null)
}
)
}
}
}
private fun <T : Any> registerListener(
eventType: ApiEvent,
listener: VkEventCallback<T>
) {
listenersMap.let { map ->
map[eventType] = (map[eventType] ?: mutableListOf()).also { it.add(listener) }
}
}
private fun <T : Any> registerListeners(
eventTypes: List<ApiEvent>,
listener: VkEventCallback<T>
) {
eventTypes.forEach { eventType -> registerListener(eventType, listener) }
}
fun onConversationPinStateChanged(listener: VkEventCallback<LongPollEvent.VkConversationPinStateChangedEvent>) {
registerListener(ApiEvent.PIN_UNPIN_CONVERSATION, listener)
}
fun onConversationPinStateChanged(block: (LongPollEvent.VkConversationPinStateChangedEvent) -> Unit) {
onConversationPinStateChanged(assembleEventCallback(block))
}
fun onMessageIncomingRead(listener: VkEventCallback<LongPollEvent.VkMessageReadIncomingEvent>) {
registerListener(ApiEvent.MESSAGE_READ_INCOMING, listener)
}
fun onMessageIncomingRead(block: (LongPollEvent.VkMessageReadIncomingEvent) -> Unit) {
onMessageIncomingRead(assembleEventCallback(block))
}
fun onMessageOutgoingRead(listener: VkEventCallback<LongPollEvent.VkMessageReadOutgoingEvent>) {
registerListener(ApiEvent.MESSAGE_READ_OUTGOING, listener)
}
fun onMessageOutgoingRead(block: (LongPollEvent.VkMessageReadOutgoingEvent) -> Unit) {
onMessageOutgoingRead(assembleEventCallback(block))
}
fun onNewMessage(listener: VkEventCallback<LongPollEvent.VkMessageNewEvent>) {
registerListener(ApiEvent.MESSAGE_NEW, listener)
}
fun onNewMessage(block: (LongPollEvent.VkMessageNewEvent) -> Unit) {
onNewMessage(assembleEventCallback(block))
}
fun onMessageEdited(listener: VkEventCallback<LongPollEvent.VkMessageEditEvent>) {
registerListener(ApiEvent.MESSAGE_EDIT, listener)
}
fun onMessageEdited(block: (LongPollEvent.VkMessageEditEvent) -> Unit) {
onMessageEdited(assembleEventCallback(block))
}
fun onInteractions(listener: VkEventCallback<LongPollEvent.Interaction>) {
registerListeners(
eventTypes = listOf(
ApiEvent.TYPING,
ApiEvent.AUDIO_MESSAGE_RECORDING,
ApiEvent.PHOTO_UPLOADING,
ApiEvent.VIDEO_UPLOADING,
ApiEvent.FILE_UPLOADING
),
listener = listener
)
}
fun onInteractions(block: (LongPollEvent.Interaction) -> Unit) {
onInteractions(assembleEventCallback(block))
}
fun clearListeners() {
listenersMap.clear()
}
}
internal inline fun <R : Any> assembleEventCallback(
crossinline block: (R) -> Unit,
): VkEventCallback<R> {
return VkEventCallback { event -> block.invoke(event) }
}
fun interface VkEventCallback<in T : Any> {
fun onEvent(event: T)
}
@@ -0,0 +1,23 @@
package com.meloda.app.fast.data
import com.meloda.app.fast.model.api.data.LongPollUpdates
import com.meloda.app.fast.model.api.data.VkLongPollData
import kotlinx.coroutines.flow.Flow
interface LongPollUseCase {
fun getLongPollServer(
needPts: Boolean,
version: Int
): Flow<State<VkLongPollData>>
fun getLongPollUpdates(
serverUrl: String,
act: String = "a_check",
key: String,
ts: Int,
wait: Int,
mode: Int,
version: Int
): Flow<State<LongPollUpdates>>
}
@@ -0,0 +1,49 @@
package com.meloda.app.fast.data
import com.meloda.app.fast.data.api.longpoll.LongPollRepository
import com.meloda.app.fast.model.api.data.LongPollUpdates
import com.meloda.app.fast.model.api.data.VkLongPollData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class LongPollUseCaseImpl(
private val repository: LongPollRepository
) : LongPollUseCase {
override fun getLongPollServer(
needPts: Boolean,
version: Int
): Flow<State<VkLongPollData>> = flow {
emit(State.Loading)
val newState = repository.getLongPollServer(
needPts = needPts,
version = version
).mapToState()
emit(newState)
}
override fun getLongPollUpdates(
serverUrl: String,
act: String,
key: String,
ts: Int,
wait: Int,
mode: Int,
version: Int
): Flow<State<LongPollUpdates>> = flow {
emit(State.Loading)
val newState = repository.getLongPollUpdates(
serverUrl,
act = act,
key = key,
ts = ts,
wait = wait,
mode = mode,
version = version
).mapToState()
emit(newState)
}
}
@@ -0,0 +1,76 @@
package com.meloda.app.fast.data
import com.meloda.app.fast.network.OAuthErrorDomain
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
sealed class State<out T> {
data object Idle : State<Nothing>()
data class Success<T>(val data: T) : State<T>()
data object Loading : State<Nothing>()
sealed class Error : State<Nothing>() {
data class ApiError(
val errorCode: Int,
val errorMessage: String,
) : Error()
data object ConnectionError : Error()
data object Unknown : Error()
data object InternalError : Error()
data class OAuthError(val error: OAuthErrorDomain) : Error()
}
fun isLoading(): Boolean = this is Loading
companion object {
val UNKNOWN_ERROR = Error.Unknown
}
}
inline fun <T> State<T>.processState(
error: (error: State.Error) -> (Unit),
success: (data: T) -> (Unit),
idle: (() -> (Unit)) = {},
loading: (() -> (Unit)) = {},
) {
when (this) {
is State.Error -> error(this)
State.Idle -> idle()
State.Loading -> loading()
is State.Success -> success(data)
}
}
inline fun <T, R> Flow<State<T>>.mapSuccess(
crossinline transform: suspend (value: T) -> R
): Flow<R> = filterIsInstance<State.Success<T>>()
.map { state -> transform.invoke(state.data) }
fun RestApiErrorDomain?.toStateApiError(): State.Error = when (this) {
null -> State.Error.ConnectionError
else -> State.Error.ApiError(code, message)
}
fun OAuthErrorDomain?.toStateApiError(): State.Error = when (this) {
null -> State.Error.ConnectionError
else -> State.Error.OAuthError(this)
}
fun <T : Any> ApiResult<T, RestApiErrorDomain>.mapToState() = when (this) {
is ApiResult.Success -> State.Success(this.value)
is ApiResult.Failure.NetworkFailure -> State.Error.ConnectionError
is ApiResult.Failure.UnknownFailure -> State.UNKNOWN_ERROR
is ApiResult.Failure.HttpFailure -> this.error.toStateApiError()
is ApiResult.Failure.ApiFailure -> this.error.toStateApiError()
}
@@ -0,0 +1,47 @@
package com.meloda.app.fast.data
import com.meloda.app.fast.model.api.data.VkMessageData
import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.model.api.domain.VkGroupDomain
import com.meloda.app.fast.model.api.domain.VkMessage
import kotlin.math.abs
class VkGroupsMap(
private val groups: List<VkGroupDomain>
) {
private val map: HashMap<Int, VkGroupDomain> by lazy {
HashMap(groups.associateBy(VkGroupDomain::id))
}
fun groups(): List<VkGroupDomain> = map.values.toList()
fun conversationGroup(conversation: VkConversation): VkGroupDomain? =
if (!conversation.peerType.isGroup()) null
else map[abs(conversation.id)]
fun messageActionGroup(message: VkMessage): VkGroupDomain? =
if (message.actionMemberId == null || message.actionMemberId!! >= 0) null
else map[abs(message.actionMemberId!!)]
fun messageActionGroup(message: VkMessageData): VkGroupDomain? =
if (message.action?.memberId == null || message.action!!.memberId!! >= 0) null
else map[abs(message.action!!.memberId!!)]
fun messageGroup(message: VkMessage): VkGroupDomain? =
if (!message.isGroup()) null
else map[abs(message.fromId)]
fun messageGroup(message: VkMessageData): VkGroupDomain? =
if (message.fromId >= 0) null
else map[abs(message.fromId)]
fun group(groupId: Int): VkGroupDomain? = map[abs(groupId)]
companion object {
fun forGroups(groups: List<VkGroupDomain>): VkGroupsMap = VkGroupsMap(groups = groups)
fun List<VkGroupDomain>.toGroupsMap(): VkGroupsMap = forGroups(this)
}
}
@@ -0,0 +1,119 @@
package com.meloda.app.fast.data
import com.meloda.app.fast.model.api.domain.VkContactDomain
import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.model.api.domain.VkGroupDomain
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.model.api.domain.VkUser
import kotlin.math.abs
object VkMemoryCache {
private val users: HashMap<Int, VkUser> = hashMapOf()
private val groups: HashMap<Int, VkGroupDomain> = hashMapOf()
private val messages: HashMap<Int, VkMessage> = hashMapOf()
private val conversations: HashMap<Int, VkConversation> = hashMapOf()
private val contacts: HashMap<Int, VkContactDomain> = hashMapOf()
fun appendUsers(users: List<VkUser>) {
users.forEach { user -> VkMemoryCache.users[user.id] = user }
}
fun appendGroups(groups: List<VkGroupDomain>) {
groups.forEach { group -> VkMemoryCache.groups[abs(group.id)] = group }
}
fun appendMessages(messages: List<VkMessage>) {
messages.forEach { message -> VkMemoryCache.messages[message.id] = message }
}
fun appendConversations(conversations: List<VkConversation>) {
conversations.forEach { conversation ->
VkMemoryCache.conversations[conversation.id] = conversation
}
}
fun appendContacts(contacts: List<VkContactDomain>) {
contacts.forEach { contact -> VkMemoryCache.contacts[contact.userId] = contact }
}
operator fun set(userId: Int, user: VkUser) {
users[userId] = user
}
operator fun set(groupId: Int, group: VkGroupDomain) {
groups[groupId] = group
}
operator fun set(messageId: Int, message: VkMessage) {
messages[messageId] = message
}
operator fun set(conversationId: Int, conversation: VkConversation) {
conversations[conversationId] = conversation
}
operator fun set(contactId: Int, contact: VkContactDomain) {
contacts[contactId] = contact
}
fun getUser(id: Int): VkUser? {
return getUsers(id).firstOrNull()
}
fun getUsers(vararg ids: Int): List<VkUser> {
return getUsers(ids.toList())
}
fun getUsers(ids: List<Int>): List<VkUser> {
return ids.mapNotNull { id -> users[id] }
}
fun getGroup(id: Int): VkGroupDomain? {
return getGroups(id).firstOrNull()
}
fun getGroups(vararg ids: Int): List<VkGroupDomain> {
return getGroups(ids.toList())
}
fun getGroups(ids: List<Int>): List<VkGroupDomain> {
return ids.mapNotNull { id -> groups[id] }
}
fun getMessage(id: Int): VkMessage? {
return getMessages(id).firstOrNull()
}
fun getMessages(vararg ids: Int): List<VkMessage> {
return getMessages(ids.toList())
}
fun getMessages(ids: List<Int>): List<VkMessage> {
return ids.mapNotNull { id -> messages[id] }
}
fun getConversation(id: Int): VkConversation? {
return getConversations(id).firstOrNull()
}
fun getConversations(vararg ids: Int): List<VkConversation> {
return getConversations(ids.toList())
}
fun getConversations(ids: List<Int>): List<VkConversation> {
return ids.mapNotNull { id -> conversations[id] }
}
fun getContact(id: Int): VkContactDomain? {
return getContacts(id).firstOrNull()
}
fun getContacts(vararg ids: Int): List<VkContactDomain> {
return getContacts(ids.toList())
}
fun getContacts(ids: List<Int>): List<VkContactDomain> {
return ids.mapNotNull { id -> contacts[id] }
}
}
@@ -0,0 +1,46 @@
package com.meloda.app.fast.data
import com.meloda.app.fast.model.api.data.VkMessageData
import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.model.api.domain.VkUser
class VkUsersMap(
private val users: List<VkUser>
) {
private val map: HashMap<Int, VkUser> by lazy {
HashMap(users.associateBy(VkUser::id))
}
fun users(): List<VkUser> = map.values.toList()
fun conversationUser(conversation: VkConversation): VkUser? =
if (!conversation.peerType.isUser()) null
else map[conversation.id]
fun messageActionUser(message: VkMessage): VkUser? =
if (message.actionMemberId == null || message.actionMemberId!! <= 0) null
else map[message.actionMemberId]
fun messageActionUser(message: VkMessageData): VkUser? =
if (message.action?.memberId == null || message.action!!.memberId!! <= 0) null
else map[message.action!!.memberId]
fun messageUser(message: VkMessage): VkUser? =
if (!message.isUser()) null
else map[message.fromId]
fun messageUser(message: VkMessageData): VkUser? =
if (message.fromId > 0) map[message.fromId]
else null
fun user(userId: Int): VkUser? = map[userId]
companion object {
fun forUsers(users: List<VkUser>): VkUsersMap = VkUsersMap(users = users)
fun List<VkUser>.toUsersMap(): VkUsersMap = forUsers(this)
}
}
@@ -0,0 +1,15 @@
package com.meloda.app.fast.data.api.account
import com.meloda.app.fast.model.api.requests.AccountSetOfflineRequest
import com.meloda.app.fast.model.api.requests.AccountSetOnlineRequest
interface AccountRepository {
suspend fun setOnline(
params: AccountSetOnlineRequest
): Boolean
suspend fun setOffline(
params: AccountSetOfflineRequest
): Boolean
}
@@ -0,0 +1,19 @@
package com.meloda.app.fast.data.api.account
import com.meloda.app.fast.model.api.requests.AccountSetOfflineRequest
import com.meloda.app.fast.model.api.requests.AccountSetOnlineRequest
import com.meloda.app.fast.network.service.account.AccountService
// TODO: 05/05/2024, Danil Nikolaev: implement
class AccountRepositoryImpl(
private val accountService: AccountService
) : com.meloda.app.fast.data.api.account.AccountRepository {
override suspend fun setOnline(params: AccountSetOnlineRequest): Boolean {
return false
}
override suspend fun setOffline(params: AccountSetOfflineRequest): Boolean {
return false
}
}
@@ -0,0 +1,16 @@
package com.meloda.app.fast.data.api.account
import com.meloda.app.fast.data.State
import kotlinx.coroutines.flow.Flow
interface AccountUseCase {
suspend fun setOnline(
voip: Boolean,
accessToken: String
): Flow<State<Unit>>
suspend fun setOffline(
accessToken: String
): Flow<State<Unit>>
}
@@ -0,0 +1,49 @@
package com.meloda.app.fast.data.api.account
import com.meloda.app.fast.data.State
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
// TODO: 05/05/2024, Danil Nikolaev: implement
class AccountUseCaseImpl(
private val accountRepository: com.meloda.app.fast.data.api.account.AccountRepository
) : com.meloda.app.fast.data.api.account.AccountUseCase {
override suspend fun setOnline(
voip: Boolean,
accessToken: String
): Flow<State<Unit>> = flow {
// emit(com.meloda.app.fast.data.State.Loading)
//
// val newState = accountRepository.setOnline(
// params = AccountSetOnlineRequest(
// voip = voip,
// accessToken = accessToken
// )
// ).fold(
// onSuccess = { response -> com.meloda.app.fast.data.State.Success(response) },
// onNetworkFailure = { com.meloda.app.fast.data.State.Error.ConnectionError },
// onUnknownFailure = { com.meloda.app.fast.data.State.UNKNOWN_ERROR },
// onHttpFailure = { result -> result.error.toStateApiError() },
// onApiFailure = { result -> result.error.toStateApiError() }
// )
// emit(newState)
}
override suspend fun setOffline(
accessToken: String
): Flow<com.meloda.app.fast.data.State<Unit>> = flow {
emit(com.meloda.app.fast.data.State.Loading)
// val newState = accountRepository.setOffline(
// params = AccountSetOfflineRequest(accessToken = accessToken)
// ).fold(
// onSuccess = { response -> com.meloda.app.fast.data.State.Success(response) },
// onNetworkFailure = { com.meloda.app.fast.data.State.Error.ConnectionError },
// onUnknownFailure = { com.meloda.app.fast.data.State.UNKNOWN_ERROR },
// onHttpFailure = { result -> result.error.toStateApiError() },
// onApiFailure = { result -> result.error.toStateApiError() }
// )
// emit(newState)
}
}
@@ -0,0 +1,26 @@
package com.meloda.app.fast.data.api.audios
import com.meloda.app.fast.model.api.responses.AudiosGetUploadServerResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.meloda.app.fast.network.service.audios.AudiosService
import com.slack.eithernet.ApiResult
import okhttp3.MultipartBody
class AudiosRepository(
private val audiosService: AudiosService
) {
suspend fun getUploadServer(): ApiResult<ApiResponse<AudiosGetUploadServerResponse>, RestApiError> =
audiosService.getUploadServer()
suspend fun upload(url: String, file: MultipartBody.Part) = audiosService.upload(url, file)
suspend fun save(server: Int, audio: String, hash: String) = audiosService.save(
mapOf(
"server" to server.toString(),
"audio" to audio,
"hash" to hash
)
)
}
@@ -0,0 +1,18 @@
package com.meloda.app.fast.data.api.auth
import com.meloda.app.fast.model.api.requests.AuthDirectRequest
import com.meloda.app.fast.model.api.responses.AuthDirectResponse
import com.meloda.app.fast.model.api.responses.SendSmsResponse
import com.meloda.app.fast.network.OAuthErrorDomain
import com.slack.eithernet.ApiResult
interface AuthRepository {
// suspend fun auth(
// params: AuthDirectRequest
// ): ApiResult<AuthDirectResponse, OAuthErrorDomain>
suspend fun sendSms(
validationSid: String
): SendSmsResponse
}
@@ -0,0 +1,35 @@
package com.meloda.app.fast.data.api.auth
import com.meloda.app.fast.model.api.requests.AuthDirectRequest
import com.meloda.app.fast.model.api.responses.AuthDirectResponse
import com.meloda.app.fast.model.api.responses.SendSmsResponse
import com.meloda.app.fast.network.OAuthErrorDomain
import com.meloda.app.fast.network.service.auth.AuthService
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class AuthRepositoryImpl(
private val authService: AuthService
) : AuthRepository {
// override suspend fun auth(
// params: AuthDirectRequest
// ): ApiResult<AuthDirectResponse, OAuthErrorDomain> {
//
// }
// TODO: 05/05/2024, Danil Nikolaev: implement
override suspend fun sendSms(
validationSid: String
): SendSmsResponse = withContext(Dispatchers.IO) {
SendSmsResponse(
validationSid = null, delay = null, validationType = null, validationResend = null
)
// authService.sendSms(validationSid).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
}
}
@@ -0,0 +1,18 @@
package com.meloda.app.fast.data.api.conversations
import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
interface ConversationsRepository {
suspend fun getConversations(
count: Int?,
offset: Int?
): ApiResult<List<VkConversation>, RestApiErrorDomain>
suspend fun storeConversations(conversations: List<VkConversation>)
suspend fun delete(peerId: Int): ApiResult<Int, RestApiErrorDomain>
suspend fun pin(peerId: Int): ApiResult<Int, RestApiErrorDomain>
suspend fun unpin(peerId: Int): ApiResult<Int, RestApiErrorDomain>
}
@@ -0,0 +1,109 @@
package com.meloda.app.fast.data.api.conversations
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.data.VkGroupsMap
import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.data.VkUsersMap
import com.meloda.app.fast.database.dao.ConversationDao
import com.meloda.app.fast.model.api.data.VkContactData
import com.meloda.app.fast.model.api.data.VkGroupData
import com.meloda.app.fast.model.api.data.VkUserData
import com.meloda.app.fast.model.api.data.asDomain
import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.model.api.domain.asEntity
import com.meloda.app.fast.model.api.requests.ConversationsDeleteRequest
import com.meloda.app.fast.model.api.requests.ConversationsGetRequest
import com.meloda.app.fast.model.api.requests.ConversationsPinRequest
import com.meloda.app.fast.model.api.requests.ConversationsUnpinRequest
import com.meloda.app.fast.network.RestApiErrorDomain
import com.meloda.app.fast.network.mapApiDefault
import com.meloda.app.fast.network.mapApiResult
import com.meloda.app.fast.network.service.conversations.ConversationsService
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class ConversationsRepositoryImpl(
private val conversationsService: ConversationsService,
private val conversationDao: ConversationDao
) : ConversationsRepository {
override suspend fun getConversations(
count: Int?,
offset: Int?
): ApiResult<List<VkConversation>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = ConversationsGetRequest(
count = count,
offset = offset,
fields = VkConstants.ALL_FIELDS,
filter = "all",
extended = true,
startMessageId = null
)
conversationsService.getConversations(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val profilesList = response.profiles.orEmpty().map(VkUserData::mapToDomain)
val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain)
val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain)
val usersMap = VkUsersMap.forUsers(profilesList)
val groupsMap = VkGroupsMap.forGroups(groupsList)
VkMemoryCache.appendUsers(profilesList)
VkMemoryCache.appendGroups(groupsList)
VkMemoryCache.appendContacts(contactsList)
response.items.map { item ->
val lastMessage = item.lastMessage?.asDomain()?.let { message ->
message.copy(
user = usersMap.messageUser(message),
group = groupsMap.messageGroup(message),
actionUser = usersMap.messageActionUser(message),
actionGroup = groupsMap.messageActionGroup(message)
).also { VkMemoryCache[message.id] = it }
}
item.conversation.asDomain(lastMessage).let { conversation ->
conversation.copy(
user = usersMap.conversationUser(conversation),
group = groupsMap.conversationGroup(conversation)
).also { VkMemoryCache[conversation.id] = it }
}
}
},
errorMapper = { error ->
error?.toDomain()
}
)
}
override suspend fun storeConversations(conversations: List<VkConversation>) {
conversationDao.insertAll(conversations.map(VkConversation::asEntity))
}
override suspend fun delete(peerId: Int): ApiResult<Int, RestApiErrorDomain> =
withContext(Dispatchers.IO) {
val requestModel = ConversationsDeleteRequest(peerId = peerId)
conversationsService.delete(requestModel.map).mapApiResult(
successMapper = { response -> response.requireResponse().lastDeletedId },
errorMapper = { error -> error?.toDomain() }
)
}
override suspend fun pin(
peerId: Int
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = ConversationsPinRequest(peerId = peerId)
conversationsService.pin(requestModel.map).mapApiDefault()
}
override suspend fun unpin(
peerId: Int
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = ConversationsUnpinRequest(peerId = peerId)
conversationsService.unpin(requestModel.map).mapApiDefault()
}
}
@@ -0,0 +1,19 @@
package com.meloda.app.fast.data.api.conversations
import com.meloda.app.fast.data.State
import com.meloda.app.fast.model.api.domain.VkConversation
import kotlinx.coroutines.flow.Flow
interface ConversationsUseCase {
fun getConversations(
count: Int?,
offset: Int?,
): Flow<State<List<VkConversation>>>
fun delete(peerId: Int): Flow<State<Int>>
fun changePinState(peerId: Int, pin: Boolean): Flow<State<Int>>
suspend fun storeConversations(conversations: List<VkConversation>)
}
@@ -0,0 +1,31 @@
package com.meloda.app.fast.data.api.files
import com.meloda.app.fast.network.service.files.FilesService
import okhttp3.MultipartBody
class FilesRepository(
private val filesService: FilesService
) {
// TODO: 05/05/2024, Danil Nikolaev: reimplement
// enum class FileType(val value: String) {
// @Json(name = "doc")
// FILE("doc"),
//
// @Json(name = "audio_message")
// AUDIO_MESSAGE("audio_message")
// }
//
// suspend fun getMessagesUploadServer(peerId: Int, type: FileType) =
// filesService.getUploadServer(
// mapOf(
// "peer_id" to peerId.toString(),
// "type" to type.value
// )
// )
suspend fun uploadFile(url: String, file: MultipartBody.Part) = filesService.upload(url, file)
suspend fun saveMessageFile(file: String) = filesService.save(mapOf("file" to file))
}
@@ -0,0 +1,26 @@
package com.meloda.app.fast.data.api.friends
import com.meloda.app.fast.model.FriendsInfo
import com.meloda.app.fast.model.api.domain.VkUser
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
interface FriendsRepository {
suspend fun getAllFriends(
count: Int?,
offset: Int?
): ApiResult<FriendsInfo, RestApiErrorDomain>
suspend fun getFriends(
count: Int?,
offset: Int?
): ApiResult<List<VkUser>, RestApiErrorDomain>
suspend fun getOnlineFriends(
count: Int?,
offset: Int?
): ApiResult<List<Int>, RestApiErrorDomain>
suspend fun storeUsers(users: List<VkUser>)
}
@@ -0,0 +1,83 @@
package com.meloda.app.fast.data.api.friends
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.database.dao.UsersDao
import com.meloda.app.fast.model.FriendsInfo
import com.meloda.app.fast.model.api.data.VkUserData
import com.meloda.app.fast.model.api.domain.VkUser
import com.meloda.app.fast.model.api.domain.asEntity
import com.meloda.app.fast.model.api.requests.GetFriendsRequest
import com.meloda.app.fast.model.api.requests.GetOnlineFriendsRequest
import com.meloda.app.fast.network.RestApiErrorDomain
import com.meloda.app.fast.network.mapApiDefault
import com.meloda.app.fast.network.mapApiResult
import com.meloda.app.fast.network.service.friends.FriendsService
import com.slack.eithernet.ApiResult
import com.slack.eithernet.successOrElse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
class FriendsRepositoryImpl(
private val service: FriendsService,
private val dao: UsersDao
) : FriendsRepository {
override suspend fun getAllFriends(
count: Int?,
offset: Int?
): ApiResult<FriendsInfo, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val friends = async { getFriends(count, offset) }.await()
.successOrElse { failure ->
return@withContext failure
}
val onlineFriends = async { getOnlineFriends(count, offset) }.await()
.successOrElse { failure ->
return@withContext failure
}.mapNotNull { userId -> friends.find { it.id == userId } }
ApiResult.success(FriendsInfo(friends, onlineFriends))
}
override suspend fun getFriends(
count: Int?,
offset: Int?
): ApiResult<List<VkUser>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = GetFriendsRequest(
order = "hints",
count = count,
offset = offset,
fields = VkConstants.USER_FIELDS
)
service.getFriends(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val users = response.items.map(VkUserData::mapToDomain)
VkMemoryCache.appendUsers(users)
users
},
errorMapper = { error -> error?.toDomain() }
)
}
override suspend fun getOnlineFriends(
count: Int?,
offset: Int?
): ApiResult<List<Int>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = GetOnlineFriendsRequest(
order = "hints",
count = count,
offset = offset,
)
service.getOnlineFriends(requestModel.map).mapApiDefault()
}
override suspend fun storeUsers(users: List<VkUser>) = withContext(Dispatchers.IO) {
dao.insertAll(users.map(VkUser::asEntity))
}
}
@@ -0,0 +1,26 @@
package com.meloda.app.fast.data.api.friends
import com.meloda.app.fast.data.State
import com.meloda.app.fast.model.FriendsInfo
import com.meloda.app.fast.model.api.domain.VkUser
import kotlinx.coroutines.flow.Flow
interface FriendsUseCase {
fun getAllFriends(
count: Int?,
offset: Int?
): Flow<State<FriendsInfo>>
fun getFriends(
count: Int?,
offset: Int?
): Flow<State<List<VkUser>>>
fun getOnlineFriends(
count: Int?,
offset: Int?
): Flow<State<List<Int>>>
suspend fun storeUsers(users: List<VkUser>)
}
@@ -0,0 +1,24 @@
package com.meloda.app.fast.data.api.longpoll
import com.meloda.app.fast.model.api.data.LongPollUpdates
import com.meloda.app.fast.model.api.data.VkLongPollData
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
interface LongPollRepository {
suspend fun getLongPollServer(
needPts: Boolean,
version: Int
): ApiResult<VkLongPollData, RestApiErrorDomain>
suspend fun getLongPollUpdates(
serverUrl: String,
act: String,
key: String,
ts: Int,
wait: Int,
mode: Int,
version: Int
): ApiResult<LongPollUpdates, RestApiErrorDomain>
}
@@ -0,0 +1,57 @@
package com.meloda.app.fast.data.api.longpoll
import com.meloda.app.fast.model.api.data.LongPollUpdates
import com.meloda.app.fast.model.api.data.VkLongPollData
import com.meloda.app.fast.model.api.requests.LongPollGetUpdatesRequest
import com.meloda.app.fast.model.api.requests.MessagesGetLongPollServerRequest
import com.meloda.app.fast.network.RestApiErrorDomain
import com.meloda.app.fast.network.mapApiResult
import com.meloda.app.fast.network.mapResult
import com.meloda.app.fast.network.service.longpoll.LongPollService
import com.meloda.app.fast.network.service.messages.MessagesService
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class LongPollRepositoryImpl(
private val longPollService: LongPollService,
private val messagesService: MessagesService
) : LongPollRepository {
override suspend fun getLongPollServer(
needPts: Boolean,
version: Int
): ApiResult<VkLongPollData, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesGetLongPollServerRequest(
needPts = needPts,
version = version
)
messagesService.getLongPollServer(requestModel.map).mapApiResult(
successMapper = { response -> response.requireResponse() },
errorMapper = { error -> error?.toDomain() }
)
}
override suspend fun getLongPollUpdates(
serverUrl: String,
act: String,
key: String,
ts: Int,
wait: Int,
mode: Int,
version: Int
): ApiResult<LongPollUpdates, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = LongPollGetUpdatesRequest(
act = act,
key = key,
ts = ts,
wait = wait,
mode = mode,
version = version
)
longPollService.getResponse(serverUrl, requestModel.map).mapResult(
successMapper = { response -> response },
errorMapper = { error -> error?.toDomain() }
)
}
}
@@ -0,0 +1,16 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.model.database.VkMessageEntity
interface MessagesLocalDataSource {
suspend fun getMessages(
conversationId: Int,
offset: Int?,
count: Int?
): List<VkMessageEntity>
suspend fun getMessage(messageId: Int): VkMessageEntity?
suspend fun storeMessages(messages: List<VkMessageEntity>)
}
@@ -0,0 +1,24 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.database.dao.MessageDao
import com.meloda.app.fast.model.database.VkMessageEntity
// TODO: 05/05/2024, Danil Nikolaev: use paging for room
class MessagesLocalDataSourceImpl(
private val messageDao: MessageDao
) : MessagesLocalDataSource {
override suspend fun getMessages(
conversationId: Int,
offset: Int?,
count: Int?
): List<VkMessageEntity> = messageDao.getAll(conversationId)
override suspend fun getMessage(
messageId: Int
): VkMessageEntity? = messageDao.getById(messageId)
override suspend fun storeMessages(messages: List<VkMessageEntity>) {
messageDao.insertAll(messages)
}
}
@@ -0,0 +1,36 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
interface MessagesNetworkDataSource {
suspend fun getMessagesHistory(
conversationId: Int,
offset: Int?,
count: Int?,
): ApiResult<MessagesHistoryDomain, RestApiErrorDomain>
suspend fun getMessageById(
messagesIds: List<Int>,
extended: Boolean?,
fields: String?
): ApiResult<VkMessage, RestApiErrorDomain>
suspend fun send(
peerId: Int,
randomId: Int,
message: String?,
replyTo: Int?,
attachments: List<VkAttachment>?
): ApiResult<Int, RestApiErrorDomain>
suspend fun markAsRead(
peerId: Int,
startMessageId: Int?
): ApiResult<Int, RestApiErrorDomain>
suspend fun getMessage(messageId: Int): VkMessage?
}
@@ -0,0 +1,164 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.data.VkGroupsMap
import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.data.VkUsersMap
import com.meloda.app.fast.model.api.data.VkContactData
import com.meloda.app.fast.model.api.data.VkGroupData
import com.meloda.app.fast.model.api.data.VkUserData
import com.meloda.app.fast.model.api.data.asDomain
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.model.api.requests.MessagesGetByIdRequest
import com.meloda.app.fast.model.api.requests.MessagesGetHistoryRequest
import com.meloda.app.fast.model.api.requests.MessagesMarkAsReadRequest
import com.meloda.app.fast.model.api.requests.MessagesSendRequest
import com.meloda.app.fast.network.RestApiErrorDomain
import com.meloda.app.fast.network.mapApiDefault
import com.meloda.app.fast.network.mapApiResult
import com.meloda.app.fast.network.service.messages.MessagesService
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class MessagesNetworkDataSourceImpl(
private val messagesService: MessagesService
) : MessagesNetworkDataSource {
override suspend fun getMessagesHistory(
conversationId: Int,
offset: Int?,
count: Int?
): ApiResult<MessagesHistoryDomain, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesGetHistoryRequest(
count = count,
offset = offset,
peerId = conversationId,
extended = true,
startMessageId = null,
rev = null,
fields = VkConstants.ALL_FIELDS
)
messagesService.getHistory(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val profilesList = response.profiles.orEmpty().map(VkUserData::mapToDomain)
val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain)
val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain)
val usersMap = VkUsersMap.forUsers(profilesList)
val groupsMap = VkGroupsMap.forGroups(groupsList)
VkMemoryCache.appendUsers(profilesList)
VkMemoryCache.appendGroups(groupsList)
VkMemoryCache.appendContacts(contactsList)
val messages = response.items.map { item ->
item.asDomain().let { message ->
message.copy(
user = usersMap.messageUser(message),
group = groupsMap.messageGroup(message),
actionUser = usersMap.messageActionUser(message),
actionGroup = groupsMap.messageActionGroup(message)
).also { VkMemoryCache[message.id] = it }
}
}
val conversations = response.conversations.orEmpty().map { item ->
val message = messages.firstOrNull { it.id == item.lastMessageId }
item.asDomain(message)
.let { conversation ->
conversation.copy(
user = usersMap.conversationUser(conversation),
group = groupsMap.conversationGroup(conversation)
).also { VkMemoryCache[conversation.id] = it }
}
}
MessagesHistoryDomain(
messages = messages,
conversations = conversations
)
},
errorMapper = { error ->
error?.toDomain()
}
)
}
override suspend fun getMessageById(
messagesIds: List<Int>,
extended: Boolean?,
fields: String?
): ApiResult<VkMessage, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesGetByIdRequest(
messagesIds = messagesIds,
extended = extended,
fields = fields
)
messagesService.getById(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val message = response.items.single()
val usersMap =
VkUsersMap.forUsers(response.profiles.orEmpty().map(VkUserData::mapToDomain))
val groupsMap =
VkGroupsMap.forGroups(response.groups.orEmpty().map(VkGroupData::mapToDomain))
message.asDomain().copy(
user = usersMap.messageUser(message),
group = groupsMap.messageGroup(message),
actionUser = usersMap.messageActionUser(message),
actionGroup = groupsMap.messageActionGroup(message)
)
},
errorMapper = { error -> error?.toDomain() }
)
}
override suspend fun send(
peerId: Int,
randomId: Int,
message: String?,
replyTo: Int?,
attachments: List<VkAttachment>?
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesSendRequest(
peerId = peerId,
randomId = randomId,
message = message,
replyTo = replyTo,
attachments = attachments
)
messagesService.send(requestModel.map).mapApiDefault()
}
override suspend fun markAsRead(
peerId: Int,
startMessageId: Int?
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesMarkAsReadRequest(
peerId = peerId,
startMessageId = startMessageId
)
messagesService.markAsRead(requestModel.map).mapApiDefault()
}
override suspend fun getMessage(messageId: Int): VkMessage? = withContext(Dispatchers.IO) {
// TODO: 05/05/2024, Danil Nikolaev: get message
null
}
}
data class MessagesHistoryDomain(
val messages: List<VkMessage>,
val conversations: List<VkConversation>
)
@@ -0,0 +1,75 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.flow.Flow
interface MessagesRepository {
suspend fun getMessagesHistory(
conversationId: Int,
offset: Int?,
count: Int?
): ApiResult<MessagesHistoryDomain, RestApiErrorDomain>
suspend fun getMessageById(
messagesIds: List<Int>,
extended: Boolean?,
fields: String?
): ApiResult<VkMessage, RestApiErrorDomain>
suspend fun send(
peerId: Int,
randomId: Int,
message: String?,
replyTo: Int?,
attachments: List<VkAttachment>?
): ApiResult<Int, RestApiErrorDomain>
suspend fun markAsRead(
peerId: Int,
startMessageId: Int?
): ApiResult<Int, RestApiErrorDomain>
suspend fun getMessage(messageId: Int): Flow<VkMessage?>
suspend fun storeMessages(messages: List<VkMessage>)
// suspend fun getHistory(
// params: MessagesGetHistoryRequest
// ): ApiResult<MessagesGetHistoryResponse, RestApiErrorDomain>
// suspend fun markAsImportant(
// params: MessagesMarkAsImportantRequest
// ): ApiResult<List<Int>, RestApiErrorDomain>
//
// suspend fun pin(
// params: MessagesPinMessageRequest
// ): ApiResult<VkMessageData, RestApiErrorDomain>
//
// suspend fun unpin(
// params: MessagesUnPinMessageRequest
// ): ApiResult<Unit, RestApiErrorDomain>
//
// suspend fun delete(
// params: MessagesDeleteRequest
// ): ApiResult<Unit, RestApiErrorDomain>
//
// suspend fun edit(
// params: MessagesEditRequest
// ): ApiResult<Int, RestApiErrorDomain>
//
// suspend fun getChat(
// params: MessagesGetChatRequest
// ): ApiResult<VkChatData, RestApiErrorDomain>
//
// suspend fun getConversationMembers(
// params: MessagesGetConversationMembersRequest
// ): ApiResult<MessagesGetConversationMembersResponse, RestApiErrorDomain>
//
// suspend fun removeChatUser(
// params: MessagesRemoveChatUserRequest
// ): ApiResult<Int, RestApiErrorDomain>
}
@@ -0,0 +1,201 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.model.api.domain.asEntity
import com.meloda.app.fast.model.database.asExternalModel
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
// TODO: 05/05/2024, Danil Nikolaev: implement syncing
class MessagesRepositoryImpl(
private val networkDataSource: MessagesNetworkDataSource,
private val localDataSource: MessagesLocalDataSource
) : MessagesRepository {
override suspend fun getMessagesHistory(
conversationId: Int,
offset: Int?,
count: Int?
): ApiResult<MessagesHistoryDomain, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// val localMessages = localDataSource.getMessages(
// conversationId = conversationId,
// offset = offset,
// count = count
// ).map(VkMessageEntity::asExternalModel)
//
// emit(localMessages)
//
// val networkMessages = networkDataSource.getMessagesHistory(
// conversationId = conversationId,
// offset = offset,
// count = count
// )
//
// emit(networkMessages)
networkDataSource.getMessagesHistory(conversationId, offset, count)
}
override suspend fun getMessageById(
messagesIds: List<Int>,
extended: Boolean?,
fields: String?
): ApiResult<VkMessage, RestApiErrorDomain> = withContext(Dispatchers.IO) {
networkDataSource.getMessageById(
messagesIds = messagesIds,
extended = extended,
fields = fields
)
}
override suspend fun send(
peerId: Int,
randomId: Int,
message: String?,
replyTo: Int?,
attachments: List<VkAttachment>?
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
networkDataSource.send(
peerId,
randomId,
message,
replyTo,
attachments
)
}
override suspend fun markAsRead(
peerId: Int,
startMessageId: Int?
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
networkDataSource.markAsRead(peerId, startMessageId)
}
override suspend fun getMessage(messageId: Int): Flow<VkMessage?> = flow {
val localMessage = localDataSource.getMessage(messageId)?.asExternalModel()
emit(localMessage)
val networkMessage = networkDataSource.getMessage(messageId)
emit(networkMessage)
}
override suspend fun storeMessages(messages: List<VkMessage>) {
localDataSource.storeMessages(messages.map(VkMessage::asEntity))
}
// override suspend fun getHistory(
// params: MessagesGetHistoryRequest
// ): ApiResult<MessagesGetHistoryResponse, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.getHistory(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun send(
// params: MessagesSendRequest
// ): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.send(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun markAsImportant(
// params: MessagesMarkAsImportantRequest
// ): ApiResult<List<Int>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.markAsImportant(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun pin(
// params: MessagesPinMessageRequest
// ): ApiResult<VkMessageData, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.pin(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun unpin(
// params: MessagesUnPinMessageRequest
// ): ApiResult<Unit, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.unpin(params.map).mapResult(
// successMapper = {},
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun delete(
// params: MessagesDeleteRequest
// ): ApiResult<Unit, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.delete(params.map).mapResult(
// successMapper = {},
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun edit(
// params: MessagesEditRequest
// ): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.edit(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun getById(
// params: MessagesGetByIdRequest
// ): ApiResult<MessagesGetByIdResponse, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.getById(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun markAsRead(
// params: MessagesMarkAsReadRequest
// ): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.markAsRead(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun getChat(
// params: MessagesGetChatRequest
// ): ApiResult<VkChatData, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.getChat(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun getConversationMembers(
// params: MessagesGetConversationMembersRequest
// ): ApiResult<MessagesGetConversationMembersResponse, RestApiErrorDomain> =
// withContext(Dispatchers.IO) {
// messagesService.getConversationMembers(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun removeChatUser(
// params: MessagesRemoveChatUserRequest
// ): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.removeChatUser(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
}
@@ -0,0 +1,43 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.data.State
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkMessage
import kotlinx.coroutines.flow.Flow
interface MessagesUseCase {
fun getMessagesHistory(
conversationId: Int,
count: Int?,
offset: Int?
): Flow<State<MessagesHistoryDomain>>
fun getById(
messageId: Int,
extended: Boolean?,
fields: String?
): Flow<State<VkMessage?>>
fun getByIds(
messageIds: List<Int>,
extended: Boolean?,
fields: String?
): Flow<State<List<VkMessage>>>
fun sendMessage(
peerId: Int,
randomId: Int,
message: String?,
replyTo: Int?,
attachments: List<VkAttachment>?
): Flow<State<Int>>
fun markAsRead(
peerId: Int,
startMessageId: Int
): Flow<State<Int>>
suspend fun storeMessage(message: VkMessage)
suspend fun storeMessages(messages: List<VkMessage>)
}
@@ -0,0 +1,15 @@
package com.meloda.app.fast.data.api.oauth
import com.meloda.app.fast.model.api.responses.AuthDirectResponse
interface OAuthRepository {
suspend fun auth(
login: String,
password: String,
forceSms: Boolean,
twoFaCode: String?,
captchaSid: String?,
captchaKey: String?
): AuthDirectResponse
}
@@ -0,0 +1,51 @@
package com.meloda.app.fast.data.api.oauth
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.model.api.requests.AuthDirectRequest
import com.meloda.app.fast.model.api.responses.AuthDirectResponse
import com.meloda.app.fast.network.service.oauth.OAuthService
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class OAuthRepositoryImpl(
private val oAuthService: OAuthService,
) : OAuthRepository {
override suspend fun auth(
login: String,
password: String,
forceSms: Boolean,
twoFaCode: String?,
captchaSid: String?,
captchaKey: String?
): AuthDirectResponse = withContext(Dispatchers.IO) {
val requestModel = AuthDirectRequest(
grantType = VkConstants.Auth.GrantType.PASSWORD,
clientId = VkConstants.VK_APP_ID,
clientSecret = VkConstants.VK_SECRET,
username = login,
password = password,
scope = VkConstants.Auth.SCOPE,
twoFaForceSms = forceSms,
twoFaCode = twoFaCode,
captchaSid = captchaSid,
captchaKey = captchaKey,
)
when (val result = oAuthService.auth(requestModel.map)) {
is ApiResult.Success -> result.value
is ApiResult.Failure.HttpFailure -> {
requireNotNull(result.error)
}
else -> throw IllegalStateException("Unknown result")
// is ApiResult.Failure.ApiFailure -> TODO()
// is ApiResult.Failure.HttpFailure -> TODO()
// is ApiResult.Failure.NetworkFailure -> TODO()
// is ApiResult.Failure.UnknownFailure -> TODO()
}
}
}
@@ -0,0 +1,19 @@
package com.meloda.app.fast.data.api.photos
import com.meloda.app.fast.model.api.requests.PhotosSaveMessagePhotoRequest
import com.meloda.app.fast.network.service.photos.PhotosService
import okhttp3.MultipartBody
class PhotosRepository(
private val photosService: PhotosService
) {
suspend fun getMessagesUploadServer(peerId: Int) =
photosService.getUploadServer(mapOf("peer_id" to peerId.toString()))
suspend fun uploadPhoto(url: String, photo: MultipartBody.Part) =
photosService.upload(url, photo)
suspend fun saveMessagePhoto(body: PhotosSaveMessagePhotoRequest) =
photosService.save(body.map)
}
@@ -0,0 +1,8 @@
package com.meloda.app.fast.data.api.users
import com.meloda.app.fast.model.api.data.VkUserData
import com.meloda.app.fast.model.api.requests.UsersGetRequest
interface UsersRepository {
suspend fun getById(params: UsersGetRequest): List<VkUserData>
}
@@ -0,0 +1,16 @@
package com.meloda.app.fast.data.api.users
import com.meloda.app.fast.model.api.data.VkUserData
import com.meloda.app.fast.model.api.requests.UsersGetRequest
import com.meloda.app.fast.network.service.users.UsersService
class UsersRepositoryImpl(
private val usersService: UsersService
) : UsersRepository {
override suspend fun getById(params: UsersGetRequest): List<VkUserData> {
// TODO: 05/05/2024, Danil Nikolaev: implement
return emptyList()
}
}
@@ -0,0 +1,23 @@
package com.meloda.app.fast.data.api.users
import com.meloda.app.fast.data.State
import com.meloda.app.fast.model.api.domain.VkUser
import kotlinx.coroutines.flow.Flow
interface UsersUseCase {
fun getUserById(
userId: Int,
fields: String?,
nomCase: String?
): Flow<State<VkUser?>>
fun getUsersByIds(
userIds: List<Int>,
fields: String?,
nomCase: String?
): Flow<State<List<VkUser>>>
suspend fun storeUser(user: VkUser)
suspend fun storeUsers(users: List<VkUser>)
}
@@ -0,0 +1,67 @@
package com.meloda.app.fast.data.api.users
import com.meloda.app.fast.data.State
import com.meloda.app.fast.model.api.domain.VkUser
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
// TODO: 05/05/2024, Danil Nikolaev: implement
class UsersUseCaseImpl(
private val usersRepository: UsersRepository,
) : UsersUseCase {
override fun getUserById(
userId: Int,
fields: String?,
nomCase: String?
): Flow<State<VkUser?>> = flow {
// emit(State.Loading)
//
// val newState = usersRepository.getById(
// UsersGetRequest(
// userIds = listOf(userId),
// fields = fields,
// nomCase = nomCase
// )
// ).fold(
// onSuccess = { response -> State.Success(response.singleOrNull()?.mapToDomain()) },
// onNetworkFailure = { State.Error.ConnectionError },
// onUnknownFailure = { State.UNKNOWN_ERROR },
// onHttpFailure = { result -> result.error.toStateApiError() },
// onApiFailure = { result -> result.error.toStateApiError() }
// )
// emit(newState)
}
override fun getUsersByIds(
userIds: List<Int>,
fields: String?,
nomCase: String?
): Flow<State<List<VkUser>>> = flow {
// emit(State.Loading)
//
// val newState = usersRepository.getById(
// UsersGetRequest(
// userIds = userIds,
// fields = fields,
// nomCase = nomCase
// )
// ).fold(
// onSuccess = { response -> State.Success(response.map(VkUserData::mapToDomain)) },
// onNetworkFailure = { State.Error.ConnectionError },
// onUnknownFailure = { State.UNKNOWN_ERROR },
// onHttpFailure = { result -> result.error.toStateApiError() },
// onApiFailure = { result -> result.error.toStateApiError() }
// )
// emit(newState)
}
override suspend fun storeUser(user: VkUser) {
}
override suspend fun storeUsers(users: List<VkUser>) {
}
}
@@ -0,0 +1,14 @@
package com.meloda.app.fast.data.api.videos
import com.meloda.app.fast.network.service.videos.VideosService
import okhttp3.MultipartBody
class VideosRepository(
private val videosService: VideosService
) {
suspend fun save() = videosService.save()
// TODO: 05/05/2024, Danil Nikolaev: research, maybe remove multipart.body
suspend fun upload(url: String, file: MultipartBody.Part) = videosService.upload(url, file)
}
@@ -0,0 +1,10 @@
package com.meloda.app.fast.data.db
import com.meloda.app.fast.model.database.AccountEntity
interface AccountsRepository {
suspend fun getAccounts(): List<AccountEntity>
suspend fun storeAccounts(accounts: List<AccountEntity>)
}
@@ -0,0 +1,15 @@
package com.meloda.app.fast.data.db
import com.meloda.app.fast.database.dao.AccountDao
import com.meloda.app.fast.model.database.AccountEntity
class AccountsRepositoryImpl(
private val accountDao: AccountDao
) : AccountsRepository {
override suspend fun getAccounts(): List<AccountEntity> = accountDao.getAll()
override suspend fun storeAccounts(
accounts: List<AccountEntity>
) = accountDao.insertAll(accounts)
}
@@ -0,0 +1,78 @@
package com.meloda.app.fast.data.di
import com.meloda.app.fast.common.di.commonModule
import com.meloda.app.fast.data.api.account.AccountRepository
import com.meloda.app.fast.data.api.account.AccountRepositoryImpl
import com.meloda.app.fast.data.api.account.AccountUseCase
import com.meloda.app.fast.data.api.account.AccountUseCaseImpl
import com.meloda.app.fast.data.api.audios.AudiosRepository
import com.meloda.app.fast.data.api.auth.AuthRepository
import com.meloda.app.fast.data.api.auth.AuthRepositoryImpl
import com.meloda.app.fast.data.api.conversations.ConversationsRepository
import com.meloda.app.fast.data.api.conversations.ConversationsRepositoryImpl
import com.meloda.app.fast.data.api.files.FilesRepository
import com.meloda.app.fast.data.api.friends.FriendsRepository
import com.meloda.app.fast.data.api.friends.FriendsRepositoryImpl
import com.meloda.app.fast.data.api.longpoll.LongPollRepository
import com.meloda.app.fast.data.api.longpoll.LongPollRepositoryImpl
import com.meloda.app.fast.data.api.messages.MessagesLocalDataSource
import com.meloda.app.fast.data.api.messages.MessagesLocalDataSourceImpl
import com.meloda.app.fast.data.api.messages.MessagesNetworkDataSource
import com.meloda.app.fast.data.api.messages.MessagesNetworkDataSourceImpl
import com.meloda.app.fast.data.api.messages.MessagesRepository
import com.meloda.app.fast.data.api.messages.MessagesRepositoryImpl
import com.meloda.app.fast.data.api.oauth.OAuthRepository
import com.meloda.app.fast.data.api.oauth.OAuthRepositoryImpl
import com.meloda.app.fast.data.api.photos.PhotosRepository
import com.meloda.app.fast.data.api.users.UsersRepository
import com.meloda.app.fast.data.api.users.UsersRepositoryImpl
import com.meloda.app.fast.data.api.users.UsersUseCase
import com.meloda.app.fast.data.api.users.UsersUseCaseImpl
import com.meloda.app.fast.data.api.videos.VideosRepository
import com.meloda.app.fast.data.db.AccountsRepository
import com.meloda.app.fast.data.db.AccountsRepositoryImpl
import com.meloda.app.fast.database.di.databaseModule
import com.meloda.app.fast.datastore.di.dataStoreModule
import com.meloda.app.fast.network.di.networkModule
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
import org.koin.dsl.module
val dataModule = module {
includes(
commonModule,
databaseModule,
dataStoreModule,
networkModule,
)
singleOf(::AccountRepositoryImpl) bind AccountRepository::class
singleOf(::AccountUseCaseImpl) bind AccountUseCase::class
singleOf(::AudiosRepository)
singleOf(::AuthRepositoryImpl) bind AuthRepository::class
singleOf(::ConversationsRepositoryImpl) bind ConversationsRepository::class
singleOf(::FilesRepository)
singleOf(::LongPollRepositoryImpl) bind LongPollRepository::class
singleOf(::MessagesLocalDataSourceImpl) bind MessagesLocalDataSource::class
singleOf(::MessagesNetworkDataSourceImpl) bind MessagesNetworkDataSource::class
singleOf(::MessagesRepositoryImpl) bind MessagesRepository::class
singleOf(::OAuthRepositoryImpl) bind OAuthRepository::class
singleOf(::PhotosRepository)
singleOf(::UsersRepositoryImpl) bind UsersRepository::class
singleOf(::UsersUseCaseImpl) bind UsersUseCase::class
singleOf(::VideosRepository)
singleOf(::AccountsRepositoryImpl) bind AccountsRepository::class
singleOf(::FriendsRepositoryImpl) bind FriendsRepository::class
}