Skip to content

Commit c7f0566

Browse files
bmartyElementBot
andauthored
Introduce PushHistoryService to store data about the received push (#4573)
* Introduce PushHistoryService to store data about the received push Add a push database. * Update screenshots * Improve preview. * Update screenshots * Add missing test. * Add test for PushHistoryView * Fix configuration issue. Was: w: /libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt:35:27 Cannot access class 'PushProvider' in the expression type. While it may work, this case indicates a configuration mistake and can lead to avoidable compilation errors, so it may be forbidden soon. Check your module classpath for missing or conflicting dependencies. --------- Co-authored-by: ElementBot <[email protected]>
1 parent 2b1a66f commit c7f0566

File tree

60 files changed

+1653
-211
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1653
-211
lines changed

appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import io.element.android.libraries.matrix.api.core.EventId
7070
import io.element.android.libraries.matrix.api.core.MAIN_SPACE
7171
import io.element.android.libraries.matrix.api.core.RoomId
7272
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
73+
import io.element.android.libraries.matrix.api.core.SessionId
7374
import io.element.android.libraries.matrix.api.core.UserId
7475
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
7576
import io.element.android.libraries.matrix.api.permalink.PermalinkData
@@ -378,6 +379,14 @@ class LoggedInFlowNode @AssistedInject constructor(
378379
override fun onOpenRoomNotificationSettings(roomId: RoomId) {
379380
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.NotificationSettings))
380381
}
382+
383+
override fun navigateTo(sessionId: SessionId, roomId: RoomId, eventId: EventId) {
384+
// We do not check the sessionId, but it will have to be done at some point (multi account)
385+
if (sessionId != matrixClient.sessionId) {
386+
Timber.e("SessionId mismatch, expected ${matrixClient.sessionId} but got $sessionId")
387+
}
388+
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Messages(eventId)))
389+
}
381390
}
382391
val inputs = PreferencesEntryPoint.Params(navTarget.initialElement)
383392
preferencesEntryPoint.nodeBuilder(this, buildContext)

features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import com.bumble.appyx.core.node.Node
1313
import com.bumble.appyx.core.plugin.Plugin
1414
import io.element.android.libraries.architecture.FeatureEntryPoint
1515
import io.element.android.libraries.architecture.NodeInputs
16+
import io.element.android.libraries.matrix.api.core.EventId
1617
import io.element.android.libraries.matrix.api.core.RoomId
18+
import io.element.android.libraries.matrix.api.core.SessionId
1719
import kotlinx.parcelize.Parcelize
1820

1921
interface PreferencesEntryPoint : FeatureEntryPoint {
@@ -29,6 +31,7 @@ interface PreferencesEntryPoint : FeatureEntryPoint {
2931
}
3032

3133
data class Params(val initialElement: InitialTarget) : NodeInputs
34+
3235
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
3336

3437
interface NodeBuilder {
@@ -41,5 +44,6 @@ interface PreferencesEntryPoint : FeatureEntryPoint {
4144
fun onOpenBugReport()
4245
fun onSecureBackupClick()
4346
fun onOpenRoomNotificationSettings(roomId: RoomId)
47+
fun navigateTo(sessionId: SessionId, roomId: RoomId, eventId: EventId)
4448
}
4549
}

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ import io.element.android.libraries.architecture.BaseFlowNode
3939
import io.element.android.libraries.architecture.appyx.canPop
4040
import io.element.android.libraries.architecture.createNode
4141
import io.element.android.libraries.di.SessionScope
42+
import io.element.android.libraries.matrix.api.core.EventId
4243
import io.element.android.libraries.matrix.api.core.RoomId
44+
import io.element.android.libraries.matrix.api.core.SessionId
4345
import io.element.android.libraries.matrix.api.user.MatrixUser
4446
import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint
47+
import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint
4548
import kotlinx.parcelize.Parcelize
4649

4750
@ContributesNode(SessionScope::class)
@@ -50,6 +53,7 @@ class PreferencesFlowNode @AssistedInject constructor(
5053
@Assisted plugins: List<Plugin>,
5154
private val lockScreenEntryPoint: LockScreenEntryPoint,
5255
private val notificationTroubleShootEntryPoint: NotificationTroubleShootEntryPoint,
56+
private val pushHistoryEntryPoint: PushHistoryEntryPoint,
5357
private val logoutEntryPoint: LogoutEntryPoint,
5458
private val openSourceLicensesEntryPoint: OpenSourceLicensesEntryPoint,
5559
private val accountDeactivationEntryPoint: AccountDeactivationEntryPoint,
@@ -83,6 +87,9 @@ class PreferencesFlowNode @AssistedInject constructor(
8387
@Parcelize
8488
data object TroubleshootNotifications : NavTarget
8589

90+
@Parcelize
91+
data object PushHistory : NavTarget
92+
8693
@Parcelize
8794
data object LockScreenSettings : NavTarget
8895

@@ -182,6 +189,10 @@ class PreferencesFlowNode @AssistedInject constructor(
182189
override fun onTroubleshootNotificationsClick() {
183190
backstack.push(NavTarget.TroubleshootNotifications)
184191
}
192+
193+
override fun onPushHistoryClick() {
194+
backstack.push(NavTarget.PushHistory)
195+
}
185196
}
186197
createNode<NotificationSettingsNode>(buildContext, listOf(notificationSettingsCallback))
187198
}
@@ -198,6 +209,23 @@ class PreferencesFlowNode @AssistedInject constructor(
198209
})
199210
.build()
200211
}
212+
NavTarget.PushHistory -> {
213+
pushHistoryEntryPoint.nodeBuilder(this, buildContext)
214+
.callback(object : PushHistoryEntryPoint.Callback {
215+
override fun onDone() {
216+
if (backstack.canPop()) {
217+
backstack.pop()
218+
} else {
219+
navigateUp()
220+
}
221+
}
222+
223+
override fun onItemClick(sessionId: SessionId, roomId: RoomId, eventId: EventId) {
224+
plugins<PreferencesEntryPoint.Callback>().forEach { it.navigateTo(sessionId, roomId, eventId) }
225+
}
226+
})
227+
.build()
228+
}
201229
is NavTarget.EditDefaultNotificationSetting -> {
202230
val callback = object : EditDefaultNotificationSettingNode.Callback {
203231
override fun openRoomNotificationSettings(roomId: RoomId) {

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class NotificationSettingsNode @AssistedInject constructor(
2727
interface Callback : Plugin {
2828
fun editDefaultNotificationMode(isOneToOne: Boolean)
2929
fun onTroubleshootNotificationsClick()
30+
fun onPushHistoryClick()
3031
}
3132

3233
private val callbacks = plugins<Callback>()
@@ -39,6 +40,10 @@ class NotificationSettingsNode @AssistedInject constructor(
3940
callbacks.forEach { it.onTroubleshootNotificationsClick() }
4041
}
4142

43+
private fun onPushHistoryClick() {
44+
callbacks.forEach { it.onPushHistoryClick() }
45+
}
46+
4247
@Composable
4348
override fun View(modifier: Modifier) {
4449
val state = presenter.present()
@@ -47,6 +52,7 @@ class NotificationSettingsNode @AssistedInject constructor(
4752
onOpenEditDefault = { openEditDefault(isOneToOne = it) },
4853
onBackClick = ::navigateUp,
4954
onTroubleshootNotificationsClick = ::onTroubleshootNotificationsClick,
55+
onPushHistoryClick = ::onPushHistoryClick,
5056
modifier = modifier,
5157
)
5258
}

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ fun NotificationSettingsView(
5050
state: NotificationSettingsState,
5151
onOpenEditDefault: (isOneToOne: Boolean) -> Unit,
5252
onTroubleshootNotificationsClick: () -> Unit,
53+
onPushHistoryClick: () -> Unit,
5354
onBackClick: () -> Unit,
5455
modifier: Modifier = Modifier,
5556
) {
@@ -82,6 +83,7 @@ fun NotificationSettingsView(
8283
// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) },
8384
onInviteForMeNotificationsChange = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) },
8485
onTroubleshootNotificationsClick = onTroubleshootNotificationsClick,
86+
onPushHistoryClick = onPushHistoryClick,
8587
)
8688
}
8789
AsyncActionView(
@@ -105,6 +107,7 @@ private fun NotificationSettingsContentView(
105107
// onCallsNotificationsChanged: (Boolean) -> Unit,
106108
onInviteForMeNotificationsChange: (Boolean) -> Unit,
107109
onTroubleshootNotificationsClick: () -> Unit,
110+
onPushHistoryClick: () -> Unit,
108111
) {
109112
val context = LocalContext.current
110113
val systemSettings: NotificationSettingsState.AppSettings = state.appSettings
@@ -203,6 +206,12 @@ private fun NotificationSettingsContentView(
203206
},
204207
onClick = onTroubleshootNotificationsClick
205208
)
209+
ListItem(
210+
headlineContent = {
211+
Text(stringResource(R.string.troubleshoot_notifications_entry_point_push_history_title))
212+
},
213+
onClick = onPushHistoryClick
214+
)
206215
}
207216
if (state.showAdvancedSettings) {
208217
PreferenceCategory(title = stringResource(id = CommonStrings.common_advanced_settings)) {
@@ -303,5 +312,6 @@ internal fun NotificationSettingsViewPreview(@PreviewParameter(NotificationSetti
303312
onBackClick = {},
304313
onOpenEditDefault = {},
305314
onTroubleshootNotificationsClick = {},
315+
onPushHistoryClick = {},
306316
)
307317
}

features/preferences/impl/src/main/res/values/localazy.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ If you proceed, some of your settings may change."</string>
5858
<string name="screen_notification_settings_system_notifications_action_required_content_link">"system settings"</string>
5959
<string name="screen_notification_settings_system_notifications_turned_off">"System notifications turned off"</string>
6060
<string name="screen_notification_settings_title">"Notifications"</string>
61+
<string name="troubleshoot_notifications_entry_point_push_history_title">"Push history"</string>
6162
<string name="troubleshoot_notifications_entry_point_section">"Troubleshoot"</string>
6263
<string name="troubleshoot_notifications_entry_point_title">"Troubleshoot notifications"</string>
6364
</resources>

features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ class NotificationSettingsViewTest {
6666
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
6767
}
6868

69+
@Config(qualifiers = "h1024dp")
70+
@Test
71+
fun `clicking on push history notification invokes the expected callback`() {
72+
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
73+
ensureCalledOnce {
74+
rule.setNotificationSettingsView(
75+
state = aValidNotificationSettingsState(
76+
eventSink = eventsRecorder
77+
),
78+
onPushHistoryClick = it
79+
)
80+
rule.clickOn(R.string.troubleshoot_notifications_entry_point_push_history_title)
81+
}
82+
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
83+
}
84+
6985
@Config(qualifiers = "h1024dp")
7086
@Test
7187
fun `clicking on group chats invokes the expected callback`() {
@@ -284,13 +300,15 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setNotif
284300
state: NotificationSettingsState,
285301
onOpenEditDefault: (isOneToOne: Boolean) -> Unit = EnsureNeverCalledWithParam(),
286302
onTroubleshootNotificationsClick: () -> Unit = EnsureNeverCalled(),
303+
onPushHistoryClick: () -> Unit = EnsureNeverCalled(),
287304
onBackClick: () -> Unit = EnsureNeverCalled(),
288305
) {
289306
setContent {
290307
NotificationSettingsView(
291308
state = state,
292309
onOpenEditDefault = onOpenEditDefault,
293310
onTroubleshootNotificationsClick = onTroubleshootNotificationsClick,
311+
onPushHistoryClick = onPushHistoryClick,
294312
onBackClick = onBackClick,
295313
)
296314
}

libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,4 @@ const val A_RECOVERY_KEY = "1234 5678"
8787
val A_SERVER_LIST = listOf("server1", "server2")
8888

8989
const val A_TIMESTAMP = 567L
90+
const val A_FORMATTED_DATE = "April 6, 1980 at 6:35 PM"

libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package io.element.android.libraries.push.api
99

1010
import io.element.android.libraries.matrix.api.MatrixClient
1111
import io.element.android.libraries.matrix.api.core.SessionId
12+
import io.element.android.libraries.push.api.history.PushHistoryItem
1213
import io.element.android.libraries.pushproviders.api.Distributor
1314
import io.element.android.libraries.pushproviders.api.PushProvider
1415
import kotlinx.coroutines.flow.Flow
@@ -51,4 +52,19 @@ interface PushService {
5152
* Return false in case of early error.
5253
*/
5354
suspend fun testPush(): Boolean
55+
56+
/**
57+
* Get a flow of total number of received Push.
58+
*/
59+
val pushCounter: Flow<Int>
60+
61+
/**
62+
* Get a flow of list of [PushHistoryItem].
63+
*/
64+
fun getPushHistoryItemsFlow(): Flow<List<PushHistoryItem>>
65+
66+
/**
67+
* Reset the push history, including the push counter.
68+
*/
69+
suspend fun resetPushHistory()
5470
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.push.api.history
9+
10+
import io.element.android.libraries.matrix.api.core.EventId
11+
import io.element.android.libraries.matrix.api.core.RoomId
12+
import io.element.android.libraries.matrix.api.core.SessionId
13+
14+
/**
15+
* Data class representing a push history item.
16+
* @property pushDate Date (timestamp).
17+
* @property formattedDate Formatted date.
18+
* @property providerInfo Push provider name / info
19+
* @property eventId EventId from the push, can be null if the received data are not correct.
20+
* @property roomId RoomId from the push, can be null if the received data are not correct.
21+
* @property sessionId The session Id, can be null if the session cannot be retrieved
22+
* @property hasBeenResolved Result of resolving the event
23+
* @property comment Comment. Can contains an error message if the event could not be resolved, or other any information.
24+
*/
25+
data class PushHistoryItem(
26+
val pushDate: Long,
27+
val formattedDate: String,
28+
val providerInfo: String,
29+
val eventId: EventId?,
30+
val roomId: RoomId?,
31+
val sessionId: SessionId?,
32+
val hasBeenResolved: Boolean,
33+
val comment: String?,
34+
)

libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/store/PushDataStore.kt

Lines changed: 0 additions & 14 deletions
This file was deleted.

libraries/push/impl/build.gradle.kts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import extension.setupAnvil
99
plugins {
1010
id("io.element.android-compose-library")
1111
alias(libs.plugins.kotlin.serialization)
12+
alias(libs.plugins.sqldelight)
1213
}
1314

1415
android {
@@ -32,9 +33,16 @@ dependencies {
3233
implementation(libs.serialization.json)
3334
implementation(libs.coil)
3435

36+
implementation(libs.sqldelight.driver.android)
37+
implementation(libs.sqlcipher)
38+
implementation(libs.sqlite)
39+
implementation(libs.sqldelight.coroutines)
40+
implementation(projects.libraries.encryptedDb)
41+
3542
implementation(projects.appconfig)
3643
implementation(projects.libraries.architecture)
3744
implementation(projects.libraries.core)
45+
implementation(projects.libraries.dateformatter.api)
3846
implementation(projects.libraries.designsystem)
3947
implementation(projects.libraries.di)
4048
implementation(projects.libraries.androidutils)
@@ -76,3 +84,11 @@ dependencies {
7684
testImplementation(projects.libraries.featureflag.test)
7785
testImplementation(libs.kotlinx.collections.immutable)
7886
}
87+
88+
sqldelight {
89+
databases {
90+
create("PushDatabase") {
91+
schemaOutputDirectory = File("src/main/sqldelight/databases")
92+
}
93+
}
94+
}

libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import io.element.android.libraries.matrix.api.MatrixClient
1414
import io.element.android.libraries.matrix.api.core.SessionId
1515
import io.element.android.libraries.push.api.GetCurrentPushProvider
1616
import io.element.android.libraries.push.api.PushService
17+
import io.element.android.libraries.push.api.history.PushHistoryItem
18+
import io.element.android.libraries.push.impl.store.PushDataStore
1719
import io.element.android.libraries.push.impl.test.TestPush
1820
import io.element.android.libraries.pushproviders.api.Distributor
1921
import io.element.android.libraries.pushproviders.api.PushProvider
@@ -34,6 +36,7 @@ class DefaultPushService @Inject constructor(
3436
private val getCurrentPushProvider: GetCurrentPushProvider,
3537
private val sessionObserver: SessionObserver,
3638
private val pushClientSecretStore: PushClientSecretStore,
39+
private val pushDataStore: PushDataStore,
3740
) : PushService, SessionListener {
3841
init {
3942
observeSessions()
@@ -125,4 +128,14 @@ class DefaultPushService @Inject constructor(
125128
pushClientSecretStore.resetSecret(sessionId)
126129
userPushStore.reset()
127130
}
131+
132+
override val pushCounter: Flow<Int> = pushDataStore.pushCounterFlow
133+
134+
override fun getPushHistoryItemsFlow(): Flow<List<PushHistoryItem>> {
135+
return pushDataStore.getPushHistoryItemsFlow()
136+
}
137+
138+
override suspend fun resetPushHistory() {
139+
pushDataStore.reset()
140+
}
128141
}

0 commit comments

Comments
 (0)