move all ui-related classes and files to ui module

This commit is contained in:
2024-07-15 18:31:58 +03:00
parent 9a1bce5707
commit ee7499f117
171 changed files with 405 additions and 1354 deletions
@@ -9,7 +9,7 @@ import com.meloda.app.fast.model.BottomNavigationItem
import com.meloda.app.fast.presentation.MainScreen
import com.meloda.app.fast.profile.navigation.Profile
import kotlinx.serialization.Serializable
import com.meloda.app.fast.designsystem.R as UiR
import com.meloda.app.fast.ui.R as UiR
@Serializable
object MainGraph
@@ -37,15 +37,15 @@ import com.meloda.app.fast.common.extensions.isSdkAtLeast
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.datastore.model.ThemeConfig
import com.meloda.app.fast.designsystem.AppTheme
import com.meloda.app.fast.designsystem.LocalTheme
import com.meloda.app.fast.service.OnlineService
import com.meloda.app.fast.service.longpolling.LongPollingService
import com.meloda.app.fast.ui.model.ThemeConfig
import com.meloda.app.fast.ui.theme.AppTheme
import com.meloda.app.fast.ui.theme.LocalTheme
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.KoinContext
import org.koin.compose.koinInject
import com.meloda.app.fast.designsystem.R as UiR
import com.meloda.app.fast.ui.R as UiR
class MainActivity : AppCompatActivity() {
@@ -26,9 +26,9 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import com.meloda.app.fast.conversations.navigation.conversationsScreen
import com.meloda.app.fast.designsystem.LocalBottomPadding
import com.meloda.app.fast.designsystem.LocalHazeState
import com.meloda.app.fast.designsystem.LocalTheme
import com.meloda.app.fast.ui.theme.LocalBottomPadding
import com.meloda.app.fast.ui.theme.LocalHazeState
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.friends.navigation.friendsScreen
import com.meloda.app.fast.model.BaseError
import com.meloda.app.fast.model.BottomNavigationItem
@@ -26,7 +26,7 @@ import com.meloda.app.fast.auth.authNavGraph
import com.meloda.app.fast.auth.navigateToAuth
import com.meloda.app.fast.chatmaterials.navigation.chatMaterialsScreen
import com.meloda.app.fast.chatmaterials.navigation.navigateToChatMaterials
import com.meloda.app.fast.designsystem.R
import com.meloda.app.fast.ui.R
import com.meloda.app.fast.languagepicker.navigation.languagePickerScreen
import com.meloda.app.fast.languagepicker.navigation.navigateToLanguagePicker
import com.meloda.app.fast.messageshistory.navigation.messagesHistoryScreen
@@ -23,7 +23,7 @@ import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.designsystem.R
import com.meloda.app.fast.ui.R
import com.meloda.app.fast.model.api.data.LongPollUpdates
import com.meloda.app.fast.model.api.data.VkLongPollData
import com.meloda.app.fast.util.NotificationsUtils
@@ -6,7 +6,7 @@ import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.meloda.app.fast.common.AppConstants
import com.meloda.app.fast.designsystem.R as UiR
import com.meloda.app.fast.ui.R as UiR
object NotificationsUtils {
@@ -1,721 +0,0 @@
package com.meloda.app.fast.common.util
//import android.content.Context
//import androidx.compose.ui.graphics.Color
//import androidx.compose.ui.text.AnnotatedString
//import androidx.compose.ui.text.SpanStyle
//import androidx.compose.ui.text.buildAnnotatedString
//import androidx.compose.ui.text.font.FontWeight
//import androidx.compose.ui.text.withStyle
//import com.meloda.app.fast.common.UiImage
//import com.meloda.app.fast.common.UiText
//import com.meloda.app.fast.common.extensions.orDots
//import com.meloda.app.fast.common.parseString
//
//
//@Suppress("MemberVisibilityCanBePrivate")
//object VkUtils {
//
// fun prepareMessageText(text: String, forConversations: Boolean = false): String {
// return text.apply {
// if (forConversations) {
// replace("\n", " ")
// }
//
// replace("&", "&")
// replace(""", "\"")
// replace("<br>", "\n")
// replace("&gt;", ">")
// replace("&lt;", "<")
// replace("<br/>", "\n")
// replace("&ndash;", "-")
// trim()
// }
// }
//
// fun parseAttachments(baseAttachments: List<VkAttachmentItemData>?): List<VkAttachment>? {
// if (baseAttachments.isNullOrEmpty()) return null
//
// val attachments = mutableListOf<VkAttachment>()
//
// for (baseAttachment in baseAttachments) {
// when (baseAttachment.getPreparedType()) {
// AttachmentType.UNKNOWN -> continue
//
// AttachmentType.PHOTO -> {
// val photo = baseAttachment.photo ?: continue
// attachments += photo.toDomain()
// }
//
// AttachmentType.VIDEO -> {
// val video = baseAttachment.video ?: continue
// attachments += video.toDomain()
// }
//
// AttachmentType.AUDIO -> {
// val audio = baseAttachment.audio ?: continue
// attachments += audio.toDomain()
// }
//
// AttachmentType.FILE -> {
// val file = baseAttachment.file ?: continue
// attachments += file.toDomain()
// }
//
// AttachmentType.LINK -> {
// val link = baseAttachment.link ?: continue
// attachments += link.toDomain()
// }
//
// AttachmentType.MINI_APP -> {
// val miniApp = baseAttachment.miniApp ?: continue
// attachments += miniApp.toDomain()
// }
//
// AttachmentType.AUDIO_MESSAGE -> {
// val voiceMessage = baseAttachment.voiceMessage ?: continue
// attachments += voiceMessage.toDomain()
// }
//
// AttachmentType.STICKER -> {
// val sticker = baseAttachment.sticker ?: continue
// attachments += sticker.toDomain()
// }
//
// AttachmentType.GIFT -> {
// val gift = baseAttachment.gift ?: continue
// attachments += gift.toDomain()
// }
//
// AttachmentType.WALL -> {
// val wall = baseAttachment.wall ?: continue
// attachments += wall.toDomain()
// }
//
// AttachmentType.GRAFFITI -> {
// val graffiti = baseAttachment.graffiti ?: continue
// attachments += graffiti.toDomain()
// }
//
// AttachmentType.POLL -> {
// val poll = baseAttachment.poll ?: continue
// attachments += poll.toDomain()
// }
//
// AttachmentType.WALL_REPLY -> {
// val wallReply = baseAttachment.wallReply ?: continue
// attachments += wallReply.toDomain()
// }
//
// AttachmentType.CALL -> {
// val call = baseAttachment.call ?: continue
// attachments += call.toDomain()
// }
//
// AttachmentType.GROUP_CALL_IN_PROGRESS -> {
// val groupCall = baseAttachment.groupCall ?: continue
// attachments += groupCall.toDomain()
// }
//
// AttachmentType.CURATOR -> {
// val curator = baseAttachment.curator ?: continue
// attachments += curator.toDomain()
// }
//
// AttachmentType.EVENT -> {
// val event = baseAttachment.event ?: continue
// attachments += event.toDomain()
// }
//
// AttachmentType.STORY -> {
// val story = baseAttachment.story ?: continue
// attachments += story.toDomain()
// }
//
// AttachmentType.WIDGET -> {
// val widget = baseAttachment.widget ?: continue
// attachments += widget.toDomain()
// }
//
// AttachmentType.ARTIST -> {
// val artist = baseAttachment.artist ?: continue
// attachments += artist.toDomain()
//
// val audios = baseAttachment.audios ?: continue
// audios.map(VkAudioData::toDomain).let(attachments::addAll)
// }
//
// AttachmentType.AUDIO_PLAYLIST -> {
// val audioPlaylist = baseAttachment.audioPlaylist ?: continue
// attachments += audioPlaylist.toDomain()
// }
//
// AttachmentType.PODCAST -> {
// val podcast = baseAttachment.podcast ?: continue
// attachments += podcast.toDomain()
// }
// }
// }
//
// return attachments
// }
//
// fun getActionMessageText(
// context: Context,
// message: VkMessage?,
// youPrefix: String,
// messageUser: VkUserDomain?,
// messageGroup: VkGroupDomain?,
// action: VkMessage.Action?,
// actionUser: VkUserDomain?,
// actionGroup: VkGroupDomain?,
// ): AnnotatedString? {
// return when {
// message == null -> null
// action == null -> null
//
// else -> buildAnnotatedString {
// when (action) {
// VkMessage.Action.CHAT_CREATE -> {
// val text = message.actionText ?: return null
//
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isGroup() -> messageGroup?.name
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// val string = UiText.ResourceParams(
// UiR.string.message_action_chat_created,
// listOf(prefix, text)
// ).parseString(context).orEmpty()
//
// append(string)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
//
// val textStartIndex = string.indexOf(text)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = textStartIndex,
// end = textStartIndex + text.length
// )
// }
//
// VkMessage.Action.CHAT_TITLE_UPDATE -> {
// val text = message.actionText ?: return null
//
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isGroup() -> messageGroup?.name
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// val string = UiText.ResourceParams(
// UiR.string.message_action_chat_renamed,
// listOf(prefix, text)
// ).parseString(context).orEmpty()
//
// append(string)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
//
// val textStartIndex = string.indexOf(text)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = textStartIndex,
// end = textStartIndex + text.length
// )
// }
//
// VkMessage.Action.CHAT_PHOTO_UPDATE -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isGroup() -> messageGroup?.name
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_photo_update,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
//
// VkMessage.Action.CHAT_PHOTO_REMOVE -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isGroup() -> messageGroup?.name
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_photo_remove,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
//
// VkMessage.Action.CHAT_KICK_USER -> {
// val memberId = message.actionMemberId ?: return null
// val isUser = memberId > 0
// val isGroup = memberId < 0
//
// if (isUser && actionUser == null) return null
// if (isGroup && actionGroup == null) return null
//
// if (memberId == message.fromId) {
// val prefix =
// if (memberId == UserConfig.userId) youPrefix
// else actionUser.toString()
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_user_left,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// } else {
// val prefix =
// if (message.fromId == UserConfig.userId) youPrefix
// else messageUser?.toString() ?: messageGroup?.toString().orDots()
//
// val postfix =
// if (memberId == UserConfig.userId) youPrefix.lowercase()
// else actionUser.toString()
//
// val string = UiText.ResourceParams(
// UiR.string.message_action_chat_user_kicked,
// listOf(prefix, postfix)
// ).parseString(context).orEmpty()
//
// append(string)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
//
// val postfixStartIndex = string.indexOf(postfix)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = postfixStartIndex,
// end = postfixStartIndex + postfix.length
// )
// }
// }
//
// VkMessage.Action.CHAT_INVITE_USER -> {
// val memberId = message.actionMemberId ?: 0
// val isUser = memberId > 0
// val isGroup = memberId < 0
//
// if (isUser && actionUser == null) return null
// if (isGroup && actionGroup == null) return null
//
// if (memberId == message.fromId) {
// val prefix =
// if (memberId == UserConfig.userId) youPrefix
// else actionUser.toString()
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_user_returned,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// } else {
// val prefix =
// if (message.fromId == UserConfig.userId) youPrefix
// else messageUser?.toString() ?: messageGroup?.toString().orDots()
//
// val postfix =
// if (memberId == UserConfig.userId) youPrefix.lowercase()
// else actionUser.toString()
//
// val string = UiText.ResourceParams(
// UiR.string.message_action_chat_user_invited,
// listOf(prefix, postfix)
// ).parseString(context).orEmpty()
//
// append(string)
//
// val postfixStartIndex = string.indexOf(postfix)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = postfixStartIndex,
// end = postfixStartIndex + postfix.length
// )
// }
// }
//
// VkMessage.Action.CHAT_INVITE_USER_BY_LINK -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_user_joined_by_link,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
//
// VkMessage.Action.CHAT_INVITE_USER_BY_CALL -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_user_joined_by_call,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
//
// VkMessage.Action.CHAT_INVITE_USER_BY_CALL_LINK -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_user_joined_by_call_link,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
//
// VkMessage.Action.CHAT_PIN_MESSAGE -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isGroup() -> messageGroup?.name
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_pin_message,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
//
// VkMessage.Action.CHAT_UNPIN_MESSAGE -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isGroup() -> messageGroup?.name
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_unpin_message,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
//
// VkMessage.Action.CHAT_SCREENSHOT -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isGroup() -> messageGroup?.name
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_screenshot,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
//
// VkMessage.Action.CHAT_STYLE_UPDATE -> {
// val prefix = when {
// message.fromId == UserConfig.userId -> youPrefix
// message.isUser() -> messageUser?.toString()
// else -> return null
// } ?: return null
//
// UiText.ResourceParams(
// UiR.string.message_action_chat_style_update,
// listOf(prefix)
// ).parseString(context).orEmpty().let(::append)
//
// addStyle(
// style = SpanStyle(fontWeight = FontWeight.SemiBold),
// start = 0,
// end = prefix.length
// )
// }
// }
// }
// }
// }
//
// fun getForwardsText(context: Context, message: VkMessage?): AnnotatedString? {
// return when {
// message == null -> null
//
// message.hasForwards() -> buildAnnotatedString {
// val forwards = message.forwards.orEmpty()
//
// withStyle(style = SpanStyle(fontWeight = FontWeight.SemiBold)) {
// append(
// UiText.Resource(
// if (forwards.size == 1) UiR.string.forwarded_message
// else UiR.string.forwarded_messages
// ).parseString(context)
// )
// }
// }
//
// else -> null
// }
// }
//
// fun getAttachmentText(
// getText: (UiText) -> String,
// message: VkMessage?
// ): AnnotatedString? {
// return when {
// message == null -> null
//
// message.geoType != null -> buildAnnotatedString {
// withStyle(style = SpanStyle(fontWeight = FontWeight.SemiBold)) {
// when (message.geoType) {
// "point" -> getText(UiText.Resource(UiR.string.message_geo_point))
// .let(::append)
//
// else -> getText(UiText.Resource(UiR.string.message_geo))
// .let(::append)
// }
// }
// }
//
// message.hasAttachments() -> buildAnnotatedString {
// val attachments = message.attachments.orEmpty()
//
// withStyle(style = SpanStyle(fontWeight = FontWeight.SemiBold)) {
// if (attachments.size == 1) {
// getText(getAttachmentUiText(attachments.first())).let(::append)
// } else {
// when {
// isAttachmentsHaveOneType(attachments) -> {
// getText(getAttachmentUiText(attachments.first(), attachments.size))
// .let(::append)
// }
//
// attachments.any { it.type == AttachmentType.ARTIST } -> {
// getText(
// getAttachmentUiText(attachments.first { it.type == AttachmentType.ARTIST })
// ).let(::append)
// }
//
// else -> {
// getText(UiText.Resource(UiR.string.message_attachments_many))
// .let(::append)
// }
// }
// }
// }
// }
//
// else -> null
// }
// }
//
// fun getAttachmentConversationIcon(message: VkMessage?): UiImage? {
// return message?.attachments?.let { attachments ->
// if (attachments.isEmpty()) return null
// if (attachments.size == 1 || isAttachmentsHaveOneType(attachments)) {
// message.geoType?.let {
// return UiImage.Resource(UiR.drawable.ic_map_marker)
// }
//
// getAttachmentIconByType(attachments.first().type)
// } else {
// UiImage.Resource(UiR.drawable.ic_baseline_attach_file_24)
// }
// }
// }
//
//
//
// fun getAttachmentUiText(
// attachment: VkAttachment,
// size: Int = 1,
// ): UiText {
// if (attachment.type.isMultiple()) {
// return when (attachment.type) {
// AttachmentType.PHOTO -> UiR.plurals.attachment_photos
// AttachmentType.VIDEO -> UiR.plurals.attachment_videos
// AttachmentType.AUDIO -> UiR.plurals.attachment_audios
// AttachmentType.FILE -> UiR.plurals.attachment_files
// else -> throw IllegalArgumentException("Unknown multiple type: ${attachment.type}")
// }.let { resId -> UiText.QuantityResource(resId, size) }
// }
//
// return when (attachment.type) {
// AttachmentType.UNKNOWN,
// AttachmentType.PHOTO,
// AttachmentType.VIDEO,
// AttachmentType.AUDIO,
// AttachmentType.FILE -> {
// throw IllegalArgumentException("Unknown multiple type: ${attachment.type}")
// }
//
// AttachmentType.LINK -> UiR.string.message_attachments_link
// AttachmentType.AUDIO_MESSAGE -> UiR.string.message_attachments_audio_message
// AttachmentType.MINI_APP -> UiR.string.message_attachments_mini_app
// AttachmentType.STICKER -> UiR.string.message_attachments_sticker
// AttachmentType.GIFT -> UiR.string.message_attachments_gift
// AttachmentType.WALL -> UiR.string.message_attachments_wall
// AttachmentType.GRAFFITI -> UiR.string.message_attachments_graffiti
// AttachmentType.POLL -> UiR.string.message_attachments_poll
// AttachmentType.WALL_REPLY -> UiR.string.message_attachments_wall_reply
// AttachmentType.CALL -> UiR.string.message_attachments_call
// AttachmentType.GROUP_CALL_IN_PROGRESS -> UiR.string.message_attachments_call_in_progress
// AttachmentType.CURATOR -> UiR.string.message_attachments_curator
// AttachmentType.EVENT -> UiR.string.message_attachments_event
// AttachmentType.STORY -> UiR.string.message_attachments_story
// AttachmentType.WIDGET -> UiR.string.message_attachments_widget
// AttachmentType.ARTIST -> UiR.string.message_attachments_artist
// AttachmentType.AUDIO_PLAYLIST -> UiR.string.message_attachments_audio_playlist
// AttachmentType.PODCAST -> UiR.string.message_attachments_podcast
// }.let(UiText::Resource)
// }
//
// fun getTextWithVisualizedMentions(
// originalText: String,
// mentionColor: Color,
// ): AnnotatedString = buildAnnotatedString {
// val regex = """\[(id|club)(\d+)\|([^]]+)]""".toRegex()
//
// val mentions = mutableListOf<MentionIndex>()
//
// var currentIndex = 0
// val replacements = mutableListOf<Pair<IntRange, String>>()
//
// // TODO: 25/04/2024, Danil Nikolaev: check why not working ([id279494346|@iworld2rist] да убери ты Елену Шлипс от меня)
// val result = regex.replace(originalText) { matchResult ->
// val idPrefix = matchResult.groups[1]?.value.orEmpty()
// val startIndex = matchResult.range.first
// val endIndex = matchResult.range.last
//
// val id = matchResult.groups[2]?.value ?: ""
// val text = matchResult.groups[3]?.value ?: ""
//
// val replaced =
// text.substring(startIndex, endIndex + 1)
// .replace("[$idPrefix$id|$text]", text)
//
// val indexRange =
// (startIndex + currentIndex)..startIndex + currentIndex + replaced.length
//
// replacements.add(indexRange to replaced)
//
// mentions += MentionIndex(
// id = id.toIntOrNull() ?: -1,
// idPrefix = idPrefix,
// indexRange = indexRange
// )
//
// currentIndex += replaced.length - (endIndex - startIndex + 1)
//
// replaced
// }
//
// append(result)
//
// mentions.forEach { mention ->
// val startIndex = mention.indexRange.first
// val endIndex = mention.indexRange.last
//
// addStyle(
// style = SpanStyle(color = mentionColor),
// start = startIndex,
// end = endIndex
// )
// addStringAnnotation(
// tag = mention.idPrefix,
// annotation = mention.id.toString(),
// start = startIndex,
// end = endIndex
// )
// }
// }
//
//
//}
@@ -2,10 +2,8 @@ package com.meloda.app.fast.data
import com.meloda.app.fast.network.OAuthErrorDomain
import com.meloda.app.fast.network.RestApiErrorDomain
import com.meloda.app.fast.network.VkErrorCode
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> {
@@ -16,7 +14,7 @@ sealed class State<out T> {
sealed class Error : State<Nothing>() {
data class ApiError(
val errorCode: Int,
val errorCode: VkErrorCode,
val errorMessage: String,
) : Error()
@@ -61,19 +59,9 @@ inline fun <T> State<T>.processState(
}
}
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)
else -> State.Error.ApiError(VkErrorCode.parse(code), message)
}
fun <T : Any> ApiResult<T, RestApiErrorDomain>.mapToState() = when (this) {
@@ -1,18 +1,12 @@
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.model.api.responses.ValidatePhoneResponse
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
interface AuthRepository {
// suspend fun auth(
// params: AuthDirectRequest
// ): ApiResult<AuthDirectResponse, OAuthErrorDomain>
suspend fun sendSms(
suspend fun validatePhone(
validationSid: String
): SendSmsResponse
): ApiResult<ValidatePhoneResponse, RestApiErrorDomain>
}
@@ -1,35 +1,20 @@
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.model.api.responses.ValidatePhoneResponse
import com.meloda.app.fast.network.RestApiErrorDomain
import com.meloda.app.fast.network.mapApiDefault
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
private val service: AuthService
) : AuthRepository {
// override suspend fun auth(
// params: AuthDirectRequest
// ): ApiResult<AuthDirectResponse, OAuthErrorDomain> {
//
// }
// TODO: 05/05/2024, Danil Nikolaev: implement
override suspend fun sendSms(
override suspend fun validatePhone(
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() }
// )
): ApiResult<ValidatePhoneResponse, RestApiErrorDomain> = withContext(Dispatchers.IO) {
service.validatePhone(validationSid).mapApiDefault()
}
}
@@ -6,7 +6,7 @@ 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.mapDefault
import com.meloda.app.fast.network.service.longpoll.LongPollService
import com.meloda.app.fast.network.service.messages.MessagesService
import com.slack.eithernet.ApiResult
@@ -49,9 +49,7 @@ class LongPollRepositoryImpl(
mode = mode,
version = version
)
longPollService.getResponse(serverUrl, requestModel.map).mapResult(
successMapper = { response -> response },
errorMapper = { error -> error?.toDomain() }
)
longPollService.getResponse(serverUrl, requestModel.map).mapDefault()
}
}
+1
View File
@@ -29,6 +29,7 @@ android {
dependencies {
api(projects.core.common)
api(projects.core.ui)
implementation(libs.koin.android)
}
@@ -4,7 +4,7 @@ import android.content.res.Resources
import android.os.PowerManager
import android.util.Log
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.datastore.model.ThemeConfig
import com.meloda.app.fast.ui.model.ThemeConfig
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
+3 -2
View File
@@ -36,16 +36,17 @@ android {
}
dependencies {
// TODO: 05/05/2024, Danil Nikolaev: maybe remove
implementation(projects.core.common)
implementation(projects.core.datastore)
implementation(projects.core.ui)
implementation(libs.appcompat)
implementation(libs.accompanist.permissions)
implementation(platform(libs.compose.bom))
implementation(libs.bundles.compose)
implementation(libs.haze)
implementation(libs.haze.materials)
debugImplementation(libs.compose.ui.tooling)
}
@@ -4,9 +4,18 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SendSmsResponse(
data class ValidatePhoneResponse(
@Json(name = "sid") val validationSid: String?,
@Json(name = "delay") val delay: Int?,
@Json(name = "validation_type") val validationType: String?,
@Json(name = "validation_resend") val validationResend: String?
)
@JsonClass(generateAdapter = true)
data class ValidateLoginResponse(
@Json(name = "result") val result: String,
@Json(name = "sid") val sid: String,
@Json(name = "phone") val phone: String?,
@Json(name = "is_email") val isEmail: Boolean?,
@Json(name = "email_reg_allowed") val emailRegAllowed: Boolean?
)
@@ -3,6 +3,13 @@ package com.meloda.app.fast.model.api.responses
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class AuthDirectErrorOnlyResponse(
@Json(name = "error") val error: String,
@Json(name = "error_description") val errorDescription: String?,
@Json(name = "error_type") val errorType: String?
)
@JsonClass(generateAdapter = true)
data class AuthDirectResponse(
@Json(name = "access_token") val accessToken: String?,
@@ -35,3 +42,9 @@ data class AuthDirectResponse(
@Json(name = "restore_url") val restoreUrl: String
)
}
@JsonClass(generateAdapter = true)
data class GetAnonymousTokenResponse(
@Json(name = "token") val token: String,
@Json(name = "expired_at") val expiredAt: Int
)
@@ -32,37 +32,6 @@ fun <Success : Any, Error : Any, SuccessDomain : Any, ErrorDomain : Any>
}
}
fun <Success : Any, Error : Any, SuccessDomain : Any, ErrorDomain : Any>
ApiResult<Success, Error>.mapOAuthResult(
successMapper: (Success) -> SuccessDomain,
errorMapper: (Error?) -> ErrorDomain?
): ApiResult<SuccessDomain, ErrorDomain> {
if (BuildConfig.DEBUG) printStackTraceIfAny()
return when (this) {
is ApiResult.Success -> {
ApiResult.success(successMapper(value))
}
is ApiResult.Failure.NetworkFailure -> {
ApiResult.networkFailure(error)
}
is ApiResult.Failure.UnknownFailure -> {
ApiResult.unknownFailure(error)
}
is ApiResult.Failure.HttpFailure -> {
ApiResult.httpFailure(code, errorMapper(error))
}
is ApiResult.Failure.ApiFailure -> {
ApiResult.apiFailure(errorMapper(error))
}
}
}
fun <Success : ApiResponse<*>, SuccessDomain : Any, ErrorDomain : Any>
ApiResult<Success, RestApiError>.mapApiResult(
successMapper: (Success) -> SuccessDomain,
@@ -86,30 +55,18 @@ fun <Success : ApiResponse<*>, SuccessDomain : Any, ErrorDomain : Any>
}
}
fun <R : Any> ApiResult<R, RestApiError>.mapDefault(): ApiResult<R, RestApiErrorDomain> =
mapResult(
successMapper = { response -> response },
errorMapper = { error -> error?.toDomain() }
)
fun <T : Any, R : ApiResponse<T>> ApiResult<R, RestApiError>.mapApiDefault(): ApiResult<T, RestApiErrorDomain> =
mapResult(
successMapper = { response -> response.requireResponse() },
errorMapper = { error -> error?.toDomain() }
)
//@OptIn(ExperimentalContracts::class)
//inline fun <R : Any, E : Any, C> OAuthResponse<R, E>.fold(
// onSuccess: (value: R) -> C,
// onFailure: (failure: E) -> C,
//): C {
// contract {
// callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
// callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
// }
// return when (this) {
// is OAuthResponse.Success -> onSuccess(response)
// is OAuthResponse.Error -> onFailure(error)
// }
//}
fun <Success : Any, Error : Any> ApiResult<Success, Error>.isSuccess(): Boolean =
this is ApiResult.Success
fun <Success : Any, Error : Any> ApiResult<Success, Error>.printStackTraceIfAny() {
val throwable = when (this) {
is ApiResult.Failure.NetworkFailure -> error
@@ -118,9 +75,3 @@ fun <Success : Any, Error : Any> ApiResult<Success, Error>.printStackTraceIfAny(
}
throwable?.printStackTrace()
}
fun ApiResult.Failure.HttpFailure<*>?.tryCastToRestErrorDomain() =
this?.error as? RestApiErrorDomain
fun ApiResult.Failure.ApiFailure<*>?.tryCastToRestErrorDomain() =
this?.error as? RestApiErrorDomain
@@ -1,156 +0,0 @@
package com.meloda.app.fast.network
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
// TODO: 09/05/2024, Danil Nikolaev: reimplement as sealed class
@JsonClass(generateAdapter = true)
open class OAuthError(
@Json(name = "error") open val error: String,
@Json(name = "error_description") open val errorDescription: String?,
@Json(name = "error_type") open val errorType: String?
)
@JsonClass(generateAdapter = true)
data class ValidationRequiredError(
@Json(name = "error") override val error: String, // "need_validation"
@Json(name = "error_description") override val errorDescription: String, // "sms sent, use code param" if sms method; "use app code" if 2fa app
@Json(name = "validation_type") val validationType: String, // 2fa_app, 2sa_sms
@Json(name = "validation_sid") val validationSid: String,
@Json(name = "phone_mask") val phoneMask: String, // "+7 *** *** ** 50"
@Json(name = "redirect_uri") val redirectUri: String,
@Json(name = "validation_resend") val validationResend: String?, // Приходит, если для отправки кода нужно вызвать метод auth.validatePhone
@Json(name = "cant_get_code_open_restore") val restoreIfCannotGetCode: Boolean?
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = null
)
@JsonClass(generateAdapter = true)
data class CaptchaRequiredError(
@Json(name = "error") override val error: String, // "need_captcha"
@Json(name = "captcha_sid") val captchaSid: String,
@Json(name = "captcha_img") val captchaImage: String,
@Json(name = "captcha_ts") val captchaTs: Double?,
@Json(name = "captcha_ratio") val captchaRatio: Double?,
@Json(name = "captcha_track") val captchaTrack: String?,
@Json(name = "is_refresh_enabled") val isRefreshEnabled: Boolean?,
@Json(name = "is_sound_captcha_available") val isSoundCaptchaAvailable: Boolean?
) : OAuthError(
error = error,
errorDescription = null,
errorType = null
)
@JsonClass(generateAdapter = true)
data class UserBannedError(
@Json(name = "error") override val error: String, // need_validation
@Json(name = "error_description") override val errorDescription: String, // user has been banned
@Json(name = "ban_info") val banInfo: BanInfo
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = null
) {
@JsonClass(generateAdapter = true)
data class BanInfo(
@Json(name = "member_name") val memberName: String,
@Json(name = "message") val message: String,
@Json(name = "access_token") val accessToken: String,
@Json(name = "restore_url") val restoreUrl: String
)
}
@JsonClass(generateAdapter = true)
data class InvalidCredentialsError(
@Json(name = "error") override val error: String, // "invalid_client"
@Json(name = "error_description") override val errorDescription: String,
@Json(name = "error_type") override val errorType: String // "username_or_password_is_incorrect"
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = errorType
)
@JsonClass(generateAdapter = true)
data class WrongValidationCodeError(
@Json(name = "error") override val error: String, // "invalid_request"
@Json(name = "error_description") override val errorDescription: String,
@Json(name = "error_type") override val errorType: String // "wrong_otp"
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = errorType
)
@JsonClass(generateAdapter = true)
data class WrongValidationCodeFormatError(
@Json(name = "error") override val error: String, // "invalid_request"
@Json(name = "error_description") override val errorDescription: String,
@Json(name = "error_type") override val errorType: String // "otp_format_is_incorrect"
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = errorType
)
@JsonClass(generateAdapter = true)
data class TooManyTriesError(
@Json(name = "error") override val error: String, // "9;Flood control"
@Json(name = "error_description") override val errorDescription: String,
@Json(name = "error_type") override val errorType: String // "password_bruteforce_attempt"
) : OAuthError(
error = error,
errorDescription = errorDescription,
errorType = errorType
)
fun OAuthError.toDomain(): OAuthErrorDomain? = when (this) {
is ValidationRequiredError -> {
OAuthErrorDomain.ValidationRequiredError(
description = errorDescription,
validationType = ValidationType.parse(validationType),
validationSid = validationSid,
phoneMask = phoneMask,
redirectUri = redirectUri,
validationResend = validationResend,
restoreIfCannotGetCode = restoreIfCannotGetCode
)
}
is CaptchaRequiredError -> {
OAuthErrorDomain.CaptchaRequiredError(
captchaSid = captchaSid,
captchaImageUrl = captchaImage
)
}
is UserBannedError -> {
OAuthErrorDomain.UserBannedError(
memberName = banInfo.memberName,
message = banInfo.message,
accessToken = banInfo.accessToken,
restoreUrl = banInfo.restoreUrl
)
}
is InvalidCredentialsError -> {
OAuthErrorDomain.InvalidCredentialsError
}
is WrongValidationCodeError -> {
OAuthErrorDomain.WrongValidationCode
}
is WrongValidationCodeFormatError -> {
OAuthErrorDomain.WrongValidationCodeFormat
}
is TooManyTriesError -> {
OAuthErrorDomain.TooManyTriesError
}
else -> null
}
@@ -2,6 +2,8 @@ package com.meloda.app.fast.network
sealed class OAuthErrorDomain {
data object UnknownError : OAuthErrorDomain()
data class ValidationRequiredError(
val description: String,
val validationType: ValidationType,
@@ -28,6 +30,4 @@ sealed class OAuthErrorDomain {
data object WrongValidationCode : OAuthErrorDomain()
data object WrongValidationCodeFormat : OAuthErrorDomain()
data object TooManyTriesError: OAuthErrorDomain()
data object UnknownError : OAuthErrorDomain()
}
@@ -1,8 +1,6 @@
package com.meloda.app.fast.network
sealed interface OAuthResponse<out R : Any, out E : Any> {
data class Success<out R : Any>(val response: R) : OAuthResponse<R, Nothing>
data class Error<out E : Any>(val error: E?) : OAuthResponse<Nothing, E>
}
@@ -1,6 +1,6 @@
package com.meloda.app.fast.network
import android.util.Log
import com.meloda.app.fast.model.api.responses.AuthDirectErrorOnlyResponse
import com.squareup.moshi.Moshi
import okhttp3.Request
import okio.Timeout
@@ -28,10 +28,10 @@ class OAuthResultCallFactory(private val moshi: Moshi) : CallAdapter.Factory() {
if (getRawType(callInnerType) == OAuthResponse::class.java) {
if (callInnerType is ParameterizedType) {
val resultInnerType = getParameterUpperBound(0, callInnerType)
return ResultCallAdapter<Any, OAuthError>(resultInnerType, moshi)
return ResultCallAdapter<Any>(resultInnerType, moshi)
}
return ResultCallAdapter<Nothing, Nothing>(Nothing::class.java, moshi)
return ResultCallAdapter<Nothing>(Nothing::class.java, moshi)
}
}
}
@@ -60,36 +60,36 @@ internal abstract class CallDelegate<In, Out>(protected val proxy: Call<In>) : C
abstract fun cloneImpl(): Call<Out>
}
private class ResultCallAdapter<R : Any, E : OAuthError>(
private class ResultCallAdapter<R : Any>(
private val type: Type,
private val moshi: Moshi
) : CallAdapter<R, Call<OAuthResponse<R, E>>> {
) : CallAdapter<R, Call<OAuthResponse<R, AuthDirectErrorOnlyResponse>>> {
override fun responseType() = type
override fun adapt(call: Call<R>): Call<OAuthResponse<R, E>> = ResultCall(call, moshi)
override fun adapt(call: Call<R>): Call<OAuthResponse<R, AuthDirectErrorOnlyResponse>> =
ResultCall(call, moshi)
}
internal class ResultCall<R : Any, E : OAuthError>(
internal class ResultCall<R : Any>(
proxy: Call<R>,
private val moshi: Moshi
) : CallDelegate<R, OAuthResponse<R, E>>(proxy) {
) : CallDelegate<R, OAuthResponse<R, AuthDirectErrorOnlyResponse>>(proxy) {
override fun enqueueImpl(callback: Callback<OAuthResponse<R, E>>) {
override fun enqueueImpl(callback: Callback<OAuthResponse<R, AuthDirectErrorOnlyResponse>>) {
proxy.enqueue(ResultCallback(this, callback, moshi))
}
override fun cloneImpl(): ResultCall<R, E> {
override fun cloneImpl(): ResultCall<R> {
return ResultCall(proxy.clone(), moshi)
}
private class ResultCallback<R : Any, E : OAuthError>(
private val proxy: ResultCall<R, E>,
private val callback: Callback<OAuthResponse<R, E>>,
private class ResultCallback<R : Any>(
private val proxy: ResultCall<R>,
private val callback: Callback<OAuthResponse<R, AuthDirectErrorOnlyResponse>>,
private val moshi: Moshi
) : Callback<R> {
@Suppress("UNCHECKED_CAST")
override fun onResponse(call: Call<R>, response: Response<R>) {
when {
response.isSuccessful -> {
@@ -106,92 +106,19 @@ internal class ResultCall<R : Any, E : OAuthError>(
else -> {
val errorBodyString = response.errorBody()?.string()
val baseError: OAuthError = moshi.adapter(OAuthError::class.java)
val baseError = moshi.adapter(AuthDirectErrorOnlyResponse::class.java)
.fromJson(errorBodyString.orEmpty()) ?: return
val error: OAuthError? = when (baseError.error) {
"9;Flood control" -> {
moshi.adapter(TooManyTriesError::class.java)
.fromJson(errorBodyString.orEmpty())
}
"invalid_client" -> {
moshi.adapter(InvalidCredentialsError::class.java)
.fromJson(errorBodyString.orEmpty())
}
"need_captcha" -> {
moshi.adapter(CaptchaRequiredError::class.java)
.fromJson(errorBodyString.orEmpty())
}
"invalid_request" -> {
when (val type = baseError.errorType) {
"wrong_otp" -> {
moshi.adapter(WrongValidationCodeError::class.java)
.fromJson(errorBodyString.orEmpty())
}
"otp_format_is_incorrect" -> {
moshi.adapter(WrongValidationCodeFormatError::class.java)
.fromJson(errorBodyString.orEmpty())
}
else -> {
Log.d(
"ResultCallback",
"onResponse: invalid_request; error_type: $type"
)
error("Unknown type: $type")
}
}
}
"need_validation" -> {
when (val description = baseError.errorDescription) {
"user has been banned" -> {
moshi.adapter(UserBannedError::class.java)
.fromJson(errorBodyString.orEmpty())
}
"sms sent, use code param",
"use app code" -> {
moshi.adapter(ValidationRequiredError::class.java)
.fromJson(errorBodyString.orEmpty())
}
else -> {
Log.d(
"ResultCallback",
"onResponse: need_validation; description: $description"
)
error("Unknown description: $description")
}
}
}
else -> null
}
error?.let {
callback.onResponse(
proxy,
Response.success(OAuthResponse.Error(error) as OAuthResponse<R, E>)
Response.success(OAuthResponse.Error(baseError) as OAuthResponse<R, AuthDirectErrorOnlyResponse>)
)
}
}
}
}
override fun onFailure(call: Call<R>, error: Throwable) {
val b = error
// TODO: 12/04/2024, Danil Nikolaev: handle
// callback.onResponse(
// proxy,
// Response.success(OAuthAnswer.Error((throwable = error)))
// )
throw error
}
}
@@ -1,7 +1,8 @@
package com.meloda.app.fast.network;
enum class ValidationType(val value: String) {
APP("2fa_app"), SMS("2fa_sms");
APP("2fa_app"),
SMS("2fa_sms");
companion object {
fun parse(value: String): ValidationType = entries.first { it.value == value }
@@ -0,0 +1,52 @@
package com.meloda.app.fast.network
enum class VkErrorCode(val code: Int) {
UNKNOWN_ERROR(1),
APP_DISABLED(2),
UNKNOWN_METHOD(3),
INVALID_SIGNATURE(4),
USER_AUTHORIZATION_FAILED(5),
TOO_MANY_REQUESTS(6),
NO_RIGHTS(7),
BAD_REQUEST(8),
TOO_MANY_SIMILAR_ACTIONS(9),
INTERNAL_SERVER_ERROR(10),
IN_TEST_MODE(11),
EXECUTE_CODE_COMPILE_ERROR(12),
EXECUTE_CODE_RUNTIME_ERROR(13),
CAPTCHA_NEEDED(14),
ACCESS_DENIED(15),
REQUIRES_REQUESTS_OVER_HTTPS(16),
VALIDATION_REQUIRED(17),
USER_BANNED_OR_DELETED(18),
ACTION_PROHIBITED(20),
ACTION_ALLOWED_ONLY_FOR_STANDALONE(21),
METHOD_OFF(23),
CONFIRMATION_REQUIRED(24),
PARAMETER_IS_NOT_SPECIFIED(100),
INCORRECT_APP_ID(101),
OUT_OF_LIMITS(103),
INCORRECT_USER_ID(113),
INCORRECT_TIMESTAMP(150),
ACCESS_TO_ALBUM_DENIED(200),
ACCESS_TO_AUDIO_DENIED(201),
ACCESS_TO_GROUP_DENIED(203),
ALBUM_IS_FULL(300),
ACTION_DENIED(500),
PERMISSION_DENIED(600),
CANNOT_SEND_MESSAGE_BLACK_LIST(900),
CANNOT_SEND_MESSAGE_GROUP(901),
INVALID_DOC_ID(1150),
INVALID_DOC_TITLE(1152),
ACCESS_TO_DOC_DENIED(1153),
SOME_AUTH_ERROR(104),
ACCESS_TOKEN_EXPIRED(1117);
companion object {
fun parse(code: Int): VkErrorCode = entries.firstOrNull { it.code == code }
?: throw IllegalArgumentException("Unknown error with value: $code")
}
}
@@ -1,62 +0,0 @@
package com.meloda.app.fast.network
@Suppress("unused")
object VkErrorCodes {
const val UnknownError = 1
const val AppDisabled = 2
const val UnknownMethod = 3
const val InvalidSignature = 4
const val UserAuthorizationFailed = 5
const val TooManyRequests = 6
const val NoRights = 7
const val BadRequest = 8
const val TooManySimilarActions = 9
const val InternalServerError = 10
const val InTestMode = 11
const val ExecuteCodeCompileError = 12
const val ExecuteCodeRuntimeError = 13
const val CaptchaNeeded = 14
const val AccessDenied = 15
const val RequiresRequestsOverHttps = 16
const val ValidationRequired = 17
const val UserBannedOrDeleted = 18
const val ActionProhibited = 20
const val ActionAllowedOnlyForStandalone = 21
const val MethodOff = 23
const val ConfirmationRequired = 24
const val ParameterIsNotSpecified = 100
const val IncorrectAppId = 101
const val OutOfLimits = 103
const val IncorrectUserId = 113
const val IncorrectTimestamp = 150
const val AccessToAlbumDenied = 200
const val AccessToAudioDenied = 201
const val AccessToGroupDenied = 203
const val AlbumIsFull = 300
const val ActionDenied = 500
const val PermissionDenied = 600
const val CannotSendMessageBlackList = 900
const val CannotSendMessageGroup = 901
const val InvalidDocId = 1150
const val InvalidDocTitle = 1152
const val AccessToDocDenied = 1153
const val AccessTokenExpired = 1117
}
object VkOAuthErrors {
const val UNKNOWN = "unknown_error"
const val NEED_VALIDATION = "need_validation"
const val NEED_CAPTCHA = "need_captcha"
const val INVALID_CLIENT = "invalid_client"
const val INVALID_REQUEST = "invalid_request"
const val FLOOD_CONTROL = "9;Flood control"
}
object VkErrorTypes {
const val WRONG_OTP_FORMAT = "otp_format_is_incorrect"
const val WRONG_OTP = "wrong_otp"
const val PASSWORD_BRUTEFORCE_ATTEMPT = "password_bruteforce_attempt"
}
@@ -0,0 +1,16 @@
package com.meloda.app.fast.network
enum class VkOAuthError(val value: String) {
UNKNOWN("unknown_error"),
NEED_VALIDATION("need_validation"),
NEED_CAPTCHA("need_captcha"),
INVALID_CLIENT("invalid_client"),
INVALID_REQUEST("invalid_request"),
FLOOD_CONTROL("9;Flood control");
companion object {
fun parse(value: String): VkOAuthError = entries.firstOrNull { it.value == value }
?: throw IllegalArgumentException("Unknown error with value: $value")
}
}
@@ -0,0 +1,12 @@
package com.meloda.app.fast.network;
enum class VkOAuthErrorType(val value: String) {
WRONG_OTP_FORMAT("otp_format_is_incorrect"),
WRONG_OTP("wrong_otp"),
PASSWORD_BRUTEFORCE_ATTEMPT("password_bruteforce_attempt");
companion object {
fun parse(value: String): VkOAuthErrorType = entries.firstOrNull { it.value == value }
?: throw IllegalArgumentException("Unknown error type with value: $value")
}
}
@@ -1,14 +1,23 @@
package com.meloda.app.fast.network.service.auth
import com.meloda.app.fast.model.api.responses.SendSmsResponse
import com.meloda.app.fast.model.api.responses.ValidateLoginResponse
import com.meloda.app.fast.model.api.responses.ValidatePhoneResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
import com.slack.eithernet.ApiResult
import retrofit2.http.GET
import retrofit2.http.Query
import retrofit2.http.QueryMap
interface AuthService {
@GET(AuthUrls.SEND_SMS)
suspend fun sendSms(@Query("sid") validationSid: String): ApiResult<ApiResponse<SendSmsResponse>, RestApiError>
@GET(AuthUrls.VALIDATE_PHONE)
suspend fun validatePhone(
@Query("sid") validationSid: String
): ApiResult<ApiResponse<ValidatePhoneResponse>, RestApiError>
@GET(AuthUrls.VALIDATE_LOGIN)
suspend fun validateLogin(
@QueryMap param: Map<String, String>
): ApiResult<ApiResponse<ValidateLoginResponse>, RestApiError>
}
@@ -3,6 +3,8 @@ package com.meloda.app.fast.network.service.auth
import com.meloda.app.fast.common.AppConstants
object AuthUrls {
private const val URL = AppConstants.URL_API
const val SEND_SMS = "${AppConstants.URL_API}/auth.validatePhone"
const val VALIDATE_PHONE = "$URL/auth.validatePhone"
const val VALIDATE_LOGIN = "$URL/auth.validateLogin"
}
@@ -1,6 +1,7 @@
package com.meloda.app.fast.network.service.oauth
import com.meloda.app.fast.model.api.responses.AuthDirectResponse
import com.meloda.app.fast.model.api.responses.GetAnonymousTokenResponse
import com.slack.eithernet.ApiResult
import com.slack.eithernet.DecodeErrorBody
import retrofit2.http.GET
@@ -11,6 +12,12 @@ interface OAuthService {
@DecodeErrorBody
@GET(OAuthUrls.DIRECT_AUTH)
suspend fun auth(
@QueryMap param: Map<String, String?>
@QueryMap param: Map<String, String>
): ApiResult<AuthDirectResponse, AuthDirectResponse>
@DecodeErrorBody
@GET(OAuthUrls.GET_ANONYMOUS_TOKEN)
suspend fun getAnonymousToken(
@QueryMap param: Map<String, String>
): ApiResult<GetAnonymousTokenResponse, GetAnonymousTokenResponse>
}
@@ -3,6 +3,8 @@ package com.meloda.app.fast.network.service.oauth
import com.meloda.app.fast.common.AppConstants
object OAuthUrls {
private const val URL = AppConstants.URL_OAUTH
const val DIRECT_AUTH = "${AppConstants.URL_OAUTH}/token"
const val DIRECT_AUTH = "$URL/token"
const val GET_ANONYMOUS_TOKEN = "$URL/get_anonym_token"
}
+6 -1
View File
@@ -32,9 +32,14 @@ android {
}
dependencies {
api(projects.core.designsystem)
implementation(projects.core.common)
api(projects.core.model)
implementation(libs.haze)
implementation(libs.haze.materials)
implementation(platform(libs.compose.bom))
implementation(libs.bundles.compose)
debugImplementation(libs.compose.ui.tooling)
}
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.basic
import android.os.Build
import android.view.autofill.AutofillManager
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.basic
/*
* Copyright 2020 The Android Open Source Project
@@ -22,6 +22,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.luminance
import com.meloda.app.fast.ui.util.isUsingDarkTheme
/**
* Default alpha levels used by Material components.
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.basic
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@@ -1,4 +1,4 @@
package com.meloda.app.fast.conversations
package com.meloda.app.fast.ui.components
// TODO: 26.08.2023, Danil Nikolaev: rewrite
@@ -264,7 +264,7 @@ fun DotsCollision() {
animationSpec = infiniteRepeatable(animation = keyframes {
durationMillis = delayUnit * 3
0f at 0 using LinearEasing
-maxOffset at delayUnit / 2 using LinearEasing
maxOffset at delayUnit / 2 using LinearEasing
0f at delayUnit
}), label = ""
)
@@ -1,4 +1,4 @@
package com.meloda.app.fast.ui
package com.meloda.app.fast.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -11,6 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
@@ -40,3 +41,12 @@ fun ErrorView(
}
}
}
@Preview
@Composable
private fun ErrorViewPreview() {
ErrorView(
text = "Some error occurred",
buttonText = "Restart"
)
}
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem.components
package com.meloda.app.fast.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -7,6 +7,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun FullScreenLoader(modifier: Modifier = Modifier) {
@@ -19,3 +20,9 @@ fun FullScreenLoader(modifier: Modifier = Modifier) {
CircularProgressIndicator()
}
}
@Preview
@Composable
private fun FullScreenLoaderPreview() {
FullScreenLoader()
}
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@@ -35,7 +35,9 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.parseString
import com.meloda.app.fast.designsystem.ImmutableList.Companion.toImmutableList
import com.meloda.app.fast.ui.util.ImmutableList
import com.meloda.app.fast.ui.util.ImmutableList.Companion.toImmutableList
import com.meloda.app.fast.ui.util.getString
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -239,7 +241,7 @@ fun MaterialDialog(
}
}
// TODO: 08.04.2023, Danil Nikolaev: refactor this
// TODO: 08.04.2023, Danil Nikolaev: remove this
@Deprecated("need refactoring")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -452,8 +454,9 @@ fun MaterialDialog(
}
}
@Composable
private fun AlertItems(
fun AlertItems(
selectionType: ItemsSelectionType,
items: ImmutableList<DialogItem>,
onItemClick: ((index: Int) -> Unit)? = null,
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem.components
package com.meloda.app.fast.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -8,7 +8,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.meloda.app.fast.designsystem.R
import androidx.compose.ui.tooling.preview.Preview
import com.meloda.app.fast.ui.R
@Composable
fun NoItemsView(
@@ -25,3 +26,11 @@ fun NoItemsView(
)
}
}
@Preview
@Composable
private fun NoItemsViewPreview() {
NoItemsView(
customText = "Nothing here..."
)
}
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -10,8 +10,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.meloda.app.fast.ui.R
@Preview
@Composable
fun TextFieldErrorText(
modifier: Modifier = Modifier,
@@ -30,3 +30,9 @@ fun TextFieldErrorText(
)
}
}
@Preview
@Composable
private fun TextFieldErrorPreview() {
TextFieldErrorText(text = "Error")
}
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.model
data class TabItem(
val titleResId: Int?,
@@ -1,4 +1,4 @@
package com.meloda.app.fast.datastore.model
package com.meloda.app.fast.ui.model
data class ThemeConfig(
val usingDarkStyle: Boolean,
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.theme
import android.app.Activity
import android.os.Build
@@ -20,8 +20,8 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import com.meloda.app.fast.datastore.model.ThemeConfig
import com.meloda.app.fast.designsystem.colorschemes.ClassicColorScheme
import com.meloda.app.fast.ui.R
import com.meloda.app.fast.ui.model.ThemeConfig
import dev.chrisbanes.haze.HazeState
private val googleSansFonts = FontFamily(
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem.colorschemes
package com.meloda.app.fast.ui.theme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
@@ -1,8 +1,7 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.util
import android.content.res.Configuration
import android.view.KeyEvent
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
@@ -17,17 +16,17 @@ import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.util.AndroidUtils
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.SettingsKeys
@Composable
fun isUsingDarkTheme(): Boolean {
val nightThemeMode = SettingsController.getInt(
SettingsKeys.KEY_APPEARANCE_DARK_THEME,
SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_THEME
)
val appForceDarkMode = nightThemeMode == AppCompatDelegate.MODE_NIGHT_YES
val appBatterySaver = nightThemeMode == AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
// val nightThemeMode = AppCompatDelegate.MODE_NIGHT_YES
// SettingsController.getInt(
// SettingsKeys.KEY_APPEARANCE_DARK_THEME,
// SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_THEME
// )
// val appForceDarkMode = nightThemeMode == AppCompatDelegate.MODE_NIGHT_YES
// val appBatterySaver = nightThemeMode == AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
val context = LocalContext.current
@@ -37,7 +36,8 @@ fun isUsingDarkTheme(): Boolean {
val isSystemUsingDarkTheme =
systemUiNightMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
return appForceDarkMode || (appBatterySaver && isSystemBatterySaver) || (!appBatterySaver && isSystemUsingDarkTheme && nightThemeMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
return true
// return appForceDarkMode || (appBatterySaver && isSystemBatterySaver) || (!appBatterySaver && isSystemUsingDarkTheme && nightThemeMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
@Composable
@@ -1,4 +1,4 @@
package com.meloda.app.fast.designsystem
package com.meloda.app.fast.ui.util
import androidx.compose.runtime.Immutable

Some files were not shown because too many files have changed in this diff Show More