diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/chats/ChatsDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/chats/ChatsDI.kt index 0d93614809..4ce07835e0 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/chats/ChatsDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/chats/ChatsDI.kt @@ -15,6 +15,10 @@ import com.anytypeio.anytype.domain.debugging.Logger import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer +import com.anytypeio.anytype.domain.multiplayer.GenerateSpaceInviteLink +import com.anytypeio.anytype.domain.multiplayer.GetSpaceInviteLink +import com.anytypeio.anytype.domain.multiplayer.MakeSpaceShareable +import com.anytypeio.anytype.domain.multiplayer.RevokeSpaceInviteLink import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.notifications.NotificationBuilder @@ -105,4 +109,8 @@ interface ChatComponentDependencies : ComponentDependencies { fun notificationPermissionManager(): NotificationPermissionManager fun storelessSubscriptionContainer(): StorelessSubscriptionContainer fun notificationBuilder(): NotificationBuilder + fun generateSpaceInviteLink(): GenerateSpaceInviteLink + fun makeSpaceShareable(): MakeSpaceShareable + fun getSpaceInviteLink(): GetSpaceInviteLink + fun revokeSpaceInviteLink(): RevokeSpaceInviteLink } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/chats/ChatFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/chats/ChatFragment.kt index 83813a701c..8c560427e5 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/chats/ChatFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/chats/ChatFragment.kt @@ -1,6 +1,8 @@ package com.anytypeio.anytype.ui.chats import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -8,9 +10,11 @@ import android.view.ViewGroup import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding @@ -20,11 +24,14 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.colorResource @@ -42,6 +49,8 @@ import com.anytypeio.anytype.core_utils.ext.toast import com.anytypeio.anytype.core_utils.intents.SystemAction.OpenUrl import com.anytypeio.anytype.core_utils.intents.proceedWithAction import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment +import com.anytypeio.anytype.core_ui.features.multiplayer.GenerateInviteLinkCard +import com.anytypeio.anytype.core_ui.features.multiplayer.ShareInviteLinkCard import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.ext.daggerViewModel import com.anytypeio.anytype.feature_chats.presentation.ChatViewModel @@ -59,6 +68,9 @@ import com.anytypeio.anytype.ui.profile.ParticipantFragment import com.anytypeio.anytype.ui.search.GlobalSearchScreen import com.anytypeio.anytype.ui.sets.ObjectSetFragment import com.anytypeio.anytype.ui.settings.typography +import com.anytypeio.anytype.presentation.multiplayer.ShareSpaceViewModel.ShareLinkViewState +import com.anytypeio.anytype.ui.multiplayer.DeleteSpaceInviteLinkWarning +import com.anytypeio.anytype.core_ui.views.BaseAlertDialog import javax.inject.Inject import timber.log.Timber @@ -88,8 +100,13 @@ class ChatFragment : BaseComposeFragment() { val notificationsSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) var showGlobalSearchBottomSheet by remember { mutableStateOf(false) } + val inviteModalState = vm.inviteModalState.collectAsStateWithLifecycle().value val showNotificationPermissionDialog = vm.showNotificationPermissionDialog.collectAsStateWithLifecycle().value + val canCreateInviteLink = vm.canCreateInviteLink.collectAsStateWithLifecycle().value + val isGeneratingInviteLink = vm.isGeneratingInviteLink.collectAsStateWithLifecycle().value + + ErrorScreen() Column( modifier = Modifier @@ -181,6 +198,65 @@ class ChatFragment : BaseComposeFragment() { } else { componentManager().globalSearchComponent.release() } + + when (inviteModalState) { + is ChatViewModel.InviteModalState.ShowShareCard -> { + ModalBottomSheet( + onDismissRequest = { + vm.onInviteModalDismissed() + }, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + containerColor = Color.Transparent, + contentColor = Color.Transparent, + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), + dragHandle = null + ) { + ShareInviteLinkCard( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .background( + shape = RoundedCornerShape(16.dp), + color = colorResource(id = R.color.widget_background) + ), + link = inviteModalState.link, + isCurrentUserOwner = canCreateInviteLink, + onShareInviteClicked = { vm.onShareInviteLinkFromCardClicked() }, + onDeleteLinkClicked = { vm.onDeleteLinkClicked() }, + onShowQrCodeClicked = { vm.onShareQrCodeClicked() } + ) + } + } + is ChatViewModel.InviteModalState.ShowGenerateCard -> { + ModalBottomSheet( + onDismissRequest = { + vm.onInviteModalDismissed() + }, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + containerColor = Color.Transparent, + contentColor = Color.Transparent, + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), + dragHandle = null + ) { + GenerateInviteLinkCard( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .background( + shape = RoundedCornerShape(16.dp), + color = colorResource(id = R.color.widget_background) + ), + onGenerateInviteLinkClicked = { + vm.onGenerateInviteLinkClicked() + }, + isLoading = isGeneratingInviteLink + ) + } + } + ChatViewModel.InviteModalState.Hidden -> { + // No modal shown + } + } } LaunchedEffect(Unit) { vm.navigation.collect { nav -> @@ -303,6 +379,42 @@ class ChatFragment : BaseComposeFragment() { Timber.e(it, "Error while opening bookmark from chat") } } + is ChatViewModel.ViewModelCommand.ShareInviteLink -> { + runCatching { + val intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, command.link) + type = "text/plain" + } + startActivity(Intent.createChooser(intent, null)) + }.onFailure { + Timber.e(it, "Error while sharing invite link") + } + } + is ChatViewModel.ViewModelCommand.ShareQrCode -> { + runCatching { + Timber.d("ShareQrCode command received with link: ${command.link}") + toast("QR Code sharing - to be implemented") + }.onFailure { + Timber.e(it, "Error while opening QR code") + } + } + is ChatViewModel.ViewModelCommand.ShowDeleteLinkWarning -> { + runCatching { + val dialog = DeleteSpaceInviteLinkWarning() + dialog.onAccepted = { + vm.onDeleteLinkAccepted().also { + dialog.dismiss() + } + } + dialog.onCancelled = { + // Do nothing. + } + dialog.show(childFragmentManager, null) + }.onFailure { + Timber.e(it, "Error while showing delete link warning") + } + } } } } @@ -346,6 +458,25 @@ class ChatFragment : BaseComposeFragment() { // Do not apply. } + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun ErrorScreen() { + val errorStateScreen = vm.errorState.collectAsStateWithLifecycle() + when (val state = errorStateScreen.value) { + ChatViewModel.UiErrorState.Hidden -> { + // Do nothing + } + is ChatViewModel.UiErrorState.Show -> { + BaseAlertDialog( + dialogText = state.msg, + buttonText = stringResource(id = R.string.membership_error_button_text_dismiss), + onButtonClick = vm::hideError, + onDismissRequest = vm::hideError + ) + } + } + } + companion object { private const val CTX_KEY = "arg.discussion.ctx" private const val SPACE_KEY = "arg.discussion.space" diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index 92fb02276d..b63dc2cd65 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -172,7 +172,9 @@ android:label="Chat" > + app:destination="@id/homeScreen" + app:popUpTo="@id/vaultScreen" + app:popUpToInclusive="false" /> Unit + onGenerateInviteLinkClicked: () -> Unit, + isLoading: Boolean = false ) { ElevatedCard( modifier = modifier, @@ -240,13 +243,14 @@ fun GenerateInviteLinkCard( .fillMaxWidth() .padding(horizontal = 20.dp) ) { - ButtonPrimary( + ButtonPrimaryLoading( text = stringResource(R.string.multiplayer_generate_invite_link), onClick = onGenerateInviteLinkClicked, size = ButtonSize.Large, - modifier = Modifier + modifierButton = Modifier .fillMaxWidth() - .height(48.dp) + .height(48.dp), + loading = isLoading ) } Spacer(modifier = Modifier.height(20.dp)) diff --git a/core-ui/src/main/res/values-night/colors.xml b/core-ui/src/main/res/values-night/colors.xml index b6c94854d0..39d758072a 100644 --- a/core-ui/src/main/res/values-night/colors.xml +++ b/core-ui/src/main/res/values-night/colors.xml @@ -26,6 +26,7 @@ #29FFFFFF #66FFFFFF + #26FFFFFF #FFFFFF #7B7B7B diff --git a/core-ui/src/main/res/values/colors.xml b/core-ui/src/main/res/values/colors.xml index 6c90bd1246..970d3c5730 100644 --- a/core-ui/src/main/res/values/colors.xml +++ b/core-ui/src/main/res/values/colors.xml @@ -186,6 +186,7 @@ #0D000000 #00000000 #66000000 + #26000000 #000000 #F3F2EC #DFDDD0 diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/chats/ChatContainerTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/chats/ChatContainerTest.kt index 3a62181392..9c9d8cd96c 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/chats/ChatContainerTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/chats/ChatContainerTest.kt @@ -483,7 +483,10 @@ class ChatContainerTest { ) assertEquals( - expected = ChatContainer.Intent.ScrollToMessage(id = "90"), + expected = ChatContainer.Intent.ScrollToMessage( + id = "90", + startOfUnreadMessageSection = true + ), actual = initial.intent, ) diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt index 875c2e71a9..8806396edb 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModel.kt @@ -12,6 +12,10 @@ import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.core_models.chats.Chat import com.anytypeio.anytype.core_models.ext.EMPTY_STRING_VALUE +import com.anytypeio.anytype.core_models.multiplayer.InviteType +import com.anytypeio.anytype.core_models.multiplayer.SpaceAccessType +import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions +import com.anytypeio.anytype.core_models.multiplayer.SpaceUxType import com.anytypeio.anytype.core_models.primitives.Space import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_ui.text.splitByMarks @@ -19,6 +23,8 @@ import com.anytypeio.anytype.core_utils.common.DefaultFileInfo import com.anytypeio.anytype.core_utils.tools.DEFAULT_URL_REGEX import com.anytypeio.anytype.domain.auth.interactor.GetAccount import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.base.getOrThrow import com.anytypeio.anytype.domain.base.onFailure import com.anytypeio.anytype.domain.base.onSuccess import com.anytypeio.anytype.domain.chats.AddChatMessage @@ -31,6 +37,10 @@ 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.ActiveSpaceMemberSubscriptionContainer.Store +import com.anytypeio.anytype.domain.multiplayer.GenerateSpaceInviteLink +import com.anytypeio.anytype.domain.multiplayer.GetSpaceInviteLink +import com.anytypeio.anytype.domain.multiplayer.MakeSpaceShareable +import com.anytypeio.anytype.domain.multiplayer.RevokeSpaceInviteLink import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.notifications.NotificationBuilder @@ -66,6 +76,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber +import com.anytypeio.anytype.presentation.multiplayer.ShareSpaceViewModel.ShareLinkViewState class ChatViewModel @Inject constructor( private val vmParams: Params.Default, @@ -87,7 +98,11 @@ class ChatViewModel @Inject constructor( private val createObjectFromUrl: CreateObjectFromUrl, private val notificationPermissionManager: NotificationPermissionManager, private val spacePermissionProvider: UserPermissionProvider, - private val notificationBuilder: NotificationBuilder + private val notificationBuilder: NotificationBuilder, + private val generateSpaceInviteLink: GenerateSpaceInviteLink, + private val makeSpaceShareable: MakeSpaceShareable, + private val getSpaceInviteLink: GetSpaceInviteLink, + private val revokeSpaceInviteLink: RevokeSpaceInviteLink ) : BaseViewModel(), ExitToVaultDelegate by exitToVaultDelegate { private val visibleRangeUpdates = MutableSharedFlow>( @@ -106,6 +121,10 @@ class ChatViewModel @Inject constructor( val mentionPanelState = MutableStateFlow(MentionPanelState.Hidden) val showNotificationPermissionDialog = MutableStateFlow(false) val canCreateInviteLink = MutableStateFlow(false) + val inviteModalState = MutableStateFlow(InviteModalState.Hidden) + val isGeneratingInviteLink = MutableStateFlow(false) + val spaceAccessType = MutableStateFlow(null) + val errorState = MutableStateFlow(UiErrorState.Hidden) private val dateFormatter = SimpleDateFormat("d MMMM YYYY") private val messageRateLimiter = MessageRateLimiter() @@ -150,6 +169,28 @@ class ChatViewModel @Inject constructor( } } + // Check if we should show invite modal for newly created Chat spaces + viewModelScope.launch { + combine( + spaceViews.observe(vmParams.space), + canCreateInviteLink, + uiState + ) { spaceView, canCreateInvite, chatState -> + // Show invite modal if: + // 1. It's a Chat space (spaceUxType == SpaceUxType.CHAT) + // 2. User can create invite links (is owner) + // 3. Chat is empty (no messages) - indicates it's newly created + spaceView.spaceUxType == SpaceUxType.CHAT && + canCreateInvite && + chatState.messages.isEmpty() + }.collect { shouldShow -> + Timber.d("DROID-3626 Should show invite modal: $shouldShow") + if (shouldShow) { + inviteModalState.value = InviteModalState.ShowGenerateCard + } + } + } + viewModelScope.launch { visibleRangeUpdates .distinctUntilChanged() @@ -172,6 +213,8 @@ class ChatViewModel @Inject constructor( chat = vmParams.ctx ) } + + proceedWithSpaceSubscription() } fun onResume() { @@ -1059,6 +1102,159 @@ class ChatViewModel @Inject constructor( } } + fun onInviteModalDismissed() { + inviteModalState.value = InviteModalState.Hidden + } + + fun onGenerateInviteLinkClicked() { + viewModelScope.launch { + isGeneratingInviteLink.value = true + proceedWithGeneratingInviteLink() + } + } + + private suspend fun proceedWithGeneratingInviteLink( + inviteType: InviteType = InviteType.MEMBER, + permissions: SpaceMemberPermissions = SpaceMemberPermissions.READER + ) { + if (spaceAccessType.value == SpaceAccessType.PRIVATE) { + makeSpaceShareable.async( + params = vmParams.space + ).fold( + onSuccess = { + Timber.d("Successfully made space shareable") + generateInviteLink( + inviteType = inviteType, + permissions = permissions + ) + }, + onFailure = { error -> + Timber.e(error, "Error while making space shareable") + isGeneratingInviteLink.value = false + inviteModalState.value = InviteModalState.Hidden + errorState.value = UiErrorState.Show( + "Failed to make space shareable. Please try again." + ) + } + ) + } else { + generateInviteLink( + inviteType = inviteType, + permissions = permissions + ) + } + } + + private suspend fun generateInviteLink( + inviteType: InviteType, + permissions: SpaceMemberPermissions + ) { + generateSpaceInviteLink.async( + params = GenerateSpaceInviteLink.Params( + space = vmParams.space, + inviteType = inviteType, + permissions = permissions + ) + ).fold( + onSuccess = { inviteLink -> + Timber.d("Successfully generated invite link: ${inviteLink.scheme}") + isGeneratingInviteLink.value = false + inviteModalState.value = InviteModalState.ShowShareCard(inviteLink.scheme) + }, + onFailure = { error -> + Timber.e(error, "Error while generating invite link") + isGeneratingInviteLink.value = false + inviteModalState.value = InviteModalState.Hidden + errorState.value = UiErrorState.Show( + "Failed to generate invite link. Please try again." + ) + } + ) + } + + fun onShareInviteLinkClicked() { + viewModelScope.launch { + // Check if we already have a link, if so show share card, otherwise show generate card + when (val currentState = inviteModalState.value) { + is InviteModalState.ShowShareCard -> { + // Already showing share card - do nothing or could toggle visibility + return@launch + } + else -> { + // Check if we have an existing link + val existingLink = getSpaceInviteLink.async(vmParams.space) + if (existingLink.isSuccess) { + inviteModalState.value = InviteModalState.ShowShareCard(existingLink.getOrThrow().scheme) + } else { + inviteModalState.value = InviteModalState.ShowGenerateCard + } + } + } + } + } + + fun onShareInviteLinkFromCardClicked() { + viewModelScope.launch { + when (val state = inviteModalState.value) { + is InviteModalState.ShowShareCard -> { + commands.emit(ViewModelCommand.ShareInviteLink(state.link)) + } + else -> { + Timber.w("Ignoring share invite click while in state: $state") + } + } + } + } + + fun onShareQrCodeClicked() { + viewModelScope.launch { + when (val state = inviteModalState.value) { + is InviteModalState.ShowShareCard -> { + commands.emit(ViewModelCommand.ShareQrCode(state.link)) + } + else -> { + Timber.w("Ignoring QR-code click while in state: $state") + } + } + } + } + + fun onDeleteLinkClicked() { + Timber.d("onDeleteLinkClicked") + viewModelScope.launch { + if (canCreateInviteLink.value) { + commands.emit(ViewModelCommand.ShowDeleteLinkWarning) + } else { + Timber.w("Something wrong with permissions.") + } + } + } + + fun onDeleteLinkAccepted() { + Timber.d("onDeleteLinkAccepted") + viewModelScope.launch { + if (canCreateInviteLink.value) { + revokeSpaceInviteLink.async( + params = vmParams.space + ).fold( + onSuccess = { + Timber.d("Revoked space invite link") + inviteModalState.value = InviteModalState.Hidden + }, + onFailure = { e -> + Timber.e(e, "Error while revoking space invite link") + inviteModalState.value = InviteModalState.Hidden + errorState.value = UiErrorState.Show( + "Failed to delete invite link. Please try again." + ) + } + ) + } else { + Timber.w("Something wrong with permissions.") + } + } + } + fun onMentionClicked(member: Id) { viewModelScope.launch { commands.emit( @@ -1234,6 +1430,25 @@ class ChatViewModel @Inject constructor( } } + fun hideError() { + errorState.value = UiErrorState.Hidden + } + + private fun proceedWithSpaceSubscription() { + viewModelScope.launch { + spaceViews.observe().collect { spaces -> + val space = spaces.firstOrNull { it.targetSpaceId == vmParams.space.id } + spaceAccessType.value = space?.spaceAccessType + } + } + } + + sealed class InviteModalState { + data object Hidden : InviteModalState() + data object ShowGenerateCard : InviteModalState() + data class ShowShareCard(val link: String) : InviteModalState() + } + data class ChatBoxMediaUri( val uri: String, val isVideo: Boolean = false @@ -1247,6 +1462,9 @@ class ChatViewModel @Inject constructor( data class SelectChatReaction(val msg: Id) : ViewModelCommand() data class ViewChatReaction(val msg: Id, val emoji: String) : ViewModelCommand() data class ViewMemberCard(val member: Id, val space: SpaceId) : ViewModelCommand() + data class ShareInviteLink(val link: String) : ViewModelCommand() + data class ShareQrCode(val link: String) : ViewModelCommand() + data object ShowDeleteLinkWarning : ViewModelCommand() } sealed class UXCommand { @@ -1314,6 +1532,11 @@ class ChatViewModel @Inject constructor( ) : HeaderView() } + sealed class UiErrorState { + data object Hidden : UiErrorState() + data class Show(val msg: String) : UiErrorState() + } + sealed class Params { abstract val space: Space data class Default( diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModelFactory.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModelFactory.kt index bfca3d0f3b..bd002308fd 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModelFactory.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/ChatViewModelFactory.kt @@ -13,6 +13,10 @@ import com.anytypeio.anytype.domain.media.UploadFile 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.GenerateSpaceInviteLink +import com.anytypeio.anytype.domain.multiplayer.GetSpaceInviteLink +import com.anytypeio.anytype.domain.multiplayer.MakeSpaceShareable +import com.anytypeio.anytype.domain.multiplayer.RevokeSpaceInviteLink import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.notifications.NotificationBuilder @@ -43,7 +47,11 @@ class ChatViewModelFactory @Inject constructor( private val createObjectFromUrl: CreateObjectFromUrl, private val notificationPermissionManager: NotificationPermissionManager, private val spacePermissionProvider: UserPermissionProvider, - private val notificationBuilder: NotificationBuilder + private val notificationBuilder: NotificationBuilder, + private val generateSpaceInviteLink: GenerateSpaceInviteLink, + private val makeSpaceShareable: MakeSpaceShareable, + private val getSpaceInviteLink: GetSpaceInviteLink, + private val revokeSpaceInviteLink: RevokeSpaceInviteLink ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = ChatViewModel( @@ -66,6 +74,10 @@ class ChatViewModelFactory @Inject constructor( createObjectFromUrl = createObjectFromUrl, notificationPermissionManager = notificationPermissionManager, spacePermissionProvider = spacePermissionProvider, - notificationBuilder = notificationBuilder + notificationBuilder = notificationBuilder, + generateSpaceInviteLink = generateSpaceInviteLink, + makeSpaceShareable = makeSpaceShareable, + getSpaceInviteLink = getSpaceInviteLink, + revokeSpaceInviteLink = revokeSpaceInviteLink ) as T } \ No newline at end of file diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt index 53cc9b673d..0c123338ab 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -247,7 +248,7 @@ fun ChatScreenWrapper( onVisibleRangeChanged = vm::onVisibleRangeChanged, onUrlInserted = vm::onUrlPasted, onGoToMentionClicked = vm::onGoToMentionClicked, - onShareInviteClicked = { /* TODO: implement share invite */ }, + onShareInviteClicked = vm::onShareInviteLinkClicked, canCreateInviteLink = vm.canCreateInviteLink.collectAsStateWithLifecycle().value, isReadOnly = vm.chatBoxMode .collectAsStateWithLifecycle() @@ -969,11 +970,11 @@ fun Messages( .padding(horizontal = 20.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - AlertIcon( - icon = AlertConfig.Icon( - gradient = GRADIENT_TYPE_BLUE, - icon = R.drawable.ic_alert_message - ) + Image( + modifier = Modifier.size(56.dp), + painter = painterResource(id = R.drawable.ic_vault_create_space), + contentDescription = "Empty state icon", + colorFilter = ColorFilter.tint(colorResource(id = R.color.transparent_inactive)) ) Text( text = stringResource(R.string.chat_empty_state_title),