move all ui-related classes and files to ui module
This commit is contained in:
@@ -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(">", ">")
|
||||
// replace("<", "<")
|
||||
// replace("<br/>", "\n")
|
||||
// replace("–", "-")
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
+20
-93
@@ -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>)
|
||||
)
|
||||
}
|
||||
callback.onResponse(
|
||||
proxy,
|
||||
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,9 +1,10 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
+12
-3
@@ -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"
|
||||
}
|
||||
|
||||
+8
-1
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
-1
@@ -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
|
||||
+2
-1
@@ -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
-1
@@ -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
|
||||
@@ -0,0 +1,341 @@
|
||||
package com.meloda.app.fast.ui.components
|
||||
|
||||
// TODO: 26.08.2023, Danil Nikolaev: rewrite
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.keyframes
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
const val numberOfDots = 3
|
||||
val dotSize = 6.dp
|
||||
val dotColor: Color = Color.Blue
|
||||
const val delayUnit = 300
|
||||
const val duration = numberOfDots * delayUnit
|
||||
val spaceBetween = 2.dp
|
||||
|
||||
@Composable
|
||||
fun DotsPulsing() {
|
||||
|
||||
@Composable
|
||||
fun Dot(scale: Float) {
|
||||
Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.scale(scale)
|
||||
.background(
|
||||
color = dotColor,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "")
|
||||
|
||||
@Composable
|
||||
fun animateScaleWithDelay(delay: Int) = infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(animation = keyframes {
|
||||
durationMillis = delayUnit * numberOfDots
|
||||
0f at delay using LinearEasing
|
||||
1f at delay + delayUnit using LinearEasing
|
||||
0f at delay + duration
|
||||
}), label = ""
|
||||
)
|
||||
|
||||
val scales = arrayListOf<State<Float>>()
|
||||
for (i in 0 until numberOfDots) {
|
||||
scales.add(animateScaleWithDelay(delay = i * delayUnit))
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
scales.forEach {
|
||||
Dot(it.value)
|
||||
Spacer(Modifier.width(spaceBetween))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun DotsElastic() {
|
||||
val minScale = 0.6f
|
||||
|
||||
@Composable
|
||||
fun Dot(scale: Float) {
|
||||
Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.scale(scaleX = minScale, scaleY = scale)
|
||||
.background(
|
||||
color = dotColor,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "")
|
||||
|
||||
@Composable
|
||||
fun animateScaleWithDelay(delay: Int) = infiniteTransition.animateFloat(
|
||||
initialValue = minScale,
|
||||
targetValue = minScale,
|
||||
animationSpec = infiniteRepeatable(animation = keyframes {
|
||||
durationMillis = duration
|
||||
minScale at delay using LinearEasing
|
||||
1f at delay + delayUnit using LinearEasing
|
||||
minScale at delay + duration
|
||||
}), label = ""
|
||||
)
|
||||
|
||||
val scales = arrayListOf<State<Float>>()
|
||||
for (i in 0 until numberOfDots) {
|
||||
scales.add(animateScaleWithDelay(delay = i * delayUnit))
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
scales.forEach {
|
||||
Dot(it.value)
|
||||
Spacer(Modifier.width(spaceBetween))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotsFlashing(
|
||||
modifier: Modifier = Modifier,
|
||||
dotSize: Dp = 6.dp,
|
||||
dotColor: Color = Color.Blue,
|
||||
spaceBetween: Dp = 2.dp,
|
||||
numberOfDots: Int = 3,
|
||||
) {
|
||||
val minAlpha = 0.1f
|
||||
|
||||
@Composable
|
||||
fun Dot(alpha: Float) = Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.alpha(alpha)
|
||||
.background(
|
||||
color = dotColor, shape = CircleShape
|
||||
)
|
||||
)
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "")
|
||||
|
||||
@Composable
|
||||
fun animateAlphaWithDelay(delay: Int) = infiniteTransition.animateFloat(
|
||||
initialValue = minAlpha,
|
||||
targetValue = minAlpha,
|
||||
animationSpec = infiniteRepeatable(animation = keyframes {
|
||||
durationMillis = duration
|
||||
minAlpha at delay using LinearEasing
|
||||
1f at delay + delayUnit using LinearEasing
|
||||
minAlpha at delay + duration
|
||||
}), label = ""
|
||||
)
|
||||
|
||||
val alphas = arrayListOf<State<Float>>()
|
||||
for (i in 0 until numberOfDots) {
|
||||
alphas.add(animateAlphaWithDelay(delay = i * delayUnit))
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
alphas.forEach {
|
||||
Dot(it.value)
|
||||
Spacer(Modifier.width(spaceBetween))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotsTyping(
|
||||
modifier: Modifier = Modifier,
|
||||
dotSize: Dp = 6.dp,
|
||||
dotColor: Color = Color.Blue,
|
||||
spaceBetween: Dp = 2.dp,
|
||||
numberOfDots: Int = 3,
|
||||
) {
|
||||
val maxOffset = (numberOfDots * 2).toFloat()
|
||||
|
||||
@Composable
|
||||
fun Dot(offset: Float) {
|
||||
Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.offset(y = -offset.dp)
|
||||
.background(
|
||||
color = dotColor,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "")
|
||||
|
||||
@Composable
|
||||
fun animateOffsetWithDelay(delay: Int) = infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(animation = keyframes {
|
||||
durationMillis = duration
|
||||
0f at delay using LinearEasing
|
||||
maxOffset at delay + delayUnit using LinearEasing
|
||||
0f at delay + (duration / 2)
|
||||
}), label = ""
|
||||
)
|
||||
|
||||
val offsets = arrayListOf<State<Float>>()
|
||||
for (i in 0 until numberOfDots) {
|
||||
offsets.add(animateOffsetWithDelay(delay = i * delayUnit))
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier.padding(top = maxOffset.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
offsets.forEach {
|
||||
Dot(it.value)
|
||||
Spacer(Modifier.width(spaceBetween))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotsCollision() {
|
||||
val maxOffset = 30f
|
||||
val delayUnit = 500
|
||||
|
||||
@Composable
|
||||
fun Dot(offset: Float) {
|
||||
Spacer(
|
||||
Modifier
|
||||
.size(dotSize)
|
||||
.offset(x = offset.dp)
|
||||
.background(
|
||||
color = dotColor,
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "")
|
||||
|
||||
val offsetLeft by infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(animation = keyframes {
|
||||
durationMillis = delayUnit * 3
|
||||
0f at 0 using LinearEasing
|
||||
maxOffset at delayUnit / 2 using LinearEasing
|
||||
0f at delayUnit
|
||||
}), label = ""
|
||||
)
|
||||
val offsetRight by infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 0f,
|
||||
animationSpec = infiniteRepeatable(animation = keyframes {
|
||||
durationMillis = delayUnit * 3
|
||||
0f at delayUnit using LinearEasing
|
||||
maxOffset at delayUnit + delayUnit / 2 using LinearEasing
|
||||
0f at delayUnit * 2
|
||||
}), label = ""
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(horizontal = maxOffset.dp)
|
||||
) {
|
||||
Dot(offsetLeft)
|
||||
Spacer(Modifier.width(spaceBetween))
|
||||
for (i in 0 until numberOfDots - 2) {
|
||||
Dot(0f)
|
||||
Spacer(Modifier.width(spaceBetween))
|
||||
}
|
||||
Dot(offsetRight)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun DotsPreview() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
val spaceSize = 16.dp
|
||||
|
||||
Text(
|
||||
text = "Dots pulsing", //style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsPulsing()
|
||||
|
||||
Spacer(Modifier.height(spaceSize))
|
||||
|
||||
Text(
|
||||
text = "Dots elastic", //style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsElastic()
|
||||
|
||||
Spacer(Modifier.height(spaceSize))
|
||||
|
||||
Text(
|
||||
text = "Dots flashing", //style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsFlashing()
|
||||
|
||||
Spacer(Modifier.height(spaceSize))
|
||||
|
||||
Text(
|
||||
text = "Dots typing", //style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsTyping()
|
||||
|
||||
Spacer(Modifier.height(spaceSize))
|
||||
|
||||
Text(
|
||||
text = "Dots collision", //style = MaterialTheme.typography.h5
|
||||
)
|
||||
DotsCollision()
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -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"
|
||||
)
|
||||
}
|
||||
+8
-1
@@ -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()
|
||||
}
|
||||
+7
-4
@@ -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,
|
||||
+11
-2
@@ -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..."
|
||||
)
|
||||
}
|
||||
+8
-2
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
package com.meloda.app.fast.designsystem
|
||||
package com.meloda.app.fast.ui.model
|
||||
|
||||
data class TabItem(
|
||||
val titleResId: Int?,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.meloda.app.fast.datastore.model
|
||||
package com.meloda.app.fast.ui.model
|
||||
|
||||
data class ThemeConfig(
|
||||
val usingDarkStyle: Boolean,
|
||||
+3
-3
@@ -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
-1
@@ -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
|
||||
+11
-11
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
package com.meloda.app.fast.designsystem
|
||||
package com.meloda.app.fast.ui.util
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
|
Before Width: | Height: | Size: 688 B After Width: | Height: | Size: 688 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user