Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 0 additions & 10 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,16 +262,6 @@ pub async fn main() {
});
}
}

// Start event notification after DB is initialized
{
use tauri_plugin_notification::NotificationPluginExt;
if app_clone.get_event_notification().unwrap_or(false) {
if let Err(e) = app_clone.start_event_notification().await {
tracing::error!("start_event_notification_failed: {:?}", e);
}
}
}
});

Ok(())
Expand Down
24 changes: 12 additions & 12 deletions apps/desktop/src/components/settings/views/notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ export default function NotificationsComponent() {
mutationFn: async (v: Schema) => {
if (v.event) {
notificationCommands.setEventNotification(true);
notificationCommands.showNotification({
title: "Test",
message: "Test",
url: "https://hypr.ai",
timeout: { secs: 5, nanos: 0 },
});
} else {
notificationCommands.setEventNotification(false);
}
Expand All @@ -54,6 +48,12 @@ export default function NotificationsComponent() {
eventNotification.refetch();
if (active) {
notificationCommands.startEventNotification();
notificationCommands.showNotification({
title: "You're all set!",
message: "This is how notifications look.",
timeout: { secs: 10, nanos: 0 },
url: null,
});
} else {
notificationCommands.stopEventNotification();
}
Expand All @@ -64,12 +64,6 @@ export default function NotificationsComponent() {
mutationFn: async (v: Schema) => {
if (v.detect) {
notificationCommands.setDetectNotification(true);
notificationCommands.showNotification({
title: "Test",
message: "Test",
url: "https://hypr.ai",
timeout: { secs: 5, nanos: 0 },
});
} else {
notificationCommands.setDetectNotification(false);
}
Expand All @@ -79,6 +73,12 @@ export default function NotificationsComponent() {
detectNotification.refetch();
if (active) {
notificationCommands.startDetectNotification();
notificationCommands.showNotification({
title: "You're all set!",
message: "This is how notifications look.",
timeout: { secs: 10, nanos: 0 },
url: null,
});
} else {
notificationCommands.stopDetectNotification();
}
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,11 @@ msgstr "{weeks} weeks later"
#~ msgid "in {hours} hours"
#~ msgstr "in {hours} hours"

#: src/components/settings/views/notifications.tsx:105
#: src/components/settings/views/notifications.tsx:113
msgid "(Beta) Detect meetings automatically"
msgstr "(Beta) Detect meetings automatically"

#: src/components/settings/views/notifications.tsx:132
#: src/components/settings/views/notifications.tsx:140
msgid "(Beta) Upcoming meeting notifications"
msgstr "(Beta) Upcoming meeting notifications"

Expand Down Expand Up @@ -1428,11 +1428,11 @@ msgstr "Share usage data"
msgid "Shorten summary"
msgstr "Shorten summary"

#: src/components/settings/views/notifications.tsx:135
#: src/components/settings/views/notifications.tsx:143
msgid "Show notifications when you have meetings starting soon in your calendar."
msgstr "Show notifications when you have meetings starting soon in your calendar."

#: src/components/settings/views/notifications.tsx:108
#: src/components/settings/views/notifications.tsx:116
msgid "Show notifications when you join a meeting."
msgstr "Show notifications when you join a meeting."

Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/locales/ko/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,11 @@ msgstr ""
#~ msgid "in {hours} hours"
#~ msgstr ""

#: src/components/settings/views/notifications.tsx:105
#: src/components/settings/views/notifications.tsx:113
msgid "(Beta) Detect meetings automatically"
msgstr ""

#: src/components/settings/views/notifications.tsx:132
#: src/components/settings/views/notifications.tsx:140
msgid "(Beta) Upcoming meeting notifications"
msgstr ""

Expand Down Expand Up @@ -1428,11 +1428,11 @@ msgstr ""
msgid "Shorten summary"
msgstr ""

#: src/components/settings/views/notifications.tsx:135
#: src/components/settings/views/notifications.tsx:143
msgid "Show notifications when you have meetings starting soon in your calendar."
msgstr ""

#: src/components/settings/views/notifications.tsx:108
#: src/components/settings/views/notifications.tsx:116
msgid "Show notifications when you join a meeting."
msgstr ""

Expand Down
2 changes: 1 addition & 1 deletion crates/detect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub use mic::*;

use utils::*;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum DetectEvent {
MicStarted,
MicStopped,
Expand Down
14 changes: 10 additions & 4 deletions crates/notification-macos/examples/test_notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ use std::time::Duration;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{define_class, msg_send, MainThreadOnly};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2_app_kit::{
NSAppearance, NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate,
};
use objc2_foundation::{ns_string, MainThreadMarker, NSObject, NSObjectProtocol};

#[derive(Debug, Default)]
struct AppDelegateIvars {}
Expand Down Expand Up @@ -35,6 +37,10 @@ fn main() {
let app = NSApplication::sharedApplication(mtm);
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);

if let Some(appearance) = NSAppearance::appearanceNamed(ns_string!("NSAppearanceNameAqua")) {
app.setAppearance(Some(&appearance));
}

let delegate = AppDelegate::new(mtm);
app.setDelegate(Some(&ProtocolObject::from_ref(&*delegate)));

Expand All @@ -46,11 +52,11 @@ fn main() {
.title("Test Notification")
.message("Hover/click should now react")
.url("https://example.com")
.timeout(Duration::from_secs(20))
.timeout(Duration::from_secs(30))
.build();

show(&notification);
std::thread::sleep(Duration::from_secs(5));
std::thread::sleep(Duration::from_secs(30));
std::process::exit(0);
});

Expand Down
61 changes: 23 additions & 38 deletions crates/notification-macos/swift-lib/src/lib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,20 @@ class ActionButton: NSButton {
font = NSFont.systemFont(ofSize: 14, weight: .semibold)
focusRingType = .none

contentTintColor = NSColor.white
contentTintColor = NSColor(calibratedWhite: 0.1, alpha: 1.0)
if #available(macOS 11.0, *) {
bezelColor = NSColor.white.withAlphaComponent(0.16)
bezelColor = NSColor(calibratedWhite: 0.9, alpha: 1.0)
}

layer?.cornerRadius = 10
layer?.backgroundColor = NSColor.white.withAlphaComponent(0.16).cgColor
layer?.borderColor = NSColor.white.withAlphaComponent(0.22).cgColor
layer?.backgroundColor = NSColor(calibratedWhite: 0.95, alpha: 0.9).cgColor
layer?.borderColor = NSColor(calibratedWhite: 0.7, alpha: 0.5).cgColor
layer?.borderWidth = 0.5

layer?.shadowColor = NSColor(calibratedWhite: 0.0, alpha: 0.5).cgColor
layer?.shadowOpacity = 0.3
layer?.shadowRadius = 3
layer?.shadowOffset = CGSize(width: 0, height: 1)
}

override var intrinsicContentSize: NSSize {
Expand Down Expand Up @@ -461,22 +467,20 @@ class NotificationManager {
url: String?,
notification: NotificationInstance
) {
let descriptionText = makeDescription(from: url)
let hasUrl = (url != nil && !url!.isEmpty)

let contentView = createNotificationView(
description: descriptionText,
title: title,
body: message,
buttonTitle: hasUrl ? "Open" : nil,
buttonTitle: hasUrl ? "Take Notes" : nil,
notification: notification
)
contentView.translatesAutoresizingMaskIntoConstraints = false
effectView.addSubview(contentView)

NSLayoutConstraint.activate([
contentView.leadingAnchor.constraint(equalTo: effectView.leadingAnchor, constant: 12),
contentView.trailingAnchor.constraint(equalTo: effectView.trailingAnchor, constant: -10), // nudge left a bit
contentView.trailingAnchor.constraint(equalTo: effectView.trailingAnchor, constant: -10),
contentView.topAnchor.constraint(equalTo: effectView.topAnchor, constant: 9),
contentView.bottomAnchor.constraint(equalTo: effectView.bottomAnchor, constant: -9),
])
Expand All @@ -486,7 +490,6 @@ class NotificationManager {
}

private func createNotificationView(
description: String,
title: String,
body: String,
buttonTitle: String? = nil,
Expand All @@ -502,58 +505,50 @@ class NotificationManager {
iconContainer.wantsLayer = true
iconContainer.layer?.cornerRadius = 9
iconContainer.translatesAutoresizingMaskIntoConstraints = false
iconContainer.widthAnchor.constraint(equalToConstant: 36).isActive = true
iconContainer.heightAnchor.constraint(equalToConstant: 36).isActive = true
iconContainer.widthAnchor.constraint(equalToConstant: 42).isActive = true
iconContainer.heightAnchor.constraint(equalToConstant: 42).isActive = true

let iconImageView = createAppIconView()
iconContainer.addSubview(iconImageView)
NSLayoutConstraint.activate([
iconImageView.centerXAnchor.constraint(equalTo: iconContainer.centerXAnchor),
iconImageView.centerYAnchor.constraint(equalTo: iconContainer.centerYAnchor),
iconImageView.widthAnchor.constraint(equalToConstant: 24),
iconImageView.heightAnchor.constraint(equalToConstant: 24),
iconImageView.widthAnchor.constraint(equalToConstant: 32),
iconImageView.heightAnchor.constraint(equalToConstant: 32),
])

// Middle: text stack
let textStack = NSStackView()
textStack.orientation = .vertical
textStack.spacing = 3
textStack.spacing = 4
textStack.alignment = .leading
textStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
textStack.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

let descriptionLabel = NSTextField(labelWithString: description)
descriptionLabel.font = NSFont.systemFont(ofSize: 11)
descriptionLabel.textColor = NSColor.secondaryLabelColor
descriptionLabel.lineBreakMode = .byTruncatingTail
descriptionLabel.maximumNumberOfLines = 1

// Title
let titleLabel = NSTextField(labelWithString: title)
titleLabel.font = NSFont.systemFont(ofSize: 14, weight: .semibold)
titleLabel.font = NSFont.systemFont(ofSize: 16, weight: .semibold)
titleLabel.textColor = NSColor.labelColor
titleLabel.lineBreakMode = .byTruncatingTail
titleLabel.maximumNumberOfLines = 1
titleLabel.allowsDefaultTighteningForTruncation = true

// Body
let bodyLabel = NSTextField(labelWithString: body)
bodyLabel.font = NSFont.systemFont(ofSize: 12)
bodyLabel.font = NSFont.systemFont(ofSize: 12, weight: .light)
bodyLabel.textColor = NSColor.secondaryLabelColor
bodyLabel.lineBreakMode = .byWordWrapping
bodyLabel.maximumNumberOfLines = 2
bodyLabel.maximumNumberOfLines = 1
bodyLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

textStack.addArrangedSubview(descriptionLabel)
textStack.addArrangedSubview(titleLabel)
textStack.addArrangedSubview(bodyLabel)
textStack.setCustomSpacing(4, after: descriptionLabel)

// Assemble so far
container.addArrangedSubview(iconContainer)
container.addArrangedSubview(textStack)

// Right: larger pill action button with a fixed spacer to avoid setCustomSpacing crash
// Right: action button
if let buttonTitle {
// Small fixed spacer between text and button (adjust width to move button left/right)
let gap = NSView()
gap.translatesAutoresizingMaskIntoConstraints = false
gap.widthAnchor.constraint(equalToConstant: 8).isActive = true
Expand Down Expand Up @@ -583,16 +578,6 @@ class NotificationManager {
notification.dismiss()
}

private func makeDescription(from urlString: String?) -> String {
if let urlString, let url = URL(string: urlString), let host = url.host, !host.isEmpty {
return host
}
if let appName = NSRunningApplication.current.localizedName {
return appName
}
return "Notification"
}

private func createAppIconView() -> NSImageView {
let imageView = NSImageView()
if let appIcon = NSApp.applicationIconImage {
Expand Down
1 change: 1 addition & 0 deletions crates/notification/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::time::{Duration, Instant};
pub use hypr_notification_interface::*;

static RECENT_NOTIFICATIONS: OnceLock<Mutex<HashMap<String, Instant>>> = OnceLock::new();

const DEDUPE_WINDOW: Duration = Duration::from_secs(60 * 5);

#[cfg(target_os = "macos")]
Expand Down
1 change: 0 additions & 1 deletion crates/whisper-local/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ impl Segment {
#[cfg(test)]
mod tests {
use super::*;
use futures_util::StreamExt;

#[test]
fn test_whisper() {
Expand Down
2 changes: 2 additions & 0 deletions plugins/notification/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ hypr-detect = { workspace = true }
hypr-notification = { workspace = true }

tauri-plugin-db = { workspace = true }
tauri-plugin-listener = { workspace = true }
tauri-plugin-store2 = { workspace = true }
tauri-plugin-windows = { workspace = true }

serde = { workspace = true }
specta = { workspace = true }
Expand Down
Loading
Loading