Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -233,6 +233,13 @@ object EventsDictionary {
const val leaveSpace = "LeaveSpace"
const val approveLeaveRequest = "ApproveLeaveRequest"

// New sharing spaces events
const val clickShareSpaceNewLink = "ClickShareSpaceNewLink"
const val screenQr = "ScreenQr"
const val clickShareSpaceCopyLink = "ClickShareSpaceCopyLink"
const val clickJoinSpaceWithoutApproval = "ClickJoinSpaceWithoutApproval"
const val clickShareSpaceShareLink = "ClickShareSpaceShareLink"

//Version history
const val screenHistory = "ScreenHistory"
const val screenHistoryVersion = "ScreenHistoryVersion"
Expand Down Expand Up @@ -273,6 +280,27 @@ object EventsDictionary {
const val shareTypeShareQr = "ShareQr"
}

object ShareSpaceLinkTypes {
const val EDITOR = "Editor"
const val VIEWER = "Viewer"
const val MANUAL = "Manual"
}

object ScreenQrRoutes {
const val INVITE_LINK = "InviteLink"
const val SETTINGS_SPACE = "SettingsSpace"
}

object CopyLinkRoutes {
const val BUTTON = "Button"
const val MENU = "Menu"
}

object InviteRequestTypes {
const val APPROVAL = "Approval"
const val WITHOUT_APPROVAL = "WithoutApproval"
}

object SharingInviteRequest {
const val reader = "Read"
const val writer = "Write"
Expand Down
1 change: 1 addition & 0 deletions core-ui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies {
implementation project(':localization')
implementation project(':presentation')
implementation project(':library-emojifier')
implementation project(':analytics')

implementation libs.kotlinxSerializationJson
implementation libs.appcompat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.rememberAsyncImagePainter
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.multiplayer.ParticipantStatus
Expand Down Expand Up @@ -119,8 +120,8 @@ fun ShareSpaceScreen(
onInviteLinkAccessChangeCancel: () -> Unit,

onShareInviteLinkClicked: (String) -> Unit,
onCopyInviteLinkClicked: (String) -> Unit,
onShareQrCodeClicked: (String) -> Unit
onCopyInviteLinkClicked: (String, String) -> Unit,
onShareQrCodeClicked: (String, String) -> Unit
) {
val nestedScrollInteropConnection = rememberNestedScrollInteropConnection()
var showInviteLinkAccessSelector by remember(false) { mutableStateOf(false) }
Expand Down Expand Up @@ -736,9 +737,9 @@ fun SpaceMemberIcon(
fun InviteLinkDisplay(
modifier: Modifier = Modifier,
link: String,
onCopyClicked: (String) -> Unit,
onCopyClicked: (String, String) -> Unit,
onShareClicked: (String) -> Unit,
onQrCodeClicked: (String) -> Unit
onQrCodeClicked: (String, String) -> Unit
) {
var showMenu by remember { mutableStateOf(false) }

Expand Down Expand Up @@ -793,7 +794,7 @@ fun InviteLinkDisplay(
// Copy link
DropdownMenuItem(
onClick = {
onCopyClicked(link)
onCopyClicked(link, EventsDictionary.CopyLinkRoutes.MENU)
showMenu = false
}
) {
Expand Down Expand Up @@ -844,7 +845,7 @@ fun InviteLinkDisplay(
// Show QR code
DropdownMenuItem(
onClick = {
onQrCodeClicked(link)
onQrCodeClicked(link, EventsDictionary.CopyLinkRoutes.MENU)
showMenu = false
}
) {
Expand All @@ -871,7 +872,7 @@ fun InviteLinkDisplay(
modifierBox = Modifier.fillMaxWidth(),
text = stringResource(R.string.copy_link),
onClick = {
onCopyClicked(link)
onCopyClicked(link, EventsDictionary.CopyLinkRoutes.BUTTON)
},
size = ButtonSize.Large,
)
Expand Down Expand Up @@ -1027,7 +1028,7 @@ fun ShareSpaceScreenPreview1() {
)
},
onContextActionClicked = { _, _ -> },
onShareQrCodeClicked = {},
onShareQrCodeClicked = { _, _ -> },
incentiveState = SpaceLimitsState.EditorsLimit(4),
onIncentiveClicked = {},
isLoadingInProgress = false,
Expand All @@ -1038,7 +1039,7 @@ fun ShareSpaceScreenPreview1() {
onInviteLinkAccessLevelSelected = {},
onInviteLinkAccessChangeConfirmed = {},
onInviteLinkAccessChangeCancel = {},
onCopyInviteLinkClicked = {},
onCopyInviteLinkClicked = { _, _ -> },
isCurrentUserOwner = true,
onManageSpacesClicked = {}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import com.anytypeio.anytype.core_models.TextStyle
import com.anytypeio.anytype.core_models.ThemeMode
import com.anytypeio.anytype.core_models.WidgetLayout
import com.anytypeio.anytype.core_models.getSingleValue
import com.anytypeio.anytype.core_models.multiplayer.InviteType
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
import com.anytypeio.anytype.core_models.multiplayer.SpaceUxType
import com.anytypeio.anytype.core_models.primitives.RelationKey
Expand Down Expand Up @@ -2467,6 +2468,32 @@ suspend fun Analytics.sendAnalyticsApproveInvite(
)
}

/**
* Send analytics event when a new space invite link is generated.
*
* @param inviteType The type of invite (MEMBER, GUEST, or WITHOUT_APPROVE)
* @param permissions The member permissions for the invite link (READER or WRITER)
*/
suspend fun Analytics.sendAnalyticsShareSpaceNewLink(
inviteType: InviteType?,
permissions: SpaceMemberPermissions?
) {
val linkType = when {
permissions == SpaceMemberPermissions.WRITER && inviteType == InviteType.WITHOUT_APPROVE ->
EventsDictionary.ShareSpaceLinkTypes.EDITOR
permissions == SpaceMemberPermissions.READER && inviteType == InviteType.WITHOUT_APPROVE ->
EventsDictionary.ShareSpaceLinkTypes.VIEWER
else ->
EventsDictionary.ShareSpaceLinkTypes.MANUAL
}
sendEvent(
eventName = EventsDictionary.clickShareSpaceNewLink,
props = Props(
mapOf(EventsPropertiesKey.type to linkType)
)
)
}

//region Version History
fun CoroutineScope.sendAnalyticsShowVersionHistoryScreen(
analytics: Analytics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.EventsDictionary.clickJoinSpaceWithoutApproval
import com.anytypeio.anytype.analytics.base.EventsDictionary.screenInviteRequest
import com.anytypeio.anytype.analytics.base.EventsDictionary.screenRequestSent
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
Expand Down Expand Up @@ -95,19 +97,36 @@ class RequestJoinSpaceViewModel(
analytics.sendEvent(
eventName = screenInviteRequest,
props = Props(
map = when(spaceView.spaceUxType) {
SpaceUxType.DATA -> mapOf(
EventsPropertiesKey.uxType to "Space"
)
SpaceUxType.CHAT -> mapOf(
EventsPropertiesKey.uxType to "Chat"
)
else -> emptyMap()
map = buildMap {
when(spaceView.spaceUxType) {
SpaceUxType.DATA -> put(EventsPropertiesKey.uxType, "Space")
SpaceUxType.CHAT -> put(EventsPropertiesKey.uxType, "Chat")
else -> {}
}
// Analytics Event #5: ScreenInviteRequest with type property
val inviteType = if (view.withoutApprove) {
EventsDictionary.InviteRequestTypes.WITHOUT_APPROVAL
} else {
EventsDictionary.InviteRequestTypes.APPROVAL
}
put(EventsPropertiesKey.type, inviteType)
}
)
)
state.value = TypedViewState.Error(ErrorView.RequestAlreadySent)
} else {
// Analytics Event #5: ScreenInviteRequest with type property
val inviteType = if (view.withoutApprove) {
EventsDictionary.InviteRequestTypes.WITHOUT_APPROVAL
} else {
EventsDictionary.InviteRequestTypes.APPROVAL
}
analytics.sendEvent(
eventName = screenInviteRequest,
props = Props(
mapOf(EventsPropertiesKey.type to inviteType)
)
)
state.value = TypedViewState.Success(view)
}
}
Expand Down Expand Up @@ -213,6 +232,8 @@ class RequestJoinSpaceViewModel(
)
)

analytics.sendEvent(eventName = clickJoinSpaceWithoutApproval)

val shouldNotify = data.withoutApprove
val notificationsEnabled = notificator.areNotificationsEnabled

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary.clickSettingsSpaceS
import com.anytypeio.anytype.analytics.base.EventsDictionary.removeSpaceMember
import com.anytypeio.anytype.analytics.base.EventsDictionary.screenSettingsSpaceMembers
import com.anytypeio.anytype.analytics.base.EventsDictionary.screenSettingsSpaceShare
import com.anytypeio.anytype.analytics.base.EventsDictionary.shareSpace
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.presentation.extension.sendAnalyticsShareSpaceNewLink
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
Expand Down Expand Up @@ -255,6 +257,13 @@ class ShareSpaceViewModel(
).fold(
onSuccess = { inviteLink ->
Timber.d("Successfully generated invite link, link: ${inviteLink.scheme}")

// Analytics Event #1: ClickShareSpaceNewLink with type property
analytics.sendAnalyticsShareSpaceNewLink(
inviteType = inviteType,
permissions = permissions
)

proceedWithRequestCurrentInviteLink()
// Reset loading state and close confirmation dialog after successful generation
inviteLinkAccessLoading.value = false
Expand All @@ -272,6 +281,10 @@ class ShareSpaceViewModel(
Timber.d("onShareInviteLinkClicked, link: $link")
viewModelScope.launch {
commands.emit(ShareInviteLink(link))

// Analytics Event #6: ClickShareSpaceShareLink
analytics.sendEvent(eventName = EventsDictionary.clickShareSpaceShareLink)

analytics.sendEvent(
eventName = clickSettingsSpaceShare,
props = Props(
Expand All @@ -281,15 +294,24 @@ class ShareSpaceViewModel(
}
}

fun onShareQrCodeClicked(link: String) {
Timber.d("onShareQrCodeClicked, link: $link")
fun onShareQrCodeClicked(link: String, route: String = EventsDictionary.ScreenQrRoutes.INVITE_LINK) {
Timber.d("onShareQrCodeClicked, link: $link, route: $route")
viewModelScope.launch {
val spaceView = _spaceViews ?: return@launch
uiQrCodeState.value = UiSpaceQrCodeState.SpaceInvite(
link = link,
spaceName = spaceView.name.orEmpty(),
icon = spaceView.spaceIcon(urlBuilder)
)

// Analytics Event #3: ScreenQr with route property
analytics.sendEvent(
eventName = EventsDictionary.screenQr,
props = Props(
mapOf(EventsPropertiesKey.route to route)
)
)

analytics.sendEvent(
eventName = clickSettingsSpaceShare,
props = Props(
Expand Down Expand Up @@ -503,13 +525,22 @@ class ShareSpaceViewModel(
inviteLinkConfirmationDialog.value = null
}

fun onCopyInviteLinkClicked(link: String) {
Timber.d("onCopyInviteLinkClicked, link: $link")
fun onCopyInviteLinkClicked(link: String, route: String = EventsDictionary.CopyLinkRoutes.BUTTON) {
Timber.d("onCopyInviteLinkClicked, link: $link, route: $route")
viewModelScope.launch {
try {
copyInviteLinkToClipboard.run(
CopyInviteLinkToClipboard.Params(link)
)

// Analytics Event #4: ClickShareSpaceCopyLink with route property
analytics.sendEvent(
eventName = EventsDictionary.clickShareSpaceCopyLink,
props = Props(
mapOf(EventsPropertiesKey.route to route)
)
)

sendToast("Invite link copied to clipboard")
} catch (error: Exception) {
Timber.e(error, "Failed to copy invite link to clipboard")
Expand All @@ -522,7 +553,10 @@ class ShareSpaceViewModel(
viewModelScope.launch {
makeSpaceShareable(
space = vmParams.space,
actionSuccess = { proceedWithUpdatingInviteLink(newLevel) },
actionSuccess = {
analytics.sendEvent(eventName = shareSpace)
proceedWithUpdatingInviteLink(newLevel)
},
actionFailure = { proceedWithMultiplayerError(it) }
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.analytics.base.EventsDictionary.shareSpace
import com.anytypeio.anytype.analytics.base.EventsPropertiesKey
import com.anytypeio.anytype.analytics.base.sendEvent
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.presentation.extension.sendAnalyticsShareSpaceNewLink
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Relations
Expand Down Expand Up @@ -309,6 +311,7 @@ class CreateSpaceViewModel(
makeSpaceShareable.async(params = spaceId).fold(
onSuccess = {
Timber.d("Successfully made space shareable")
analytics.sendEvent(eventName = shareSpace)
generateInviteLink(
spaceId = spaceId,
startingObject = startingObject
Expand Down Expand Up @@ -356,6 +359,13 @@ class CreateSpaceViewModel(
).fold(
onSuccess = { inviteLink ->
Timber.d("Successfully generated invite link: ${inviteLink.scheme}")

// Analytics: Track chat space invite link generation
analytics.sendAnalyticsShareSpaceNewLink(
inviteType = CHAT_SPACE_INVITE_TYPE,
permissions = CHAT_SPACE_DEFAULT_PERMISSIONS
)

finishSpaceCreation(
spaceId = spaceId.id,
startingObject = startingObject
Expand Down
Loading