feat: Update send icon to ArrowUp in TelegramCaptionBar and MultiImageEditorScreen
This commit is contained in:
@@ -551,6 +551,7 @@ fun MainScreen(
|
|||||||
// Appearance: background blur color preference
|
// Appearance: background blur color preference
|
||||||
val prefsManager = remember { com.rosetta.messenger.data.PreferencesManager(context) }
|
val prefsManager = remember { com.rosetta.messenger.data.PreferencesManager(context) }
|
||||||
val backgroundBlurColorId by prefsManager.backgroundBlurColorId.collectAsState(initial = "avatar")
|
val backgroundBlurColorId by prefsManager.backgroundBlurColorId.collectAsState(initial = "avatar")
|
||||||
|
val pinnedChats by prefsManager.pinnedChats.collectAsState(initial = emptySet())
|
||||||
|
|
||||||
// AvatarRepository для работы с аватарами
|
// AvatarRepository для работы с аватарами
|
||||||
val avatarRepository = remember(accountPublicKey) {
|
val avatarRepository = remember(accountPublicKey) {
|
||||||
@@ -614,6 +615,12 @@ fun MainScreen(
|
|||||||
},
|
},
|
||||||
onUserSelect = { selectedChatUser -> selectedUser = selectedChatUser },
|
onUserSelect = { selectedChatUser -> selectedUser = selectedChatUser },
|
||||||
backgroundBlurColorId = backgroundBlurColorId,
|
backgroundBlurColorId = backgroundBlurColorId,
|
||||||
|
pinnedChats = pinnedChats,
|
||||||
|
onTogglePin = { opponentKey ->
|
||||||
|
mainScreenScope.launch {
|
||||||
|
prefsManager.togglePinChat(opponentKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
avatarRepository = avatarRepository,
|
avatarRepository = avatarRepository,
|
||||||
onLogout = onLogout
|
onLogout = onLogout
|
||||||
)
|
)
|
||||||
@@ -905,8 +912,7 @@ fun MainScreen(
|
|||||||
showOtherProfileScreen = false
|
showOtherProfileScreen = false
|
||||||
selectedOtherUser = null
|
selectedOtherUser = null
|
||||||
},
|
},
|
||||||
avatarRepository = avatarRepository,
|
avatarRepository = avatarRepository
|
||||||
backgroundBlurColorId = backgroundBlurColorId
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
|
|||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -45,6 +46,9 @@ class PreferencesManager(private val context: Context) {
|
|||||||
|
|
||||||
// Appearance / Customization
|
// Appearance / Customization
|
||||||
val BACKGROUND_BLUR_COLOR_ID = stringPreferencesKey("background_blur_color_id") // id from BackgroundBlurPresets
|
val BACKGROUND_BLUR_COLOR_ID = stringPreferencesKey("background_blur_color_id") // id from BackgroundBlurPresets
|
||||||
|
|
||||||
|
// Pinned Chats (max 3)
|
||||||
|
val PINNED_CHATS = stringSetPreferencesKey("pinned_chats") // Set of opponent public keys
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═════════════════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════════════════
|
||||||
@@ -205,4 +209,33 @@ class PreferencesManager(private val context: Context) {
|
|||||||
suspend fun setBackgroundBlurColorId(value: String) {
|
suspend fun setBackgroundBlurColorId(value: String) {
|
||||||
context.dataStore.edit { preferences -> preferences[BACKGROUND_BLUR_COLOR_ID] = value }
|
context.dataStore.edit { preferences -> preferences[BACKGROUND_BLUR_COLOR_ID] = value }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
// 📌 PINNED CHATS
|
||||||
|
// ═════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
val pinnedChats: Flow<Set<String>> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[PINNED_CHATS] ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setPinnedChats(value: Set<String>) {
|
||||||
|
context.dataStore.edit { preferences -> preferences[PINNED_CHATS] = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun togglePinChat(opponentKey: String): Boolean {
|
||||||
|
var wasPinned = false
|
||||||
|
context.dataStore.edit { preferences ->
|
||||||
|
val current = preferences[PINNED_CHATS] ?: emptySet()
|
||||||
|
if (current.contains(opponentKey)) {
|
||||||
|
// Unpin
|
||||||
|
preferences[PINNED_CHATS] = current - opponentKey
|
||||||
|
wasPinned = true
|
||||||
|
} else if (current.size < 3) {
|
||||||
|
// Pin (max 3)
|
||||||
|
preferences[PINNED_CHATS] = current + opponentKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wasPinned
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ fun ChatsListScreen(
|
|||||||
onNewChat: () -> Unit,
|
onNewChat: () -> Unit,
|
||||||
onUserSelect: (com.rosetta.messenger.network.SearchUser) -> Unit = {},
|
onUserSelect: (com.rosetta.messenger.network.SearchUser) -> Unit = {},
|
||||||
backgroundBlurColorId: String = "avatar",
|
backgroundBlurColorId: String = "avatar",
|
||||||
|
pinnedChats: Set<String> = emptySet(),
|
||||||
|
onTogglePin: (String) -> Unit = {},
|
||||||
chatsViewModel: ChatsListViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
|
chatsViewModel: ChatsListViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
|
||||||
avatarRepository: com.rosetta.messenger.repository.AvatarRepository? = null,
|
avatarRepository: com.rosetta.messenger.repository.AvatarRepository? = null,
|
||||||
onLogout: () -> Unit
|
onLogout: () -> Unit
|
||||||
@@ -961,7 +963,13 @@ android.util.Log.d("ChatsListScreen", "✅ Total LaunchedEffect: ${System.curren
|
|||||||
val listBackgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF2F2F7)
|
val listBackgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF2F2F7)
|
||||||
// 🔥 Берем dialogs из chatsState для
|
// 🔥 Берем dialogs из chatsState для
|
||||||
// консистентности
|
// консистентности
|
||||||
val currentDialogs = chatsState.dialogs
|
// 📌 Сортируем: pinned сначала, потом по времени
|
||||||
|
val currentDialogs = remember(chatsState.dialogs, pinnedChats) {
|
||||||
|
chatsState.dialogs.sortedWith(
|
||||||
|
compareByDescending<DialogUiModel> { pinnedChats.contains(it.opponentKey) }
|
||||||
|
.thenByDescending { it.lastMessageTimestamp }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Telegram-style: only one item can be swiped open at a time
|
// Telegram-style: only one item can be swiped open at a time
|
||||||
var swipedItemKey by remember { mutableStateOf<String?>(null) }
|
var swipedItemKey by remember { mutableStateOf<String?>(null) }
|
||||||
@@ -1074,7 +1082,9 @@ android.util.Log.d("ChatsListScreen", "✅ Total LaunchedEffect: ${System.curren
|
|||||||
onUnblock = {
|
onUnblock = {
|
||||||
dialogToUnblock =
|
dialogToUnblock =
|
||||||
dialog
|
dialog
|
||||||
}
|
},
|
||||||
|
isPinned = pinnedChats.contains(dialog.opponentKey),
|
||||||
|
onPin = { onTogglePin(dialog.opponentKey) }
|
||||||
)
|
)
|
||||||
|
|
||||||
// 🔥 СЕПАРАТОР -
|
// 🔥 СЕПАРАТОР -
|
||||||
@@ -1516,11 +1526,19 @@ fun SwipeableDialogItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onDelete: () -> Unit = {},
|
onDelete: () -> Unit = {},
|
||||||
onBlock: () -> Unit = {},
|
onBlock: () -> Unit = {},
|
||||||
onUnblock: () -> Unit = {}
|
onUnblock: () -> Unit = {},
|
||||||
|
isPinned: Boolean = false,
|
||||||
|
onPin: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF2F2F7)
|
val backgroundColor = if (isPinned) {
|
||||||
|
if (isDarkTheme) Color(0xFF232323) else Color(0xFFE8E8ED)
|
||||||
|
} else {
|
||||||
|
if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF2F2F7)
|
||||||
|
}
|
||||||
var offsetX by remember { mutableStateOf(0f) }
|
var offsetX by remember { mutableStateOf(0f) }
|
||||||
val swipeWidthDp = if (isSavedMessages) 80.dp else 160.dp
|
// 📌 3 кнопки: Pin + Block/Unblock + Delete (для SavedMessages: Pin + Delete)
|
||||||
|
val buttonCount = if (isSavedMessages) 2 else 3
|
||||||
|
val swipeWidthDp = (buttonCount * 80).dp
|
||||||
val density = androidx.compose.ui.platform.LocalDensity.current
|
val density = androidx.compose.ui.platform.LocalDensity.current
|
||||||
val swipeWidthPx = with(density) { swipeWidthDp.toPx() }
|
val swipeWidthPx = with(density) { swipeWidthDp.toPx() }
|
||||||
|
|
||||||
@@ -1551,6 +1569,43 @@ fun SwipeableDialogItem(
|
|||||||
.height(itemHeight)
|
.height(itemHeight)
|
||||||
.width(swipeWidthDp)
|
.width(swipeWidthDp)
|
||||||
) {
|
) {
|
||||||
|
// 📌 Кнопка Pin/Unpin
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier.width(80.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.background(Color(0xFFFF9500)) // iOS orange
|
||||||
|
.clickable {
|
||||||
|
onPin()
|
||||||
|
offsetX = 0f
|
||||||
|
onSwipeClosed()
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector =
|
||||||
|
if (isPinned) TablerIcons.PinnedOff
|
||||||
|
else TablerIcons.Pin,
|
||||||
|
contentDescription =
|
||||||
|
if (isPinned) "Unpin" else "Pin",
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(22.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
if (isPinned) "Unpin" else "Pin",
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Кнопка Block/Unblock (только если не Saved Messages)
|
// Кнопка Block/Unblock (только если не Saved Messages)
|
||||||
if (!isSavedMessages) {
|
if (!isSavedMessages) {
|
||||||
Box(
|
Box(
|
||||||
@@ -1604,7 +1659,6 @@ fun SwipeableDialogItem(
|
|||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.background(PrimaryBlue)
|
.background(PrimaryBlue)
|
||||||
.clickable {
|
.clickable {
|
||||||
// Закрываем свайп мгновенно перед удалением
|
|
||||||
offsetX = 0f
|
offsetX = 0f
|
||||||
onSwipeClosed()
|
onSwipeClosed()
|
||||||
onDelete()
|
onDelete()
|
||||||
@@ -1693,6 +1747,7 @@ fun SwipeableDialogItem(
|
|||||||
dialog = dialog,
|
dialog = dialog,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
isTyping = isTyping,
|
isTyping = isTyping,
|
||||||
|
isPinned = isPinned,
|
||||||
avatarRepository = avatarRepository,
|
avatarRepository = avatarRepository,
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
)
|
)
|
||||||
@@ -1714,6 +1769,7 @@ fun DialogItemContent(
|
|||||||
dialog: DialogUiModel,
|
dialog: DialogUiModel,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
isTyping: Boolean = false,
|
isTyping: Boolean = false,
|
||||||
|
isPinned: Boolean = false,
|
||||||
avatarRepository: com.rosetta.messenger.repository.AvatarRepository? = null,
|
avatarRepository: com.rosetta.messenger.repository.AvatarRepository? = null,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -1944,9 +2000,8 @@ fun DialogItemContent(
|
|||||||
Modifier.width(4.dp)
|
Modifier.width(4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
1 -> {
|
||||||
// DELIVERED (1) или SENDING (0) -
|
// DELIVERED - одна серая галочка
|
||||||
// одна серая галочка
|
|
||||||
Icon(
|
Icon(
|
||||||
imageVector =
|
imageVector =
|
||||||
TablerIcons.Check,
|
TablerIcons.Check,
|
||||||
@@ -1965,6 +2020,26 @@ fun DialogItemContent(
|
|||||||
Modifier.width(4.dp)
|
Modifier.width(4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
else -> {
|
||||||
|
// SENDING (0) - часики
|
||||||
|
Icon(
|
||||||
|
imageVector =
|
||||||
|
TablerIcons.Clock,
|
||||||
|
contentDescription = "Sending",
|
||||||
|
tint =
|
||||||
|
secondaryTextColor
|
||||||
|
.copy(
|
||||||
|
alpha =
|
||||||
|
0.6f
|
||||||
|
),
|
||||||
|
modifier =
|
||||||
|
Modifier.size(14.dp)
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier =
|
||||||
|
Modifier.width(4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2049,6 +2124,17 @@ fun DialogItemContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 📌 Pin icon
|
||||||
|
if (isPinned) {
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Icon(
|
||||||
|
imageVector = TablerIcons.Pin,
|
||||||
|
contentDescription = "Pinned",
|
||||||
|
tint = secondaryTextColor.copy(alpha = 0.5f),
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1152,12 +1152,11 @@ private fun TelegramCaptionBar(
|
|||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
// Стрелка отправки - видна когда compact (progress близок к 0)
|
// Стрелка отправки - видна когда compact (progress близок к 0)
|
||||||
Icon(
|
Icon(
|
||||||
TablerIcons.Send,
|
TablerIcons.ArrowUp,
|
||||||
contentDescription = "Send",
|
contentDescription = "Send",
|
||||||
tint = Color.White.copy(alpha = 1f - progress),
|
tint = Color.White.copy(alpha = 1f - progress),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(iconSize)
|
.size(iconSize)
|
||||||
.offset(x = 1.dp)
|
|
||||||
.graphicsLayer { alpha = 1f - progress }
|
.graphicsLayer { alpha = 1f - progress }
|
||||||
)
|
)
|
||||||
// Галочка - видна когда expanded (progress близок к 1)
|
// Галочка - видна когда expanded (progress близок к 1)
|
||||||
@@ -1932,12 +1931,11 @@ fun MultiImageEditorScreen(
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
TablerIcons.Send,
|
TablerIcons.ArrowUp,
|
||||||
contentDescription = "Send",
|
contentDescription = "Send",
|
||||||
tint = Color.White,
|
tint = Color.White,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(22.dp)
|
.size(22.dp)
|
||||||
.offset(x = 1.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,35 +273,21 @@ fun ImageViewerScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🎬 Shared element dismiss - возврат к исходному положению
|
// 🎬 Swipe dismiss - плавный fade out на месте
|
||||||
fun smoothDismiss() {
|
fun smoothDismiss() {
|
||||||
if (isClosing) return
|
if (isClosing) return
|
||||||
isClosing = true
|
isClosing = true
|
||||||
onClosingStart() // Сразу сбрасываем status bar
|
onClosingStart()
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
// Сначала сбрасываем offset от drag
|
// Простой быстрый fade out — как в Telegram
|
||||||
animatedOffsetY.snapTo(0f)
|
dismissAlpha.animateTo(
|
||||||
|
targetValue = 0f,
|
||||||
if (sourceBounds != null && pagerState.currentPage == initialIndex) {
|
animationSpec = tween(
|
||||||
// 🔥 Telegram-style: возвращаемся к исходному положению с shared element
|
durationMillis = 200,
|
||||||
animationProgress.animateTo(
|
easing = FastOutSlowInEasing
|
||||||
targetValue = 0f,
|
|
||||||
animationSpec = tween(
|
|
||||||
durationMillis = 280,
|
|
||||||
easing = FastOutSlowInEasing
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
} else {
|
)
|
||||||
// Fallback: простой fade-out если пролистнули на другое фото
|
|
||||||
dismissAlpha.animateTo(
|
|
||||||
targetValue = 0f,
|
|
||||||
animationSpec = tween(
|
|
||||||
durationMillis = 250,
|
|
||||||
easing = LinearEasing
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,7 +307,7 @@ fun ImageViewerScreen(
|
|||||||
|
|
||||||
// Alpha на основе drag прогресса
|
// Alpha на основе drag прогресса
|
||||||
val dismissProgress = (animatedOffsetY.value.absoluteValue / 300f).coerceIn(0f, 1f)
|
val dismissProgress = (animatedOffsetY.value.absoluteValue / 300f).coerceIn(0f, 1f)
|
||||||
val backgroundAlpha = animationProgress.value * dismissAlpha.value * (1f - dismissProgress * 0.5f)
|
val backgroundAlpha = animationProgress.value * dismissAlpha.value * (1f - dismissProgress * 0.7f)
|
||||||
|
|
||||||
// Current image info
|
// Current image info
|
||||||
val currentImage = images.getOrNull(pagerState.currentPage)
|
val currentImage = images.getOrNull(pagerState.currentPage)
|
||||||
@@ -407,7 +393,25 @@ fun ImageViewerScreen(
|
|||||||
ZoomableImage(
|
ZoomableImage(
|
||||||
image = image,
|
image = image,
|
||||||
privateKey = privateKey,
|
privateKey = privateKey,
|
||||||
onTap = { showControls = !showControls },
|
onTap = { tapOffset ->
|
||||||
|
// 👆 Tap on left/right edge (20% zone) to navigate
|
||||||
|
val edgeZone = screenSize.width * 0.20f
|
||||||
|
val tapX = tapOffset.x
|
||||||
|
val screenW = screenSize.width.toFloat()
|
||||||
|
when {
|
||||||
|
tapX < edgeZone && pagerState.currentPage > 0 -> {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.scrollToPage(pagerState.currentPage - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tapX > screenW - edgeZone && pagerState.currentPage < images.size - 1 -> {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.scrollToPage(pagerState.currentPage + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> showControls = !showControls
|
||||||
|
}
|
||||||
|
},
|
||||||
onVerticalDrag = { dragAmount, velocity ->
|
onVerticalDrag = { dragAmount, velocity ->
|
||||||
dragOffsetY += dragAmount
|
dragOffsetY += dragAmount
|
||||||
dragVelocity = velocity
|
dragVelocity = velocity
|
||||||
@@ -530,7 +534,7 @@ fun ImageViewerScreen(
|
|||||||
private fun ZoomableImage(
|
private fun ZoomableImage(
|
||||||
image: ViewableImage,
|
image: ViewableImage,
|
||||||
privateKey: String,
|
privateKey: String,
|
||||||
onTap: () -> Unit,
|
onTap: (Offset) -> Unit,
|
||||||
onVerticalDrag: (Float, Float) -> Unit = { _, _ -> }, // dragAmount, velocity
|
onVerticalDrag: (Float, Float) -> Unit = { _, _ -> }, // dragAmount, velocity
|
||||||
onDragEnd: () -> Unit = {}
|
onDragEnd: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
@@ -662,7 +666,7 @@ private fun ZoomableImage(
|
|||||||
.onSizeChanged { containerSize = it }
|
.onSizeChanged { containerSize = it }
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onTap = { onTap() },
|
onTap = { offset -> onTap(offset) },
|
||||||
onDoubleTap = { tapOffset ->
|
onDoubleTap = { tapOffset ->
|
||||||
if (scale > 1.1f) {
|
if (scale > 1.1f) {
|
||||||
// Zoom out - плавно возвращаемся
|
// Zoom out - плавно возвращаемся
|
||||||
|
|||||||
Reference in New Issue
Block a user