mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 05:05:19 +00:00
- Reduce VStack spacing from 4 to 2 and nested VStack from 2 to 0 - Set list row insets top/bottom to 0 to minimize spacing on iPhone - Remove all markdown link parsing and formatting - Share plain URLs instead of markdown when using share extension - Keep only plain text URL detection and preview functionality
188 lines
6.6 KiB
Swift
188 lines
6.6 KiB
Swift
//
|
|
// BitchatApp.swift
|
|
// bitchat
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import SwiftUI
|
|
import UserNotifications
|
|
|
|
@main
|
|
struct BitchatApp: App {
|
|
@StateObject private var chatViewModel = ChatViewModel()
|
|
#if os(iOS)
|
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
|
#elseif os(macOS)
|
|
@NSApplicationDelegateAdaptor(MacAppDelegate.self) var appDelegate
|
|
#endif
|
|
|
|
init() {
|
|
UNUserNotificationCenter.current().delegate = NotificationDelegate.shared
|
|
}
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
ContentView()
|
|
.environmentObject(chatViewModel)
|
|
.onAppear {
|
|
NotificationDelegate.shared.chatViewModel = chatViewModel
|
|
#if os(iOS)
|
|
appDelegate.chatViewModel = chatViewModel
|
|
#elseif os(macOS)
|
|
appDelegate.chatViewModel = chatViewModel
|
|
#endif
|
|
// Check for shared content
|
|
checkForSharedContent()
|
|
}
|
|
.onOpenURL { url in
|
|
handleURL(url)
|
|
}
|
|
#if os(iOS)
|
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
|
|
// Check for shared content when app becomes active
|
|
checkForSharedContent()
|
|
}
|
|
#endif
|
|
}
|
|
#if os(macOS)
|
|
.windowStyle(.hiddenTitleBar)
|
|
.windowResizability(.contentSize)
|
|
#endif
|
|
}
|
|
|
|
private func handleURL(_ url: URL) {
|
|
if url.scheme == "bitchat" && url.host == "share" {
|
|
// Handle shared content
|
|
checkForSharedContent()
|
|
}
|
|
}
|
|
|
|
private func checkForSharedContent() {
|
|
// Check app group for shared content from extension
|
|
guard let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") else {
|
|
return
|
|
}
|
|
|
|
guard let sharedContent = userDefaults.string(forKey: "sharedContent"),
|
|
let sharedDate = userDefaults.object(forKey: "sharedContentDate") as? Date else {
|
|
return
|
|
}
|
|
|
|
// Only process if shared within last 30 seconds
|
|
if Date().timeIntervalSince(sharedDate) < 30 {
|
|
let contentType = userDefaults.string(forKey: "sharedContentType") ?? "text"
|
|
|
|
// Clear the shared content
|
|
userDefaults.removeObject(forKey: "sharedContent")
|
|
userDefaults.removeObject(forKey: "sharedContentType")
|
|
userDefaults.removeObject(forKey: "sharedContentDate")
|
|
userDefaults.synchronize()
|
|
|
|
// Show notification about shared content
|
|
DispatchQueue.main.async {
|
|
// Add system message about sharing
|
|
let systemMessage = BitchatMessage(
|
|
sender: "system",
|
|
content: "preparing to share \(contentType)...",
|
|
timestamp: Date(),
|
|
isRelay: false
|
|
)
|
|
self.chatViewModel.messages.append(systemMessage)
|
|
}
|
|
|
|
// Send the shared content after a short delay
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
|
if contentType == "url" {
|
|
// Try to parse as JSON first
|
|
if let data = sharedContent.data(using: .utf8),
|
|
let urlData = try? JSONSerialization.jsonObject(with: data) as? [String: String],
|
|
let url = urlData["url"] {
|
|
// Send plain URL
|
|
self.chatViewModel.sendMessage(url)
|
|
} else {
|
|
// Fallback to simple URL
|
|
self.chatViewModel.sendMessage(sharedContent)
|
|
}
|
|
} else {
|
|
self.chatViewModel.sendMessage(sharedContent)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if os(iOS)
|
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
|
weak var chatViewModel: ChatViewModel?
|
|
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
|
return true
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if os(macOS)
|
|
import AppKit
|
|
|
|
class MacAppDelegate: NSObject, NSApplicationDelegate {
|
|
weak var chatViewModel: ChatViewModel?
|
|
|
|
func applicationWillTerminate(_ notification: Notification) {
|
|
chatViewModel?.applicationWillTerminate()
|
|
}
|
|
|
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
|
return true
|
|
}
|
|
}
|
|
#endif
|
|
|
|
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
|
static let shared = NotificationDelegate()
|
|
weak var chatViewModel: ChatViewModel?
|
|
|
|
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
|
let identifier = response.notification.request.identifier
|
|
let userInfo = response.notification.request.content.userInfo
|
|
|
|
// Check if this is a private message notification
|
|
if identifier.hasPrefix("private-") {
|
|
// Get peer ID from userInfo
|
|
if let peerID = userInfo["peerID"] as? String {
|
|
DispatchQueue.main.async {
|
|
self.chatViewModel?.startPrivateChat(with: peerID)
|
|
}
|
|
}
|
|
}
|
|
|
|
completionHandler()
|
|
}
|
|
|
|
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
let identifier = notification.request.identifier
|
|
let userInfo = notification.request.content.userInfo
|
|
|
|
// Check if this is a private message notification
|
|
if identifier.hasPrefix("private-") {
|
|
// Get peer ID from userInfo
|
|
if let peerID = userInfo["peerID"] as? String {
|
|
// Don't show notification if the private chat is already open
|
|
if chatViewModel?.selectedPrivateChatPeer == peerID {
|
|
completionHandler([])
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Show notification in all other cases
|
|
completionHandler([.banner, .sound])
|
|
}
|
|
}
|
|
|
|
extension String {
|
|
var nilIfEmpty: String? {
|
|
self.isEmpty ? nil : self
|
|
}
|
|
} |