mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-06 17:01:33 +08:00
more cleanup
This commit is contained in:
parent
ee885fea44
commit
4af058bd02
@ -1,10 +1,25 @@
|
||||
// Package notifications provides cross-platform notification capabilities for desktop applications.
|
||||
// It supports macOS, Windows, and Linux with a consistent API while handling platform-specific
|
||||
// differences internally. Key features include:
|
||||
// - Basic notifications with title, subtitle, and body
|
||||
// - Interactive notifications with buttons and actions
|
||||
// - Notification categories for reusing configurations
|
||||
// - User feedback handling with a unified callback system
|
||||
//
|
||||
// Platform-specific notes:
|
||||
// - macOS: Requires a properly bundled and signed application
|
||||
// - Windows: Uses Windows Toast notifications
|
||||
// - Linux: Falls back between D-Bus, notify-send, or other methods and does not support text inputs
|
||||
package notifications
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Service represents the notifications service
|
||||
type Service struct {
|
||||
// notificationResponseCallback is called when a notification response is received
|
||||
// notificationResponseCallback is called when a notification result is received.
|
||||
// Only one callback can be assigned at a time.
|
||||
notificationResultCallback func(result NotificationResult)
|
||||
|
||||
@ -93,3 +108,15 @@ func (ns *Service) handleNotificationResult(result NotificationResult) {
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
|
||||
func validateNotificationOptions(options NotificationOptions) error {
|
||||
if options.ID == "" {
|
||||
return fmt.Errorf("notification ID cannot be empty")
|
||||
}
|
||||
|
||||
if options.Title == "" {
|
||||
return fmt.Errorf("notification title cannot be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func CheckBundleIdentifier() bool {
|
||||
}
|
||||
|
||||
// RequestNotificationAuthorization requests permission for notifications.
|
||||
// Default timeout is 5 minutes
|
||||
// Default timeout is 15 minutes
|
||||
func (ns *Service) RequestNotificationAuthorization() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*900)
|
||||
defer cancel()
|
||||
@ -94,6 +94,10 @@ func (ns *Service) CheckNotificationAuthorization() (bool, error) {
|
||||
|
||||
// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
|
||||
func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@ -139,6 +143,10 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
|
||||
// If a NotificationCategory is not registered a basic notification will be sent.
|
||||
func (ns *Service) SendNotificationWithActions(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@ -243,7 +251,7 @@ func (ns *Service) RemoveNotificationCategory(categoryId string) error {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
return fmt.Errorf("category registration failed")
|
||||
return fmt.Errorf("category removal failed")
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
|
@ -106,37 +106,43 @@ void checkNotificationAuthorization(int channelID) {
|
||||
}];
|
||||
}
|
||||
|
||||
void sendNotification(int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) {
|
||||
ensureDelegateInitialized();
|
||||
|
||||
NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
|
||||
// Helper function to create notification content
|
||||
UNMutableNotificationContent* createNotificationContent(const char *title, const char *subtitle,
|
||||
const char *body, const char *data_json) {
|
||||
NSString *nsTitle = [NSString stringWithUTF8String:title];
|
||||
NSString *nsSubtitle = [NSString stringWithUTF8String:subtitle];
|
||||
NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @"";
|
||||
NSString *nsBody = [NSString stringWithUTF8String:body];
|
||||
|
||||
NSMutableDictionary *customData = [NSMutableDictionary dictionary];
|
||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = nsTitle;
|
||||
if (![nsSubtitle isEqualToString:@""]) {
|
||||
content.subtitle = nsSubtitle;
|
||||
}
|
||||
content.body = nsBody;
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
|
||||
// Parse JSON data if provided
|
||||
if (data_json) {
|
||||
NSString *dataJsonStr = [NSString stringWithUTF8String:data_json];
|
||||
NSData *jsonData = [dataJsonStr dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSError *error = nil;
|
||||
NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
|
||||
if (error) {
|
||||
NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
|
||||
captureResult(channelID, false, [errorMsg UTF8String]);
|
||||
return;
|
||||
}
|
||||
if (parsedData) {
|
||||
[customData addEntriesFromDictionary:parsedData];
|
||||
if (!error && parsedData) {
|
||||
content.userInfo = parsedData;
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
void sendNotification(int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) {
|
||||
ensureDelegateInitialized();
|
||||
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = nsTitle;
|
||||
content.subtitle = nsSubtitle;
|
||||
content.body = nsBody;
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
|
||||
UNMutableNotificationContent *content = createNotificationContent(title, subtitle, body, data_json);
|
||||
NSMutableDictionary *customData = [NSMutableDictionary dictionary];
|
||||
|
||||
if (customData.count > 0) {
|
||||
content.userInfo = customData;
|
||||
@ -157,35 +163,16 @@ void sendNotification(int channelID, const char *identifier, const char *title,
|
||||
}
|
||||
|
||||
void sendNotificationWithActions(int channelID, const char *identifier, const char *title, const char *subtitle,
|
||||
const char *body, const char *categoryId, const char *actions_json) {
|
||||
const char *body, const char *categoryId, const char *data_json) {
|
||||
ensureDelegateInitialized();
|
||||
|
||||
NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
|
||||
NSString *nsTitle = [NSString stringWithUTF8String:title];
|
||||
NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @"";
|
||||
NSString *nsBody = [NSString stringWithUTF8String:body];
|
||||
NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId];
|
||||
|
||||
NSMutableDictionary *customData = [NSMutableDictionary dictionary];
|
||||
if (actions_json) {
|
||||
NSString *actionsJsonStr = [NSString stringWithUTF8String:actions_json];
|
||||
NSData *jsonData = [actionsJsonStr dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSError *error = nil;
|
||||
NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
|
||||
if (!error && parsedData) {
|
||||
[customData addEntriesFromDictionary:parsedData];
|
||||
}
|
||||
}
|
||||
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = nsTitle;
|
||||
if (![nsSubtitle isEqualToString:@""]) {
|
||||
content.subtitle = nsSubtitle;
|
||||
}
|
||||
content.body = nsBody;
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
|
||||
NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId];
|
||||
UNMutableNotificationContent *content = createNotificationContent(title, subtitle, body, data_json);
|
||||
NSMutableDictionary *customData = [NSMutableDictionary dictionary];
|
||||
|
||||
content.categoryIdentifier = nsCategoryId;
|
||||
|
||||
if (customData.count > 0) {
|
||||
|
@ -415,6 +415,10 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
return errors.New("notification service not initialized")
|
||||
}
|
||||
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notifier.Lock()
|
||||
defer notifier.Unlock()
|
||||
|
||||
@ -454,6 +458,10 @@ func (ns *Service) SendNotificationWithActions(options NotificationOptions) erro
|
||||
return errors.New("notification service not initialized")
|
||||
}
|
||||
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notificationLock.RLock()
|
||||
category, exists := notificationCategories[options.CategoryID]
|
||||
notificationLock.RUnlock()
|
||||
|
@ -128,6 +128,10 @@ func (ns *Service) CheckNotificationAuthorization() bool {
|
||||
// SendNotification sends a basic notification with a name, title, and body. All other options are ignored on Windows.
|
||||
// (subtitle and category id are only available on macOS)
|
||||
func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := saveIconToDir(); err != nil {
|
||||
fmt.Printf("Error saving icon: %v\n", err)
|
||||
}
|
||||
@ -140,9 +144,10 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
|
||||
if options.Data != nil {
|
||||
encodedPayload, err := encodePayload(DefaultActionIdentifier, options.Data)
|
||||
if err == nil {
|
||||
n.ActivationArguments = encodedPayload
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification data: %w", err)
|
||||
}
|
||||
n.ActivationArguments = encodedPayload
|
||||
}
|
||||
|
||||
return n.Push()
|
||||
@ -153,6 +158,10 @@ func (ns *Service) SendNotification(options NotificationOptions) error {
|
||||
// If a NotificationCategory is not registered a basic notification will be sent.
|
||||
// (subtitle and category id are only available on macOS)
|
||||
func (ns *Service) SendNotificationWithActions(options NotificationOptions) error {
|
||||
if err := validateNotificationOptions(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := saveIconToDir(); err != nil {
|
||||
fmt.Printf("Error saving icon: %v\n", err)
|
||||
}
|
||||
@ -191,15 +200,15 @@ func (ns *Service) SendNotificationWithActions(options NotificationOptions) erro
|
||||
n.ActivationArguments, _ = encodePayload(n.ActivationArguments, options.Data)
|
||||
|
||||
for index := range n.Actions {
|
||||
n.Actions[index].Arguments, _ = encodePayload(n.Actions[index].Arguments, options.Data)
|
||||
encodedPayload, err := encodePayload(n.Actions[index].Arguments, options.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode notification data: %w", err)
|
||||
}
|
||||
n.Actions[index].Arguments = encodedPayload
|
||||
}
|
||||
}
|
||||
|
||||
err := n.Push()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return n.Push()
|
||||
}
|
||||
|
||||
// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
|
||||
@ -344,20 +353,25 @@ func loadCategoriesFromRegistry() error {
|
||||
)
|
||||
if err != nil {
|
||||
if err == registry.ErrNotExist {
|
||||
// Not an error, no saved categories
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
return fmt.Errorf("failed to open registry key: %w", err)
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
data, _, err := key.GetStringValue(NotificationCategoriesRegistryKey)
|
||||
if err != nil {
|
||||
return err
|
||||
if err == registry.ErrNotExist {
|
||||
// No value yet, but key exists
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to read categories from registry: %w", err)
|
||||
}
|
||||
|
||||
categories := make(map[string]NotificationCategory)
|
||||
if err := json.Unmarshal([]byte(data), &categories); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to parse notification categories from registry: %w", err)
|
||||
}
|
||||
|
||||
notificationCategoriesLock.Lock()
|
||||
|
Loading…
Reference in New Issue
Block a user