Upstream changes (#23)

This commit is contained in:
2024-07-11 02:12:32 +03:00
committed by GitHub
parent 8a6378f509
commit 3503ecffab
906 changed files with 23577 additions and 24115 deletions
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,10 @@
package com.meloda.app.fast.common
object AppConstants {
const val INSTALL_APP_MIME_TYPE = "application/vnd.android.package-archive"
const val API_VERSION = "5.173"
const val URL_OAUTH = "https://oauth.vk.com"
const val URL_API = "https://api.vk.com/method"
}
@@ -0,0 +1,31 @@
package com.meloda.app.fast.common
import androidx.core.net.toUri
import okhttp3.Interceptor
import okhttp3.Response
import java.net.URLEncoder
class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().url.newBuilder()
val uri = builder.build().toUri().toString().toUri()
if (uri.getQueryParameter("v") == null) {
builder.addQueryParameter(
name = "v",
value = URLEncoder.encode(AppConstants.API_VERSION, "utf-8")
)
}
if (UserConfig.accessToken.isNotBlank()) {
builder.addQueryParameter(
"access_token",
URLEncoder.encode(UserConfig.accessToken, "utf-8")
)
}
return chain.proceed(chain.request().newBuilder().apply { url(builder.build()) }.build())
}
}
@@ -0,0 +1,22 @@
package com.meloda.app.fast.common
import android.os.Bundle
import android.os.Parcelable
import androidx.core.os.BundleCompat
import androidx.navigation.NavType
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
inline fun <reified T : Parcelable> customNavType(
isNullableAllowed: Boolean = false,
json: Json = Json
) = object : NavType<T>(isNullableAllowed = isNullableAllowed) {
override fun get(bundle: Bundle, key: String) =
BundleCompat.getParcelable(bundle, key, T::class.java)
override fun parseValue(value: String): T = json.decodeFromString(value)
override fun serializeAsValue(value: T): String = json.encodeToString(value)
override fun put(bundle: Bundle, key: String, value: T) = bundle.putParcelable(key, value)
}
@@ -0,0 +1,29 @@
package com.meloda.app.fast.common
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
sealed class UiImage {
data class Resource(@DrawableRes val resId: Int) : UiImage()
data class Simple(val drawable: Drawable) : UiImage()
data class Color(@ColorInt val color: Int) : UiImage()
data class ColorResource(@ColorRes val resId: Int) : UiImage()
data class Url(val url: String) : UiImage()
fun extractUrl(): String? = when (this) {
is Url -> this.url
else -> null
}
fun extractResId(): Int = when (this) {
is Resource -> this.resId
else -> throw IllegalStateException("this UiImage is not Resource")
}
}
@@ -0,0 +1,40 @@
package com.meloda.app.fast.common
import android.content.res.Resources
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
sealed class UiText {
data object Empty : UiText()
data class Resource(@StringRes val resId: Int) : UiText()
data class ResourceParams(
@StringRes val value: Int,
val args: List<Any?>,
) : UiText()
data class Simple(val text: String) : UiText()
data class QuantityResource(@PluralsRes val resId: Int, val quantity: Int) : UiText()
}
fun UiText?.parseString(resources: Resources): String? {
return when (this) {
is UiText.Resource -> resources.getString(resId)
is UiText.ResourceParams -> {
val processedArgs = args.map { any ->
when (any) {
is UiText -> any.parseString(resources)
else -> any
}
}
resources.getString(value, *processedArgs.toTypedArray())
}
is UiText.QuantityResource -> resources.getQuantityString(resId, quantity, quantity)
is UiText.Simple -> text
else -> null
}
}
@@ -0,0 +1,39 @@
package com.meloda.app.fast.common
import android.content.SharedPreferences
import androidx.core.content.edit
import kotlin.properties.Delegates
object UserConfig {
private const val ARG_CURRENT_USER_ID = "current_user_id"
private var preferences: SharedPreferences by Delegates.notNull()
fun init(preferences: SharedPreferences) {
this.preferences = preferences
}
var currentUserId: Int = -1
get() = preferences.getInt(ARG_CURRENT_USER_ID, -1)
set(value) {
field = value
preferences.edit { putInt(ARG_CURRENT_USER_ID, value) }
}
var userId: Int = -1
var accessToken: String = ""
var fastToken: String? = ""
var trustedHash: String? = null
fun clear() {
currentUserId = -1
accessToken = ""
fastToken = ""
userId = -1
}
fun isLoggedIn(): Boolean {
return currentUserId > 0 && userId > 0 && accessToken.isNotBlank()
}
}
@@ -0,0 +1,53 @@
package com.meloda.app.fast.common
object VkConstants {
const val GROUP_FIELDS = "description,members_count,counters,status,verified"
const val USER_FIELDS =
"photo_50,photo_100,photo_200,status,screen_name,online,online_mobile,last_seen,verified,sex,online_info,bdate"
const val ALL_FIELDS =
"$USER_FIELDS,$GROUP_FIELDS"
const val LP_VERSION = 10
const val VK_APP_ID = "2274003"
const val VK_SECRET = "hHbZxrka2uZ6jB1inYsH"
const val FAST_GROUP_ID = -119516304
const val FAST_APP_ID = "6964679"
object Auth {
const val SCOPE = "notify," +
"friends," +
"photos," +
"audio," +
"video," +
"docs," +
"status," +
"notes," +
"pages," +
"wall," +
"groups," +
"messages," +
"offline," +
"notifications"
object GrantType {
const val PASSWORD = "password"
}
}
// val restrictedToEditAttachments = listOf<Class<out VkAttachment>>(
// VkCallDomain::class.java,
// VkCuratorDomain::class.java,
// VkEventDomain::class.java,
// VkGiftDomain::class.java,
// VkGraffitiDomain::class.java,
// VkGroupCallDomain::class.java,
// VkStoryDomain::class.java,
// VkAudioMessageDomain::class.java,
// VkWidgetDomain::class.java
// )
}
@@ -0,0 +1,12 @@
package com.meloda.app.fast.common.di
import coil.ImageLoader
import org.koin.dsl.module
val commonModule = module {
single {
ImageLoader.Builder(get())
.crossfade(true)
.build()
}
}
@@ -0,0 +1,151 @@
package com.meloda.app.fast.common.extensions
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
fun Context.restartApp() {
(this as? Activity)?.let { activity ->
activity.finishAffinity()
activity.startActivity(
Intent(
this,
Class.forName("com.meloda.app.fast.MainActivity")
)
)
}
}
inline fun <T> Iterable<T>.findWithIndex(predicate: (T) -> Boolean): Pair<Int, T>? {
val value = firstOrNull(predicate) ?: return null
return indexOf(value).let { index -> if (index == -1) null else index to value }
}
fun <T> MutableList<T>.addIf(element: T, condition: () -> Boolean) {
if (condition.invoke()) add(element)
}
context(ViewModel)
fun <T> Flow<T>.listenValue(action: suspend (T) -> Unit) = listenValue(viewModelScope, action)
fun <T> Flow<T>.listenValue(
coroutineScope: CoroutineScope,
action: suspend (T) -> Unit
): Job = onEach(action::invoke).launchIn(coroutineScope)
fun createTimerFlow(
time: Int,
onStartAction: (suspend () -> Unit)? = null,
onTickAction: (suspend (remainedTime: Int) -> Unit)? = null,
onTimeoutAction: (suspend () -> Unit)? = null,
interval: Duration = 1.seconds
): Flow<Int> = (time downTo 0)
.asSequence()
.asFlow()
.onStart { onStartAction?.invoke() }
.onEach { timeLeft ->
onTickAction?.invoke(timeLeft)
if (timeLeft == 0) {
onTimeoutAction?.invoke()
} else {
delay(interval)
}
}
fun createTimerFlow(
isNeedToEndCondition: suspend () -> Boolean,
onStartAction: (suspend () -> Unit)? = null,
onTickAction: (suspend () -> Unit)? = null,
onEndAction: (suspend () -> Unit)? = null,
interval: Duration = 1.seconds
): Flow<Boolean> = flow {
while (true) {
val isNeedToEnd = isNeedToEndCondition()
emit(isNeedToEnd)
if (isNeedToEnd) break
}
}
.onStart { onStartAction?.invoke() }
.onEach { isNeedToEnd ->
onTickAction?.invoke()
if (isNeedToEnd) {
onEndAction?.invoke()
} else {
delay(interval)
}
}
context(ViewModel)
fun <T> MutableSharedFlow<T>.emitOnMainScope(value: T) = emitOnScope(Dispatchers.Main) { value }
context(ViewModel)
fun <T> MutableSharedFlow<T>.emitOnScope(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
value: () -> T,
) {
viewModelScope.launch(coroutineContext) {
emit(value())
}
}
context(CoroutineScope)
suspend fun <T> MutableSharedFlow<T>.emitWithMain(value: T) {
withContext(Dispatchers.Main) {
emit(value)
}
}
context(ViewModel)
fun <T> MutableStateFlow<T>.updateValue(newValue: T) = this.update { newValue }
fun <T> MutableStateFlow<T>.setValue(function: (T) -> T) {
val newValue = function(value)
update { newValue }
}
fun Any.asInt(): Int {
return when (this) {
is Number -> this.toInt()
else -> throw IllegalArgumentException("Object is not numeric")
}
}
fun <T> Any.toList(mapper: (old: Any) -> T): List<T> {
return when (this) {
is List<*> -> this.mapNotNull { it?.run(mapper) }
else -> emptyList()
}
}
fun isSdkAtLeast(sdkInt: Int, action: (() -> Unit)? = null): Boolean {
return if (Build.VERSION.SDK_INT >= sdkInt) {
action?.invoke()
true
} else {
false
}
}
@@ -0,0 +1,17 @@
package com.meloda.app.fast.common.extensions
inline fun String?.ifEmpty(defaultValue: () -> String?): String? =
if (this?.isEmpty() == true) defaultValue() else this
fun String?.orDots(count: Int = 3): String {
return this ?: ("." * count)
}
operator fun String.times(count: Int): String {
val builder = StringBuilder()
for (i in 0 until count) {
builder.append(this)
}
return builder.toString()
}
@@ -0,0 +1,18 @@
package com.meloda.app.fast.common.extensions.navigation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import org.koin.androidx.compose.koinViewModel
import org.koin.androidx.compose.navigation.koinNavViewModel
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(navController: NavController): T {
val navGraphRoute = destination.parent?.route ?: return koinViewModel()
val parentEntry = remember(this) {
navController.getBackStackEntry(navGraphRoute)
}
return koinNavViewModel(viewModelStoreOwner = parentEntry)
}
@@ -0,0 +1,208 @@
package com.meloda.app.fast.common.util
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import android.widget.Toast
import androidx.core.content.FileProvider
import java.io.File
import java.io.FileOutputStream
private object BuildConfig {
const val DEBUG = true
const val APPLICATION_ID = "com.meloda.app.fast"
}
object AndroidUtils {
fun copyText(
context: Context,
label: String? = "",
text: String,
withToast: Boolean = false
) {
val clipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(ClipData.newPlainText(label, text))
if (withToast && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show()
}
}
fun copyImage(
context: Context,
label: String? = "",
imageUri: Uri,
withToast: Boolean = false
) {
val clipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(ClipData.newRawUri(label, imageUri))
if (withToast && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show()
}
}
fun bytesToMegabytes(bytes: Double): Double {
return bytes / 1024 / 1024
}
fun bytesToHumanReadableSize(bytes: Double): String = when {
bytes >= 1 shl 30 -> "%.1f GB".format(bytes / (1 shl 30))
bytes >= 1 shl 20 -> "%.1f MB".format(bytes / (1 shl 20))
bytes >= 1 shl 10 -> "%.1f KB".format(bytes / (1 shl 10))
else -> "$bytes B"
}
fun openAppNotificationsSettings(context: Context) {
val packageName = context.packageName
val intent = Intent("android.settings.APP_NOTIFICATION_SETTINGS")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.putExtra("android.provider.extra.APP_PACKAGE", packageName)
} else {
intent.putExtra("app_package", packageName)
intent.putExtra("app_uid", context.applicationInfo.uid)
}
context.startActivity(intent)
}
@Suppress("DEPRECATION")
fun isCanInstallUnknownApps(context: Context): Boolean {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Settings.Secure.getInt(
context.contentResolver,
Settings.Secure.INSTALL_NON_MARKET_APPS
) == 1
} else {
context.packageManager.canRequestPackageInstalls()
}
}
fun openInstallUnknownAppsScreen(context: Context) {
context.startActivity(Intent().apply {
action = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Settings.ACTION_SECURITY_SETTINGS
} else {
data = Uri.parse("package:${BuildConfig.APPLICATION_ID}")
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES
}
})
}
fun getInstallPackageIntent(
context: Context,
providerPath: String,
fileToRead: File,
): Intent {
val intent = Intent(Intent.ACTION_VIEW)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
intent.data = FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + providerPath,
fileToRead
)
return intent
}
fun isBatterySaverOn(context: Context): Boolean {
return (context.getSystemService(Context.POWER_SERVICE) as? PowerManager)?.isPowerSaveMode == true
}
fun getImageToShare(context: Context, existingFile: File): Uri? {
val imageFolder = File(context.cacheDir, "images")
return try {
imageFolder.mkdirs()
val copyToFile = File(imageFolder, "shared_image.png")
if (copyToFile.exists()) {
copyToFile.delete()
}
val file = existingFile.copyTo(copyToFile)
FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", file)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun getImageToShare(context: Context, bitmap: Bitmap): Uri? {
val imageFolder = File(context.cacheDir, "images")
return try {
imageFolder.mkdirs()
val file = File(imageFolder, "shared_image.png")
val outputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, outputStream)
outputStream.flush()
outputStream.close()
FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", file)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun showShareSheet(context: Context, content: ShareContent) {
val intent = Intent(Intent.ACTION_SEND).apply {
type = when (content) {
is ShareContent.Text -> {
putExtra(Intent.EXTRA_TEXT, content.text)
"text/plain"
}
is ShareContent.Image -> {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
putExtra(Intent.EXTRA_STREAM, content.uri)
"image/png"
}
is ShareContent.TextWithImage -> {
putExtra(Intent.EXTRA_TEXT, content.text)
putExtra(Intent.EXTRA_STREAM, content.imageUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
"image/png"
}
}
}
val contentType = when (content) {
is ShareContent.Text -> "Text"
is ShareContent.Image -> "Image"
is ShareContent.TextWithImage -> "Text with image"
}
val chooserIntent = Intent.createChooser(intent, "Share $contentType")
context.startActivity(chooserIntent)
}
}
sealed class ShareContent {
data class Text(val text: String) : ShareContent()
data class Image(val uri: Uri) : ShareContent()
data class TextWithImage(val text: String, val imageUri: Uri) : ShareContent()
}
@@ -0,0 +1,83 @@
package com.meloda.app.fast.common.util
import android.content.res.Resources
import com.conena.nanokt.jvm.util.dayOfMonth
import com.conena.nanokt.jvm.util.hour
import com.conena.nanokt.jvm.util.hourOfDay
import com.conena.nanokt.jvm.util.millisecond
import com.conena.nanokt.jvm.util.minute
import com.conena.nanokt.jvm.util.month
import com.conena.nanokt.jvm.util.second
import com.conena.nanokt.jvm.util.year
import com.meloda.app.fast.common.R
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
object TimeUtils {
fun removeTime(date: Date): Long {
return Calendar.getInstance().apply {
time = date
hourOfDay = 0
minute = 0
second = 0
millisecond = 0
}.timeInMillis
}
fun getLocalizedDate(resources: Resources, date: Long): String {
val now = Calendar.getInstance()
val then = Calendar.getInstance().also { it.timeInMillis = date }
val pattern = when {
now.year != then.year -> "dd MMM yyyy"
now.month != then.month -> "dd MMMM"
now.dayOfMonth != then.dayOfMonth -> {
if (now.dayOfMonth - then.dayOfMonth == 1) {
return resources.getString(R.string.yesterday)
} else {
"dd MMMM"
}
}
else -> return resources.getString(R.string.today)
}
return SimpleDateFormat(pattern, Locale.getDefault()).format(date)
}
fun getLocalizedTime(resources: Resources, date: Long): String {
val now = Calendar.getInstance()
val then = Calendar.getInstance().also { it.timeInMillis = date }
return when {
now.year != then.year -> {
"${now.year - then.year}${resources.getString(R.string.year_short).lowercase()}"
}
now.month != then.month -> {
"${now.month - then.month}${resources.getString(R.string.month_short).lowercase()}"
}
now.dayOfMonth != then.dayOfMonth -> {
val change = now.dayOfMonth - then.dayOfMonth
if (change % 7 == 0) {
"${change / 7}${resources.getString(R.string.week_short).lowercase()}"
} else {
"$change${resources.getString(R.string.day_short).lowercase()}"
}
}
now.hour == then.hour && now.minute == then.minute -> {
resources.getString(R.string.time_now).lowercase()
}
else -> {
SimpleDateFormat("HH:mm", Locale.getDefault()).format(date)
}
}
}
}
@@ -0,0 +1,721 @@
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("&amp;", "&")
// replace("&quot;", "\"")
// 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
// )
// }
// }
//
//
//}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="yesterday">Yesterday</string>
<string name="today">Today</string>
<string name="year_short">Y</string>
<string name="month_short">M</string>
<string name="week_short">W</string>
<string name="day_short">D</string>
<string name="time_now">Now</string>
</resources>