Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ee6f184
v0.38.14
konstantiniiv May 26, 2025
6020d15
v0.38.15
konstantiniiv May 26, 2025
91d23f6
DROID-3622 Widgets | Fix | Emit "Chat" widget placeholder immediately…
uburoiubu May 26, 2025
a229788
DROID-3115 Chats | Fix | Read-only state for chat box (#2465)
uburoiubu May 26, 2025
522e48e
l10n | Enable Japanese translation (#2460)
mottcha May 26, 2025
fef916e
l10n | Enhancement (#2459)
any-association May 26, 2025
16f475d
l10n | Enhancement (Japanese translations) (#2466)
any-association May 26, 2025
d085141
DROID-3702 Protocol | Enhancement | MW 0.41.0-rc14
uburoiubu May 26, 2025
518da56
DROID-3362 Widgets | Fix | Should not trigger container creation on i…
uburoiubu May 26, 2025
362451b
DROID-3637 Chats | Fix | From-notification-to-chat UX flow fixes (#2468)
uburoiubu May 27, 2025
6e49023
DROID-3696 Invite link | Onboarding flow with a no approval invite li…
konstantiniiv May 27, 2025
f520f25
DROID-3703 Invite | Join without approve link, success notification (…
konstantiniiv May 27, 2025
a179cbb
v0.38.16
konstantiniiv May 27, 2025
e6f1156
DROID-3705 Protocol | Enhancement | MW v0.41.0-rc15
uburoiubu May 27, 2025
b1ba53f
DROID-3355 App | Tech | Release stabilisation fixes (#2472)
uburoiubu May 27, 2025
d68ba4e
DROID-3699 Chats | Enhancement | Unsubscribe from chats on back navig…
uburoiubu May 27, 2025
fa1ba9e
DROID-3704 Sentry | Release 11 (#2471)
konstantiniiv May 27, 2025
1db896a
v0.38.17
uburoiubu May 27, 2025
f84aeab
DROID-3704 Sentry | Release 11 - Hotfix (#2471)
uburoiubu May 27, 2025
121b2b3
v0.38.18
uburoiubu May 27, 2025
82e46db
DROID-3706 Chats | Fix | Restrict "Add reaction" to message for viewe…
uburoiubu May 28, 2025
bf12edd
l10n | Enhancement (#2474)
any-association May 28, 2025
71ab93f
DROID-2966 Chats | Enhancement | Add pub/sub for attachments (#2477)
uburoiubu May 29, 2025
6790b13
DROID-3258 Chats | Fix | Should not scroll to bottom after editing a …
uburoiubu May 29, 2025
08902e6
DROID-3707 Notifications | Clear push notifications for opened chat (…
konstantiniiv May 29, 2025
529b949
l10n | Enhancement (#2479)
any-association May 29, 2025
dcd40e7
v0.38.19
uburoiubu May 29, 2025
dfdf7a2
DROID-3717 Protocol | Enhancement | MW 0.41.0-rc16 (#2485)
uburoiubu Jun 2, 2025
8422152
DROID-2966 Chats | Fix | Fix keys for attachment subscription (#2486)
uburoiubu Jun 2, 2025
3f6dbfd
v0.38.20
uburoiubu Jun 2, 2025
2762f2b
DROID-3765 Crash | App crashes when creating new page object (#2578)
konstantiniiv Jun 26, 2025
61e4747
v0.38.21
konstantiniiv Jun 26, 2025
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
2 changes: 1 addition & 1 deletion app/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version.versionMajor=0
version.versionMinor=38
version.versionPatch=13
version.versionPatch=21
version.useDatedVersionName=false
31 changes: 21 additions & 10 deletions app/src/main/java/com/anytypeio/anytype/app/Notifications.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,34 @@ class AnytypeNotificationService @Inject constructor(
)
}
is NotificationPayload.ParticipantRequestApproved -> {
Timber.d("Processing participant request approved notification : ${notification}")
val placeholder = context.resources.getString(R.string.untitled)
val title = context.resources.getString(
R.string.multiplayer_notification_member_request_approved
)
val actionTitle = context.resources.getString(
R.string.multiplayer_notification_go_to_space
)
val body = if (payload.permissions.isOwnerOrEditor()) {
context.resources.getString(
R.string.multiplayer_notification_member_request_approved_with_edit_rights,
payload.spaceName.ifEmpty { placeholder }
)
} else {
context.resources.getString(
R.string.multiplayer_notification_member_request_approved_with_read_only_rights,
payload.spaceName.ifEmpty { placeholder }
)
val permissions = payload.permissions
val body = when {
permissions == null -> {
context.resources.getString(
R.string.multiplayer_notification_member_request_approved_unknown_rights,
payload.spaceName.ifEmpty { placeholder }
)
}
permissions.isOwnerOrEditor() -> {
context.resources.getString(
R.string.multiplayer_notification_member_request_approved_with_edit_rights,
payload.spaceName.ifEmpty { placeholder }
)
}
else -> {
context.resources.getString(
R.string.multiplayer_notification_member_request_approved_with_read_only_rights,
payload.spaceName.ifEmpty { placeholder }
)
}
}
val intent = Intent(context, MainActivity::class.java).apply {
putExtra(Relations.SPACE_ID, payload.spaceId.id)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.anytypeio.anytype.device

import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.DecryptedPushContent
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.domain.notifications.NotificationBuilder
import com.anytypeio.anytype.domain.resources.StringResourceProvider
import com.anytypeio.anytype.ui.main.MainActivity
import kotlin.math.absoluteValue
import timber.log.Timber

class NotificationBuilderImpl(
private val context: Context,
private val notificationManager: NotificationManager,
private val resourceProvider: StringResourceProvider
) : NotificationBuilder {

private val attachmentText get() = resourceProvider.getAttachmentText()
private val createdChannels = mutableSetOf<String>()

override fun buildAndNotify(message: DecryptedPushContent.Message, spaceId: Id) {
val channelId = "${spaceId}_${message.chatId}"

ensureChannelExists(
channelId = channelId,
channelName = sanitizeChannelName(message.spaceName)
)

// Create pending intent to open chat
val pending = createChatPendingIntent(
context = context,
chatId = message.chatId,
spaceId = spaceId
)

// Format the notification body text
val bodyText = message.formatNotificationBody(attachmentText)
val singleLine = "${message.senderName.trim()}: $bodyText"

val notif = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_app_notification)
.setContentTitle(message.spaceName.trim())
.setContentText(singleLine)
.setStyle(NotificationCompat.BigTextStyle().bigText(singleLine))
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pending)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setFullScreenIntent(pending, true)
.setLights(0xFF0000FF.toInt(), 300, 1000)
.setVibrate(longArrayOf(0, 500, 200, 500))
.build()

// TODO maybe use message ID as notification ID?
notificationManager.notify(System.currentTimeMillis().toInt(), notif)
}

/**
* Ensures the notification channel (and group) exist before notifying.
*/
private fun ensureChannelExists(channelId: String, channelName: String) {
createChannelGroupIfNeeded()
if (createdChannels.contains(channelId)) return
val channel = NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "New messages notifications"
enableLights(true)
enableVibration(true)
setShowBadge(true)
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
group = CHANNEL_GROUP_ID
}
}
notificationManager.createNotificationChannel(channel)
createdChannels.add(channelId)
}

/**
* Creates the tap-action intent and wraps it in a PendingIntent for notifications.
*/
private fun createChatPendingIntent(
context: Context,
chatId: String,
spaceId: Id
): PendingIntent {
// 1) Build the intent that'll open your MainActivity in the right chat
val intent = Intent(context, MainActivity::class.java).apply {
action = AnytypePushService.ACTION_OPEN_CHAT
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra(Relations.CHAT_ID, chatId)
putExtra(Relations.SPACE_ID, spaceId)
}

// A unique PendingIntent per chat target.
val requestCode = (chatId + spaceId).hashCode().absoluteValue

// 2) Wrap it in a one-shot immutable PendingIntent
return PendingIntent.getActivity(
context,
requestCode,
intent,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
}

fun createChannelGroupIfNeeded() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
val existingGroup =
notificationManager.getNotificationChannelGroup(CHANNEL_GROUP_ID)
if (existingGroup == null) {
val group = NotificationChannelGroup(CHANNEL_GROUP_ID, CHANNEL_GROUP_NAME)
notificationManager.createNotificationChannelGroup(group)
}
} catch (e: NoSuchMethodError) {
Timber.e(e, "Error while creating or getting notification group")
// Some devices might not support getNotificationChannelGroup even on Android O
// Just create the group without checking if it exists
val group = NotificationChannelGroup(CHANNEL_GROUP_ID, CHANNEL_GROUP_NAME)
notificationManager.createNotificationChannelGroup(group)
} catch (e: Exception) {
Timber.e(e, "Error while creating or getting notification group")
val group = NotificationChannelGroup(CHANNEL_GROUP_ID, CHANNEL_GROUP_NAME)
notificationManager.createNotificationChannelGroup(group)
}
}
}

/**
* Deletes notifications and the channel for a specific chat in a space, so that
* when the user opens that chat, old notifications are cleared.
*/
override fun clearNotificationChannel(spaceId: String, chatId: String) {
val channelId = "${spaceId}_${chatId}"

// Remove posted notifications for this specific chat channel
notificationManager.activeNotifications
.filter { it.notification.channelId == channelId }
.forEach { notificationManager.cancel(it.id) }

// Delete the specific chat channel
notificationManager.deleteNotificationChannel(channelId)
createdChannels.remove(channelId)
}

private fun sanitizeChannelName(name: String): String {
return name.trim().replace(Regex("[^a-zA-Z0-9 _-]"), "_")
}

companion object {
private const val CHANNEL_GROUP_ID = "chats_group"
private const val CHANNEL_GROUP_NAME = "Chats"
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.anytypeio.anytype.device

import android.util.Base64
import com.anytypeio.anytype.domain.notifications.NotificationBuilder
import com.anytypeio.anytype.presentation.notifications.DecryptionPushContentService

interface PushMessageProcessor {
Expand Down
Loading