diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index a0a66a6d..5485d760 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -193,18 +193,18 @@ 3FA8FF26ABDC1C642A8C7AE5 /* BitchatMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitchatMessageTests.swift; sourceTree = ""; }; 527EB217EFDFAD4CF1C91F07 /* bitchat.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = bitchat.entitlements; sourceTree = ""; }; 53D535D9CE0B875F47402290 /* BinaryProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocolTests.swift; sourceTree = ""; }; - 61F92EBA29C47C0FCC482F1F /* bitchatShareExtension.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.extensionkit-extension"; path = bitchatShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 61F92EBA29C47C0FCC482F1F /* bitchatShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = bitchatShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6E2446380E7A44E49A35B664 /* IdentityModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityModels.swift; sourceTree = ""; }; 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = ""; }; 8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePaddingTests.swift; sourceTree = ""; }; - 8F3A7C058C2C8E1A06C8CF8B /* bitchat_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = bitchat_macOS.app; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8F3A7C058C2C8E1A06C8CF8B /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 95F16C3A4A5621C74461D8D3 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 96D0D41CA19EE5A772AA8434 /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9AC141774F6671FCDC347DC7 /* LinkPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewView.swift; sourceTree = ""; }; A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = ""; }; AA4D7595A613F7ED3B386132 /* MessageRetryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRetryService.swift; sourceTree = ""; }; - C0DB1DE27F0AAB5092663E8E /* bitchatTests_iOS.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = bitchatTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C0DB1DE27F0AAB5092663E8E /* bitchatTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = bitchatTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C1B378C16594575FCC7F9C75 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; CB043CA5EEB9AC8B07D61E97 /* OptimizedBloomFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptimizedBloomFilter.swift; sourceTree = ""; }; D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothMeshService.swift; sourceTree = ""; }; @@ -293,7 +293,7 @@ isa = PBXGroup; children = ( 96D0D41CA19EE5A772AA8434 /* bitchat.app */, - 8F3A7C058C2C8E1A06C8CF8B /* bitchat_macOS.app */, + 8F3A7C058C2C8E1A06C8CF8B /* bitchat.app */, 61F92EBA29C47C0FCC482F1F /* bitchatShareExtension.appex */, C0DB1DE27F0AAB5092663E8E /* bitchatTests_iOS.xctest */, 03C57F452B55FD0FD8F51421 /* bitchatTests_macOS.xctest */, @@ -392,7 +392,7 @@ packageProductDependencies = ( ); productName = bitchat_macOS; - productReference = 8F3A7C058C2C8E1A06C8CF8B /* bitchat_macOS.app */; + productReference = 8F3A7C058C2C8E1A06C8CF8B /* bitchat.app */; productType = "com.apple.product-type.application"; }; 47FF23248747DD7CB666CB91 /* bitchatTests_macOS */ = { @@ -475,7 +475,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1640; TargetAttributes = { 0576A29205865664C0937536 = { DevelopmentTeam = L3N5LHJD5Y; @@ -704,7 +704,6 @@ CODE_SIGNING_ALLOWED = YES; CODE_SIGNING_REQUIRED = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = L3N5LHJD5Y; INFOPLIST_FILE = bitchatTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -727,7 +726,6 @@ CODE_SIGNING_ALLOWED = YES; CODE_SIGNING_REQUIRED = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = L3N5LHJD5Y; INFOPLIST_FILE = bitchatTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -751,7 +749,7 @@ CODE_SIGNING_REQUIRED = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = L3N5LHJD5Y; + DEAD_CODE_STRIPPING = YES; INFOPLIST_FILE = bitchatTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -774,7 +772,6 @@ CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; CODE_SIGN_ENTITLEMENTS = bitchatShareExtension/bitchatShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = L3N5LHJD5Y; INFOPLIST_FILE = bitchatShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -799,7 +796,6 @@ CODE_SIGN_ENTITLEMENTS = bitchat/bitchat.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = bitchat; @@ -829,7 +825,7 @@ CODE_SIGNING_REQUIRED = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = L3N5LHJD5Y; + DEAD_CODE_STRIPPING = YES; INFOPLIST_FILE = bitchatTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -854,7 +850,6 @@ CODE_SIGN_ENTITLEMENTS = bitchat/bitchat.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = bitchat; @@ -886,7 +881,7 @@ CODE_SIGN_ENTITLEMENTS = "bitchat/bitchat-macOS.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = L3N5LHJD5Y; + DEAD_CODE_STRIPPING = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -896,6 +891,7 @@ MACOSX_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_NAME = bitchat; + REGISTER_APP_GROUPS = YES; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; @@ -936,9 +932,12 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -969,7 +968,7 @@ CODE_SIGN_ENTITLEMENTS = "bitchat/bitchat-macOS.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = L3N5LHJD5Y; + DEAD_CODE_STRIPPING = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -979,6 +978,7 @@ MACOSX_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_NAME = bitchat; + REGISTER_APP_GROUPS = YES; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; @@ -1019,9 +1019,12 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1057,7 +1060,6 @@ CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES; CODE_SIGN_ENTITLEMENTS = bitchatShareExtension/bitchatShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = L3N5LHJD5Y; INFOPLIST_FILE = bitchatShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/bitchat/Info.plist b/bitchat/Info.plist index 6e6d9504..fd87f8b5 100644 --- a/bitchat/Info.plist +++ b/bitchat/Info.plist @@ -35,6 +35,12 @@ bitchat uses Bluetooth to create a secure mesh network for chatting with nearby users. NSBluetoothPeripheralUsageDescription bitchat uses Bluetooth to discover and connect with other bitchat users nearby. + UIBackgroundModes + + bluetooth-central + bluetooth-peripheral + remote-notification + UILaunchStoryboardName LaunchScreen UISupportedInterfaceOrientations diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift index f798cd4f..d1213923 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -81,6 +81,7 @@ class BluetoothMeshService: NSObject { private let maxTTL: UInt8 = 7 // Maximum hops for long-distance delivery private var announcedToPeers = Set() // Track which peers we've announced to private var announcedPeers = Set() // Track peers who have already been announced + private var hasNotifiedNetworkAvailable = false // Track if we've notified about network availability private var intentionalDisconnects = Set() // Track peripherals we're disconnecting intentionally private var peerLastSeenTimestamps = LRUCache(maxSize: 100) // Bounded cache for peer timestamps private var cleanupTimer: Timer? // Timer to clean up stale peers @@ -599,6 +600,7 @@ class BluetoothMeshService: NSObject { activePeers.removeAll() } announcedPeers.removeAll() + hasNotifiedNetworkAvailable = false // Clear announcement tracking announcedToPeers.removeAll() @@ -1231,6 +1233,7 @@ class BluetoothMeshService: NSObject { peripheralRSSI.removeAll() announcedToPeers.removeAll() announcedPeers.removeAll() + hasNotifiedNetworkAvailable = false processedMessages.removeAll() incomingFragments.removeAll() fragmentMetadata.removeAll() @@ -1331,6 +1334,12 @@ class BluetoothMeshService: NSObject { if !peersToRemove.isEmpty { notifyPeerListUpdate() + + // Reset notification flag if network is now empty + let currentNetworkSize = collectionsQueue.sync { activePeers.count } + if currentNetworkSize == 0 { + hasNotifiedNetworkAvailable = false + } } } @@ -2079,6 +2088,13 @@ class BluetoothMeshService: NSObject { } self.notifyPeerListUpdate(immediate: true) + // Send network available notification if this is the first peer we've seen + let currentNetworkSize = collectionsQueue.sync { self.activePeers.count } + if currentNetworkSize > 0 && !hasNotifiedNetworkAvailable { + hasNotifiedNetworkAvailable = true + NotificationService.shared.sendNetworkAvailableNotification(peerCount: currentNetworkSize) + } + DispatchQueue.main.async { // Check if this is a favorite peer and send notification // Note: This might not work immediately if key exchange hasn't happened yet @@ -2928,6 +2944,12 @@ extension BluetoothMeshService: CBCentralManagerDelegate { DispatchQueue.main.async { self.delegate?.didDisconnectFromPeer(peerID) } + + // Reset notification flag if network is now empty + let currentNetworkSize = collectionsQueue.sync { activePeers.count } + if currentNetworkSize == 0 { + hasNotifiedNetworkAvailable = false + } } self.notifyPeerListUpdate() } diff --git a/bitchat/Services/NotificationService.swift b/bitchat/Services/NotificationService.swift index 61268a7b..74930709 100644 --- a/bitchat/Services/NotificationService.swift +++ b/bitchat/Services/NotificationService.swift @@ -77,10 +77,18 @@ class NotificationService { } func sendFavoriteOnlineNotification(nickname: String) { - let title = "⭐ \(nickname) is online" + let title = "⭐ \(nickname) is online!" let body = "wanna get in there?" let identifier = "favorite-online-\(UUID().uuidString)" sendLocalNotification(title: title, body: body, identifier: identifier) } + + func sendNetworkAvailableNotification(peerCount: Int) { + let title = "👥 bitchatters nearby!" + let body = peerCount == 1 ? "1 person around" : "\(peerCount) people around" + let identifier = "network-available" + + sendLocalNotification(title: title, body: body, identifier: identifier) + } } diff --git a/bitchat/Views/AppInfoView.swift b/bitchat/Views/AppInfoView.swift index f6b673d0..2c651b96 100644 --- a/bitchat/Views/AppInfoView.swift +++ b/bitchat/Views/AppInfoView.swift @@ -18,7 +18,7 @@ struct AppInfoView: View { // MARK: - Constants private enum Strings { - static let appName = "bitchat" + static let appName = "bitchat/" static let tagline = "mesh sidegroupchat" enum Features {