diff --git a/bitchat/BitchatApp.swift b/bitchat/BitchatApp.swift
index e9152714..efc1d6f6 100644
--- a/bitchat/BitchatApp.swift
+++ b/bitchat/BitchatApp.swift
@@ -26,6 +26,14 @@ struct BitchatApp: App {
.environmentObject(chatViewModel)
.onAppear {
NotificationDelegate.shared.chatViewModel = chatViewModel
+ #if os(iOS)
+ appDelegate.chatViewModel = chatViewModel
+ #endif
+ // Check for shared content
+ checkForSharedContent()
+ }
+ .onOpenURL { url in
+ handleURL(url)
}
}
#if os(macOS)
@@ -33,10 +41,57 @@ struct BitchatApp: App {
.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"),
+ 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) {
+ let prefix = contentType == "url" ? "Shared link: " : ""
+ self.chatViewModel.sendMessage(prefix + 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
}
diff --git a/bitchat/Info.plist b/bitchat/Info.plist
index 6b60e722..20ca8dfe 100644
--- a/bitchat/Info.plist
+++ b/bitchat/Info.plist
@@ -40,5 +40,16 @@
UIInterfaceOrientationPortrait
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ bitchat
+
+ CFBundleURLName
+ chat.bitchat
+
+
diff --git a/bitchat/bitchat.entitlements b/bitchat/bitchat.entitlements
new file mode 100644
index 00000000..80566616
--- /dev/null
+++ b/bitchat/bitchat.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.application-groups
+
+ group.chat.bitchat
+
+
+
\ No newline at end of file
diff --git a/bitchatShareExtension/Info.plist b/bitchatShareExtension/Info.plist
new file mode 100644
index 00000000..d2b306a6
--- /dev/null
+++ b/bitchatShareExtension/Info.plist
@@ -0,0 +1,43 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ bitchat
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSExtension
+
+ NSExtensionAttributes
+
+ NSExtensionActivationRule
+
+ NSExtensionActivationSupportsText
+
+ NSExtensionActivationSupportsWebURLWithMaxCount
+ 1
+ NSExtensionActivationSupportsImageWithMaxCount
+ 1
+
+
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).ShareViewController
+ NSExtensionPointIdentifier
+ com.apple.share-services
+
+
+
\ No newline at end of file
diff --git a/bitchatShareExtension/ShareViewController.swift b/bitchatShareExtension/ShareViewController.swift
new file mode 100644
index 00000000..c323653a
--- /dev/null
+++ b/bitchatShareExtension/ShareViewController.swift
@@ -0,0 +1,122 @@
+//
+// ShareViewController.swift
+// bitchatShareExtension
+//
+// This is free and unencumbered software released into the public domain.
+// For more information, see
+//
+
+import UIKit
+import Social
+import UniformTypeIdentifiers
+
+class ShareViewController: SLComposeServiceViewController {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Set placeholder text
+ placeholder = "Share to bitchat..."
+ // Set character limit (optional)
+ charactersRemaining = 500
+ }
+
+ override func isContentValid() -> Bool {
+ // Validate that we have text content or attachments
+ if let text = contentText, !text.isEmpty {
+ return true
+ }
+ // Check if we have attachments
+ if let item = extensionContext?.inputItems.first as? NSExtensionItem,
+ let attachments = item.attachments,
+ !attachments.isEmpty {
+ return true
+ }
+ return false
+ }
+
+ override func didSelectPost() {
+ // Get the shared content
+ guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else {
+ self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+ return
+ }
+
+ // Process different types of shared content
+ for itemProvider in extensionItem.attachments ?? [] {
+ if itemProvider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
+ itemProvider.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { [weak self] (item, error) in
+ if let text = item as? String {
+ self?.handleSharedText(text)
+ }
+ }
+ } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
+ itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] (item, error) in
+ if let url = item as? URL {
+ self?.handleSharedURL(url)
+ }
+ }
+ } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
+ itemProvider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { [weak self] (item, error) in
+ if let image = item as? UIImage {
+ self?.handleSharedImage(image)
+ } else if let data = item as? Data {
+ if let image = UIImage(data: data) {
+ self?.handleSharedImage(image)
+ }
+ }
+ }
+ }
+ }
+
+ // If we have content text, share it
+ if let text = contentText, !text.isEmpty {
+ handleSharedText(text)
+ }
+
+ // Complete the share action
+ self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+ }
+
+ override func configurationItems() -> [Any]! {
+ // No configuration items needed
+ return []
+ }
+
+ // MARK: - Helper Methods
+
+ private func handleSharedText(_ text: String) {
+ // Save to shared user defaults to pass to main app
+ saveToSharedDefaults(content: text, type: "text")
+ openMainApp()
+ }
+
+ private func handleSharedURL(_ url: URL) {
+ // Convert URL to text and share
+ let text = url.absoluteString
+ saveToSharedDefaults(content: text, type: "url")
+ openMainApp()
+ }
+
+ private func handleSharedImage(_ image: UIImage) {
+ // For now, we'll just notify that image sharing isn't supported
+ // In the future, we could implement image sharing via the mesh
+ saveToSharedDefaults(content: "Image sharing coming soon!", type: "image")
+ openMainApp()
+ }
+
+ private func saveToSharedDefaults(content: String, type: String) {
+ // Use app groups to share data between extension and main app
+ if let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") {
+ userDefaults.set(content, forKey: "sharedContent")
+ userDefaults.set(type, forKey: "sharedContentType")
+ userDefaults.set(Date(), forKey: "sharedContentDate")
+ userDefaults.synchronize()
+ }
+ }
+
+ private func openMainApp() {
+ // Note: Share extensions cannot directly open the containing app
+ // The user will need to tap on the notification or manually open the app
+ // to see the shared content
+ }
+}
\ No newline at end of file
diff --git a/bitchatShareExtension/bitchatShareExtension.entitlements b/bitchatShareExtension/bitchatShareExtension.entitlements
new file mode 100644
index 00000000..80566616
--- /dev/null
+++ b/bitchatShareExtension/bitchatShareExtension.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.application-groups
+
+ group.chat.bitchat
+
+
+
\ No newline at end of file
diff --git a/project.yml b/project.yml
index 6741aebd..ba00597c 100644
--- a/project.yml
+++ b/project.yml
@@ -47,6 +47,32 @@ targets:
CODE_SIGNING_ALLOWED: YES
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS: YES
+ CODE_SIGN_ENTITLEMENTS: bitchat/bitchat.entitlements
+ dependencies:
+ - target: bitchatShareExtension
+ platformFilter: ios
+ embed: true
+
+ bitchatShareExtension:
+ type: app-extension
+ platform: iOS
+ sources:
+ - bitchatShareExtension
+ info:
+ path: bitchatShareExtension/Info.plist
+ properties:
+ CFBundleDisplayName: bitchat
+ CFBundleShortVersionString: $(MARKETING_VERSION)
+ CFBundleVersion: $(CURRENT_PROJECT_VERSION)
+ settings:
+ PRODUCT_BUNDLE_IDENTIFIER: chat.bitchat.ShareExtension
+ INFOPLIST_FILE: bitchatShareExtension/Info.plist
+ SWIFT_VERSION: 5.0
+ IPHONEOS_DEPLOYMENT_TARGET: 16.0
+ CODE_SIGN_STYLE: Automatic
+ CODE_SIGNING_REQUIRED: YES
+ CODE_SIGNING_ALLOWED: YES
+ CODE_SIGN_ENTITLEMENTS: bitchatShareExtension/bitchatShareExtension.entitlements
bitchatTests_iOS:
type: bundle.unit-test