diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index 3b59b2f9..1306435d 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -3,17 +3,19 @@ archiveVersion = 1; classes = { }; - objectVersion = 63; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 0245710AEAA58AD0A1425234 /* OptimizedBloomFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB043CA5EEB9AC8B07D61E97 /* OptimizedBloomFilter.swift */; }; 0DAFF1DDE9BA83FF648D5AB3 /* BitchatMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA8FF26ABDC1C642A8C7AE5 /* BitchatMessageTests.swift */; }; 10E68BB889356219189E38EC /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; }; + 17901751FD8010AFC8E750F2 /* bitchatShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 61F92EBA29C47C0FCC482F1F /* bitchatShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1D9674FA5F998503831DC281 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08E03AA0C63E97C91749AEC /* ContentView.swift */; }; 1F48A8CEEE9399D1EBD08F0C /* OptimizedBloomFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB043CA5EEB9AC8B07D61E97 /* OptimizedBloomFilter.swift */; }; - 230B11C5BF035D35638B21C8 /* PasswordProtectedRoomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12482E4AD78B861C538449 /* PasswordProtectedRoomTests.swift */; }; + 24F17B1446E13F42652B7B08 /* PasswordProtectedChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036A1A705AAF9EC21F4354BE /* PasswordProtectedChannelTests.swift */; }; 2E71E320EA921498C57E023B /* BitchatMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA8FF26ABDC1C642A8C7AE5 /* BitchatMessageTests.swift */; }; + 31D147471B9F4E2815352DDA /* LinkPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC141774F6671FCDC347DC7 /* LinkPreviewView.swift */; }; 4274B6016F755946FBF2513E /* MessageRetentionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A85BFDDE65B4CD8BDF6DDB /* MessageRetentionService.swift */; }; 4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */; }; 4E778E5A414571ACAC2A0F01 /* MessageRetentionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A85BFDDE65B4CD8BDF6DDB /* MessageRetentionService.swift */; }; @@ -25,6 +27,7 @@ 749D8CF8A362B6CD0786782D /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448F84BF86A42A3CC4A9379 /* NotificationService.swift */; }; 7576A357B278E5733E9D9F33 /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */; }; 7A50E2F04A3515A7E90EEAE4 /* BluetoothMeshService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */; }; + 7A5B1AB5642FEC168E917949 /* LinkPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC141774F6671FCDC347DC7 /* LinkPreviewView.swift */; }; 7DCA0DBCB8884E3B31C7BCE3 /* CompressionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F149C43D1915831B60FE09 /* CompressionUtil.swift */; }; 7DD72D928FF9DD3CA81B46B0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */; }; 846E2B446E36639159704730 /* BloomFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB3A8FE16333ED12FCB8ACB /* BloomFilterTests.swift */; }; @@ -32,7 +35,10 @@ 8F0BFC2D2B2A5E7B70C3B485 /* BinaryProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D535D9CE0B875F47402290 /* BinaryProtocolTests.swift */; }; 8F737CE0435792CC2AD65FCB /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136696FC4436A02D98CE6A77 /* KeychainManager.swift */; }; 923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */; }; + 9269B4230187A9EA969BEDB7 /* PasswordProtectedChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036A1A705AAF9EC21F4354BE /* PasswordProtectedChannelTests.swift */; }; 92D34E7A07C990C8A815B0CE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08E03AA0C63E97C91749AEC /* ContentView.swift */; }; + 97BC7E9FAB24FFE643DB5EB2 /* bitchatShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 61F92EBA29C47C0FCC482F1F /* bitchatShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 9C7D287C8E67AAE576A5ECB7 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B378C16594575FCC7F9C75 /* ShareViewController.swift */; }; ABAF130D88561F4A646F0430 /* AppInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */; }; AD11E46940D742AEAF547EB2 /* AppInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */; }; ADC66F95FBD513B10411ADB3 /* MessagePaddingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */; }; @@ -49,7 +55,6 @@ DDA1DFAB1FF7AADE52DC0F53 /* EncryptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC1563390A15C042D059CF9 /* EncryptionService.swift */; }; F00B713D5053617FB5F3F1BE /* MessagePaddingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */; }; F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */; }; - F4A689F5F34125AE1BFD5599 /* PasswordProtectedRoomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB12482E4AD78B861C538449 /* PasswordProtectedRoomTests.swift */; }; FB8819B4C84FAFEF5C36B216 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136696FC4436A02D98CE6A77 /* KeychainManager.swift */; }; /* End PBXBuildFile section */ @@ -61,6 +66,20 @@ remoteGlobalIDString = AF077EA0474EDEDE2C72716C; remoteInfo = bitchat_iOS; }; + DCB3AD9121A64769FAD7BAD0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 475D96681D0EA0AE57A4E06E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57CA17A36A2532A6CFF367BB; + remoteInfo = bitchatShareExtension; + }; + E35E7AF9854A2E72452DD34F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 475D96681D0EA0AE57A4E06E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57CA17A36A2532A6CFF367BB; + remoteInfo = bitchatShareExtension; + }; FF470234EF8C6BB8865B80B5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 475D96681D0EA0AE57A4E06E /* Project object */; @@ -70,7 +89,33 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 1AE5E5D867B14EFDBE2C8889 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 97BC7E9FAB24FFE643DB5EB2 /* bitchatShareExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + B6C356449BAE4E0F650565D1 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 17901751FD8010AFC8E750F2 /* bitchatShareExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 036A1A705AAF9EC21F4354BE /* PasswordProtectedChannelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordProtectedChannelTests.swift; sourceTree = ""; }; 03C57F452B55FD0FD8F51421 /* bitchatTests_macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = bitchatTests_macOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 12B9C3EDF3BC73D3BC106DA4 /* DeliveryTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryTracker.swift; sourceTree = ""; }; 136696FC4436A02D98CE6A77 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; @@ -78,19 +123,25 @@ 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitchatProtocol.swift; sourceTree = ""; }; 32F149C43D1915831B60FE09 /* CompressionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompressionUtil.swift; sourceTree = ""; }; 3448F84BF86A42A3CC4A9379 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 3668EEBB42FD4A24D5D83B7B /* bitchatShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = bitchatShareExtension.entitlements; sourceTree = ""; }; + 3A556661F74B7D5AE2F0521B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 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.app-extension"; path = bitchatShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 67A85BFDDE65B4CD8BDF6DDB /* MessageRetentionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRetentionService.swift; sourceTree = ""; }; 6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = ""; }; 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = ""; }; 7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagePaddingTests.swift; sourceTree = ""; }; - 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; 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; }; + 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 = ""; }; D69A18D27F9A565FD6041E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; @@ -98,7 +149,6 @@ EA706D8E5097785414646A8E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; ED176FF3B274E35C2D827894 /* BatteryOptimizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryOptimizer.swift; sourceTree = ""; }; EF625BB3AD919322C01A46B2 /* BitchatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitchatApp.swift; sourceTree = ""; }; - FB12482E4AD78B861C538449 /* PasswordProtectedRoomTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordProtectedRoomTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXGroup section */ @@ -106,6 +156,7 @@ isa = PBXGroup; children = ( 2F82C5FC8433F4064F079D1F /* bitchat */, + A2E8C336FA1ADBEC03261DFD /* bitchatShareExtension */, C3D98EB3E1B455E321F519F4 /* bitchatTests */, 9F37F9F2C353B58AC809E93B /* Products */, ); @@ -115,6 +166,7 @@ isa = PBXGroup; children = ( 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */, + 527EB217EFDFAD4CF1C91F07 /* bitchat.entitlements */, EF625BB3AD919322C01A46B2 /* BitchatApp.swift */, EA706D8E5097785414646A8E /* Info.plist */, ADD53BCDA233C02E53458926 /* Protocols */, @@ -149,17 +201,29 @@ children = ( 997D512074C64904D75DDD40 /* bitchat.app */, 7EEBDA723E1CFD88758DA4AC /* bitchat.app */, + 61F92EBA29C47C0FCC482F1F /* bitchatShareExtension.appex */, C0DB1DE27F0AAB5092663E8E /* bitchatTests_iOS.xctest */, 03C57F452B55FD0FD8F51421 /* bitchatTests_macOS.xctest */, ); name = Products; sourceTree = ""; }; + A2E8C336FA1ADBEC03261DFD /* bitchatShareExtension */ = { + isa = PBXGroup; + children = ( + 3668EEBB42FD4A24D5D83B7B /* bitchatShareExtension.entitlements */, + 3A556661F74B7D5AE2F0521B /* Info.plist */, + C1B378C16594575FCC7F9C75 /* ShareViewController.swift */, + ); + path = bitchatShareExtension; + sourceTree = ""; + }; A55126E93155456CAA8D6656 /* Views */ = { isa = PBXGroup; children = ( 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */, A08E03AA0C63E97C91749AEC /* ContentView.swift */, + 9AC141774F6671FCDC347DC7 /* LinkPreviewView.swift */, ); path = Views; sourceTree = ""; @@ -181,7 +245,7 @@ 1EB3A8FE16333ED12FCB8ACB /* BloomFilterTests.swift */, D69A18D27F9A565FD6041E12 /* Info.plist */, 8DE9CDF66D4E52D268851048 /* MessagePaddingTests.swift */, - FB12482E4AD78B861C538449 /* PasswordProtectedRoomTests.swift */, + 036A1A705AAF9EC21F4354BE /* PasswordProtectedChannelTests.swift */, ); path = bitchatTests; sourceTree = ""; @@ -209,10 +273,12 @@ buildPhases = ( 137ABE739BF20ACDDF8CC605 /* Sources */, 0214973A876129753D39EB47 /* Resources */, + 1AE5E5D867B14EFDBE2C8889 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + D8120CFFA88B852C3D106FA6 /* PBXTargetDependency */, ); name = bitchat_macOS; packageProductDependencies = ( @@ -239,6 +305,23 @@ productReference = 03C57F452B55FD0FD8F51421 /* bitchatTests_macOS.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 57CA17A36A2532A6CFF367BB /* bitchatShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = E4EA6DC648DF55FF84032EB5 /* Build configuration list for PBXNativeTarget "bitchatShareExtension" */; + buildPhases = ( + 0A08E70F08F55FD5BA8C7EF3 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = bitchatShareExtension; + packageProductDependencies = ( + ); + productName = bitchatShareExtension; + productReference = 61F92EBA29C47C0FCC482F1F /* bitchatShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 6CB97DF2EA57234CB3E563B8 /* bitchatTests_iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 38C4AF6313E5037F25CEF30B /* Build configuration list for PBXNativeTarget "bitchatTests_iOS" */; @@ -263,10 +346,12 @@ buildPhases = ( 4E49E34F00154C051AE90FED /* Sources */, CD6E8F32BC38357473954F97 /* Resources */, + B6C356449BAE4E0F650565D1 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 6EB655BA5DB11909C1DEC460 /* PBXTargetDependency */, ); name = bitchat_iOS; packageProductDependencies = ( @@ -285,9 +370,15 @@ LastUpgradeCheck = 1430; TargetAttributes = { 0576A29205865664C0937536 = { + DevelopmentTeam = L3N5LHJD5Y; + ProvisioningStyle = Automatic; + }; + 57CA17A36A2532A6CFF367BB = { + DevelopmentTeam = L3N5LHJD5Y; ProvisioningStyle = Automatic; }; AF077EA0474EDEDE2C72716C = { + DevelopmentTeam = L3N5LHJD5Y; ProvisioningStyle = Automatic; }; }; @@ -302,9 +393,11 @@ ); mainGroup = 18198ED912AAF495D8AF7763; minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 54; projectDirPath = ""; projectRoot = ""; targets = ( + 57CA17A36A2532A6CFF367BB /* bitchatShareExtension */, 6CB97DF2EA57234CB3E563B8 /* bitchatTests_iOS */, 47FF23248747DD7CB666CB91 /* bitchatTests_macOS */, AF077EA0474EDEDE2C72716C /* bitchat_iOS */, @@ -333,6 +426,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 0A08E70F08F55FD5BA8C7EF3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9C7D287C8E67AAE576A5ECB7 /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 137ABE739BF20ACDDF8CC605 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -349,6 +450,7 @@ 8DCEEA289EF7C49E7CD38B08 /* DeliveryTracker.swift in Sources */, 739429DFDE5C5829CF70DA7D /* EncryptionService.swift in Sources */, FB8819B4C84FAFEF5C36B216 /* KeychainManager.swift in Sources */, + 31D147471B9F4E2815352DDA /* LinkPreviewView.swift in Sources */, 4E778E5A414571ACAC2A0F01 /* MessageRetentionService.swift in Sources */, C99763A4761567F587D21688 /* MessageRetryService.swift in Sources */, 749D8CF8A362B6CD0786782D /* NotificationService.swift in Sources */, @@ -372,6 +474,7 @@ CD0AE423F03AC52BAFC16834 /* DeliveryTracker.swift in Sources */, DDA1DFAB1FF7AADE52DC0F53 /* EncryptionService.swift in Sources */, 8F737CE0435792CC2AD65FCB /* KeychainManager.swift in Sources */, + 7A5B1AB5642FEC168E917949 /* LinkPreviewView.swift in Sources */, 4274B6016F755946FBF2513E /* MessageRetentionService.swift in Sources */, CEAE115C9C3EB3C4ED82F128 /* MessageRetryService.swift in Sources */, 61C81ED5F679D5E973EE0C07 /* NotificationService.swift in Sources */, @@ -387,7 +490,7 @@ 2E71E320EA921498C57E023B /* BitchatMessageTests.swift in Sources */, C91FDE97070433E6CFE95C55 /* BloomFilterTests.swift in Sources */, ADC66F95FBD513B10411ADB3 /* MessagePaddingTests.swift in Sources */, - F4A689F5F34125AE1BFD5599 /* PasswordProtectedRoomTests.swift in Sources */, + 24F17B1446E13F42652B7B08 /* PasswordProtectedChannelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -399,7 +502,7 @@ 0DAFF1DDE9BA83FF648D5AB3 /* BitchatMessageTests.swift in Sources */, 846E2B446E36639159704730 /* BloomFilterTests.swift in Sources */, F00B713D5053617FB5F3F1BE /* MessagePaddingTests.swift in Sources */, - 230B11C5BF035D35638B21C8 /* PasswordProtectedRoomTests.swift in Sources */, + 9269B4230187A9EA969BEDB7 /* PasswordProtectedChannelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -411,6 +514,16 @@ target = 0576A29205865664C0937536 /* bitchat_macOS */; targetProxy = FF470234EF8C6BB8865B80B5 /* PBXContainerItemProxy */; }; + 6EB655BA5DB11909C1DEC460 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 57CA17A36A2532A6CFF367BB /* bitchatShareExtension */; + targetProxy = E35E7AF9854A2E72452DD34F /* PBXContainerItemProxy */; + }; + D8120CFFA88B852C3D106FA6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 57CA17A36A2532A6CFF367BB /* bitchatShareExtension */; + targetProxy = DCB3AD9121A64769FAD7BAD0 /* PBXContainerItemProxy */; + }; D8C09F21DB7DC06E8E672C21 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AF077EA0474EDEDE2C72716C /* bitchat_iOS */; @@ -476,6 +589,28 @@ }; name = Release; }; + 3DCF45111852FB2AEBE05E31 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGNING_ALLOWED = YES; + CODE_SIGNING_REQUIRED = 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 = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat.ShareExtension; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 702E7395723CADA4B830F4A9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -483,6 +618,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGNING_ALLOWED = YES; CODE_SIGNING_REQUIRED = YES; + CODE_SIGN_ENTITLEMENTS = bitchat/bitchat.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = L3N5LHJD5Y; @@ -529,6 +665,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGNING_ALLOWED = YES; CODE_SIGNING_REQUIRED = YES; + CODE_SIGN_ENTITLEMENTS = bitchat/bitchat.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = L3N5LHJD5Y; @@ -556,8 +693,10 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGNING_ALLOWED = YES; CODE_SIGNING_REQUIRED = YES; + CODE_SIGN_ENTITLEMENTS = bitchat/bitchat.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -639,8 +778,10 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGNING_ALLOWED = YES; CODE_SIGNING_REQUIRED = YES; + CODE_SIGN_ENTITLEMENTS = bitchat/bitchat.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -722,6 +863,28 @@ }; name = Debug; }; + DAC5E82049F8A97360BE63D6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGNING_ALLOWED = YES; + CODE_SIGNING_REQUIRED = 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 = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat.ShareExtension; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -770,6 +933,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + E4EA6DC648DF55FF84032EB5 /* Build configuration list for PBXNativeTarget "bitchatShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DAC5E82049F8A97360BE63D6 /* Debug */, + 3DCF45111852FB2AEBE05E31 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; /* End XCConfigurationList section */ }; rootObject = 475D96681D0EA0AE57A4E06E /* Project object */; diff --git a/bitchat/BitchatApp.swift b/bitchat/BitchatApp.swift index e9152714..10964f5d 100644 --- a/bitchat/BitchatApp.swift +++ b/bitchat/BitchatApp.swift @@ -26,17 +26,108 @@ 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(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 { + print("DEBUG: Failed to access app group UserDefaults") + return + } + + guard let sharedContent = userDefaults.string(forKey: "sharedContent"), + let sharedDate = userDefaults.object(forKey: "sharedContentDate") as? Date else { + print("DEBUG: No shared content found in UserDefaults") + return + } + + print("DEBUG: Found shared content: \(sharedContent)") + print("DEBUG: Shared date: \(sharedDate)") + print("DEBUG: Time since shared: \(Date().timeIntervalSince(sharedDate)) seconds") + + // Only process if shared within last 30 seconds + if Date().timeIntervalSince(sharedDate) < 30 { + let contentType = userDefaults.string(forKey: "sharedContentType") ?? "text" + print("DEBUG: Content type: \(contentType)") + + // 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" { + print("DEBUG: Processing URL content") + // 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"], + let title = urlData["title"] { + // Send just emoji with hidden markdown link + let markdownLink = "👇 [\(title)](\(url))" + print("DEBUG: Sending markdown link: \(markdownLink)") + self.chatViewModel.sendMessage(markdownLink) + } else { + // Fallback to simple URL + print("DEBUG: Failed to parse JSON, sending as plain URL") + self.chatViewModel.sendMessage("Shared link: \(sharedContent)") + } + } else { + print("DEBUG: Sending plain text: \(sharedContent)") + self.chatViewModel.sendMessage(sharedContent) + } + } + } else { + print("DEBUG: Shared content is too old, ignoring") + } + } } #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..a056533c 100644 --- a/bitchat/Info.plist +++ b/bitchat/Info.plist @@ -40,5 +40,14 @@ UIInterfaceOrientationPortrait + CFBundleURLTypes + + + CFBundleURLSchemes + + bitchat + + + diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 71f54b24..f9bdd262 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -1498,8 +1498,34 @@ class ChatViewModel: ObservableObject { senderStyle.font = .system(size: 14, weight: .medium, design: .monospaced) result.append(sender.mergingAttributes(senderStyle)) - // Process content with hashtags and mentions - let content = message.content + // Process content with hashtags, mentions, and markdown links + var content = message.content + + // First, check if content starts with 👇 followed by markdown link + if content.hasPrefix("👇 [") { + // This is a URL share - remove everything after the emoji + if let linkStart = content.firstIndex(of: "[") { + let indexBeforeLink = content.index(before: linkStart) + content = String(content[.. +// + +import SwiftUI +#if os(iOS) +import LinkPresentation +#endif + +struct LinkPreviewView: View { + let url: URL + let title: String? + @Environment(\.colorScheme) var colorScheme + @State private var metadata: LPLinkMetadata? + + private var textColor: Color { + colorScheme == .dark ? Color.green : Color(red: 0, green: 0.5, blue: 0) + } + + private var backgroundColor: Color { + colorScheme == .dark ? Color.black : Color.white + } + + private var borderColor: Color { + textColor.opacity(0.3) + } + + var body: some View { + // Always use our custom compact view for consistent appearance + compactLinkView + .onAppear { + loadMetadata() + } + } + + #if os(iOS) + @State private var previewImage: UIImage? = nil + #endif + + private var compactLinkView: some View { + Button(action: { + #if os(iOS) + UIApplication.shared.open(url) + #else + NSWorkspace.shared.open(url) + #endif + }) { + HStack(spacing: 12) { + // Preview image or icon + Group { + #if os(iOS) + if let image = previewImage { + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 60, height: 60) + .clipped() + .cornerRadius(8) + } else { + // Favicon or default icon + RoundedRectangle(cornerRadius: 8) + .fill(Color.blue.opacity(0.1)) + .frame(width: 60, height: 60) + .overlay( + Image(systemName: "link") + .font(.system(size: 24)) + .foregroundColor(Color.blue) + ) + } + #else + RoundedRectangle(cornerRadius: 8) + .fill(Color.blue.opacity(0.1)) + .frame(width: 60, height: 60) + .overlay( + Image(systemName: "link") + .font(.system(size: 24)) + .foregroundColor(Color.blue) + ) + #endif + } + + VStack(alignment: .leading, spacing: 4) { + // Title + Text(metadata?.title ?? title ?? url.host ?? "Link") + .font(.system(size: 14, weight: .semibold, design: .monospaced)) + .foregroundColor(textColor) + .lineLimit(2) + .multilineTextAlignment(.leading) + + // Host + Text(url.host ?? url.absoluteString) + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(textColor.opacity(0.6)) + .lineLimit(1) + } + + Spacer() + } + .padding(10) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(colorScheme == .dark ? Color.gray.opacity(0.15) : Color.gray.opacity(0.08)) + ) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(borderColor, lineWidth: 1) + ) + } + .buttonStyle(.plain) + } + + private var simpleLinkView: some View { + Button(action: { + #if os(iOS) + UIApplication.shared.open(url) + #else + NSWorkspace.shared.open(url) + #endif + }) { + HStack(spacing: 12) { + // Link icon + Image(systemName: "link.circle.fill") + .font(.system(size: 32)) + .foregroundColor(Color.blue.opacity(0.8)) + .frame(width: 40, height: 40) + + VStack(alignment: .leading, spacing: 4) { + // Title + Text(title ?? url.host ?? "Link") + .font(.system(size: 14, weight: .semibold, design: .monospaced)) + .foregroundColor(textColor) + .lineLimit(2) + .multilineTextAlignment(.leading) + + // URL + Text(url.absoluteString) + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(Color.blue) + .lineLimit(1) + .truncationMode(.middle) + } + + Spacer() + + // Arrow indicator + Image(systemName: "chevron.right") + .font(.system(size: 14)) + .foregroundColor(textColor.opacity(0.5)) + } + .padding(12) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(colorScheme == .dark ? Color.gray.opacity(0.15) : Color.gray.opacity(0.08)) + ) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(borderColor, lineWidth: 1) + ) + } + .buttonStyle(.plain) + } + + private func loadMetadata() { + #if os(iOS) + guard metadata == nil else { return } + + let provider = LPMetadataProvider() + provider.startFetchingMetadata(for: url) { fetchedMetadata, error in + DispatchQueue.main.async { + if let fetchedMetadata = fetchedMetadata { + // Use the fetched metadata, or create new with our title + if let title = self.title, !title.isEmpty { + fetchedMetadata.title = title + } + self.metadata = fetchedMetadata + + // Try to extract image + if let imageProvider = fetchedMetadata.imageProvider { + imageProvider.loadObject(ofClass: UIImage.self) { image, error in + DispatchQueue.main.async { + if let image = image as? UIImage { + self.previewImage = image + } + } + } + } + } + } + } + #endif + } +} + +#if os(iOS) +// UIViewRepresentable wrapper for LPLinkView +struct LinkPreview: UIViewRepresentable { + let metadata: LPLinkMetadata + + func makeUIView(context: Context) -> UIView { + let containerView = UIView() + containerView.backgroundColor = .clear + + let linkView = LPLinkView(metadata: metadata) + linkView.isUserInteractionEnabled = false // We handle taps at the SwiftUI level + linkView.translatesAutoresizingMaskIntoConstraints = false + + containerView.addSubview(linkView) + NSLayoutConstraint.activate([ + linkView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + linkView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + linkView.topAnchor.constraint(equalTo: containerView.topAnchor), + linkView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) + ]) + + return containerView + } + + func updateUIView(_ uiView: UIView, context: Context) { + // Update if needed + } +} +#endif + +// Helper to extract URLs from text +extension String { + func extractURLs() -> [(url: URL, range: Range)] { + var urls: [(URL, Range)] = [] + + // Check for markdown-style links [title](url) + let markdownPattern = #"\[([^\]]+)\]\(([^)]+)\)"# + if let regex = try? NSRegularExpression(pattern: markdownPattern) { + let matches = regex.matches(in: self, range: NSRange(location: 0, length: self.utf16.count)) + for match in matches { + if let urlRange = Range(match.range(at: 2), in: self), + let url = URL(string: String(self[urlRange])), + let fullRange = Range(match.range, in: self) { + urls.append((url, fullRange)) + } + } + } + + // Also check for plain URLs + let types: NSTextCheckingResult.CheckingType = .link + if let detector = try? NSDataDetector(types: types.rawValue) { + let matches = detector.matches(in: self, range: NSRange(location: 0, length: self.utf16.count)) + for match in matches { + if let range = Range(match.range, in: self), + let url = match.url { + // Don't add if this URL is already part of a markdown link + let isPartOfMarkdown = urls.contains { $0.1.overlaps(range) } + if !isPartOfMarkdown { + urls.append((url, range)) + } + } + } + } + + return urls + } + + func extractMarkdownLink() -> (title: String, url: URL)? { + print("DEBUG: Checking for markdown link in: \(self)") + let pattern = #"\[([^\]]+)\]\(([^)]+)\)"# + if let regex = try? NSRegularExpression(pattern: pattern), + let match = regex.firstMatch(in: self, range: NSRange(location: 0, length: self.utf16.count)) { + if let titleRange = Range(match.range(at: 1), in: self), + let urlRange = Range(match.range(at: 2), in: self), + let url = URL(string: String(self[urlRange])) { + let result = (String(self[titleRange]), url) + print("DEBUG: Found markdown link - title: \(result.0), url: \(result.1)") + return result + } + } + print("DEBUG: No markdown link found") + return nil + } +} + +#Preview { + VStack { + LinkPreviewView(url: URL(string: "https://example.com")!, title: "Example Website") + .padding() + } +} \ No newline at end of file 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..caa4c793 --- /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 + XPC! + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsImageWithMaxCount + 1 + NSExtensionActivationSupportsText + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionPointIdentifier + com.apple.share-services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController + + + diff --git a/bitchatShareExtension/ShareViewController.swift b/bitchatShareExtension/ShareViewController.swift new file mode 100644 index 00000000..17175263 --- /dev/null +++ b/bitchatShareExtension/ShareViewController.swift @@ -0,0 +1,218 @@ +// +// 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() { + guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else { + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + return + } + + print("ShareExtension: Processing share with \(extensionItem.attachments?.count ?? 0) attachments") + + // Get the page title from the compose view or extension item + let pageTitle = self.contentText ?? extensionItem.attributedContentText?.string ?? extensionItem.attributedTitle?.string + print("ShareExtension: Page title: \(pageTitle ?? "none")") + + var foundURL: URL? = nil + let group = DispatchGroup() + + // IMPORTANT: Check if the NSExtensionItem itself has a URL + // Safari often provides the URL as an attributedString with a link + if let attributedText = extensionItem.attributedContentText { + print("ShareExtension: Checking attributed text for URLs") + let text = attributedText.string + let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) + let matches = detector?.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count)) + if let firstMatch = matches?.first, let url = firstMatch.url { + print("ShareExtension: Found URL in attributed text: \(url.absoluteString)") + foundURL = url + } + } + + // Only check attachments if we haven't found a URL yet + if foundURL == nil { + for (index, itemProvider) in (extensionItem.attachments ?? []).enumerated() { + print("ShareExtension: Attachment \(index) types: \(itemProvider.registeredTypeIdentifiers)") + + // Try multiple URL type identifiers that Safari might use + let urlTypes = [ + UTType.url.identifier, + "public.url", + "public.file-url" + ] + + for urlType in urlTypes { + if itemProvider.hasItemConformingToTypeIdentifier(urlType) { + group.enter() + itemProvider.loadItem(forTypeIdentifier: urlType, options: nil) { (item, error) in + defer { group.leave() } + + if let url = item as? URL { + print("ShareExtension: Found URL: \(url.absoluteString)") + foundURL = url + } else if let data = item as? Data, + let urlString = String(data: data, encoding: .utf8), + let url = URL(string: urlString) { + print("ShareExtension: Found URL from data: \(url.absoluteString)") + foundURL = url + } else if let string = item as? String, + let url = URL(string: string) { + print("ShareExtension: Found URL from string: \(url.absoluteString)") + foundURL = url + } + } + break // Found a URL type, no need to check other types + } + } + + // Also check for plain text that might be a URL + if foundURL == nil && itemProvider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) { + group.enter() + itemProvider.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { (item, error) in + defer { group.leave() } + + if let text = item as? String { + // Check if the text is actually a URL + if let url = URL(string: text), + (url.scheme == "http" || url.scheme == "https") { + print("ShareExtension: Found URL in plain text: \(url.absoluteString)") + foundURL = url + } + } + } + } + } + } // End of if foundURL == nil + + // Process after all checks complete + group.notify(queue: .main) { [weak self] in + if let url = foundURL { + // We have a URL! Create the JSON data + let urlData: [String: String] = [ + "url": url.absoluteString, + "title": pageTitle ?? url.host ?? "Shared Link" + ] + + print("ShareExtension: Saving URL share - url: \(url.absoluteString), title: \(urlData["title"] ?? "")") + + if let jsonData = try? JSONSerialization.data(withJSONObject: urlData), + let jsonString = String(data: jsonData, encoding: .utf8) { + self?.saveToSharedDefaults(content: jsonString, type: "url") + } + } else if let title = pageTitle, !title.isEmpty { + // No URL found, just share the text + print("ShareExtension: No URL found, sharing as text: \(title)") + self?.saveToSharedDefaults(content: title, type: "text") + } + + 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) { + // Get the page title if available from the extension context + var pageTitle: String? = nil + if let item = extensionContext?.inputItems.first as? NSExtensionItem { + pageTitle = item.attributedContentText?.string ?? item.attributedTitle?.string + } + + // Create a structured format for URL sharing + let urlData: [String: String] = [ + "url": url.absoluteString, + "title": pageTitle ?? url.host ?? "Shared Link" + ] + + // Convert to JSON string + if let jsonData = try? JSONSerialization.data(withJSONObject: urlData), + let jsonString = String(data: jsonData, encoding: .utf8) { + saveToSharedDefaults(content: jsonString, type: "url") + } else { + // Fallback to simple URL + saveToSharedDefaults(content: url.absoluteString, 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 + guard let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") else { + print("ShareExtension: Failed to access app group UserDefaults") + return + } + + userDefaults.set(content, forKey: "sharedContent") + userDefaults.set(type, forKey: "sharedContentType") + userDefaults.set(Date(), forKey: "sharedContentDate") + userDefaults.synchronize() + + print("ShareExtension: Saved content of type \(type) to shared defaults") + print("ShareExtension: Content: \(content)") + + // Force open the main app + self.openMainApp() + } + + private func openMainApp() { + // Share extensions cannot directly open the containing app + // The app will check for shared content when it becomes active + // Show success feedback to user + DispatchQueue.main.async { + self.textView.text = "✓ Shared to bitchat" + self.textView.isEditable = false + } + } +} \ 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..ca6d40df 100644 --- a/project.yml +++ b/project.yml @@ -34,6 +34,9 @@ targets: UIColorName: Black UISupportedInterfaceOrientations: - UIInterfaceOrientationPortrait + CFBundleURLTypes: + - CFBundleURLSchemes: + - bitchat settings: PRODUCT_BUNDLE_IDENTIFIER: chat.bitchat INFOPLIST_FILE: bitchat/Info.plist @@ -47,6 +50,42 @@ targets: CODE_SIGNING_ALLOWED: YES ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS: YES + CODE_SIGN_ENTITLEMENTS: bitchat/bitchat.entitlements + DEVELOPMENT_TEAM: L3N5LHJD5Y + 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) + NSExtension: + NSExtensionPointIdentifier: com.apple.share-services + NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController + NSExtensionAttributes: + NSExtensionActivationRule: + NSExtensionActivationSupportsText: true + NSExtensionActivationSupportsWebURLWithMaxCount: 1 + NSExtensionActivationSupportsImageWithMaxCount: 1 + 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 + DEVELOPMENT_TEAM: L3N5LHJD5Y bitchatTests_iOS: type: bundle.unit-test