Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

NotificationLog

NotificationLog preview

NotificationLog — a scrollable, day-bucketed list of archived notifications.

Public functions

NotificationLog

Detailed description

Renders a NotificationArchiveModel as a scrollable column of StandardListItem rows grouped under section headers (Today / Yesterday / This week / Earlier), computed against the user's local timezone on every archive mutation. An optional toolbar row provides mark-all-read and clear buttons. Unread rows show the title in BodyBold; read rows use Body. An empty-state hint is shown when the archive is empty.

Sizing

The log grows into a host that bounds its height and compresses inside one shorter than its natural height (floored at one row); only a host that hugs its content — which is how the overlay layer measures the NotificationCenterButton popover — falls back to preferred_width / preferred_height. Row text is elided, not wrapped, with the full text on the row's rich tooltip: notification prose is arbitrary and the log does not control its own width, so a wrapping row would over-constrain itself and push its trailing action buttons out of view.

When to use

  • Embed directly inside a side panel or settings page for an in-app notification centre.
  • Wrap in NotificationCenterButton for the standard bell-icon-with-popover pattern.
  • Call NotificationLogDialog::show for a one-line modal presentation.
let archive: Rc<NotificationArchiveModel> = ctx.app_state().unwrap();
let log = NotificationLog::new(archive)
    .on_action_invoked(|_entry, action, ctx| {
        if let Some(name) = &action.intent_name {
            ctx.send_intent(teksilo_core::Intent::new(name));
        }
    });

Touch and pen

A log row is a StandardListItem, so its target floor follows the density ladder with every other list row, and its tap replays the entry on the release. The trailing action strip is made of Links and Buttons, each its own target.

Density

The picture above is the widget at TargetDensity::Compact, the mouse-and-keyboard ladder. Below is the same subject on the same canvas with only the ladder changed, so what moves is the density and nothing else — where the subject no longer fits, that is what the denser targets cost it at that size. See docs/density-and-targets.md.

Touch

NotificationLog at Touch density

API reference

📖 Full rustdoc API for this module

pub struct NotificationLog

Import: use teksilo::prelude::NotificationLog; — in teksilo::prelude::*.

Configurable archive log. Shipped chrome:

  • mark-all-read + clear buttons in a toolbar row;
  • empty-state hint when the archive is empty;
  • day-bucket section headers (Today / Yesterday / This week / Earlier) above the rows for each bucket — computed against the user's local timezone, recomputed on every archive mutation;
  • StandardListItem rows with unread-as-bold differentiation.

A SearchField filter and a severity-chip filter can be composed by apps using the existing widget toolkit.

#![allow(unused)]
fn main() {
pub struct NotificationLog { /* fields */ }
}

Methods

pub fn new(archive: Rc<NotificationArchiveModel>) -> Self

Construct a log bound to the shared archive. The archive is expected to outlive the log (typically held in app_state).

pub fn for_window(mut self, window_id: TeksiloWindowId) -> Self

Scope this log to entries routed to window window_id (plus any Broadcast entry) — the shape a NotificationCenterButton mounted in that window wants for its popover body. Overrides any previous for_window / for_audience call.

pub fn for_audience(mut self, audience: ToastAudience) -> Self

Scope this log to entries routed to audience (plus any Broadcast entry). Overrides any previous for_window / for_audience call.

pub fn show_toolbar(mut self, show: bool) -> Self

Whether to render the toolbar row (mark-all-read + clear). Default true. Apps that want a chrome-less log (e.g. inside a custom panel that supplies its own toolbar) pass false.

pub fn empty_state(mut self, f: impl Fn() -> Box<dyn Widget> + 'static) -> Self

Override the empty-state hint. Default: a centered "No notifications" text. Pass a factory returning any widget for a custom empty view (illustration, call-to-action, …).

A factory rather than a widget because the log rebuilds on every archive mutation: the view has to be re-creatable each time the archive goes empty again, not just the first time.

log.empty_state(|| Box::new(TextWidget::new(tr!(inbox_zero()))))

pub fn preferred_width(mut self, width: f32) -> Self

Width the log reports when the host proposes an unbounded one. Default 380 dp.

This is load-bearing for the popover presentation (NotificationCenterButton): the overlay layer measures its content with a fully unbounded proposal, and a StandardListItem asked for an intrinsic width reports only its chrome, so without a preferred width the popover would size itself to whatever the two toolbar buttons happen to measure (~248 dp with the stock labels) and elide every title to a stub. Hosts that DO bound the width (a dialog, a side panel) ignore this value.

pub fn preferred_height(mut self, height: f32) -> Self

Height of the scrolling list area when the host proposes an unbounded height. Default 320 dp.

The log always grows into a host that bounds its height (it reports a flex weight), so this only sets the natural height a content-hugging host — again, the popover — sizes itself to.

pub fn on_entry_invoked( mut self, f: impl Fn(&NotificationEntry, &mut EventContext) + 'static, ) -> Self

Called when the user clicks anywhere on an archived entry's row body (outside any specific action button). The default behaviour is no-op — the log is read-only display unless callers wire this hook.

pub fn on_action_invoked( mut self, f: impl Fn(&NotificationEntry, &ArchivedAction, &mut EventContext) + 'static, ) -> Self

Called when an archived action button is clicked. Apps wire this hook to replay the action — typically by mapping the ArchivedAction::intent_name to one of the app's registered Actions via ctx.send_intent(...). Without this hook configured the action buttons are inert (the log keeps them visible for archival context).

Actions without an intent_name render as non-clickable past-action tags regardless of this hook — there's nothing for the framework to dispatch against once the live closure has torn down.

log.on_action_invoked(|_entry, action, ctx| {
    // Bridge the dynamic intent_name to one of the app's
    // typed AppIntent variants:
    match action.intent_name.as_deref() {
        Some("app.build.retry") => ctx.send_intent(AppIntent::BuildRetry),
        Some(name) => log::warn!("unknown archived intent: {name}"),
        None => {}
    }
})