reworked chat materials screen and some fixes
This commit is contained in:
@@ -1,75 +1,78 @@
|
|||||||
package dev.meloda.fast.model.api.data
|
package dev.meloda.fast.model.api.data
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import dev.meloda.fast.model.api.domain.VkVideoDomain
|
import dev.meloda.fast.model.api.domain.VkVideoDomain
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class VkVideoData(
|
data class VkVideoData(
|
||||||
val id: Int,
|
@Json(name = "id") val id: Int,
|
||||||
val title: String,
|
@Json(name = "title") val title: String,
|
||||||
val width: Int?,
|
@Json(name = "width") val width: Int?,
|
||||||
val height: Int?,
|
@Json(name = "height") val height: Int?,
|
||||||
val duration: Int,
|
@Json(name = "duration") val duration: Int,
|
||||||
val date: Int,
|
@Json(name = "date") val date: Int,
|
||||||
val comments: Int?,
|
@Json(name = "comments") val comments: Int?,
|
||||||
val description: String?,
|
@Json(name = "description") val description: String?,
|
||||||
val player: String?,
|
@Json(name = "player") val player: String?,
|
||||||
val added: Int?,
|
@Json(name = "added") val added: Int?,
|
||||||
val type: String,
|
@Json(name = "type") val type: String,
|
||||||
val views: Int,
|
@Json(name = "views") val views: Int,
|
||||||
val access_key: String?,
|
@Json(name = "access_key") val accessKey: String?,
|
||||||
val owner_id: Int,
|
@Json(name = "owner_id") val ownerId: Int,
|
||||||
val is_favorite: Boolean?,
|
@Json(name = "is_favorite") val isFavorite: Boolean?,
|
||||||
val image: List<Image>?,
|
@Json(name = "image") val image: List<Image>?,
|
||||||
val first_frame: List<FirstFrame>?,
|
@Json(name = "first_frame") val firstFrame: List<FirstFrame>?,
|
||||||
val files: File?
|
@Json(name = "files") val files: File?
|
||||||
) : VkAttachmentData {
|
) : VkAttachmentData {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Image(
|
data class Image(
|
||||||
val width: Int,
|
@Json(name = "width") val width: Int,
|
||||||
val height: Int,
|
@Json(name = "height") val height: Int,
|
||||||
val url: String,
|
@Json(name = "url") val url: String,
|
||||||
val with_padding: Int?
|
@Json(name = "with_padding") val withPadding: Int?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun asVideoImage() = VkVideoDomain.VideoImage(
|
fun asVideoImage() = VkVideoDomain.VideoImage(
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
url = url,
|
url = url,
|
||||||
withPadding = with_padding == 1
|
withPadding = withPadding == 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class FirstFrame(
|
data class FirstFrame(
|
||||||
val height: Int,
|
@Json(name = "height") val height: Int,
|
||||||
val width: Int,
|
@Json(name = "width") val width: Int,
|
||||||
val url: String
|
@Json(name = "url") val url: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class File(
|
data class File(
|
||||||
val mp4_240: String?,
|
@Json(name = "mp4_240") val mp4240: String?,
|
||||||
val mp4_360: String?,
|
@Json(name = "mp4_360") val mp4360: String?,
|
||||||
val mp4_480: String?,
|
@Json(name = "mp4_480") val mp4480: String?,
|
||||||
val mp4_720: String?,
|
@Json(name = "mp4_720") val mp4720: String?,
|
||||||
val mp4_1080: String?,
|
@Json(name = "mp4_1080") val mp41080: String?,
|
||||||
val mp4_1440: String?,
|
@Json(name = "mp4_1440") val mp41440: String?,
|
||||||
val hls: String?,
|
@Json(name = "hls") val hls: String?,
|
||||||
val dash_uni: String?,
|
@Json(name = "dash_uni") val dashUni: String?,
|
||||||
val dash_sep: String?,
|
@Json(name = "dash_sep") val dashSep: String?,
|
||||||
val hls_ondemand: String?,
|
@Json(name = "hls_ondemand") val hlsOnDemand: String?,
|
||||||
val dash_ondemand: String?,
|
@Json(name = "dash_ondemand") val dashOnDemand: String?,
|
||||||
val failover_host: String?
|
@Json(name = "failover_host") val failOverHost: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toDomain() = VkVideoDomain(
|
fun toDomain() = VkVideoDomain(
|
||||||
id = id,
|
id = id,
|
||||||
ownerId = owner_id,
|
ownerId = ownerId,
|
||||||
images = image.orEmpty().map { it.asVideoImage() },
|
images = image.orEmpty().map { it.asVideoImage() },
|
||||||
firstFrames = first_frame,
|
firstFrames = firstFrame,
|
||||||
accessKey = access_key,
|
accessKey = accessKey,
|
||||||
title = title
|
title = title,
|
||||||
|
views = views,
|
||||||
|
duration = duration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ data class VkVideoDomain(
|
|||||||
val firstFrames: List<VkVideoData.FirstFrame>?,
|
val firstFrames: List<VkVideoData.FirstFrame>?,
|
||||||
val accessKey: String?,
|
val accessKey: String?,
|
||||||
val title: String,
|
val title: String,
|
||||||
|
val views: Int,
|
||||||
|
val duration: Int
|
||||||
) : VkAttachment {
|
) : VkAttachment {
|
||||||
|
|
||||||
override val type: AttachmentType = AttachmentType.VIDEO
|
override val type: AttachmentType = AttachmentType.VIDEO
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M9 13V5C9 3.9 9.9 3 11 3H20C21.1 3 22 3.9 22 5V11H18.57L17.29 9.26C17.23 9.17 17.11 9.17 17.05 9.26L15.06 12C15 12.06 14.88 12.07 14.82 12L13.39 10.25C13.33 10.18 13.22 10.18 13.16 10.25L11.05 12.91C10.97 13 11.04 13.15 11.16 13.15H17.5V15H11C9.89 15 9 14.11 9 13M6 22V21H4V22H2V2H4V3H6V2H8.39C7.54 2.74 7 3.8 7 5V13C7 15.21 8.79 17 11 17H15.7C14.67 17.83 14 19.08 14 20.5C14 21.03 14.11 21.53 14.28 22H6M4 7H6V5H4V7M4 11H6V9H4V11M4 15H6V13H4V15M6 19V17H4V19H6M23 13V15H21V20.5C21 21.88 19.88 23 18.5 23S16 21.88 16 20.5 17.12 18 18.5 18C18.86 18 19.19 18.07 19.5 18.21V13H23Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M8,6.82v10.36c0,0.79 0.87,1.27 1.54,0.84l8.14,-5.18c0.62,-0.39 0.62,-1.29 0,-1.69L9.54,5.98C8.87,5.55 8,6.03 8,6.82z" />
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -218,4 +218,15 @@
|
|||||||
<string name="title_create_chat">Создать чат</string>
|
<string name="title_create_chat">Создать чат</string>
|
||||||
<string name="action_create">Создать</string>
|
<string name="action_create">Создать</string>
|
||||||
<string name="create_chat_title">Название</string>
|
<string name="create_chat_title">Название</string>
|
||||||
|
<string name="chat_materials_title">Вложения чата</string>
|
||||||
|
<string name="chat_materials_action_title">Вложения</string>
|
||||||
|
<string name="friends_order_priority">Приоритет</string>
|
||||||
|
<string name="friends_order_name">Имя</string>
|
||||||
|
<string name="friends_order_random">Случайно</string>
|
||||||
|
<string name="friends_order_by_title">Упорядочить по</string>
|
||||||
|
<string name="chat_attachment_photos">Фото</string>
|
||||||
|
<string name="chat_attachment_videos">Видео</string>
|
||||||
|
<string name="chat_attachment_music">Музыка</string>
|
||||||
|
<string name="chat_attachment_files">Файлы</string>
|
||||||
|
<string name="chat_attachment_links">Ссылки</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -283,4 +283,17 @@
|
|||||||
<string name="title_create_chat">Create chat</string>
|
<string name="title_create_chat">Create chat</string>
|
||||||
<string name="action_create">Create</string>
|
<string name="action_create">Create</string>
|
||||||
<string name="create_chat_title">Title</string>
|
<string name="create_chat_title">Title</string>
|
||||||
|
<string name="chat_materials_title">Chat materials</string>
|
||||||
|
<string name="chat_materials_action_title">Materials</string>
|
||||||
|
<string name="friends_order_priority">Priority</string>
|
||||||
|
<string name="friends_order_name">Name</string>
|
||||||
|
<string name="friends_order_random">Random</string>
|
||||||
|
<string name="friends_order_mobile" translatable="false">Mobile</string>
|
||||||
|
<string name="friends_order_smart" translatable="false">Smart</string>
|
||||||
|
<string name="friends_order_by_title">Order by</string>
|
||||||
|
<string name="chat_attachment_photos">Photos</string>
|
||||||
|
<string name="chat_attachment_videos">Videos</string>
|
||||||
|
<string name="chat_attachment_music">Music</string>
|
||||||
|
<string name="chat_attachment_files">Files</string>
|
||||||
|
<string name="chat_attachment_links">Links</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+7
-7
@@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
||||||
|
import dev.meloda.fast.chatmaterials.model.MaterialType
|
||||||
import dev.meloda.fast.chatmaterials.navigation.ChatMaterials
|
import dev.meloda.fast.chatmaterials.navigation.ChatMaterials
|
||||||
import dev.meloda.fast.chatmaterials.util.asPresentation
|
import dev.meloda.fast.chatmaterials.util.asPresentation
|
||||||
import dev.meloda.fast.common.extensions.listenValue
|
import dev.meloda.fast.common.extensions.listenValue
|
||||||
@@ -23,7 +24,7 @@ interface ChatMaterialsViewModel {
|
|||||||
val currentOffset: StateFlow<Int>
|
val currentOffset: StateFlow<Int>
|
||||||
val canPaginate: StateFlow<Boolean>
|
val canPaginate: StateFlow<Boolean>
|
||||||
|
|
||||||
fun onMetPaginationCondition()
|
fun onPaginationConditionsMet()
|
||||||
|
|
||||||
fun onRefresh()
|
fun onRefresh()
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ interface ChatMaterialsViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ChatMaterialsViewModelImpl(
|
class ChatMaterialsViewModelImpl(
|
||||||
|
private val materialType: MaterialType,
|
||||||
private val messagesUseCase: MessagesUseCase,
|
private val messagesUseCase: MessagesUseCase,
|
||||||
savedStateHandle: SavedStateHandle
|
savedStateHandle: SavedStateHandle
|
||||||
) : ViewModel(), ChatMaterialsViewModel {
|
) : ViewModel(), ChatMaterialsViewModel {
|
||||||
@@ -57,7 +59,7 @@ class ChatMaterialsViewModelImpl(
|
|||||||
loadChatMaterials()
|
loadChatMaterials()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMetPaginationCondition() {
|
override fun onPaginationConditionsMet() {
|
||||||
currentOffset.update { screenState.value.materials.size }
|
currentOffset.update { screenState.value.materials.size }
|
||||||
loadChatMaterials()
|
loadChatMaterials()
|
||||||
}
|
}
|
||||||
@@ -75,14 +77,12 @@ class ChatMaterialsViewModelImpl(
|
|||||||
loadChatMaterials(0)
|
loadChatMaterials(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadChatMaterials(
|
private fun loadChatMaterials(offset: Int = currentOffset.value) {
|
||||||
offset: Int = currentOffset.value
|
|
||||||
) {
|
|
||||||
messagesUseCase.getHistoryAttachments(
|
messagesUseCase.getHistoryAttachments(
|
||||||
peerId = screenState.value.peerId,
|
peerId = screenState.value.peerId,
|
||||||
count = LOAD_COUNT,
|
count = LOAD_COUNT,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
attachmentTypes = listOf(screenState.value.attachmentType),
|
attachmentTypes = listOf(materialType.toString()),
|
||||||
conversationMessageId = screenState.value.conversationMessageId
|
conversationMessageId = screenState.value.conversationMessageId
|
||||||
).listenValue(viewModelScope) { state ->
|
).listenValue(viewModelScope) { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
@@ -126,6 +126,6 @@ class ChatMaterialsViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val LOAD_COUNT = 100
|
const val LOAD_COUNT = 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-2
@@ -1,9 +1,45 @@
|
|||||||
package dev.meloda.fast.chatmaterials.di
|
package dev.meloda.fast.chatmaterials.di
|
||||||
|
|
||||||
import dev.meloda.fast.chatmaterials.ChatMaterialsViewModelImpl
|
import dev.meloda.fast.chatmaterials.ChatMaterialsViewModelImpl
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import dev.meloda.fast.chatmaterials.model.MaterialType
|
||||||
|
import org.koin.core.module.dsl.viewModel
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val chatMaterialsModule = module {
|
val chatMaterialsModule = module {
|
||||||
viewModelOf(::ChatMaterialsViewModelImpl)
|
viewModel(named(MaterialType.PHOTO)) {
|
||||||
|
ChatMaterialsViewModelImpl(
|
||||||
|
materialType = MaterialType.PHOTO,
|
||||||
|
messagesUseCase = get(),
|
||||||
|
savedStateHandle = get()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewModel(named(MaterialType.AUDIO)) {
|
||||||
|
ChatMaterialsViewModelImpl(
|
||||||
|
materialType = MaterialType.AUDIO,
|
||||||
|
messagesUseCase = get(),
|
||||||
|
savedStateHandle = get()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewModel(named(MaterialType.VIDEO)) {
|
||||||
|
ChatMaterialsViewModelImpl(
|
||||||
|
materialType = MaterialType.VIDEO,
|
||||||
|
messagesUseCase = get(),
|
||||||
|
savedStateHandle = get()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewModel(named(MaterialType.FILE)) {
|
||||||
|
ChatMaterialsViewModelImpl(
|
||||||
|
materialType = MaterialType.FILE,
|
||||||
|
messagesUseCase = get(),
|
||||||
|
savedStateHandle = get()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewModel(named(MaterialType.LINK)) {
|
||||||
|
ChatMaterialsViewModelImpl(
|
||||||
|
materialType = MaterialType.LINK,
|
||||||
|
messagesUseCase = get(),
|
||||||
|
savedStateHandle = get()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package dev.meloda.fast.chatmaterials.model
|
||||||
|
|
||||||
|
enum class MaterialType {
|
||||||
|
PHOTO, VIDEO, AUDIO, FILE, LINK;
|
||||||
|
|
||||||
|
override fun toString(): String = when (this) {
|
||||||
|
PHOTO -> "photo"
|
||||||
|
VIDEO -> "video"
|
||||||
|
AUDIO -> "audio"
|
||||||
|
FILE -> "doc"
|
||||||
|
LINK -> "link"
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-4
@@ -7,7 +7,10 @@ sealed class UiChatMaterial {
|
|||||||
) : UiChatMaterial()
|
) : UiChatMaterial()
|
||||||
|
|
||||||
data class Video(
|
data class Video(
|
||||||
val previewUrl: String
|
val previewUrl: String?,
|
||||||
|
val title: String,
|
||||||
|
val views: Int,
|
||||||
|
val duration: String
|
||||||
) : UiChatMaterial()
|
) : UiChatMaterial()
|
||||||
|
|
||||||
data class Audio(
|
data class Audio(
|
||||||
@@ -18,11 +21,16 @@ sealed class UiChatMaterial {
|
|||||||
) : UiChatMaterial()
|
) : UiChatMaterial()
|
||||||
|
|
||||||
data class File(
|
data class File(
|
||||||
val title: String
|
val previewUrl: String?,
|
||||||
|
val title: String,
|
||||||
|
val size: String,
|
||||||
|
val extension: String
|
||||||
) : UiChatMaterial()
|
) : UiChatMaterial()
|
||||||
|
|
||||||
data class Link(
|
data class Link(
|
||||||
val title: String,
|
val previewUrl: String?,
|
||||||
val previewUrl: String?
|
val title: String?,
|
||||||
|
val url: String,
|
||||||
|
val urlFirstChar: String
|
||||||
) : UiChatMaterial()
|
) : UiChatMaterial()
|
||||||
}
|
}
|
||||||
|
|||||||
+171
-236
@@ -1,93 +1,69 @@
|
|||||||
package dev.meloda.fast.chatmaterials.presentation
|
package dev.meloda.fast.chatmaterials.presentation
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
|
||||||
import androidx.compose.material.icons.rounded.Refresh
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
|
||||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.DpOffset
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
import dev.chrisbanes.haze.hazeEffect
|
||||||
import dev.chrisbanes.haze.hazeSource
|
|
||||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||||
import dev.meloda.fast.chatmaterials.ChatMaterialsViewModel
|
import dev.meloda.fast.chatmaterials.ChatMaterialsViewModel
|
||||||
import dev.meloda.fast.chatmaterials.ChatMaterialsViewModelImpl
|
import dev.meloda.fast.chatmaterials.ChatMaterialsViewModelImpl
|
||||||
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
import dev.meloda.fast.chatmaterials.model.MaterialType
|
||||||
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
import dev.meloda.fast.chatmaterials.presentation.materials.AudioMaterialsScreen
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.chatmaterials.presentation.materials.FileMaterialsScreen
|
||||||
|
import dev.meloda.fast.chatmaterials.presentation.materials.LinkMaterialsScreen
|
||||||
|
import dev.meloda.fast.chatmaterials.presentation.materials.PhotoMaterialsScreen
|
||||||
|
import dev.meloda.fast.chatmaterials.presentation.materials.VideoMaterialsScreen
|
||||||
|
import dev.meloda.fast.ui.model.TabItem
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import dev.meloda.fast.ui.R as UiR
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatMaterialsRoute(
|
fun ChatMaterialsRoute(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onPhotoClicked: (url: String) -> Unit,
|
onPhotoClicked: (url: String) -> Unit,
|
||||||
viewModel: ChatMaterialsViewModel = koinViewModel<ChatMaterialsViewModelImpl>()
|
|
||||||
) {
|
) {
|
||||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
ChatMaterialsScreen(
|
ChatMaterialsScreen(
|
||||||
screenState = screenState,
|
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onTypeChanged = viewModel::onTypeChanged,
|
|
||||||
onRefreshDropdownItemClicked = viewModel::onRefresh,
|
|
||||||
onRefresh = viewModel::onRefresh,
|
|
||||||
onPhotoClicked = onPhotoClicked
|
onPhotoClicked = onPhotoClicked
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -99,55 +75,36 @@ fun ChatMaterialsRoute(
|
|||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatMaterialsScreen(
|
fun ChatMaterialsScreen(
|
||||||
screenState: ChatMaterialsScreenState = ChatMaterialsScreenState.EMPTY,
|
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onTypeChanged: (String) -> Unit = {},
|
|
||||||
onRefreshDropdownItemClicked: () -> Unit = {},
|
|
||||||
onRefresh: () -> Unit = {},
|
|
||||||
onPhotoClicked: (url: String) -> Unit = {}
|
onPhotoClicked: (url: String) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
|
|
||||||
val attachments = screenState.materials
|
|
||||||
|
|
||||||
var moreClearBlur by rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hazeState = remember { HazeState() }
|
val hazeState = remember { HazeState() }
|
||||||
val hazeStyle = if (moreClearBlur) HazeMaterials.ultraThin() else HazeMaterials.regular()
|
|
||||||
|
|
||||||
var dropDownMenuExpanded by remember {
|
val titles = remember {
|
||||||
mutableStateOf(false)
|
listOf(
|
||||||
}
|
UiR.string.chat_attachment_photos,
|
||||||
var checkedTypeIndex by rememberSaveable {
|
UiR.string.chat_attachment_videos,
|
||||||
mutableIntStateOf(0)
|
UiR.string.chat_attachment_music,
|
||||||
}
|
UiR.string.chat_attachment_files,
|
||||||
|
UiR.string.chat_attachment_links,
|
||||||
LaunchedEffect(checkedTypeIndex) {
|
|
||||||
onTypeChanged(
|
|
||||||
when (checkedTypeIndex) {
|
|
||||||
0 -> "photo"
|
|
||||||
1 -> "video"
|
|
||||||
2 -> "audio"
|
|
||||||
3 -> "doc"
|
|
||||||
4 -> "link"
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val titles = listOf("Photos", "Videos", "Audios")//, "Files", "Links")
|
val tabItems = remember {
|
||||||
|
titles.map { resId ->
|
||||||
val listState = rememberLazyListState()
|
TabItem(
|
||||||
val gridState = rememberLazyGridState()
|
titleResId = resId,
|
||||||
|
unselectedIconResId = null,
|
||||||
val canScrollBackward = when (checkedTypeIndex) {
|
selectedIconResId = null
|
||||||
in 0..1 -> gridState.canScrollBackward
|
)
|
||||||
else -> listState.canScrollBackward
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("ChatMaterialsScreen", "ChatMaterialsScreen: canScrollBackward: $canScrollBackward")
|
var canScrollBackward by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
val topBarContainerColorAlpha by animateFloatAsState(
|
val topBarContainerColorAlpha by animateFloatAsState(
|
||||||
targetValue = if (!currentTheme.enableBlur || !canScrollBackward) 1f else 0f,
|
targetValue = if (!currentTheme.enableBlur || !canScrollBackward) 1f else 0f,
|
||||||
@@ -160,10 +117,8 @@ fun ChatMaterialsScreen(
|
|||||||
|
|
||||||
val topBarContainerColor by animateColorAsState(
|
val topBarContainerColor by animateColorAsState(
|
||||||
targetValue =
|
targetValue =
|
||||||
if (currentTheme.enableBlur || !canScrollBackward)
|
if (currentTheme.enableBlur || !canScrollBackward) MaterialTheme.colorScheme.surface
|
||||||
MaterialTheme.colorScheme.surface
|
else MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
||||||
else
|
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
|
||||||
label = "toolbarColorAlpha",
|
label = "toolbarColorAlpha",
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = 200,
|
durationMillis = 200,
|
||||||
@@ -171,7 +126,13 @@ fun ChatMaterialsScreen(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val pullToRefreshState = rememberPullToRefreshState()
|
val pagerState = rememberPagerState(
|
||||||
|
pageCount = tabItems::size
|
||||||
|
)
|
||||||
|
|
||||||
|
val selectedTabIndex by remember {
|
||||||
|
derivedStateOf { pagerState.currentPage }
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -181,11 +142,9 @@ fun ChatMaterialsScreen(
|
|||||||
if (currentTheme.enableBlur) {
|
if (currentTheme.enableBlur) {
|
||||||
Modifier.hazeEffect(
|
Modifier.hazeEffect(
|
||||||
state = hazeState,
|
state = hazeState,
|
||||||
style = hazeStyle
|
style = HazeMaterials.thick()
|
||||||
)
|
)
|
||||||
} else {
|
} else Modifier
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.background(topBarContainerColor.copy(alpha = topBarContainerColorAlpha))
|
.background(topBarContainerColor.copy(alpha = topBarContainerColorAlpha))
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -193,7 +152,7 @@ fun ChatMaterialsScreen(
|
|||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = "Chat Materials",
|
text = stringResource(UiR.string.chat_materials_title),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.headlineSmall
|
style = MaterialTheme.typography.headlineSmall
|
||||||
@@ -210,163 +169,139 @@ fun ChatMaterialsScreen(
|
|||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
dropDownMenuExpanded = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.MoreVert,
|
|
||||||
contentDescription = "Options button"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DropdownMenu(
|
|
||||||
modifier = Modifier.defaultMinSize(minWidth = 140.dp),
|
|
||||||
expanded = dropDownMenuExpanded,
|
|
||||||
onDismissRequest = {
|
|
||||||
dropDownMenuExpanded = false
|
|
||||||
},
|
|
||||||
offset = DpOffset(x = (-4).dp, y = (-60).dp)
|
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
onClick = {
|
|
||||||
onRefreshDropdownItemClicked()
|
|
||||||
dropDownMenuExpanded = false
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(text = stringResource(id = R.string.action_refresh))
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Refresh,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (currentTheme.enableBlur) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = {
|
|
||||||
Text(text = if (moreClearBlur) "Default blur" else "Clearer blur")
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
moreClearBlur = !moreClearBlur
|
|
||||||
dropDownMenuExpanded = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalDivider()
|
|
||||||
|
|
||||||
titles.forEachIndexed { index, title ->
|
|
||||||
DropdownMenuItem(
|
|
||||||
leadingIcon = {
|
|
||||||
RadioButton(
|
|
||||||
selected = checkedTypeIndex == index,
|
|
||||||
onClick = null
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(text = title)
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
checkedTypeIndex = index
|
|
||||||
dropDownMenuExpanded = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
PrimaryTabRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
selectedTabIndex = selectedTabIndex,
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
) {
|
||||||
|
tabItems.forEachIndexed { index, item ->
|
||||||
|
Tab(
|
||||||
|
selected = index == selectedTabIndex,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(index)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
item.titleResId?.let { resId ->
|
||||||
|
Text(text = stringResource(id = resId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
PullToRefreshBox(
|
HorizontalPager(
|
||||||
modifier = Modifier
|
state = pagerState,
|
||||||
.fillMaxSize()
|
modifier = Modifier.fillMaxSize(),
|
||||||
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
) { index ->
|
||||||
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr)),
|
when (index) {
|
||||||
state = pullToRefreshState,
|
0 -> {
|
||||||
isRefreshing = screenState.isLoading,
|
val viewModel: ChatMaterialsViewModel =
|
||||||
onRefresh = onRefresh,
|
koinViewModel<ChatMaterialsViewModelImpl>(named(MaterialType.PHOTO))
|
||||||
indicator = {
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
PullToRefreshDefaults.Indicator(
|
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||||
state = pullToRefreshState,
|
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||||
isRefreshing = screenState.isLoading,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.TopCenter)
|
|
||||||
.padding(top = padding.calculateTopPadding()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (checkedTypeIndex in listOf(0, 1)) {
|
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(3),
|
|
||||||
state = gridState,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.then(
|
|
||||||
if (currentTheme.enableBlur) {
|
|
||||||
Modifier.hazeSource(state = hazeState)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.fillMaxSize()
|
|
||||||
|
|
||||||
) {
|
PhotoMaterialsScreen(
|
||||||
repeat(3) {
|
modifier = Modifier,
|
||||||
item {
|
screenState = screenState,
|
||||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
baseError = baseError,
|
||||||
}
|
padding = padding,
|
||||||
}
|
onRefresh = viewModel::onRefresh,
|
||||||
items(attachments) { item ->
|
onSessionExpiredLogOutButtonClicked = { },
|
||||||
ChatMaterialItem(
|
setCanScrollBackward = { canScrollBackward = it },
|
||||||
item = item,
|
canPaginate = canPaginate,
|
||||||
onClick = {
|
onPaginationConditionsMet = viewModel::onPaginationConditionsMet,
|
||||||
if (item is UiChatMaterial.Photo) {
|
onPhotoClicked = onPhotoClicked
|
||||||
onPhotoClicked(item.previewUrl)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
repeat(3) {
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
modifier = Modifier
|
|
||||||
.then(
|
|
||||||
if (currentTheme.enableBlur) {
|
|
||||||
Modifier.hazeSource(state = hazeState)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.fillMaxSize()
|
|
||||||
|
|
||||||
) {
|
1 -> {
|
||||||
item {
|
val viewModel: ChatMaterialsViewModel =
|
||||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
koinViewModel<ChatMaterialsViewModelImpl>(named(MaterialType.VIDEO))
|
||||||
}
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
items(attachments) { item ->
|
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||||
ChatMaterialItem(
|
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||||
item = item,
|
|
||||||
onClick = {}
|
VideoMaterialsScreen(
|
||||||
)
|
modifier = Modifier,
|
||||||
}
|
screenState = screenState,
|
||||||
item {
|
baseError = baseError,
|
||||||
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
padding = padding,
|
||||||
}
|
onRefresh = viewModel::onRefresh,
|
||||||
|
onSessionExpiredLogOutButtonClicked = { },
|
||||||
|
setCanScrollBackward = { canScrollBackward = it },
|
||||||
|
canPaginate = canPaginate,
|
||||||
|
onPaginationConditionsMet = viewModel::onPaginationConditionsMet
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2 -> {
|
||||||
|
val viewModel: ChatMaterialsViewModel =
|
||||||
|
koinViewModel<ChatMaterialsViewModelImpl>(named(MaterialType.AUDIO))
|
||||||
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
|
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||||
|
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
AudioMaterialsScreen(
|
||||||
|
modifier = Modifier,
|
||||||
|
screenState = screenState,
|
||||||
|
baseError = baseError,
|
||||||
|
padding = padding,
|
||||||
|
onRefresh = viewModel::onRefresh,
|
||||||
|
onSessionExpiredLogOutButtonClicked = { },
|
||||||
|
setCanScrollBackward = { canScrollBackward = it },
|
||||||
|
canPaginate = canPaginate,
|
||||||
|
onPaginationConditionsMet = viewModel::onPaginationConditionsMet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
3 -> {
|
||||||
|
val viewModel: ChatMaterialsViewModel =
|
||||||
|
koinViewModel<ChatMaterialsViewModelImpl>(named(MaterialType.FILE))
|
||||||
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
|
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||||
|
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
FileMaterialsScreen(
|
||||||
|
modifier = Modifier,
|
||||||
|
screenState = screenState,
|
||||||
|
baseError = baseError,
|
||||||
|
padding = padding,
|
||||||
|
onRefresh = viewModel::onRefresh,
|
||||||
|
onSessionExpiredLogOutButtonClicked = { },
|
||||||
|
setCanScrollBackward = { canScrollBackward = it },
|
||||||
|
canPaginate = canPaginate,
|
||||||
|
onPaginationConditionsMet = viewModel::onPaginationConditionsMet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
4 -> {
|
||||||
|
val viewModel: ChatMaterialsViewModel =
|
||||||
|
koinViewModel<ChatMaterialsViewModelImpl>(named(MaterialType.LINK))
|
||||||
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
|
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||||
|
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LinkMaterialsScreen(
|
||||||
|
modifier = Modifier,
|
||||||
|
screenState = screenState,
|
||||||
|
baseError = baseError,
|
||||||
|
padding = padding,
|
||||||
|
onRefresh = viewModel::onRefresh,
|
||||||
|
onSessionExpiredLogOutButtonClicked = { },
|
||||||
|
setCanScrollBackward = { canScrollBackward = it },
|
||||||
|
canPaginate = canPaginate,
|
||||||
|
onPaginationConditionsMet = viewModel::onPaginationConditionsMet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+248
@@ -0,0 +1,248 @@
|
|||||||
|
package dev.meloda.fast.chatmaterials.presentation.materials
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowUp
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.contentColorFor
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.chrisbanes.haze.hazeSource
|
||||||
|
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
||||||
|
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
||||||
|
import dev.meloda.fast.model.BaseError
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||||
|
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||||
|
import dev.meloda.fast.ui.components.ErrorView
|
||||||
|
import dev.meloda.fast.ui.components.FullScreenLoader
|
||||||
|
import dev.meloda.fast.ui.components.NoItemsView
|
||||||
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import dev.meloda.fast.ui.R as UiR
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AudioMaterialsScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
canPaginate: Boolean,
|
||||||
|
screenState: ChatMaterialsScreenState,
|
||||||
|
baseError: BaseError?,
|
||||||
|
padding: PaddingValues,
|
||||||
|
onRefresh: () -> Unit,
|
||||||
|
onSessionExpiredLogOutButtonClicked: () -> Unit,
|
||||||
|
setCanScrollBackward: (Boolean) -> Unit,
|
||||||
|
onPaginationConditionsMet: () -> Unit
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val hazeState = LocalHazeState.current
|
||||||
|
val currentTheme = LocalThemeConfig.current
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val pullToRefreshState = rememberPullToRefreshState()
|
||||||
|
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow { listState.canScrollBackward }
|
||||||
|
.collect(setCanScrollBackward)
|
||||||
|
}
|
||||||
|
|
||||||
|
val paginationConditionMet by remember(canPaginate, listState) {
|
||||||
|
derivedStateOf {
|
||||||
|
canPaginate &&
|
||||||
|
(listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
|
||||||
|
?: -9) >= (listState.layoutInfo.totalItemsCount - 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(paginationConditionMet) {
|
||||||
|
if (paginationConditionMet && !screenState.isPaginating) {
|
||||||
|
onPaginationConditionsMet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
baseError != null -> {
|
||||||
|
when (baseError) {
|
||||||
|
is BaseError.SessionExpired -> {
|
||||||
|
ErrorView(
|
||||||
|
text = stringResource(UiR.string.session_expired),
|
||||||
|
buttonText = stringResource(UiR.string.action_log_out),
|
||||||
|
onButtonClick = onSessionExpiredLogOutButtonClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BaseError.SimpleError -> {
|
||||||
|
ErrorView(
|
||||||
|
text = baseError.message,
|
||||||
|
buttonText = stringResource(UiR.string.try_again),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
PullToRefreshBox(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
||||||
|
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr)),
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
indicator = {
|
||||||
|
PullToRefreshDefaults.Indicator(
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.padding(top = padding.calculateTopPadding()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.then(
|
||||||
|
if (currentTheme.enableBlur) {
|
||||||
|
Modifier.hazeSource(state = hazeState)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.fillMaxSize()
|
||||||
|
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||||
|
}
|
||||||
|
items(screenState.materials) { item ->
|
||||||
|
item as UiChatMaterial.Audio
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 64.dp)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.primary)
|
||||||
|
.size(42.dp)
|
||||||
|
.padding(4.dp),
|
||||||
|
painter = painterResource(UiR.drawable.round_play_arrow_24),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = contentColorFor(MaterialTheme.colorScheme.primary)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = item.title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
LocalContentAlpha(alpha = ContentAlpha.medium) {
|
||||||
|
Text(
|
||||||
|
text = item.artist,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = item.duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
if (screenState.isPaginating) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenState.isPaginationExhausted) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
|
listState.scrollToItem(14)
|
||||||
|
listState.animateScrollToItem(0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = IconButtonDefaults.filledIconButtonColors()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.KeyboardArrowUp,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenState.materials.isEmpty()) {
|
||||||
|
NoItemsView(
|
||||||
|
buttonText = stringResource(R.string.action_refresh),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+224
@@ -0,0 +1,224 @@
|
|||||||
|
package dev.meloda.fast.chatmaterials.presentation.materials
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import coil.compose.AsyncImagePainter
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import coil.imageLoader
|
||||||
|
import dev.chrisbanes.haze.hazeSource
|
||||||
|
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
||||||
|
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
||||||
|
import dev.meloda.fast.model.BaseError
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||||
|
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||||
|
import dev.meloda.fast.ui.components.ErrorView
|
||||||
|
import dev.meloda.fast.ui.components.FullScreenLoader
|
||||||
|
import dev.meloda.fast.ui.components.NoItemsView
|
||||||
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun FileMaterialsScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
canPaginate: Boolean,
|
||||||
|
screenState: ChatMaterialsScreenState,
|
||||||
|
baseError: BaseError?,
|
||||||
|
padding: PaddingValues,
|
||||||
|
onRefresh: () -> Unit,
|
||||||
|
onSessionExpiredLogOutButtonClicked: () -> Unit,
|
||||||
|
setCanScrollBackward: (Boolean) -> Unit,
|
||||||
|
onPaginationConditionsMet: () -> Unit
|
||||||
|
) {
|
||||||
|
val hazeState = LocalHazeState.current
|
||||||
|
val currentTheme = LocalThemeConfig.current
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val pullToRefreshState = rememberPullToRefreshState()
|
||||||
|
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow { listState.canScrollBackward }
|
||||||
|
.collect(setCanScrollBackward)
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
baseError != null -> {
|
||||||
|
when (baseError) {
|
||||||
|
is BaseError.SessionExpired -> {
|
||||||
|
ErrorView(
|
||||||
|
text = stringResource(R.string.session_expired),
|
||||||
|
buttonText = stringResource(R.string.action_log_out),
|
||||||
|
onButtonClick = onSessionExpiredLogOutButtonClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BaseError.SimpleError -> {
|
||||||
|
ErrorView(
|
||||||
|
text = baseError.message,
|
||||||
|
buttonText = stringResource(R.string.try_again),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
PullToRefreshBox(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
||||||
|
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr)),
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
indicator = {
|
||||||
|
PullToRefreshDefaults.Indicator(
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.padding(top = padding.calculateTopPadding()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.then(
|
||||||
|
if (currentTheme.enableBlur) {
|
||||||
|
Modifier.hazeSource(state = hazeState)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.fillMaxSize()
|
||||||
|
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||||
|
}
|
||||||
|
items(screenState.materials) { item ->
|
||||||
|
item as UiChatMaterial.File
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 64.dp)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
var errorLoading by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.previewUrl != null && !errorLoading) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.size(width = 64.dp, height = 48.dp),
|
||||||
|
painter = rememberAsyncImagePainter(
|
||||||
|
model = item.previewUrl,
|
||||||
|
imageLoader = LocalContext.current.imageLoader,
|
||||||
|
onState = {
|
||||||
|
errorLoading = it is AsyncImagePainter.State.Error
|
||||||
|
}
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme
|
||||||
|
.surfaceColorAtElevation(3.dp)
|
||||||
|
)
|
||||||
|
.size(width = 64.dp, height = 48.dp)
|
||||||
|
.padding(4.dp),
|
||||||
|
text = item.extension.uppercase(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
lineHeight = 40.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = item.title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
LocalContentAlpha(alpha = ContentAlpha.medium) {
|
||||||
|
Text(
|
||||||
|
text = item.size,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenState.materials.isEmpty()) {
|
||||||
|
NoItemsView(
|
||||||
|
buttonText = stringResource(R.string.action_refresh),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+243
@@ -0,0 +1,243 @@
|
|||||||
|
package dev.meloda.fast.chatmaterials.presentation.materials
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import coil.compose.AsyncImagePainter
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import coil.imageLoader
|
||||||
|
import dev.chrisbanes.haze.hazeSource
|
||||||
|
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
||||||
|
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
||||||
|
import dev.meloda.fast.model.BaseError
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||||
|
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||||
|
import dev.meloda.fast.ui.components.ErrorView
|
||||||
|
import dev.meloda.fast.ui.components.FullScreenLoader
|
||||||
|
import dev.meloda.fast.ui.components.NoItemsView
|
||||||
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun LinkMaterialsScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
canPaginate: Boolean,
|
||||||
|
screenState: ChatMaterialsScreenState,
|
||||||
|
baseError: BaseError?,
|
||||||
|
padding: PaddingValues,
|
||||||
|
onRefresh: () -> Unit,
|
||||||
|
onSessionExpiredLogOutButtonClicked: () -> Unit,
|
||||||
|
setCanScrollBackward: (Boolean) -> Unit,
|
||||||
|
onPaginationConditionsMet: () -> Unit
|
||||||
|
) {
|
||||||
|
val hazeState = LocalHazeState.current
|
||||||
|
val currentTheme = LocalThemeConfig.current
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val pullToRefreshState = rememberPullToRefreshState()
|
||||||
|
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow { listState.canScrollBackward }
|
||||||
|
.collect(setCanScrollBackward)
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
baseError != null -> {
|
||||||
|
when (baseError) {
|
||||||
|
is BaseError.SessionExpired -> {
|
||||||
|
ErrorView(
|
||||||
|
text = stringResource(R.string.session_expired),
|
||||||
|
buttonText = stringResource(R.string.action_log_out),
|
||||||
|
onButtonClick = onSessionExpiredLogOutButtonClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BaseError.SimpleError -> {
|
||||||
|
ErrorView(
|
||||||
|
text = baseError.message,
|
||||||
|
buttonText = stringResource(R.string.try_again),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
PullToRefreshBox(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
||||||
|
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr)),
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
indicator = {
|
||||||
|
PullToRefreshDefaults.Indicator(
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.padding(top = padding.calculateTopPadding()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.then(
|
||||||
|
if (currentTheme.enableBlur) {
|
||||||
|
Modifier.hazeSource(state = hazeState)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.fillMaxSize()
|
||||||
|
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||||
|
}
|
||||||
|
items(screenState.materials) { item ->
|
||||||
|
item as UiChatMaterial.Link
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 72.dp)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
var errorLoading by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.previewUrl != null && !errorLoading) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.size(
|
||||||
|
width = 86.dp,
|
||||||
|
height = 64.dp
|
||||||
|
),
|
||||||
|
painter = rememberAsyncImagePainter(
|
||||||
|
model = item.previewUrl,
|
||||||
|
imageLoader = LocalContext.current.imageLoader,
|
||||||
|
onState = {
|
||||||
|
errorLoading = it is AsyncImagePainter.State.Error
|
||||||
|
}
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme
|
||||||
|
.surfaceColorAtElevation(3.dp)
|
||||||
|
)
|
||||||
|
.size(
|
||||||
|
width = 86.dp,
|
||||||
|
height = 64.dp
|
||||||
|
)
|
||||||
|
.padding(4.dp),
|
||||||
|
text = item.urlFirstChar,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
lineHeight = 56.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
if (item.title != null) {
|
||||||
|
Text(
|
||||||
|
text = item.title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalContentAlpha(
|
||||||
|
alpha = if (item.title != null) ContentAlpha.medium
|
||||||
|
else ContentAlpha.high
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = item.url,
|
||||||
|
style = if (item.title != null) {
|
||||||
|
MaterialTheme.typography.bodyMedium
|
||||||
|
} else {
|
||||||
|
MaterialTheme.typography.bodyLarge
|
||||||
|
},
|
||||||
|
maxLines = if (item.title != null) 1 else 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenState.materials.isEmpty()) {
|
||||||
|
NoItemsView(
|
||||||
|
buttonText = stringResource(R.string.action_refresh),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
package dev.meloda.fast.chatmaterials.presentation.materials
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import dev.chrisbanes.haze.hazeSource
|
||||||
|
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
||||||
|
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
||||||
|
import dev.meloda.fast.model.BaseError
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.components.ErrorView
|
||||||
|
import dev.meloda.fast.ui.components.FullScreenLoader
|
||||||
|
import dev.meloda.fast.ui.components.NoItemsView
|
||||||
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun PhotoMaterialsScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
canPaginate: Boolean,
|
||||||
|
screenState: ChatMaterialsScreenState,
|
||||||
|
baseError: BaseError?,
|
||||||
|
padding: PaddingValues,
|
||||||
|
onRefresh: () -> Unit,
|
||||||
|
onSessionExpiredLogOutButtonClicked: () -> Unit,
|
||||||
|
setCanScrollBackward: (Boolean) -> Unit,
|
||||||
|
onPhotoClicked: (String) -> Unit,
|
||||||
|
onPaginationConditionsMet: () -> Unit
|
||||||
|
) {
|
||||||
|
val hazeState = LocalHazeState.current
|
||||||
|
val currentTheme = LocalThemeConfig.current
|
||||||
|
val gridState = rememberLazyGridState()
|
||||||
|
val pullToRefreshState = rememberPullToRefreshState()
|
||||||
|
|
||||||
|
LaunchedEffect(gridState) {
|
||||||
|
snapshotFlow { gridState.canScrollBackward }
|
||||||
|
.collect(setCanScrollBackward)
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
baseError != null -> {
|
||||||
|
when (baseError) {
|
||||||
|
is BaseError.SessionExpired -> {
|
||||||
|
ErrorView(
|
||||||
|
text = stringResource(R.string.session_expired),
|
||||||
|
buttonText = stringResource(R.string.action_log_out),
|
||||||
|
onButtonClick = onSessionExpiredLogOutButtonClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BaseError.SimpleError -> {
|
||||||
|
ErrorView(
|
||||||
|
text = baseError.message,
|
||||||
|
buttonText = stringResource(R.string.try_again),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
PullToRefreshBox(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
||||||
|
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr)),
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
indicator = {
|
||||||
|
PullToRefreshDefaults.Indicator(
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.padding(top = padding.calculateTopPadding()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(3),
|
||||||
|
state = gridState,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.then(
|
||||||
|
if (currentTheme.enableBlur) {
|
||||||
|
Modifier.hazeSource(state = hazeState)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.fillMaxSize()
|
||||||
|
|
||||||
|
) {
|
||||||
|
repeat(3) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(items = screenState.materials) { item ->
|
||||||
|
item as UiChatMaterial.Photo
|
||||||
|
AsyncImage(
|
||||||
|
model = item.previewUrl,
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.clickable(
|
||||||
|
onClick = {
|
||||||
|
onPhotoClicked(item.previewUrl)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
repeat(3) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenState.materials.isEmpty()) {
|
||||||
|
NoItemsView(
|
||||||
|
buttonText = stringResource(R.string.action_refresh),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
package dev.meloda.fast.chatmaterials.presentation.materials
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import coil.imageLoader
|
||||||
|
import dev.chrisbanes.haze.hazeSource
|
||||||
|
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
||||||
|
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
||||||
|
import dev.meloda.fast.model.BaseError
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||||
|
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||||
|
import dev.meloda.fast.ui.components.ErrorView
|
||||||
|
import dev.meloda.fast.ui.components.FullScreenLoader
|
||||||
|
import dev.meloda.fast.ui.components.NoItemsView
|
||||||
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun VideoMaterialsScreen(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
canPaginate: Boolean,
|
||||||
|
screenState: ChatMaterialsScreenState,
|
||||||
|
baseError: BaseError?,
|
||||||
|
padding: PaddingValues,
|
||||||
|
onRefresh: () -> Unit,
|
||||||
|
onSessionExpiredLogOutButtonClicked: () -> Unit,
|
||||||
|
setCanScrollBackward: (Boolean) -> Unit,
|
||||||
|
onPaginationConditionsMet: () -> Unit
|
||||||
|
) {
|
||||||
|
val hazeState = LocalHazeState.current
|
||||||
|
val currentTheme = LocalThemeConfig.current
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val pullToRefreshState = rememberPullToRefreshState()
|
||||||
|
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow { listState.canScrollBackward }
|
||||||
|
.collect(setCanScrollBackward)
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
baseError != null -> {
|
||||||
|
when (baseError) {
|
||||||
|
is BaseError.SessionExpired -> {
|
||||||
|
ErrorView(
|
||||||
|
text = stringResource(R.string.session_expired),
|
||||||
|
buttonText = stringResource(R.string.action_log_out),
|
||||||
|
onButtonClick = onSessionExpiredLogOutButtonClicked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BaseError.SimpleError -> {
|
||||||
|
ErrorView(
|
||||||
|
text = baseError.message,
|
||||||
|
buttonText = stringResource(R.string.try_again),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
PullToRefreshBox(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
||||||
|
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr)),
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
indicator = {
|
||||||
|
PullToRefreshDefaults.Indicator(
|
||||||
|
state = pullToRefreshState,
|
||||||
|
isRefreshing = screenState.isLoading,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.padding(top = padding.calculateTopPadding()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.then(
|
||||||
|
if (currentTheme.enableBlur) {
|
||||||
|
Modifier.hazeSource(state = hazeState)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.fillMaxSize()
|
||||||
|
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||||
|
}
|
||||||
|
items(screenState.materials) { item ->
|
||||||
|
item as UiChatMaterial.Video
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 72.dp)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.33f)
|
||||||
|
.height(64.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp)),
|
||||||
|
painter = rememberAsyncImagePainter(
|
||||||
|
model = item.previewUrl,
|
||||||
|
imageLoader = LocalContext.current.imageLoader
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(
|
||||||
|
end = 4.dp,
|
||||||
|
bottom = 4.dp
|
||||||
|
)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
.padding(
|
||||||
|
vertical = 1.dp,
|
||||||
|
horizontal = 4.dp
|
||||||
|
),
|
||||||
|
text = item.duration,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = item.title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
LocalContentAlpha(alpha = ContentAlpha.medium) {
|
||||||
|
Text(
|
||||||
|
text = "${item.views} views",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenState.materials.isEmpty()) {
|
||||||
|
NoItemsView(
|
||||||
|
buttonText = stringResource(R.string.action_refresh),
|
||||||
|
onButtonClick = onRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
-9
@@ -1,6 +1,7 @@
|
|||||||
package dev.meloda.fast.chatmaterials.util
|
package dev.meloda.fast.chatmaterials.util
|
||||||
|
|
||||||
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
||||||
|
import dev.meloda.fast.common.util.AndroidUtils
|
||||||
import dev.meloda.fast.model.api.data.AttachmentType
|
import dev.meloda.fast.model.api.data.AttachmentType
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||||
import dev.meloda.fast.model.api.domain.VkAudioDomain
|
import dev.meloda.fast.model.api.domain.VkAudioDomain
|
||||||
@@ -8,7 +9,6 @@ import dev.meloda.fast.model.api.domain.VkFileDomain
|
|||||||
import dev.meloda.fast.model.api.domain.VkLinkDomain
|
import dev.meloda.fast.model.api.domain.VkLinkDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkPhotoDomain
|
import dev.meloda.fast.model.api.domain.VkPhotoDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkVideoDomain
|
import dev.meloda.fast.model.api.domain.VkVideoDomain
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
|
fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
|
||||||
@@ -22,36 +22,110 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
|
|||||||
|
|
||||||
AttachmentType.VIDEO -> {
|
AttachmentType.VIDEO -> {
|
||||||
val attachment = this.attachment as VkVideoDomain
|
val attachment = this.attachment as VkVideoDomain
|
||||||
|
|
||||||
|
val duration = attachment.duration
|
||||||
|
|
||||||
|
val days = duration / (24 * 3600)
|
||||||
|
val hours = (duration % (24 * 3600)) / 3600
|
||||||
|
val minutes = (duration % 3600) / 60
|
||||||
|
val seconds = duration % 60
|
||||||
|
|
||||||
|
val args = mutableListOf<Int>()
|
||||||
|
if (days > 0) args.add(days)
|
||||||
|
if (hours > 0) args.add(hours)
|
||||||
|
args.add(minutes)
|
||||||
|
args.add(seconds)
|
||||||
|
|
||||||
|
val builder = StringBuilder()
|
||||||
|
if (days > 0) builder.append("%02d:")
|
||||||
|
if (hours > 0) builder.append("%02d:")
|
||||||
|
builder.append("%02d:%02d")
|
||||||
|
|
||||||
|
val formattedDuration =
|
||||||
|
builder.toString().format(Locale.getDefault(), *args.toTypedArray())
|
||||||
|
|
||||||
UiChatMaterial.Video(
|
UiChatMaterial.Video(
|
||||||
previewUrl = attachment.images.firstOrNull()?.url.orEmpty()
|
previewUrl = attachment.images.maxByOrNull(VkVideoDomain.VideoImage::width)?.url.orEmpty(),
|
||||||
|
title = attachment.title,
|
||||||
|
views = attachment.views,
|
||||||
|
duration = formattedDuration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentType.AUDIO -> {
|
AttachmentType.AUDIO -> {
|
||||||
val attachment = this.attachment as VkAudioDomain
|
val attachment = this.attachment as VkAudioDomain
|
||||||
|
|
||||||
|
val duration = attachment.duration
|
||||||
|
|
||||||
|
val days = duration / (24 * 3600)
|
||||||
|
val hours = (duration % (24 * 3600)) / 3600
|
||||||
|
val minutes = (duration % 3600) / 60
|
||||||
|
val seconds = duration % 60
|
||||||
|
|
||||||
|
val args = mutableListOf<Int>()
|
||||||
|
if (days > 0) args.add(days)
|
||||||
|
if (hours > 0) args.add(hours)
|
||||||
|
args.add(minutes)
|
||||||
|
args.add(seconds)
|
||||||
|
|
||||||
|
val builder = StringBuilder()
|
||||||
|
if (days > 0) builder.append("%02d:")
|
||||||
|
if (hours > 0) builder.append("%02d:")
|
||||||
|
builder.append("%d:%02d")
|
||||||
|
|
||||||
|
val formattedDuration =
|
||||||
|
builder.toString().format(Locale.getDefault(), *args.toTypedArray())
|
||||||
|
|
||||||
UiChatMaterial.Audio(
|
UiChatMaterial.Audio(
|
||||||
previewUrl = null,
|
previewUrl = null,
|
||||||
title = attachment.title,
|
title = attachment.title,
|
||||||
artist = attachment.artist,
|
artist = attachment.artist,
|
||||||
duration = SimpleDateFormat(
|
duration = formattedDuration
|
||||||
"mm:ss",
|
|
||||||
Locale.getDefault()
|
|
||||||
).format(attachment.duration)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentType.FILE -> {
|
AttachmentType.FILE -> {
|
||||||
val attachment = this.attachment as VkFileDomain
|
val attachment = this.attachment as VkFileDomain
|
||||||
|
|
||||||
|
val previewUrl: String? = when (val preview = attachment.preview) {
|
||||||
|
null -> null
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
when {
|
||||||
|
preview.photo != null -> {
|
||||||
|
val size = preview.photo?.sizes?.maxByOrNull { it.width }
|
||||||
|
size?.src
|
||||||
|
}
|
||||||
|
|
||||||
|
preview.video != null -> {
|
||||||
|
val size = preview.video?.src
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UiChatMaterial.File(
|
UiChatMaterial.File(
|
||||||
title = attachment.title
|
title = attachment.title,
|
||||||
|
previewUrl = previewUrl,
|
||||||
|
size = AndroidUtils.bytesToHumanReadableSize(attachment.size.toDouble()),
|
||||||
|
extension = attachment.ext.take(4)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentType.LINK -> {
|
AttachmentType.LINK -> {
|
||||||
val attachment = this.attachment as VkLinkDomain
|
val attachment = this.attachment as VkLinkDomain
|
||||||
|
|
||||||
UiChatMaterial.Link(
|
UiChatMaterial.Link(
|
||||||
title = attachment.title ?: attachment.url,
|
title = attachment.title,
|
||||||
previewUrl = attachment.photo?.getMaxSize()?.url
|
previewUrl = attachment.photo?.getMaxSize()?.url,
|
||||||
|
url = attachment.url,
|
||||||
|
urlFirstChar = attachment.url.replaceFirst("http://", "")
|
||||||
|
.replaceFirst("https://", "")
|
||||||
|
.take(1)
|
||||||
|
.uppercase()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ data class ConversationsScreenState(
|
|||||||
val isPaginationExhausted: Boolean,
|
val isPaginationExhausted: Boolean,
|
||||||
val profileImageUrl: String?,
|
val profileImageUrl: String?,
|
||||||
val scrollIndex: Int,
|
val scrollIndex: Int,
|
||||||
val scrollOffset: Int
|
val scrollOffset: Int,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
-1
@@ -137,7 +137,6 @@ fun ConversationsScreen(
|
|||||||
setScrollIndex: (Int) -> Unit = {},
|
setScrollIndex: (Int) -> Unit = {},
|
||||||
setScrollOffset: (Int) -> Unit = {}
|
setScrollOffset: (Int) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
|
|
||||||
val maxLines by remember(currentTheme) {
|
val maxLines by remember(currentTheme) {
|
||||||
|
|||||||
+16
-21
@@ -1,6 +1,7 @@
|
|||||||
package dev.meloda.fast.conversations.presentation
|
package dev.meloda.fast.conversations.presentation
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
|
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
@@ -79,8 +80,6 @@ fun CreateChatRoute(
|
|||||||
onChatCreated: (Int) -> Unit,
|
onChatCreated: (Int) -> Unit,
|
||||||
viewModel: CreateChatViewModel
|
viewModel: CreateChatViewModel
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||||
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||||
@@ -148,20 +147,24 @@ fun CreateChatScreen(
|
|||||||
|
|
||||||
val hazeState = LocalHazeState.current
|
val hazeState = LocalHazeState.current
|
||||||
|
|
||||||
val toolbarColorAlpha by animateFloatAsState(
|
val topBarContainerColorAlpha by animateFloatAsState(
|
||||||
targetValue = if (!listState.canScrollBackward) 1f else 0f,
|
targetValue = if (!currentTheme.enableBlur || !listState.canScrollBackward) 1f else 0f,
|
||||||
label = "toolbarColorAlpha",
|
label = "toolbarColorAlpha",
|
||||||
animationSpec = tween(durationMillis = 50)
|
animationSpec = tween(
|
||||||
|
durationMillis = 200,
|
||||||
|
easing = FastOutLinearInEasing
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val toolbarContainerColor by animateColorAsState(
|
val topBarContainerColor by animateColorAsState(
|
||||||
targetValue =
|
targetValue =
|
||||||
if (currentTheme.enableBlur || !listState.canScrollBackward)
|
if (currentTheme.enableBlur || !listState.canScrollBackward) MaterialTheme.colorScheme.surface
|
||||||
MaterialTheme.colorScheme.surface
|
else MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
||||||
else
|
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
|
||||||
label = "toolbarColorAlpha",
|
label = "toolbarColorAlpha",
|
||||||
animationSpec = tween(durationMillis = 50)
|
animationSpec = tween(
|
||||||
|
durationMillis = 200,
|
||||||
|
easing = FastOutLinearInEasing
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -171,11 +174,7 @@ fun CreateChatScreen(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(
|
.background(topBarContainerColor.copy(alpha = topBarContainerColorAlpha))
|
||||||
toolbarContainerColor.copy(
|
|
||||||
alpha = if (currentTheme.enableBlur) toolbarColorAlpha else 1f
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(
|
.then(
|
||||||
if (currentTheme.enableBlur) {
|
if (currentTheme.enableBlur) {
|
||||||
Modifier.hazeEffect(
|
Modifier.hazeEffect(
|
||||||
@@ -205,11 +204,7 @@ fun CreateChatScreen(
|
|||||||
style = MaterialTheme.typography.headlineSmall
|
style = MaterialTheme.typography.headlineSmall
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
|
||||||
containerColor = toolbarContainerColor.copy(
|
|
||||||
alpha = 0f
|
|
||||||
)
|
|
||||||
),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+32
-36
@@ -24,14 +24,13 @@ import androidx.compose.material3.TopAppBar
|
|||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -50,7 +49,7 @@ import dev.meloda.fast.ui.model.TabItem
|
|||||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import dev.meloda.fast.ui.R as UiR
|
import dev.meloda.fast.ui.R as UiR
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
|
||||||
@@ -60,10 +59,7 @@ fun FriendsRoute(
|
|||||||
onPhotoClicked: (url: String) -> Unit,
|
onPhotoClicked: (url: String) -> Unit,
|
||||||
onMessageClicked: (userId: Int) -> Unit,
|
onMessageClicked: (userId: Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
var selectedTabIndex by rememberSaveable {
|
val scope = rememberCoroutineScope()
|
||||||
mutableIntStateOf(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
val hazeState = LocalHazeState.current
|
val hazeState = LocalHazeState.current
|
||||||
|
|
||||||
@@ -107,20 +103,36 @@ fun FriendsRoute(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val pagerState = rememberPagerState(pageCount = tabItems::size)
|
||||||
|
|
||||||
|
val selectedTabIndex by remember {
|
||||||
|
derivedStateOf { pagerState.currentPage }
|
||||||
|
}
|
||||||
|
|
||||||
var orderType: String by remember { mutableStateOf("hints") }
|
var orderType: String by remember { mutableStateOf("hints") }
|
||||||
|
|
||||||
var showOrderDialog by remember { mutableStateOf(false) }
|
var showOrderDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val orderItems = remember {
|
val orderPriority = stringResource(UiR.string.friends_order_priority)
|
||||||
mapOf(
|
val orderName = stringResource(UiR.string.friends_order_name)
|
||||||
"hints" to "Priority",
|
val orderRandom = stringResource(UiR.string.friends_order_random)
|
||||||
"name" to "Name",
|
val orderMobile = stringResource(UiR.string.friends_order_mobile)
|
||||||
"random" to "Random",
|
val orderSmart = stringResource(UiR.string.friends_order_smart)
|
||||||
"mobile" to "Mobile",
|
|
||||||
"smart" to "Smart"
|
val orderTitleItems = remember {
|
||||||
|
ImmutableList.of(
|
||||||
|
orderPriority,
|
||||||
|
orderName,
|
||||||
|
orderRandom,
|
||||||
|
orderMobile,
|
||||||
|
orderSmart
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val orderItems = remember {
|
||||||
|
listOf("hints", "name", "random", "mobile", "smart")
|
||||||
|
}
|
||||||
|
|
||||||
var selectedIndex by remember {
|
var selectedIndex by remember {
|
||||||
mutableIntStateOf(0)
|
mutableIntStateOf(0)
|
||||||
}
|
}
|
||||||
@@ -130,17 +142,16 @@ fun FriendsRoute(
|
|||||||
onDismissRequest = { showOrderDialog = false },
|
onDismissRequest = { showOrderDialog = false },
|
||||||
confirmText = stringResource(R.string.ok),
|
confirmText = stringResource(R.string.ok),
|
||||||
confirmAction = {
|
confirmAction = {
|
||||||
orderType =
|
orderType = orderItems[selectedIndex]
|
||||||
orderItems.keys.toCollection(mutableListOf())[selectedIndex]
|
|
||||||
},
|
},
|
||||||
cancelText = stringResource(R.string.cancel),
|
cancelText = stringResource(R.string.cancel),
|
||||||
selectionType = SelectionType.Single,
|
selectionType = SelectionType.Single,
|
||||||
items = ImmutableList.copyOf(orderItems.values),
|
items = orderTitleItems,
|
||||||
preSelectedItems = ImmutableList.of(selectedIndex),
|
preSelectedItems = ImmutableList.of(selectedIndex),
|
||||||
onItemClick = {
|
onItemClick = {
|
||||||
selectedIndex = it
|
selectedIndex = it
|
||||||
},
|
},
|
||||||
title = "Order type",
|
title = stringResource(UiR.string.friends_order_by_title),
|
||||||
actionInvokeDismiss = ActionInvokeDismiss.Always
|
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -199,8 +210,8 @@ fun FriendsRoute(
|
|||||||
Tab(
|
Tab(
|
||||||
selected = index == selectedTabIndex,
|
selected = index == selectedTabIndex,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (selectedTabIndex != index) {
|
scope.launch {
|
||||||
selectedTabIndex = index
|
pagerState.animateScrollToPage(index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
@@ -214,21 +225,6 @@ fun FriendsRoute(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
val pagerState = rememberPagerState(
|
|
||||||
initialPage = selectedTabIndex
|
|
||||||
) {
|
|
||||||
tabItems.size
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(selectedTabIndex) {
|
|
||||||
pagerState.animateScrollToPage(selectedTabIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(pagerState) {
|
|
||||||
snapshotFlow { pagerState.currentPage }
|
|
||||||
.collect { selectedTabIndex = it }
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|||||||
+12
-4
@@ -229,8 +229,7 @@ fun MessagesHistoryScreen(
|
|||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
title = {
|
title = {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.weight(1f),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (selectedMessages.isEmpty()) {
|
if (selectedMessages.isEmpty()) {
|
||||||
@@ -332,6 +331,9 @@ fun MessagesHistoryScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (screenState.isLoading) {
|
||||||
|
return@TopAppBar
|
||||||
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { dropDownMenuExpanded = true }
|
onClick = { dropDownMenuExpanded = true }
|
||||||
) {
|
) {
|
||||||
@@ -362,7 +364,13 @@ fun MessagesHistoryScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = "Materials")
|
Text(text = stringResource(UiR.string.chat_materials_action_title))
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(UiR.drawable.ic_multimedia),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
@@ -371,7 +379,7 @@ fun MessagesHistoryScreen(
|
|||||||
dropDownMenuExpanded = false
|
dropDownMenuExpanded = false
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(text = "Refresh")
|
Text(text = stringResource(UiR.string.action_refresh))
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
Reference in New Issue
Block a user