forked from melod1n/fast-messenger
Refactor: Introduce FullScreenContainedLoader and use rememberUpdatedState
This commit introduces `FullScreenContainedLoader` and replaces usages of `FullScreenLoader` where appropriate. It also updates several composables to use `rememberUpdatedState` for lambda parameters to ensure the latest versions are used. Additionally, the following changes are included: - Add a setting to show/hide the attachment button in the chat input bar. - Implement navigation to `PhotoViewScreen` when a photo attachment is clicked in a message. - Add "Copy link" and "Copy image" actions to `PhotoViewScreen`. - Remove unused settings and their corresponding logic from `SettingsViewModel` and `UserSettings`.
This commit is contained in:
+2
-2
@@ -53,7 +53,7 @@ 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.FullScreenLoader
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||
@@ -105,7 +105,7 @@ fun AudioMaterialsScreen(
|
||||
VkErrorView(baseError = baseError)
|
||||
}
|
||||
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenContainedLoader()
|
||||
|
||||
else -> {
|
||||
PullToRefreshBox(
|
||||
|
||||
+2
-2
@@ -63,7 +63,7 @@ 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.FullScreenLoader
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||
@@ -114,7 +114,7 @@ fun FileMaterialsScreen(
|
||||
VkErrorView(baseError = baseError)
|
||||
}
|
||||
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenContainedLoader()
|
||||
|
||||
else -> {
|
||||
PullToRefreshBox(
|
||||
|
||||
+2
-2
@@ -63,7 +63,7 @@ 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.FullScreenLoader
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||
@@ -114,7 +114,7 @@ fun LinkMaterialsScreen(
|
||||
VkErrorView(baseError = baseError)
|
||||
}
|
||||
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenContainedLoader()
|
||||
|
||||
else -> {
|
||||
PullToRefreshBox(
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ 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.FullScreenLoader
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||
@@ -98,7 +98,7 @@ fun PhotoMaterialsScreen(
|
||||
VkErrorView(baseError = baseError)
|
||||
}
|
||||
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenContainedLoader()
|
||||
|
||||
else -> {
|
||||
PullToRefreshBox(
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ 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.FullScreenLoader
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||
@@ -107,7 +107,7 @@ fun VideoMaterialsScreen(
|
||||
VkErrorView(baseError = baseError)
|
||||
}
|
||||
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenLoader()
|
||||
screenState.isLoading && screenState.materials.isEmpty() -> FullScreenContainedLoader()
|
||||
|
||||
else -> {
|
||||
PullToRefreshBox(
|
||||
|
||||
+2
-3
@@ -44,7 +44,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -65,7 +64,7 @@ import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import dev.meloda.fast.conversations.model.ConversationsScreenState
|
||||
import dev.meloda.fast.conversations.navigation.ConversationsGraph
|
||||
import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.ui.components.FullScreenLoader
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.model.api.ConversationOption
|
||||
@@ -306,7 +305,7 @@ fun ConversationsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
screenState.isLoading && conversations.isEmpty() -> FullScreenLoader()
|
||||
screenState.isLoading && conversations.isEmpty() -> FullScreenContainedLoader()
|
||||
|
||||
else -> {
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
|
||||
+2
-4
@@ -51,7 +51,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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
|
||||
@@ -64,8 +63,7 @@ import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import dev.meloda.fast.conversations.CreateChatViewModel
|
||||
import dev.meloda.fast.conversations.model.CreateChatScreenState
|
||||
import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.ui.components.ErrorView
|
||||
import dev.meloda.fast.ui.components.FullScreenLoader
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.IconButton
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
@@ -271,7 +269,7 @@ fun CreateChatScreen(
|
||||
VkErrorView(baseError = baseError)
|
||||
}
|
||||
|
||||
screenState.isLoading && screenState.friends.isEmpty() -> FullScreenLoader()
|
||||
screenState.isLoading && screenState.friends.isEmpty() -> FullScreenContainedLoader()
|
||||
|
||||
else -> {
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
|
||||
+2
-6
@@ -1,7 +1,6 @@
|
||||
package dev.meloda.fast.friends.presentation
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
@@ -28,11 +27,9 @@ import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.meloda.fast.friends.FriendsViewModel
|
||||
import dev.meloda.fast.friends.FriendsViewModelImpl
|
||||
import dev.meloda.fast.friends.OnlineFriendsViewModelImpl
|
||||
import dev.meloda.fast.friends.navigation.Friends
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.components.FullScreenLoader
|
||||
import dev.meloda.fast.ui.components.FullScreenContainedLoader
|
||||
import dev.meloda.fast.ui.components.NoItemsView
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||
@@ -41,7 +38,6 @@ import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.meloda.fast.ui.util.ImmutableList
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -136,7 +132,7 @@ fun FriendsScreen(
|
||||
}
|
||||
|
||||
when {
|
||||
screenState.isLoading && screenState.friends.isEmpty() -> FullScreenLoader()
|
||||
screenState.isLoading && screenState.friends.isEmpty() -> FullScreenContainedLoader()
|
||||
|
||||
else -> {
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
|
||||
+55
-9
@@ -3,6 +3,7 @@ package dev.meloda.fast.messageshistory
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
@@ -15,9 +16,13 @@ import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import com.conena.nanokt.collections.indexOfFirstOrNull
|
||||
import com.conena.nanokt.text.isEmptyOrBlank
|
||||
import com.conena.nanokt.text.isNotEmptyOrBlank
|
||||
@@ -52,12 +57,16 @@ import dev.meloda.fast.model.LongPollParsedEvent
|
||||
import dev.meloda.fast.model.api.domain.FormatDataType
|
||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.model.api.domain.VkPhotoDomain
|
||||
import dev.meloda.fast.network.VkErrorCode
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
import dev.meloda.fast.ui.R as UiR
|
||||
@@ -163,10 +172,6 @@ class MessagesHistoryViewModelImpl(
|
||||
updatesParser.onMessageMarkedAsImportant(::handleMessageMarkedAsImportant)
|
||||
updatesParser.onMessageMarkedAsSpam(::handleMessageMarkedAsSpam)
|
||||
updatesParser.onMessageMarkedAsNotSpam(::handleMessageMarkedAsNotSpam)
|
||||
|
||||
userSettings.showTimeInActionMessages.listenValue(viewModelScope) {
|
||||
syncUiMessages()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNavigationConsumed() {
|
||||
@@ -1131,13 +1136,54 @@ class MessagesHistoryViewModelImpl(
|
||||
}
|
||||
|
||||
private fun copyMessage(message: VkMessage) {
|
||||
val contentToCopy = message.text.orEmpty().trim()
|
||||
if (contentToCopy.isEmpty()) return
|
||||
|
||||
val clipboardManager =
|
||||
applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText("Message", contentToCopy))
|
||||
val messageToCopy = message.text.orEmpty().trim()
|
||||
if (messageToCopy.isEmpty()) {
|
||||
val photo = with(message.attachments.orEmpty()) {
|
||||
if (size == 1 && all { it is VkPhotoDomain }) {
|
||||
first() as? VkPhotoDomain
|
||||
} else null
|
||||
} ?: return
|
||||
|
||||
val photoMaxSize = photo.getMaxSize() ?: return
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val drawable = applicationContext.imageLoader.execute(
|
||||
ImageRequest.Builder(applicationContext)
|
||||
.data(photoMaxSize.url)
|
||||
.build()
|
||||
).drawable ?: return@launch
|
||||
|
||||
val imagesDir = File(applicationContext.cacheDir, "images")
|
||||
if (!imagesDir.exists()) imagesDir.mkdirs()
|
||||
val imageFile = File(imagesDir, "shared_image_id${photo.id}.png")
|
||||
FileOutputStream(imageFile).use {
|
||||
drawable.toBitmapOrNull()?.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||
}
|
||||
|
||||
val uri = FileProvider.getUriForFile(
|
||||
applicationContext,
|
||||
"${applicationContext.packageName}.provider",
|
||||
imageFile
|
||||
)
|
||||
|
||||
val clip = ClipData.newUri(applicationContext.contentResolver, "Image", uri)
|
||||
clipboardManager.setPrimaryClip(clip)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"Image copied to clipboard",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText("Message", messageToCopy))
|
||||
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
|
||||
Toast.makeText(applicationContext, UiR.string.copied_to_clipboard, Toast.LENGTH_SHORT)
|
||||
@@ -1155,7 +1201,7 @@ class MessagesHistoryViewModelImpl(
|
||||
showName = false,
|
||||
prevMessage = messages.getOrNull(index + 1),
|
||||
nextMessage = messages.getOrNull(index - 1),
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value,
|
||||
showTimeInActionMessages = AppSettings.Experimental.showTimeInActionMessages,
|
||||
conversation = screenState.value.conversation,
|
||||
isSelected = selectedMessages.indexOfFirstOrNull { it.id == message.id } != null
|
||||
)
|
||||
|
||||
+5
-2
@@ -5,6 +5,7 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.toRoute
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.messageshistory.model.MessagesHistoryArguments
|
||||
import dev.meloda.fast.messageshistory.presentation.MessagesHistoryRoute
|
||||
import dev.meloda.fast.model.BaseError
|
||||
@@ -27,13 +28,15 @@ data class MessagesHistory(val arguments: MessagesHistoryArguments) {
|
||||
fun NavGraphBuilder.messagesHistoryScreen(
|
||||
onError: (BaseError) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onNavigateToChatMaterials: (peerId: Long, cmId: Long) -> Unit
|
||||
onNavigateToChatMaterials: (peerId: Long, cmId: Long) -> Unit,
|
||||
onNavigateToPhotoViewer: (images: List<String>, index: Int) -> Unit,
|
||||
) {
|
||||
composable<MessagesHistory>(typeMap = MessagesHistory.typeMap) {
|
||||
MessagesHistoryRoute(
|
||||
onError = onError,
|
||||
onBack = onBack,
|
||||
onNavigateToChatMaterials = onNavigateToChatMaterials
|
||||
onNavigateToChatMaterials = onNavigateToChatMaterials,
|
||||
onNavigateToPhotoViewer = onNavigateToPhotoViewer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -16,6 +16,8 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
@@ -38,6 +40,9 @@ fun IncomingMessageBubble(
|
||||
onClick: (VkAttachment) -> Unit = {},
|
||||
onLongClick: (VkAttachment) -> Unit = {}
|
||||
) {
|
||||
val currentOnClick by rememberUpdatedState(onClick)
|
||||
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -98,8 +103,8 @@ fun IncomingMessageBubble(
|
||||
isImportant = message.isImportant,
|
||||
isSelected = message.isSelected,
|
||||
attachments = message.attachments?.toImmutableList(),
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
onClick = currentOnClick,
|
||||
onLongClick = currentOnLongClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -18,6 +18,7 @@ import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -50,6 +51,9 @@ fun MessageBubble(
|
||||
onClick: (VkAttachment) -> Unit = {},
|
||||
onLongClick: (VkAttachment) -> Unit = {}
|
||||
) {
|
||||
val currentOnClick by rememberUpdatedState(onClick)
|
||||
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
||||
|
||||
val theme = LocalThemeConfig.current
|
||||
val backgroundColor = if (!isOut) {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
||||
@@ -173,8 +177,8 @@ fun MessageBubble(
|
||||
Attachments(
|
||||
modifier = Modifier,
|
||||
attachments = attachments,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
onClick = currentOnClick,
|
||||
onLongClick = currentOnLongClick
|
||||
)
|
||||
|
||||
val dateStatusBackground = if (theme.darkMode) Color.Black.copy(alpha = 0.5f)
|
||||
|
||||
+32
-29
@@ -63,7 +63,9 @@ fun MessagesHistoryInputBar(
|
||||
modifier: Modifier = Modifier,
|
||||
message: TextFieldValue,
|
||||
hazeState: HazeState,
|
||||
enableHaptic: Boolean,
|
||||
showEmojiButton: Boolean,
|
||||
showAttachmentButton: Boolean,
|
||||
actionMode: ActionMode,
|
||||
onMessageInputChanged: (TextFieldValue) -> Unit = {},
|
||||
onBoldRequested: () -> Unit = {},
|
||||
@@ -179,7 +181,7 @@ fun MessagesHistoryInputBar(
|
||||
}
|
||||
|
||||
TextField(
|
||||
modifier = modifier
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.addTextContextMenuComponents {
|
||||
separator()
|
||||
@@ -236,46 +238,47 @@ fun MessagesHistoryInputBar(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (showAttachmentButton) {
|
||||
val attachmentRotation = remember { Animatable(0f) }
|
||||
|
||||
val attachmentRotation = remember { Animatable(0f) }
|
||||
|
||||
Column(verticalArrangement = Arrangement.Bottom) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onAttachmentButtonClicked()
|
||||
if (AppSettings.General.enableHaptic) {
|
||||
view.performHapticFeedback(
|
||||
HapticFeedbackConstantsCompat.REJECT
|
||||
)
|
||||
}
|
||||
scope.launch {
|
||||
for (i in 20 downTo 0 step 4) {
|
||||
attachmentRotation.animateTo(
|
||||
targetValue = i.toFloat(),
|
||||
animationSpec = tween(50)
|
||||
Column(verticalArrangement = Arrangement.Bottom) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onAttachmentButtonClicked()
|
||||
if (enableHaptic) {
|
||||
view.performHapticFeedback(
|
||||
HapticFeedbackConstantsCompat.REJECT
|
||||
)
|
||||
if (i > 0) {
|
||||
}
|
||||
scope.launch {
|
||||
for (i in 20 downTo 0 step 4) {
|
||||
attachmentRotation.animateTo(
|
||||
targetValue = -i.toFloat(),
|
||||
targetValue = i.toFloat(),
|
||||
animationSpec = tween(50)
|
||||
)
|
||||
if (i > 0) {
|
||||
attachmentRotation.animateTo(
|
||||
targetValue = -i.toFloat(),
|
||||
animationSpec = tween(50)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.round_attach_file_24),
|
||||
contentDescription = "Add attachment button",
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.rotate(30f + attachmentRotation.value)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.round_attach_file_24),
|
||||
contentDescription = "Add attachment button",
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.rotate(30f + attachmentRotation.value)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
}
|
||||
|
||||
val micRotation = remember { Animatable(0f) }
|
||||
|
||||
+7
-6
@@ -4,20 +4,21 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import dev.meloda.fast.datastore.UserSettings
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.datastore.AppSettings
|
||||
import dev.meloda.fast.messageshistory.MessagesHistoryViewModel
|
||||
import dev.meloda.fast.messageshistory.MessagesHistoryViewModelImpl
|
||||
import dev.meloda.fast.messageshistory.model.MessageNavigation
|
||||
import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
fun MessagesHistoryRoute(
|
||||
onError: (BaseError) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onNavigateToChatMaterials: (peerId: Long, conversationMessageId: Long) -> Unit,
|
||||
onNavigateToPhotoViewer: (images: List<String>, index: Int) -> Unit,
|
||||
viewModel: MessagesHistoryViewModel = koinViewModel<MessagesHistoryViewModelImpl>()
|
||||
) {
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
@@ -30,9 +31,6 @@ fun MessagesHistoryRoute(
|
||||
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||
val scrollIndex by viewModel.isNeedToScrollToIndex.collectAsStateWithLifecycle()
|
||||
|
||||
val userSettings: UserSettings = koinInject()
|
||||
val showEmojiButton by userSettings.showEmojiButton.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(navigationEvent) {
|
||||
val needToConsume = when (val navigation = navigationEvent) {
|
||||
null -> false
|
||||
@@ -55,7 +53,9 @@ fun MessagesHistoryRoute(
|
||||
selectedMessages = selectedMessages.toImmutableList(),
|
||||
baseError = baseError,
|
||||
canPaginate = canPaginate,
|
||||
showEmojiButton = showEmojiButton,
|
||||
showEmojiButton = AppSettings.General.showEmojiButton,
|
||||
showAttachmentButton = AppSettings.General.showAttachmentButton,
|
||||
enableHaptic = AppSettings.General.enableHaptic,
|
||||
onBack = onBack,
|
||||
onClose = viewModel::onCloseButtonClicked,
|
||||
onScrolledToIndex = viewModel::onScrolledToIndex,
|
||||
@@ -69,6 +69,7 @@ fun MessagesHistoryRoute(
|
||||
onEmojiButtonLongClicked = viewModel::onEmojiButtonLongClicked,
|
||||
onMessageClicked = viewModel::onMessageClicked,
|
||||
onMessageLongClicked = viewModel::onMessageLongClicked,
|
||||
onPhotoClicked = onNavigateToPhotoViewer,
|
||||
onPinnedMessageClicked = viewModel::onPinnedMessageClicked,
|
||||
onUnpinMessageButtonClicked = viewModel::onUnpinMessageClicked,
|
||||
onDeleteSelectedButtonClicked = viewModel::onDeleteSelectedMessagesClicked,
|
||||
|
||||
+9
-3
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
@@ -45,6 +44,7 @@ import dev.meloda.fast.messageshistory.model.UiItem
|
||||
import dev.meloda.fast.messageshistory.util.indexOfMessageByCmId
|
||||
import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.ui.components.Loader
|
||||
import dev.meloda.fast.ui.components.VkErrorView
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.meloda.fast.ui.util.ImmutableList
|
||||
@@ -68,6 +68,8 @@ fun MessagesHistoryScreen(
|
||||
baseError: BaseError? = null,
|
||||
canPaginate: Boolean = false,
|
||||
showEmojiButton: Boolean = false,
|
||||
showAttachmentButton: Boolean = false,
|
||||
enableHaptic: Boolean = false,
|
||||
onBack: () -> Unit = {},
|
||||
onClose: () -> Unit = {},
|
||||
onScrolledToIndex: () -> Unit = {},
|
||||
@@ -81,6 +83,7 @@ fun MessagesHistoryScreen(
|
||||
onEmojiButtonLongClicked: () -> Unit = {},
|
||||
onMessageClicked: (Long) -> Unit = {},
|
||||
onMessageLongClicked: (Long) -> Unit = {},
|
||||
onPhotoClicked: (images: List<String>, index: Int) -> Unit = { _, _ -> },
|
||||
onPinnedMessageClicked: (Long) -> Unit = {},
|
||||
onUnpinMessageButtonClicked: () -> Unit = {},
|
||||
onDeleteSelectedButtonClicked: () -> Unit = {},
|
||||
@@ -231,7 +234,8 @@ fun MessagesHistoryScreen(
|
||||
}
|
||||
currentOnMessageClicked.invoke(id)
|
||||
},
|
||||
onMessageLongClicked = onMessageLongClicked
|
||||
onMessageLongClicked = onMessageLongClicked,
|
||||
onPhotoClicked = onPhotoClicked
|
||||
)
|
||||
|
||||
MessagesHistoryInputBar(
|
||||
@@ -244,7 +248,9 @@ fun MessagesHistoryScreen(
|
||||
onLinkRequested = onLinkRequested,
|
||||
onRegularRequested = onRegularRequested,
|
||||
hazeState = hazeState,
|
||||
enableHaptic = enableHaptic,
|
||||
showEmojiButton = showEmojiButton,
|
||||
showAttachmentButton = showAttachmentButton,
|
||||
actionMode = screenState.actionMode,
|
||||
onSetMessageBarHeight = { messageBarHeight = it },
|
||||
onEmojiButtonLongClicked = onEmojiButtonLongClicked,
|
||||
@@ -254,7 +260,7 @@ fun MessagesHistoryScreen(
|
||||
|
||||
when {
|
||||
screenState.isLoading && messages.values.isEmpty() -> {
|
||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||
Loader(modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
|
||||
baseError != null -> {
|
||||
|
||||
+24
-12
@@ -23,7 +23,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -56,25 +56,36 @@ fun MessagesList(
|
||||
messageBarHeight: Dp,
|
||||
onRequestScrollToCmId: (cmId: Long) -> Unit = {},
|
||||
onMessageClicked: (Long) -> Unit = {},
|
||||
onMessageLongClicked: (Long) -> Unit = {}
|
||||
onMessageLongClicked: (Long) -> Unit = {},
|
||||
onPhotoClicked: (images: List<String>, index: Int) -> Unit = { _, _ -> }
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val theme = LocalThemeConfig.current
|
||||
val view = LocalView.current
|
||||
|
||||
val onAttachmentClick = remember {
|
||||
val onAttachmentClick by rememberUpdatedState(
|
||||
{ message: UiItem.Message, attachment: VkAttachment ->
|
||||
if (isSelectedAtLeastOne) {
|
||||
onMessageClicked(message.id)
|
||||
} else {
|
||||
when (attachment) {
|
||||
is VkPhotoDomain -> {
|
||||
val maxSize = attachment.getMaxSize()
|
||||
maxSize?.let {
|
||||
context.startActivity(
|
||||
Intent(Intent.ACTION_VIEW, maxSize.url.toUri())
|
||||
)
|
||||
}
|
||||
val photos = message.attachments
|
||||
.orEmpty()
|
||||
.filterIsInstance<VkPhotoDomain>()
|
||||
.mapNotNull { photo -> photo.getMaxSize()?.url }
|
||||
|
||||
onPhotoClicked(
|
||||
photos,
|
||||
photos.indexOfFirst { it == attachment.getMaxSize()?.url }
|
||||
)
|
||||
|
||||
// val maxSize = attachment.getMaxSize()
|
||||
// maxSize?.let {
|
||||
// context.startActivity(
|
||||
// Intent(Intent.ACTION_VIEW, maxSize.url.toUri())
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
is VkFileDomain -> {
|
||||
@@ -91,9 +102,9 @@ fun MessagesList(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val onAttachmentLongClick = remember {
|
||||
val onAttachmentLongClick by rememberUpdatedState(
|
||||
{ message: UiItem.Message, attachment: VkAttachment ->
|
||||
if (isSelectedAtLeastOne) {
|
||||
onMessageLongClicked(message.id)
|
||||
@@ -107,7 +118,7 @@ fun MessagesList(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
@@ -200,6 +211,7 @@ fun MessagesList(
|
||||
),
|
||||
message = item,
|
||||
onClick = { attachment ->
|
||||
|
||||
onAttachmentClick(item, attachment)
|
||||
},
|
||||
onLongClick = { attachment ->
|
||||
|
||||
+88
-3
@@ -1,21 +1,43 @@
|
||||
package dev.meloda.fast.photoviewer
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import dev.meloda.fast.common.extensions.setValue
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.photoviewer.model.PhotoViewScreenState
|
||||
import dev.meloda.fast.photoviewer.navigation.PhotoView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URLDecoder
|
||||
import java.util.UUID
|
||||
|
||||
interface PhotoViewViewModel {
|
||||
val screenState: StateFlow<PhotoViewScreenState>
|
||||
|
||||
fun onPageChanged(newPage: Int)
|
||||
|
||||
fun onCopyLinkClicked()
|
||||
fun onCopyClicked()
|
||||
}
|
||||
|
||||
class PhotoViewViewModelImpl(
|
||||
savedStateHandle: SavedStateHandle
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val applicationContext: Context
|
||||
) : PhotoViewViewModel, ViewModel() {
|
||||
|
||||
override val screenState = MutableStateFlow(PhotoViewScreenState.EMPTY)
|
||||
@@ -25,10 +47,73 @@ class PhotoViewViewModelImpl(
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
images = arguments.images
|
||||
images = arguments.imageUrls
|
||||
.map { URLDecoder.decode(it, "utf-8") }
|
||||
.map(UiImage::Url)
|
||||
.map(UiImage::Url),
|
||||
selectedPage = arguments.selectedIndex?.takeIf { it != -1 } ?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageChanged(newPage: Int) {
|
||||
screenState.setValue { old -> old.copy(selectedPage = newPage) }
|
||||
}
|
||||
|
||||
override fun onCopyLinkClicked() {
|
||||
val url = screenState.value.images
|
||||
.getOrNull(screenState.value.selectedPage)
|
||||
?.extractUrl() ?: return
|
||||
|
||||
val clipboardManager =
|
||||
applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText("URL", url))
|
||||
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"URL copied to clipboard",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onCopyClicked() {
|
||||
val clipboardManager =
|
||||
applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
val url = screenState.value.images
|
||||
.getOrNull(screenState.value.selectedPage)
|
||||
?.extractUrl() ?: return
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val drawable = applicationContext.imageLoader.execute(
|
||||
ImageRequest.Builder(applicationContext)
|
||||
.data(url)
|
||||
.build()
|
||||
).drawable ?: return@launch
|
||||
|
||||
val imagesDir = File(applicationContext.cacheDir, "images")
|
||||
if (!imagesDir.exists()) imagesDir.mkdirs()
|
||||
val imageFile = File(imagesDir, "shared_image_id${UUID.randomUUID()}.png")
|
||||
FileOutputStream(imageFile).use {
|
||||
drawable.toBitmapOrNull()?.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||
}
|
||||
|
||||
val uri = FileProvider.getUriForFile(
|
||||
applicationContext,
|
||||
"${applicationContext.packageName}.provider",
|
||||
imageFile
|
||||
)
|
||||
|
||||
val clip = ClipData.newUri(applicationContext.contentResolver, "Image", uri)
|
||||
clipboardManager.setPrimaryClip(clip)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
"Image copied to clipboard",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -7,5 +7,6 @@ import kotlinx.serialization.Serializable
|
||||
@Parcelize
|
||||
@Serializable
|
||||
data class PhotoViewArguments(
|
||||
val images: List<String>
|
||||
val imageUrls: List<String>,
|
||||
val selectedIndex: Int?
|
||||
) : Parcelable
|
||||
|
||||
+4
-2
@@ -5,12 +5,14 @@ import dev.meloda.fast.common.model.UiImage
|
||||
|
||||
@Immutable
|
||||
data class PhotoViewScreenState(
|
||||
val images: List<UiImage>
|
||||
val images: List<UiImage>,
|
||||
val selectedPage: Int
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val EMPTY: PhotoViewScreenState = PhotoViewScreenState(
|
||||
images = emptyList()
|
||||
images = emptyList(),
|
||||
selectedPage = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -30,11 +30,15 @@ fun NavGraphBuilder.photoViewScreen(
|
||||
}
|
||||
}
|
||||
|
||||
fun NavController.navigateToPhotoView(images: List<String>) {
|
||||
fun NavController.navigateToPhotoView(
|
||||
images: List<String>,
|
||||
selectedIndex: Int? = null
|
||||
) {
|
||||
this.navigate(
|
||||
PhotoView(
|
||||
arguments = PhotoViewArguments(
|
||||
images.map { URLEncoder.encode(it, "utf-8") }
|
||||
imageUrls = images.map { URLEncoder.encode(it, "utf-8") },
|
||||
selectedIndex = selectedIndex
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
+77
-31
@@ -7,6 +7,7 @@ import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
@@ -14,27 +15,36 @@ import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil.compose.AsyncImage
|
||||
import com.conena.nanokt.android.content.pxToDp
|
||||
@@ -56,16 +66,30 @@ fun PhotoViewRoute(
|
||||
|
||||
PhotoViewScreen(
|
||||
screenState = screenState,
|
||||
onBack = onBack
|
||||
onBack = onBack,
|
||||
onPageChanged = viewModel::onPageChanged,
|
||||
onCopyLinkClicked = viewModel::onCopyLinkClicked,
|
||||
onCopyClicked = viewModel::onCopyClicked
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PhotoViewScreen(
|
||||
screenState: PhotoViewScreenState = PhotoViewScreenState.EMPTY,
|
||||
onBack: () -> Unit = {}
|
||||
onBack: () -> Unit = {},
|
||||
onPageChanged: (index: Int) -> Unit = {},
|
||||
onCopyLinkClicked: () -> Unit = {},
|
||||
onCopyClicked: () -> Unit = {}
|
||||
) {
|
||||
val pagerState = rememberPagerState(pageCount = { screenState.images.size })
|
||||
val pagerState = rememberPagerState(
|
||||
pageCount = { screenState.images.size },
|
||||
initialPage = screenState.selectedPage
|
||||
)
|
||||
|
||||
LaunchedEffect(pagerState) {
|
||||
snapshotFlow { pagerState.currentPage }
|
||||
.collect(onPageChanged)
|
||||
}
|
||||
|
||||
var offsetY by remember { mutableFloatStateOf(0f) }
|
||||
|
||||
@@ -81,7 +105,13 @@ fun PhotoViewScreen(
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.graphicsLayer(alpha = calculatedAlpha),
|
||||
topBar = { TopBar(onBack = onBack) },
|
||||
topBar = {
|
||||
TopBar(
|
||||
onBack = onBack,
|
||||
onCopyClicked = onCopyClicked,
|
||||
onCopyLinkClicked = onCopyLinkClicked,
|
||||
)
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.background.copy(
|
||||
alpha = calculatedAlpha
|
||||
)
|
||||
@@ -103,14 +133,18 @@ fun PhotoViewScreen(
|
||||
@Composable
|
||||
fun TopBar(
|
||||
modifier: Modifier = Modifier,
|
||||
onBack: () -> Unit
|
||||
onBack: () -> Unit,
|
||||
onCopyClicked: () -> Unit,
|
||||
onCopyLinkClicked: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
var dropdownMenuShown by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val hideDropDownMenu by rememberUpdatedState(
|
||||
{ dropdownMenuShown = false }
|
||||
)
|
||||
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
title = {},
|
||||
@@ -123,29 +157,40 @@ fun TopBar(
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
// IconButton.kt(
|
||||
// onClick = { dropdownMenuShown = true }
|
||||
// ) {
|
||||
// Icon(
|
||||
// imageVector = Icons.Rounded.MoreVert,
|
||||
// contentDescription = "Options"
|
||||
// )
|
||||
// }
|
||||
IconButton(
|
||||
onClick = { dropdownMenuShown = true }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.MoreVert,
|
||||
contentDescription = "Options"
|
||||
)
|
||||
}
|
||||
|
||||
// DropdownMenu(
|
||||
// modifier = Modifier.defaultMinSize(minWidth = 140.dp),
|
||||
// expanded = dropdownMenuShown,
|
||||
// onDismissRequest = { dropdownMenuShown = false },
|
||||
// offset = DpOffset(x = (10).dp, y = (-60).dp)
|
||||
// ) {
|
||||
// DropdownMenuItem(
|
||||
// onClick = {
|
||||
// Toast.makeText(context, "Save clicked", Toast.LENGTH_SHORT).show()
|
||||
// dropdownMenuShown = false
|
||||
// },
|
||||
// text = { Text(text = "Save") },
|
||||
// )
|
||||
// }
|
||||
DropdownMenu(
|
||||
modifier = Modifier.defaultMinSize(minWidth = 140.dp),
|
||||
expanded = dropdownMenuShown,
|
||||
onDismissRequest = { dropdownMenuShown = false },
|
||||
offset = DpOffset(x = (10).dp, y = (-60).dp)
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
hideDropDownMenu()
|
||||
onCopyLinkClicked()
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(UiR.string.action_copy_link))
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
hideDropDownMenu()
|
||||
onCopyClicked()
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(UiR.string.action_copy_image))
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
|
||||
)
|
||||
@@ -230,7 +275,8 @@ private fun PhotoViewScreenPreview() {
|
||||
screenState = PhotoViewScreenState(
|
||||
images = List(200) {
|
||||
UiImage.Resource(UiR.drawable.test_captcha)
|
||||
}
|
||||
},
|
||||
selectedPage = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -201,24 +201,12 @@ class SettingsViewModelImpl(
|
||||
userSettings.onAppLanguageChanged(newLanguage)
|
||||
}
|
||||
|
||||
|
||||
SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT -> {
|
||||
val newText = newValue as? String ?: SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT
|
||||
userSettings.onFastTextChanged(newText)
|
||||
}
|
||||
|
||||
|
||||
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
|
||||
val isUsing = newValue as? Boolean
|
||||
?: SettingsKeys.DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS
|
||||
userSettings.onSendOnlineStatusChanged(isUsing)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT -> {
|
||||
val show = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||
userSettings.onShowAlertAfterCrashChanged(show)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_LONG_POLL_IN_BACKGROUND -> {
|
||||
val inBackground = newValue as? Boolean
|
||||
?: SettingsKeys.DEFAULT_LONG_POLL_IN_BACKGROUND
|
||||
@@ -240,17 +228,6 @@ class SettingsViewModelImpl(
|
||||
userSettings.onUseBlurChanged(isUsing)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_SHOW_EMOJI_BUTTON -> {
|
||||
val show = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||
userSettings.onShowEmojiButtonChanged(show)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_SHOW_TIME_IN_ACTION_MESSAGES -> {
|
||||
val show = newValue as? Boolean
|
||||
?: SettingsKeys.DEFAULT_SHOW_TIME_IN_ACTION_MESSAGES
|
||||
userSettings.onShowTimeInActionMessagesChanged(show)
|
||||
}
|
||||
|
||||
SettingsKeys.KEY_USE_SYSTEM_FONT -> {
|
||||
val use = newValue as? Boolean ?: SettingsKeys.DEFAULT_USE_SYSTEM_FONT
|
||||
userSettings.onUseSystemFontChanged(use)
|
||||
@@ -302,6 +279,12 @@ class SettingsViewModelImpl(
|
||||
text = UiText.Resource(UiR.string.settings_general_show_emoji_button_summary),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||
)
|
||||
val generalShowAttachmentButton = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_SHOW_ATTACHMENT_BUTTON,
|
||||
title = UiText.Resource(UiR.string.settings_general_show_attachment_button_title),
|
||||
text = UiText.Resource(UiR.string.settings_general_show_attachment_button_summary),
|
||||
defaultValue = SettingsKeys.DEFAULT_VALUE_SHOW_ATTACHMENT_BUTTON
|
||||
)
|
||||
val generalEnableHaptic = SettingsItem.Switch(
|
||||
key = SettingsKeys.KEY_ENABLE_HAPTIC,
|
||||
defaultValue = SettingsKeys.DEFAULT_ENABLE_HAPTIC,
|
||||
@@ -476,6 +459,7 @@ class SettingsViewModelImpl(
|
||||
generalTitle,
|
||||
generalUseContactNames,
|
||||
generalShowEmojiButton,
|
||||
generalShowAttachmentButton,
|
||||
generalEnableHaptic
|
||||
)
|
||||
val appearanceList = listOf(
|
||||
|
||||
Reference in New Issue
Block a user