mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 14:25:20 +00:00
* UI: replace textual 'close' with X icon\n\n- AppInfoView (iOS): use xmark icon in nav bar to match Location Notes style.\n- LocationChannelsSheet: use xmark icon for close on iOS/macOS toolbars; add accessibility label. * Location Notes: prefix usernames with @ and lighten #geohash\n\n- Show @ before usernames in notes list.\n- Split header into '@' and '#geohash' and color the geohash with secondary green for consistency. * Header spacing: add breathing room between channel badge, notes button, and people count\n\n- Add trailing padding after #mesh/#geohash badge.\n- Add leading padding before notes button and people counter to improve readability. * Header: nudge #mesh/#geohash badge right with leading padding * Location Notes + Header polish\n\n- Header: add space in '@ #geohash' and use darker green for geohash.\n- Notes list: render '@name#abcd' with darker green for #abcd to match chat.\n- Header: move geochat bookmark icon after #geohash badge with consistent spacing. * Location Notes: @name regular green, #abcd darker; nudge #hash\n\n- Render '@' and base name in regular green, suffix '#abcd' in darker green.\n- Add extra left padding before '#geohash' in notes header.\n- Increase leading padding for channel badge to push #mesh/#geohash further right. * Fix notes icon color: subscribe/count at block-level geohash\n\n- Use block (precision 7) geohash for notes: when opening sheet, on channel changes, and when subscribing the counter.\n- Aligns with LocationNotesManager which publishes at street-level geohash, allowing the counter to detect notes and turn icon blue. * Header spacing: move #mesh/#geohash closer to notes/bookmark\n\n- Reduce trailing padding on channel badge and leading padding on notes/bookmark to cluster them together.\n- Keeps larger gap before people count for readability. * Notes: standardize on building-level (8 chars) for publish/read\n\n- Use .building geohash when opening notes, reacting to channel updates, and subscribing the counter.\n- Update comments to reflect building-level scope. * Location Channels sheet: use black sheet background like other sheets\n\n- Add backgroundColor and apply to container and list.\n- Hide list default background with scrollContentBackground(.hidden). * Notes icon: use green when notes exist (matches app green) * Location Channels: boxed 'bookmarked' section; keep 'remove location access' outside box\n\n- Wrap bookmarked list in a rounded, subtle grey box within the list.\n- Ensure the 'remove location access' button is not inside the box and clears list row background. * Notes counter UX: avoid grey flicker when closing sheet\n\n- Preserve last count during resubscribe to prevent brief 0 state.\n- Keep existing subscription when building geohash temporarily unavailable; only cancel if none or permission revoked. * Notes counter: unsubscribe without clearing count on resubscribe\n\n- Avoid calling cancel() in subscribe; just unsubscribe old sub to prevent wiping count to 0.\n- Prevents green→grey flicker after closing sheet or location updates. * Notes icon: use sheet count until counter finishes initial load; don’t zero on sheet close\n\n- Compute hasNotes using max(count, sheetCount) while initialLoadComplete is false.\n- Remove sheetNotesCount reset on sheet disappear to avoid transient grey. * Location Notes header: remove extra gap before #geohash (drop leading padding) * Notes sheet: color #abcd suffix as darker green via opacity (match chat) * Notes sheet: show timestamp in brackets; drop #abcd from @name * chore: commit remaining local changes * Header: move notes + bookmark to left of #mesh/#geohash * Header spacing: tighten gap between #mesh/#geohash and peer count --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
128 lines
4.6 KiB
Swift
128 lines
4.6 KiB
Swift
//
|
|
// NotificationService.swift
|
|
// bitchat
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import Foundation
|
|
import UserNotifications
|
|
#if os(iOS)
|
|
import UIKit
|
|
#elseif os(macOS)
|
|
import AppKit
|
|
#endif
|
|
|
|
final class NotificationService {
|
|
static let shared = NotificationService()
|
|
|
|
private init() {}
|
|
|
|
func requestAuthorization() {
|
|
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
|
if granted {
|
|
// Permission granted
|
|
} else {
|
|
// Permission denied
|
|
}
|
|
}
|
|
}
|
|
|
|
func sendLocalNotification(title: String, body: String, identifier: String, userInfo: [String: Any]? = nil) {
|
|
// For now, skip app state check entirely to avoid thread issues
|
|
// The NotificationDelegate will handle foreground presentation
|
|
DispatchQueue.main.async {
|
|
let content = UNMutableNotificationContent()
|
|
content.title = title
|
|
content.body = body
|
|
content.sound = .default
|
|
if let userInfo = userInfo {
|
|
content.userInfo = userInfo
|
|
}
|
|
|
|
let request = UNNotificationRequest(
|
|
identifier: identifier,
|
|
content: content,
|
|
trigger: nil // Deliver immediately
|
|
)
|
|
|
|
UNUserNotificationCenter.current().add(request) { _ in
|
|
// Notification added
|
|
}
|
|
}
|
|
}
|
|
|
|
func sendMentionNotification(from sender: String, message: String) {
|
|
let title = "🫵 you were mentioned by \(sender)"
|
|
let body = message
|
|
let identifier = "mention-\(UUID().uuidString)"
|
|
|
|
sendLocalNotification(title: title, body: body, identifier: identifier)
|
|
}
|
|
|
|
func sendPrivateMessageNotification(from sender: String, message: String, peerID: String) {
|
|
let title = "🔒 DM from \(sender)"
|
|
let body = message
|
|
let identifier = "private-\(UUID().uuidString)"
|
|
let userInfo = ["peerID": peerID, "senderName": sender]
|
|
|
|
sendLocalNotification(title: title, body: body, identifier: identifier, userInfo: userInfo)
|
|
}
|
|
|
|
func sendFavoriteOnlineNotification(nickname: String) {
|
|
// Send directly without checking app state for favorites
|
|
DispatchQueue.main.async {
|
|
let content = UNMutableNotificationContent()
|
|
content.title = "⭐ \(nickname) is online!"
|
|
content.body = "wanna get in there?"
|
|
content.sound = .default
|
|
|
|
let request = UNNotificationRequest(
|
|
identifier: "favorite-online-\(UUID().uuidString)",
|
|
content: content,
|
|
trigger: nil
|
|
)
|
|
|
|
UNUserNotificationCenter.current().add(request) { _ in
|
|
// Notification added
|
|
}
|
|
}
|
|
}
|
|
|
|
// Geohash public chat notification with deep link to a specific geohash
|
|
func sendGeohashActivityNotification(geohash: String, titlePrefix: String = "#", bodyPreview: String) {
|
|
let title = "\(titlePrefix)\(geohash)"
|
|
let identifier = "geo-activity-\(geohash)-\(Date().timeIntervalSince1970)"
|
|
let deeplink = "bitchat://geohash/\(geohash)"
|
|
let userInfo: [String: Any] = ["deeplink": deeplink]
|
|
sendLocalNotification(title: title, body: bodyPreview, identifier: identifier, userInfo: userInfo)
|
|
}
|
|
|
|
func sendNetworkAvailableNotification(peerCount: Int) {
|
|
let title = "👥 bitchatters nearby!"
|
|
let body = peerCount == 1 ? "1 person around" : "\(peerCount) people around"
|
|
let identifier = "network-available-\(Date().timeIntervalSince1970)"
|
|
|
|
// For network notifications, we want to show them even in foreground
|
|
// No app state check - let the notification delegate handle presentation
|
|
DispatchQueue.main.async {
|
|
let content = UNMutableNotificationContent()
|
|
content.title = title
|
|
content.body = body
|
|
content.sound = .default
|
|
content.interruptionLevel = .timeSensitive // Make it more prominent
|
|
|
|
let request = UNNotificationRequest(
|
|
identifier: identifier,
|
|
content: content,
|
|
trigger: nil // Deliver immediately
|
|
)
|
|
|
|
UNUserNotificationCenter.current().add(request) { _ in
|
|
// Notification added
|
|
}
|
|
}
|
|
}
|
|
}
|