mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3115 Chats | Fix | Read-only state for chat box (#2465)
This commit is contained in:
parent
e130fb5602
commit
2baf2e1204
6 changed files with 160 additions and 65 deletions
|
@ -32,6 +32,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
|
|||
import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer.Store
|
||||
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
|
||||
import com.anytypeio.anytype.domain.objects.CreateObjectFromUrl
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.getTypeOfObject
|
||||
|
@ -83,7 +84,8 @@ class ChatViewModel @Inject constructor(
|
|||
private val exitToVaultDelegate: ExitToVaultDelegate,
|
||||
private val getLinkPreview: GetLinkPreview,
|
||||
private val createObjectFromUrl: CreateObjectFromUrl,
|
||||
private val notificationPermissionManager: NotificationPermissionManager
|
||||
private val notificationPermissionManager: NotificationPermissionManager,
|
||||
private val spacePermissionProvider: UserPermissionProvider
|
||||
) : BaseViewModel(), ExitToVaultDelegate by exitToVaultDelegate {
|
||||
|
||||
private val visibleRangeUpdates = MutableSharedFlow<Pair<Id, Id>>(
|
||||
|
@ -110,6 +112,20 @@ class ChatViewModel @Inject constructor(
|
|||
|
||||
// generateDummyChatHistory()
|
||||
|
||||
viewModelScope.launch {
|
||||
spacePermissionProvider
|
||||
.observe(vmParams.space)
|
||||
.collect { permission ->
|
||||
if (permission?.isOwnerOrEditor() == true) {
|
||||
if (chatBoxMode.value is ChatBoxMode.ReadOnly) {
|
||||
chatBoxMode.value = ChatBoxMode.Default()
|
||||
}
|
||||
} else {
|
||||
chatBoxMode.value = ChatBoxMode.ReadOnly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
spaceViews
|
||||
.observe(
|
||||
|
@ -663,6 +679,9 @@ class ChatViewModel @Inject constructor(
|
|||
chatBoxAttachments.value = emptyList()
|
||||
chatBoxMode.value = ChatBoxMode.Default()
|
||||
}
|
||||
is ChatBoxMode.ReadOnly -> {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1150,6 +1169,9 @@ class ChatViewModel @Inject constructor(
|
|||
|
||||
abstract val isSendingMessageBlocked: Boolean
|
||||
|
||||
data object ReadOnly : ChatBoxMode() {
|
||||
override val isSendingMessageBlocked: Boolean = true
|
||||
}
|
||||
data class Default(
|
||||
override val isSendingMessageBlocked: Boolean = false
|
||||
) : ChatBoxMode()
|
||||
|
@ -1170,6 +1192,7 @@ class ChatViewModel @Inject constructor(
|
|||
is ChatBoxMode.Default -> copy(isSendingMessageBlocked = isBlocked)
|
||||
is ChatBoxMode.EditMessage -> copy(isSendingMessageBlocked = isBlocked)
|
||||
is ChatBoxMode.Reply -> copy(isSendingMessageBlocked = isBlocked)
|
||||
is ChatBoxMode.ReadOnly -> this
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.anytypeio.anytype.domain.misc.GetLinkPreview
|
|||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
|
||||
import com.anytypeio.anytype.domain.`object`.OpenObject
|
||||
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
|
||||
import com.anytypeio.anytype.domain.objects.CreateObjectFromUrl
|
||||
|
@ -41,7 +42,8 @@ class ChatViewModelFactory @Inject constructor(
|
|||
private val exitToVaultDelegate: ExitToVaultDelegate,
|
||||
private val getLinkPreview: GetLinkPreview,
|
||||
private val createObjectFromUrl: CreateObjectFromUrl,
|
||||
private val notificationPermissionManager: NotificationPermissionManager
|
||||
private val notificationPermissionManager: NotificationPermissionManager,
|
||||
private val spacePermissionProvider: UserPermissionProvider
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = ChatViewModel(
|
||||
|
@ -62,6 +64,7 @@ class ChatViewModelFactory @Inject constructor(
|
|||
exitToVaultDelegate = exitToVaultDelegate,
|
||||
getLinkPreview = getLinkPreview,
|
||||
createObjectFromUrl = createObjectFromUrl,
|
||||
notificationPermissionManager = notificationPermissionManager
|
||||
notificationPermissionManager = notificationPermissionManager,
|
||||
spacePermissionProvider = spacePermissionProvider
|
||||
) as T
|
||||
}
|
|
@ -46,6 +46,7 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.colorResource
|
||||
|
@ -63,6 +64,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.Url
|
||||
import com.anytypeio.anytype.core_models.primitives.Space
|
||||
import com.anytypeio.anytype.core_ui.common.DEFAULT_DISABLED_ALPHA
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.common.FULL_ALPHA
|
||||
|
@ -149,10 +151,13 @@ fun ChatBox(
|
|||
)
|
||||
when(mode) {
|
||||
is ChatBoxMode.Default -> {
|
||||
|
||||
// Do nothing
|
||||
}
|
||||
is ChatBoxMode.EditMessage -> {
|
||||
|
||||
// Do nothing
|
||||
}
|
||||
is ChatBoxMode.ReadOnly -> {
|
||||
// Do nothing
|
||||
}
|
||||
is ChatBoxMode.Reply -> {
|
||||
Box(
|
||||
|
@ -884,6 +889,41 @@ fun ChatBoxEditPanel(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReaderChatBox(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = colorResource(R.color.navigation_panel),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_chatbox_lock),
|
||||
contentDescription = "Lock icon"
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
text = "Only editors can send messages. Contact the owner to request access.",
|
||||
style = Caption1Regular,
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun ReaderChatBoxPreview() {
|
||||
ReaderChatBox()
|
||||
}
|
||||
|
||||
|
||||
sealed class ChatMarkupEvent {
|
||||
data object Bold : ChatMarkupEvent()
|
||||
data object Italic : ChatMarkupEvent()
|
||||
|
|
|
@ -94,7 +94,8 @@ fun Bubble(
|
|||
onScrollToReplyClicked: (ChatView.Message.Reply) -> Unit,
|
||||
onAddReactionClicked: () -> Unit,
|
||||
onViewChatReaction: (String) -> Unit,
|
||||
onMentionClicked: (Id) -> Unit
|
||||
onMentionClicked: (Id) -> Unit,
|
||||
isReadOnly: Boolean = false
|
||||
) {
|
||||
var showDropdownMenu by remember { mutableStateOf(false) }
|
||||
var showDeleteMessageWarning by remember { mutableStateOf(false) }
|
||||
|
@ -318,19 +319,21 @@ fun Bubble(
|
|||
)
|
||||
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
|
||||
}
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.chats_reply),
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.padding(end = 64.dp)
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
onReply()
|
||||
showDropdownMenu = false
|
||||
}
|
||||
)
|
||||
if (!isReadOnly) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.chats_reply),
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.padding(end = 64.dp)
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
onReply()
|
||||
showDropdownMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
if (content.msg.isNotEmpty()) {
|
||||
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
|
||||
DropdownMenuItem(
|
||||
|
|
|
@ -228,7 +228,10 @@ fun ChatScreenWrapper(
|
|||
onScrollToBottomClicked = vm::onScrollToBottomClicked,
|
||||
onVisibleRangeChanged = vm::onVisibleRangeChanged,
|
||||
onUrlInserted = vm::onUrlPasted,
|
||||
onGoToMentionClicked = vm::onGoToMentionClicked
|
||||
onGoToMentionClicked = vm::onGoToMentionClicked,
|
||||
isReadOnly = vm.chatBoxMode
|
||||
.collectAsStateWithLifecycle()
|
||||
.value is ChatBoxMode.ReadOnly
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
vm.uXCommands.collect { command ->
|
||||
|
@ -342,7 +345,8 @@ fun ChatScreen(
|
|||
onScrollToBottomClicked: (Id?) -> Unit,
|
||||
onVisibleRangeChanged: (Id, Id) -> Unit,
|
||||
onUrlInserted: (Url) -> Unit,
|
||||
onGoToMentionClicked: () -> Unit
|
||||
onGoToMentionClicked: () -> Unit,
|
||||
isReadOnly: Boolean = false
|
||||
) {
|
||||
|
||||
Timber.d("DROID-2966 Render called with state, number of messages: ${messages.size}")
|
||||
|
@ -518,7 +522,8 @@ fun ChatScreen(
|
|||
onViewChatReaction = onViewChatReaction,
|
||||
onMemberIconClicked = onMemberIconClicked,
|
||||
onMentionClicked = onMentionClicked,
|
||||
onScrollToReplyClicked = onScrollToReplyClicked
|
||||
onScrollToReplyClicked = onScrollToReplyClicked,
|
||||
isReadOnly = isReadOnly
|
||||
)
|
||||
|
||||
GoToMentionButton(
|
||||
|
@ -694,52 +699,61 @@ fun ChatScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
ChatBox(
|
||||
mode = chatBoxMode,
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.navigationBarsPadding(),
|
||||
chatBoxFocusRequester = chatBoxFocusRequester,
|
||||
onMessageSent = { text, markup ->
|
||||
onMessageSent(text, markup)
|
||||
},
|
||||
resetScroll = {
|
||||
if (!isPerformingScrollIntent.value) {
|
||||
scope.launch {
|
||||
lazyListState.scrollToItem(0)
|
||||
awaitFrame()
|
||||
while (!isAtBottom) {
|
||||
val offset = lazyListState.firstVisibleItemScrollOffset
|
||||
val delta = (-offset).coerceAtLeast(-80)
|
||||
lazyListState.animateScrollBy(delta.toFloat())
|
||||
|
||||
if (isReadOnly) {
|
||||
ReaderChatBox(
|
||||
modifier = Modifier
|
||||
.padding(start = 20.dp, end = 20.dp, bottom = 12.dp)
|
||||
.navigationBarsPadding()
|
||||
)
|
||||
} else {
|
||||
ChatBox(
|
||||
mode = chatBoxMode,
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.navigationBarsPadding(),
|
||||
chatBoxFocusRequester = chatBoxFocusRequester,
|
||||
onMessageSent = { text, markup ->
|
||||
onMessageSent(text, markup)
|
||||
},
|
||||
resetScroll = {
|
||||
if (!isPerformingScrollIntent.value) {
|
||||
scope.launch {
|
||||
lazyListState.scrollToItem(0)
|
||||
awaitFrame()
|
||||
while (!isAtBottom) {
|
||||
val offset = lazyListState.firstVisibleItemScrollOffset
|
||||
val delta = (-offset).coerceAtLeast(-80)
|
||||
lazyListState.animateScrollBy(delta.toFloat())
|
||||
awaitFrame()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
attachments = attachments,
|
||||
clearText = {
|
||||
text = TextFieldValue()
|
||||
},
|
||||
onAttachObjectClicked = onAttachObjectClicked,
|
||||
onClearAttachmentClicked = onClearAttachmentClicked,
|
||||
onClearReplyClicked = onClearReplyClicked,
|
||||
onChatBoxMediaPicked = onChatBoxMediaPicked,
|
||||
onChatBoxFilePicked = onChatBoxFilePicked,
|
||||
onExitEditMessageMode = {
|
||||
onExitEditMessageMode().also {
|
||||
},
|
||||
attachments = attachments,
|
||||
clearText = {
|
||||
text = TextFieldValue()
|
||||
}
|
||||
},
|
||||
onValueChange = { t, s ->
|
||||
text = t
|
||||
spans = s
|
||||
onTextChanged(t)
|
||||
},
|
||||
text = text,
|
||||
spans = spans,
|
||||
onUrlInserted = onUrlInserted
|
||||
)
|
||||
},
|
||||
onAttachObjectClicked = onAttachObjectClicked,
|
||||
onClearAttachmentClicked = onClearAttachmentClicked,
|
||||
onClearReplyClicked = onClearReplyClicked,
|
||||
onChatBoxMediaPicked = onChatBoxMediaPicked,
|
||||
onChatBoxFilePicked = onChatBoxFilePicked,
|
||||
onExitEditMessageMode = {
|
||||
onExitEditMessageMode().also {
|
||||
text = TextFieldValue()
|
||||
}
|
||||
},
|
||||
onValueChange = { t, s ->
|
||||
text = t
|
||||
spans = s
|
||||
onTextChanged(t)
|
||||
},
|
||||
text = text,
|
||||
spans = spans,
|
||||
onUrlInserted = onUrlInserted
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -760,6 +774,7 @@ fun Messages(
|
|||
onMemberIconClicked: (Id?) -> Unit,
|
||||
onMentionClicked: (Id) -> Unit,
|
||||
onScrollToReplyClicked: (Id) -> Unit,
|
||||
isReadOnly: Boolean = false
|
||||
) {
|
||||
// Timber.d("DROID-2966 Messages composition: ${messages.map { if (it is ChatView.Message) it.content.msg else it }}")
|
||||
val scope = rememberCoroutineScope()
|
||||
|
@ -848,7 +863,8 @@ fun Messages(
|
|||
onViewChatReaction = { emoji ->
|
||||
onViewChatReaction(msg.id, emoji)
|
||||
},
|
||||
onMentionClicked = onMentionClicked
|
||||
onMentionClicked = onMentionClicked,
|
||||
isReadOnly = isReadOnly
|
||||
)
|
||||
}
|
||||
if (idx == messages.lastIndex) {
|
||||
|
|
10
feature-chats/src/main/res/drawable/ic_chatbox_lock.xml
Normal file
10
feature-chats/src/main/res/drawable/ic_chatbox_lock.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="18"
|
||||
android:viewportHeight="18">
|
||||
<path
|
||||
android:pathData="M9,3.75C10.243,3.75 11.25,4.757 11.25,6V8H6.75V6C6.75,4.757 7.757,3.75 9,3.75ZM5.5,8L5.5,6C5.5,4.067 7.067,2.5 9,2.5C10.933,2.5 12.5,4.067 12.5,6V8C13.328,8 14,8.672 14,9.5V14C14,14.828 13.328,15.5 12.5,15.5H5.5C4.672,15.5 4,14.828 4,14V9.5C4,8.672 4.672,8 5.5,8Z"
|
||||
android:fillColor="@color/glyph_button"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
Loading…
Add table
Add a link
Reference in a new issue