Skip to content

Commit 7f53430

Browse files
fix(desktop): show app details in mic ignore prompt
Use detected app names and footer icons in mic notification ignore prompts, and render footer icons in compact macOS notifications.
1 parent 4f39f81 commit 7f53430

8 files changed

Lines changed: 98 additions & 18 deletions

File tree

apps/desktop/src/stt/contexts.test.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,12 @@ describe("ListenerProvider detect events", () => {
308308
event_ids: [],
309309
},
310310
footer: {
311-
text: "Ignore this app?",
311+
text: "Ignore Zoom?",
312312
actionLabel: "Yes",
313+
icon: {
314+
type: "bundle_id",
315+
bundle_id: "us.zoom.xos",
316+
},
313317
},
314318
icon: null,
315319
}),

apps/desktop/src/stt/contexts.tsx

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
commands as detectCommands,
77
events as detectEvents,
88
} from "@hypr/plugin-detect";
9-
import { commands as notificationCommands } from "@hypr/plugin-notification";
9+
import {
10+
commands as notificationCommands,
11+
type NotificationIcon,
12+
} from "@hypr/plugin-notification";
1013

1114
import { getSessionEventById } from "~/session/utils";
1215
import * as main from "~/store/tinybase/store/main";
@@ -39,12 +42,49 @@ const BROWSER_MEETING_APP_IDS = new Set([
3942

4043
type MainStore = NonNullable<ReturnType<typeof main.UI.useStore>>;
4144

42-
function getIgnorableAppIds(apps: { id: string }[]) {
43-
return [
44-
...new Set(
45-
apps.map((app) => app.id).filter((id) => id && !id.startsWith("pid:")),
46-
),
47-
];
45+
function getIgnorableApps(apps: { id: string; name: string }[]) {
46+
const seen = new Set<string>();
47+
48+
return apps.filter((app) => {
49+
if (!app.id || app.id.startsWith("pid:") || seen.has(app.id)) {
50+
return false;
51+
}
52+
53+
seen.add(app.id);
54+
return true;
55+
});
56+
}
57+
58+
function getNotificationIconForAppId(appId: string): NotificationIcon | null {
59+
if (!appId || appId.startsWith("pid:")) {
60+
return null;
61+
}
62+
63+
if (appId.startsWith("/") || appId.startsWith("~/")) {
64+
return { type: "path", path: appId };
65+
}
66+
67+
return { type: "bundle_id", bundle_id: appId };
68+
}
69+
70+
function getIgnoreAppsFooterText(apps: { name: string }[]) {
71+
const firstName = apps[0]?.name.trim();
72+
73+
if (apps.length === 1) {
74+
return firstName ? `Ignore ${firstName}?` : "Ignore this app?";
75+
}
76+
77+
if (!firstName) {
78+
return "Ignore these apps?";
79+
}
80+
81+
const secondName = apps[1]?.name.trim();
82+
if (apps.length === 2 && secondName) {
83+
return `Ignore ${firstName} and ${secondName}?`;
84+
}
85+
86+
const otherAppCount = apps.length - 1;
87+
return `Ignore ${firstName} and ${otherAppCount} other app${otherAppCount === 1 ? "" : "s"}?`;
4888
}
4989

5090
function parseEventTimeMs(value: string | undefined): number | null {
@@ -227,7 +267,8 @@ const useHandleDetectEvents = (store: ListenerStore) => {
227267
detectEvents.detectEvent
228268
.listen(({ payload }) => {
229269
if (payload.type === "micDetected") {
230-
const appIds = getIgnorableAppIds(payload.apps);
270+
const ignorableApps = getIgnorableApps(payload.apps);
271+
const appIds = ignorableApps.map((app) => app.id);
231272

232273
if (store.getState().live.status === "active") {
233274
if (appIds.length > 0) {
@@ -250,13 +291,11 @@ const useHandleDetectEvents = (store: ListenerStore) => {
250291
const options =
251292
nearbyEvents.length > 0 ? nearbyEvents.map((e) => e.title) : null;
252293
const footer =
253-
appIds.length > 0
294+
ignorableApps.length > 0
254295
? {
255-
text:
256-
appIds.length === 1
257-
? "Ignore this app?"
258-
: "Ignore these apps?",
296+
text: getIgnoreAppsFooterText(ignorableApps),
259297
actionLabel: "Yes",
298+
icon: getNotificationIconForAppId(ignorableApps[0]!.id),
260299
}
261300
: null;
262301

crates/notification-interface/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pub struct EventDetails {
9494
pub struct NotificationFooter {
9595
pub text: String,
9696
pub action_label: String,
97+
pub icon: Option<NotificationIcon>,
9798
}
9899

99100
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -381,6 +382,7 @@ mod tests {
381382
.footer(NotificationFooter {
382383
text: "Ignore this app?".to_string(),
383384
action_label: "YES".to_string(),
385+
icon: None,
384386
})
385387
.build();
386388

crates/notification-macos/examples/test_notification_without_event.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ fn main() {
2626
})
2727
.action_label("Open")
2828
.footer(NotificationFooter {
29-
text: "Ignore reminders like this?".to_string(),
29+
text: "Ignore Slack?".to_string(),
3030
action_label: "YES".to_string(),
31+
icon: NotificationIcon::from_app_id("com.tinyspeck.slackmacgap"),
3132
})
3233
.build();
3334

crates/notification-macos/swift-lib/src/Models.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct EventDetails: Codable {
1515
struct NotificationFooter: Codable {
1616
let text: String
1717
let actionLabel: String
18+
let icon: NotificationIcon?
1819
}
1920

2021
enum NotificationIconAsset: Codable {

crates/notification-macos/swift-lib/src/NotificationManager+CompactView.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,30 @@ extension NotificationManager {
131131
footerRow.spacing = 8
132132
footerRow.translatesAutoresizingMaskIntoConstraints = false
133133

134+
let footerTextStack = NSStackView()
135+
footerTextStack.orientation = .horizontal
136+
footerTextStack.alignment = .centerY
137+
footerTextStack.distribution = .fill
138+
footerTextStack.spacing = 4
139+
footerTextStack.translatesAutoresizingMaskIntoConstraints = false
140+
footerTextStack.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
141+
142+
if let footerIconView = createNotificationIconView(
143+
for: footer.icon, fallbackToDefault: false
144+
) {
145+
footerIconView.widthAnchor.constraint(equalToConstant: 14).isActive = true
146+
footerIconView.heightAnchor.constraint(equalToConstant: 14).isActive = true
147+
footerIconView.setContentHuggingPriority(.required, for: .horizontal)
148+
footerTextStack.addArrangedSubview(footerIconView)
149+
}
150+
134151
let footerLabel = NSTextField(labelWithString: footer.text)
135152
footerLabel.font = NSFont.systemFont(ofSize: 10, weight: .medium)
136153
footerLabel.textColor = NSColor.secondaryLabelColor
137154
footerLabel.lineBreakMode = .byTruncatingTail
138155
footerLabel.maximumNumberOfLines = 1
139156
footerLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
157+
footerTextStack.addArrangedSubview(footerLabel)
140158

141159
let footerButton = compactFooterButton()
142160
footerButton.title = footer.actionLabel
@@ -145,7 +163,7 @@ extension NotificationManager {
145163
}
146164
footerButton.setContentHuggingPriority(.required, for: .horizontal)
147165

148-
footerRow.addArrangedSubview(footerLabel)
166+
footerRow.addArrangedSubview(footerTextStack)
149167
footerRow.addArrangedSubview(footerButton)
150168

151169
root.addSubview(divider)

crates/notification-macos/swift-lib/src/NotificationManager+Creation.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,22 @@ extension NotificationManager {
220220
}
221221

222222
func createNotificationIconView(for payload: NotificationPayload) -> NSImageView? {
223-
guard let image = resolveNotificationIcon(payload.icon) else {
223+
createNotificationIconView(for: payload.icon, fallbackToDefault: true)
224+
}
225+
226+
func createNotificationIconView(
227+
for icon: NotificationIcon?, fallbackToDefault: Bool
228+
) -> NSImageView? {
229+
let image: NSImage?
230+
if fallbackToDefault {
231+
image = resolveNotificationIcon(icon)
232+
} else if let icon {
233+
image = resolveNotificationIcon(icon)
234+
} else {
235+
image = nil
236+
}
237+
238+
guard let image else {
224239
return nil
225240
}
226241

plugins/notification/js/bindings.gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export type Duration = { secs: number; nanos: number }
4343
export type EventDetails = { what: string; timezone: string | null; location: string | null }
4444
export type Notification = { key: string | null; title: string; message: string; timeout: Duration | null; source: NotificationSource | null; start_time: number | null; participants: Participant[] | null; event_details: EventDetails | null; action_label: string | null; options: string[] | null; footer: NotificationFooter | null; icon: NotificationIcon | null }
4545
export type NotificationEvent = { type: "notification_confirm"; key: string; source: NotificationSource | null } | { type: "notification_accept"; key: string; source: NotificationSource | null } | { type: "notification_dismiss"; key: string; source: NotificationSource | null } | { type: "notification_timeout"; key: string; source: NotificationSource | null } | { type: "notification_option_selected"; key: string; source: NotificationSource | null; selected_index: number } | { type: "notification_footer_action"; key: string; source: NotificationSource | null }
46-
export type NotificationFooter = { text: string; actionLabel: string }
46+
export type NotificationFooter = { text: string; actionLabel: string; icon: NotificationIcon | null }
4747
export type NotificationIcon = { type: "hidden" } | { type: "bundle_id"; bundle_id: string } | { type: "path"; path: string } | { type: "overlay"; base: NotificationIconAsset; badge: NotificationIconAsset }
4848
export type NotificationIconAsset = { type: "app_icon" } | { type: "calendar" } | { type: "bundle_id"; bundle_id: string } | { type: "path"; path: string }
4949
export type NotificationSource = { type: "calendar_event"; event_id: string } | { type: "mic_detected"; app_names: string[]; app_ids?: string[]; event_ids?: string[] }

0 commit comments

Comments
 (0)