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 <android@element.io>
This commit is contained in:
Benoit Marty 2025-04-11 12:56:54 +02:00 committed by GitHub
parent a0b619007a
commit a1d8322738
60 changed files with 1656 additions and 214 deletions

View file

@ -39,9 +39,12 @@ import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.appyx.canPop
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint
import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint
import kotlinx.parcelize.Parcelize
@ContributesNode(SessionScope::class)
@ -50,6 +53,7 @@ class PreferencesFlowNode @AssistedInject constructor(
@Assisted plugins: List<Plugin>,
private val lockScreenEntryPoint: LockScreenEntryPoint,
private val notificationTroubleShootEntryPoint: NotificationTroubleShootEntryPoint,
private val pushHistoryEntryPoint: PushHistoryEntryPoint,
private val logoutEntryPoint: LogoutEntryPoint,
private val openSourceLicensesEntryPoint: OpenSourceLicensesEntryPoint,
private val accountDeactivationEntryPoint: AccountDeactivationEntryPoint,
@ -83,6 +87,9 @@ class PreferencesFlowNode @AssistedInject constructor(
@Parcelize
data object TroubleshootNotifications : NavTarget
@Parcelize
data object PushHistory : NavTarget
@Parcelize
data object LockScreenSettings : NavTarget
@ -182,6 +189,10 @@ class PreferencesFlowNode @AssistedInject constructor(
override fun onTroubleshootNotificationsClick() {
backstack.push(NavTarget.TroubleshootNotifications)
}
override fun onPushHistoryClick() {
backstack.push(NavTarget.PushHistory)
}
}
createNode<NotificationSettingsNode>(buildContext, listOf(notificationSettingsCallback))
}
@ -198,6 +209,23 @@ class PreferencesFlowNode @AssistedInject constructor(
})
.build()
}
NavTarget.PushHistory -> {
pushHistoryEntryPoint.nodeBuilder(this, buildContext)
.callback(object : PushHistoryEntryPoint.Callback {
override fun onDone() {
if (backstack.canPop()) {
backstack.pop()
} else {
navigateUp()
}
}
override fun onItemClick(sessionId: SessionId, roomId: RoomId, eventId: EventId) {
plugins<PreferencesEntryPoint.Callback>().forEach { it.navigateTo(sessionId, roomId, eventId) }
}
})
.build()
}
is NavTarget.EditDefaultNotificationSetting -> {
val callback = object : EditDefaultNotificationSettingNode.Callback {
override fun openRoomNotificationSettings(roomId: RoomId) {

View file

@ -27,6 +27,7 @@ class NotificationSettingsNode @AssistedInject constructor(
interface Callback : Plugin {
fun editDefaultNotificationMode(isOneToOne: Boolean)
fun onTroubleshootNotificationsClick()
fun onPushHistoryClick()
}
private val callbacks = plugins<Callback>()
@ -39,6 +40,10 @@ class NotificationSettingsNode @AssistedInject constructor(
callbacks.forEach { it.onTroubleshootNotificationsClick() }
}
private fun onPushHistoryClick() {
callbacks.forEach { it.onPushHistoryClick() }
}
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
@ -47,6 +52,7 @@ class NotificationSettingsNode @AssistedInject constructor(
onOpenEditDefault = { openEditDefault(isOneToOne = it) },
onBackClick = ::navigateUp,
onTroubleshootNotificationsClick = ::onTroubleshootNotificationsClick,
onPushHistoryClick = ::onPushHistoryClick,
modifier = modifier,
)
}

View file

@ -50,6 +50,7 @@ fun NotificationSettingsView(
state: NotificationSettingsState,
onOpenEditDefault: (isOneToOne: Boolean) -> Unit,
onTroubleshootNotificationsClick: () -> Unit,
onPushHistoryClick: () -> Unit,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -82,6 +83,7 @@ fun NotificationSettingsView(
// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) },
onInviteForMeNotificationsChange = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) },
onTroubleshootNotificationsClick = onTroubleshootNotificationsClick,
onPushHistoryClick = onPushHistoryClick,
)
}
AsyncActionView(
@ -105,6 +107,7 @@ private fun NotificationSettingsContentView(
// onCallsNotificationsChanged: (Boolean) -> Unit,
onInviteForMeNotificationsChange: (Boolean) -> Unit,
onTroubleshootNotificationsClick: () -> Unit,
onPushHistoryClick: () -> Unit,
) {
val context = LocalContext.current
val systemSettings: NotificationSettingsState.AppSettings = state.appSettings
@ -203,6 +206,12 @@ private fun NotificationSettingsContentView(
},
onClick = onTroubleshootNotificationsClick
)
ListItem(
headlineContent = {
Text(stringResource(R.string.troubleshoot_notifications_entry_point_push_history_title))
},
onClick = onPushHistoryClick
)
}
if (state.showAdvancedSettings) {
PreferenceCategory(title = stringResource(id = CommonStrings.common_advanced_settings)) {
@ -303,5 +312,6 @@ internal fun NotificationSettingsViewPreview(@PreviewParameter(NotificationSetti
onBackClick = {},
onOpenEditDefault = {},
onTroubleshootNotificationsClick = {},
onPushHistoryClick = {},
)
}

View file

@ -58,6 +58,7 @@ If you proceed, some of your settings may change."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"system settings"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"System notifications turned off"</string>
<string name="screen_notification_settings_title">"Notifications"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Push history"</string>
<string name="troubleshoot_notifications_entry_point_section">"Troubleshoot"</string>
<string name="troubleshoot_notifications_entry_point_title">"Troubleshoot notifications"</string>
</resources>

View file

@ -66,6 +66,22 @@ class NotificationSettingsViewTest {
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
}
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on push history notification invokes the expected callback`() {
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
ensureCalledOnce {
rule.setNotificationSettingsView(
state = aValidNotificationSettingsState(
eventSink = eventsRecorder
),
onPushHistoryClick = it
)
rule.clickOn(R.string.troubleshoot_notifications_entry_point_push_history_title)
}
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
}
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on group chats invokes the expected callback`() {
@ -284,6 +300,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setNotif
state: NotificationSettingsState,
onOpenEditDefault: (isOneToOne: Boolean) -> Unit = EnsureNeverCalledWithParam(),
onTroubleshootNotificationsClick: () -> Unit = EnsureNeverCalled(),
onPushHistoryClick: () -> Unit = EnsureNeverCalled(),
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
setContent {
@ -291,6 +308,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setNotif
state = state,
onOpenEditDefault = onOpenEditDefault,
onTroubleshootNotificationsClick = onTroubleshootNotificationsClick,
onPushHistoryClick = onPushHistoryClick,
onBackClick = onBackClick,
)
}