Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
33b8b69
feat(chats): fast reply gesture
andr0d1v Apr 4, 2026
a73d370
improve fast reply indicator design
andr0d1v Apr 4, 2026
3baed7d
feat: fast reply for album messages + fix bugs with pointer input
andr0d1v Apr 5, 2026
1371efe
feat(channels): fast reply gesture
andr0d1v Apr 5, 2026
1d85dec
refactor
andr0d1v Apr 5, 2026
e74b31c
refactor fast reply
andr0d1v Apr 5, 2026
4adc908
fix reply icon offset
andr0d1v Apr 6, 2026
e8e6b07
Merge branch 'monogram-android:develop' into feat/fast-reply
andr0d1v Apr 6, 2026
14b225d
Merge branch 'monogram-android:develop' into feat/fast-reply
andr0d1v Apr 7, 2026
3669df4
disable fast reply in pinned messages list
andr0d1v Apr 7, 2026
d86dd9a
disable fast reply for closed topics
andr0d1v Apr 7, 2026
3295104
fix: fast reply in closed topics for admins
andr0d1v Apr 7, 2026
98a2de6
Merge branch 'monogram-android:develop' into feat/fast-reply
andr0d1v Apr 7, 2026
d8fe47e
fix compile
andr0d1v Apr 7, 2026
ac21ae5
Merge remote-tracking branch 'origin/feat/fast-reply' into feat/fast-…
andr0d1v Apr 7, 2026
caeabab
Fix TDLib update latency by removing channel relay
gdlbo Apr 4, 2026
dbf36b2
fix(auth): handle unknown country codes in phone input (#167)
kaajjo Apr 4, 2026
76cc2c2
fix #78
gdlbo Apr 4, 2026
4bf4799
fix button size
gdlbo Apr 4, 2026
ac715cc
fix #80
gdlbo Apr 4, 2026
9f3ec70
Add build instructions for libvpx (#173)
curtisy1 Apr 4, 2026
6857299
cleanup video/photo menu, fixed statusbar showing in video/photo
gdlbo Apr 5, 2026
809f141
Add libvpx build steps and submodule clone
gdlbo Apr 5, 2026
31b8f7f
Show expanded input actions on tablets
gdlbo Apr 5, 2026
93ca211
docs(readme): add independent client note across all README translations
gdlbo Apr 5, 2026
a632119
feature: quote blocks (#185)
SnowVolf Apr 5, 2026
5d81999
Added Armenian translation (#186)
keimoger Apr 5, 2026
66d8d6d
Added Spanish translation (#156)
CJ-1347 Apr 5, 2026
ee5d7ce
Added Brazilian Portuguese translation (#183)
ruizlenato Apr 5, 2026
196b723
Add new locales: hy, es, and pt-BR
gdlbo Apr 5, 2026
04cc5a7
Complete rewrite and split of the repository module (#187)
gdlbo Apr 5, 2026
1c3d3c6
Optimize chat recomposition with Compose stability config and stable …
gdlbo Apr 5, 2026
ebf1df7
Fix malformed Spanish strings XML
gdlbo Apr 5, 2026
1d1fe81
remove compose runtime dependency from domain models
gdlbo Apr 5, 2026
43186a7
Fix random avatar/sticker/video/photo swapping caused by stale cache …
gdlbo Apr 6, 2026
389271a
avoid null cast to ShowKeyboard in chat input
gdlbo Apr 6, 2026
0b13933
Bump to version 6
gdlbo Apr 6, 2026
75f00ab
refactor(data): replace ScopeProvider with CoroutineScope and remove …
gdlbo Apr 6, 2026
8dc669a
Rewritten mappings of chats/messages (#200)
gdlbo Apr 6, 2026
fa213c6
Better mappings + memory logger (#204)
gdlbo Apr 6, 2026
06fe583
photo: better crop (#188)
aliveoutside Apr 6, 2026
40cdb49
feat(photo-editor): add rotate to crop
aliveoutside Apr 6, 2026
9382dfd
feat(photo-editor): smoother rotate animation
aliveoutside Apr 6, 2026
d3cb673
better notifications info caching
gdlbo Apr 6, 2026
7a8ccd0
feat(chat): smoother mic/send button animation
aliveoutside Apr 6, 2026
71e87a8
fixed runtime update user info in chat
gdlbo Apr 6, 2026
b2e50b0
thread send message fix + caption message fix
gdlbo Apr 6, 2026
15621a5
fix #174
gdlbo Apr 6, 2026
193347c
slow mode, mute etc. support
gdlbo Apr 6, 2026
4c23aac
fix disable drag-to-back in chats
gdlbo Apr 6, 2026
6d4df00
disable fast reply in pinned messages list
andr0d1v Apr 7, 2026
1fabd24
disable fast reply for closed topics
andr0d1v Apr 7, 2026
7bfa626
fix: fast reply in closed topics for admins
andr0d1v Apr 7, 2026
e823347
fix compile
andr0d1v Apr 7, 2026
d7874b2
fix chat settings ui
gdlbo Apr 7, 2026
7221654
pins redesign + fix #73
gdlbo Apr 7, 2026
d14f740
Merge remote-tracking branch 'origin/feat/fast-reply' into feat/fast-…
andr0d1v Apr 7, 2026
3053664
Merge branch 'develop' into feat/fast-reply
andr0d1v Apr 7, 2026
4809dce
.
andr0d1v Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -444,26 +444,6 @@ fun ChatContent(
.background(MaterialTheme.colorScheme.background)
.onGloballyPositioned { containerSize = it.size }
) {
/*if (isDragToBackEnabled && !isTablet && !isCustomBackHandlingEnabled && dragOffsetX.value > 0 && previousChild != null) {
Box(
modifier = Modifier.fillMaxSize()
) {
renderChild(previousChild)
Box(
modifier = Modifier
.fillMaxSize()
.background(
Color.Black.copy(
alpha = 0.3f * (1f - (dragOffsetX.value / containerSize.width.toFloat()).coerceIn(
0f,
1f
))
)
)
)
}
}*/

Box(
modifier = Modifier
.fillMaxSize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ private fun MessageBubbleSwitcher(
isAnyViewerOpen: Boolean = false
) {
val isChannel = state.isChannel && state.currentTopicId == null
val isTopicClosed = state.topics.find { it.id.toLong() == state.currentTopicId }?.isClosed?: false

when (item) {
is GroupedMessageItem.Single -> {
Expand Down Expand Up @@ -591,6 +592,8 @@ private fun MessageBubbleSwitcher(
onCommentsClick = { component.onCommentsClick(it) },
toProfile = toProfile,
onViaBotClick = onViaBotClick,
canReply = state.canWrite && !isSelectionMode,
onReplySwipe = { component.onReplyMessage(it) },
onYouTubeClick = { component.onOpenYouTube(it) },
onInstantViewClick = { component.onOpenInstantView(it) },
downloadUtils = downloadUtils,
Expand Down Expand Up @@ -694,7 +697,7 @@ private fun MessageBubbleSwitcher(
onPositionChange = { _, pos, size -> onMessagePositionChange(pos, size) },
toProfile = toProfile,
onViaBotClick = onViaBotClick,
canReply = state.canWrite && !isSelectionMode,
canReply = state.canWrite && !isSelectionMode && (!isTopicClosed || state.isAdmin),
onReplySwipe = { component.onReplyMessage(it) },
swipeEnabled = !isSelectionMode,
downloadUtils = downloadUtils,
Expand Down Expand Up @@ -762,7 +765,7 @@ private fun MessageBubbleSwitcher(
onCommentsClick = { component.onCommentsClick(it) },
toProfile = toProfile,
onViaBotClick = onViaBotClick,
canReply = state.canWrite && !isSelectionMode,
canReply = state.canWrite && !isSelectionMode && (!isTopicClosed || state.isAdmin),
onReplySwipe = { component.onReplyMessage(it) },
swipeEnabled = !isSelectionMode,
downloadUtils = downloadUtils,
Expand Down Expand Up @@ -874,7 +877,8 @@ private fun RootMessageSection(
onRetractVote = { component.onRetractVote(it) },
onShowVoters = { id, opt -> component.onShowVoters(id, opt) },
onClosePoll = { component.onClosePoll(it) },
toProfile = toProfile, swipeEnabled = false,
toProfile = toProfile,
swipeEnabled = false,
onViaBotClick = onViaBotClick,
onInstantViewClick = { component.onOpenInstantView(it) },
onYouTubeClick = { component.onOpenYouTube(it) },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.monogram.presentation.features.chats.currentChat.components

import android.content.res.Configuration
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
Expand All @@ -14,6 +15,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
Expand Down Expand Up @@ -97,11 +99,21 @@ fun AlbumMessageBubbleContainer(
var bubblePosition by remember { mutableStateOf(Offset.Zero) }
var bubbleSize by remember { mutableStateOf(IntSize.Zero) }

val dragOffsetX = remember { Animatable(0f) }

Column(
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { outerColumnPosition = it.positionInWindow() }
.padding(top = topSpacing, bottom = 2.dp)
.offset { IntOffset(dragOffsetX.value.toInt(), 0) }
.fastReplyPointer(
canReply = canReply,
dragOffsetX = dragOffsetX,
scope = rememberCoroutineScope(),
onReplySwipe = { onReplySwipe(messages.first()) },
maxWidth = maxWidth.value
)
.pointerInput(Unit) {
detectTapGestures(
onTap = { offset ->
Expand Down Expand Up @@ -137,109 +149,121 @@ fun AlbumMessageBubbleContainer(
Spacer(modifier = Modifier.width(8.dp))
}

Column(
modifier = Modifier
.then(if (isChannel) Modifier.padding(horizontal = 8.dp) else Modifier)
.widthIn(max = maxWidth)
.then(if (isChannel) Modifier.fillMaxWidth() else Modifier)
.onGloballyPositioned { coordinates ->
bubblePosition = coordinates.positionInWindow()
bubbleSize = coordinates.size
if (shouldReportPosition) {
onPositionChange(lastMsg.id, bubblePosition, bubbleSize)
Box(
modifier = Modifier.wrapContentSize()
) {
Column(
modifier = Modifier
.then(if (isChannel) Modifier.padding(horizontal = 8.dp) else Modifier)
.widthIn(max = maxWidth)
.then(if (isChannel) Modifier.fillMaxWidth() else Modifier)
.onGloballyPositioned { coordinates ->
bubblePosition = coordinates.positionInWindow()
bubbleSize = coordinates.size
if (shouldReportPosition) {
onPositionChange(lastMsg.id, bubblePosition, bubbleSize)
}
}
) {
if (isGroup && !isOutgoing && !isChannel) {
Text(
text = firstMsg.senderName,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 12.dp, bottom = 4.dp)
)
}
) {
if (isGroup && !isOutgoing && !isChannel) {
Text(
text = firstMsg.senderName,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 12.dp, bottom = 4.dp)
)
}

if (isChannel) {
ChannelAlbumMessageBubble(
messages = messages,
isSameSenderAbove = isSameSenderAbove,
isSameSenderBelow = isSameSenderBelow,
autoplayGifs = autoplayGifs,
autoplayVideos = autoplayVideos,
autoDownloadMobile = autoDownloadMobile,
autoDownloadWifi = autoDownloadWifi,
autoDownloadRoaming = autoDownloadRoaming,
onPhotoClick = onPhotoClick,
onDownloadPhoto = onDownloadPhoto,
onVideoClick = onVideoClick,
onDocumentClick = onDocumentClick,
onAudioClick = onAudioClick,
onCancelDownload = onCancelDownload,
onLongClick = { offset ->
onReplyClick(
bubblePosition,
bubbleSize,
bubblePosition + offset
)
},
onReplyClick = onGoToReply,
onReactionClick = { onReactionClick(lastMsg.id, it) },
onCommentsClick = onCommentsClick,
showComments = showComments,
toProfile = toProfile,
modifier = Modifier.fillMaxWidth(),
fontSize = fontSize,
bubbleRadius = bubbleRadius,
downloadUtils = downloadUtils,
isAnyViewerOpen = isAnyViewerOpen
)
} else {
ChatAlbumMessageBubble(
messages = messages,
isOutgoing = isOutgoing,
isGroup = isGroup,
isSameSenderAbove = isSameSenderAbove,
isSameSenderBelow = isSameSenderBelow,
autoplayGifs = autoplayGifs,
autoplayVideos = autoplayVideos,
autoDownloadMobile = autoDownloadMobile,
autoDownloadWifi = autoDownloadWifi,
autoDownloadRoaming = autoDownloadRoaming,
onPhotoClick = onPhotoClick,
onDownloadPhoto = onDownloadPhoto,
onVideoClick = onVideoClick,
onDocumentClick = onDocumentClick,
onAudioClick = onAudioClick,
onCancelDownload = onCancelDownload,
onLongClick = { offset ->
onReplyClick(
bubblePosition,
bubbleSize,
bubblePosition + offset
)
},
onReplyClick = onGoToReply,
onReactionClick = { onReactionClick(lastMsg.id, it) },
toProfile = toProfile,
modifier = Modifier,
fontSize = fontSize,
downloadUtils = downloadUtils,
isAnyViewerOpen = isAnyViewerOpen
)
}
if (isChannel) {
ChannelAlbumMessageBubble(
messages = messages,
isSameSenderAbove = isSameSenderAbove,
isSameSenderBelow = isSameSenderBelow,
autoplayGifs = autoplayGifs,
autoplayVideos = autoplayVideos,
autoDownloadMobile = autoDownloadMobile,
autoDownloadWifi = autoDownloadWifi,
autoDownloadRoaming = autoDownloadRoaming,
onPhotoClick = onPhotoClick,
onDownloadPhoto = onDownloadPhoto,
onVideoClick = onVideoClick,
onDocumentClick = onDocumentClick,
onAudioClick = onAudioClick,
onCancelDownload = onCancelDownload,
onLongClick = { offset ->
onReplyClick(
bubblePosition,
bubbleSize,
bubblePosition + offset
)
},
onReplyClick = onGoToReply,
onReactionClick = { onReactionClick(lastMsg.id, it) },
onCommentsClick = onCommentsClick,
showComments = showComments,
toProfile = toProfile,
modifier = Modifier.fillMaxWidth(),
fontSize = fontSize,
bubbleRadius = bubbleRadius,
downloadUtils = downloadUtils,
isAnyViewerOpen = isAnyViewerOpen
)
} else {
ChatAlbumMessageBubble(
messages = messages,
isOutgoing = isOutgoing,
isGroup = isGroup,
isSameSenderAbove = isSameSenderAbove,
isSameSenderBelow = isSameSenderBelow,
autoplayGifs = autoplayGifs,
autoplayVideos = autoplayVideos,
autoDownloadMobile = autoDownloadMobile,
autoDownloadWifi = autoDownloadWifi,
autoDownloadRoaming = autoDownloadRoaming,
onPhotoClick = onPhotoClick,
onDownloadPhoto = onDownloadPhoto,
onVideoClick = onVideoClick,
onDocumentClick = onDocumentClick,
onAudioClick = onAudioClick,
onCancelDownload = onCancelDownload,
onLongClick = { offset ->
onReplyClick(
bubblePosition,
bubbleSize,
bubblePosition + offset
)
},
onReplyClick = onGoToReply,
onReactionClick = { onReactionClick(lastMsg.id, it) },
toProfile = toProfile,
modifier = Modifier,
fontSize = fontSize,
downloadUtils = downloadUtils,
isAnyViewerOpen = isAnyViewerOpen
)
}

lastMsg.replyMarkup?.let { markup ->
ReplyMarkupView(
replyMarkup = markup,
onButtonClick = { onReplyMarkupButtonClick(lastMsg.id, it) }
lastMsg.replyMarkup?.let { markup ->
ReplyMarkupView(
replyMarkup = markup,
onButtonClick = { onReplyMarkupButtonClick(lastMsg.id, it) }
)
}

MessageViaBotAttribution(
msg = lastMsg,
isOutgoing = isOutgoing,
onViaBotClick = onViaBotClick,
modifier = Modifier.align(if (isOutgoing) Alignment.End else Alignment.Start)
)
}

MessageViaBotAttribution(
msg = lastMsg,
FastReplyIndicator(
modifier = Modifier
.align(if (isOutgoing) Alignment.CenterEnd else Alignment.CenterStart),
dragOffsetX = dragOffsetX,
isOutgoing = isOutgoing,
onViaBotClick = onViaBotClick,
modifier = Modifier.align(if (isOutgoing) Alignment.End else Alignment.Start)
maxWidth = maxWidth,
)
}
}
Expand Down
Loading