Follow Packs
This PR adds and enables follow packs in the universe view. Closes: #3012 Changelog-Added: Added follow list kind 39089 Changelog-Added: Added follow pack preview Changelog-Added: Added follow pack timeline to Universe View Changelog-Removed: Removed hashtags in Universe View Signed-off-by: ericholguin <ericholguin@apache.org>
This commit is contained in:
committed by
Daniel D’Aquino
parent
f436291209
commit
414c67a919
@@ -418,11 +418,26 @@
|
|||||||
5C0567592C8FBDE30073F23A /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
5C0567592C8FBDE30073F23A /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
|
||||||
5C05675A2C8FBDE70073F23A /* NDBSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0567572C8FBC560073F23A /* NDBSearchView.swift */; };
|
5C05675A2C8FBDE70073F23A /* NDBSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0567572C8FBC560073F23A /* NDBSearchView.swift */; };
|
||||||
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
|
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
|
||||||
|
5C09FD122DF283D700823661 /* FollowPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C09FD112DF283D200823661 /* FollowPackModel.swift */; };
|
||||||
|
5C09FD132DF283D700823661 /* FollowPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C09FD112DF283D200823661 /* FollowPackModel.swift */; };
|
||||||
|
5C09FD142DF283D700823661 /* FollowPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C09FD112DF283D200823661 /* FollowPackModel.swift */; };
|
||||||
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; };
|
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; };
|
||||||
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
|
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
|
||||||
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */; };
|
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */; };
|
||||||
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
||||||
5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */; };
|
5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */; };
|
||||||
|
5C4FA7EC2DC29AE900CE658C /* FollowPackEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */; };
|
||||||
|
5C4FA7ED2DC29AE900CE658C /* FollowPackEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */; };
|
||||||
|
5C4FA7EE2DC29AE900CE658C /* FollowPackEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */; };
|
||||||
|
5C4FA7FB2DC29C3800CE658C /* FollowPackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */; };
|
||||||
|
5C4FA7FC2DC29C3800CE658C /* FollowPackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */; };
|
||||||
|
5C4FA7FD2DC29C3800CE658C /* FollowPackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */; };
|
||||||
|
5C4FA7FF2DC5119300CE658C /* FollowPackPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */; };
|
||||||
|
5C4FA8002DC5119300CE658C /* FollowPackPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */; };
|
||||||
|
5C4FA8012DC5119300CE658C /* FollowPackPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */; };
|
||||||
|
5C4FA8032DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */; };
|
||||||
|
5C4FA8042DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */; };
|
||||||
|
5C4FA8052DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */; };
|
||||||
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
|
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
|
||||||
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
||||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; };
|
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; };
|
||||||
@@ -2437,11 +2452,16 @@
|
|||||||
5C0567542C8B60C20073F23A /* OffsetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetExtension.swift; sourceTree = "<group>"; };
|
5C0567542C8B60C20073F23A /* OffsetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetExtension.swift; sourceTree = "<group>"; };
|
||||||
5C0567572C8FBC560073F23A /* NDBSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NDBSearchView.swift; sourceTree = "<group>"; };
|
5C0567572C8FBC560073F23A /* NDBSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NDBSearchView.swift; sourceTree = "<group>"; };
|
||||||
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
|
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
|
||||||
|
5C09FD112DF283D200823661 /* FollowPackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackModel.swift; sourceTree = "<group>"; };
|
||||||
5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySoftwareDetail.swift; sourceTree = "<group>"; };
|
5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySoftwareDetail.swift; sourceTree = "<group>"; };
|
||||||
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = "<group>"; };
|
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = "<group>"; };
|
||||||
5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayNipList.swift; sourceTree = "<group>"; };
|
5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayNipList.swift; sourceTree = "<group>"; };
|
||||||
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
||||||
5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDraftContentView.swift; sourceTree = "<group>"; };
|
5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDraftContentView.swift; sourceTree = "<group>"; };
|
||||||
|
5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackEvent.swift; sourceTree = "<group>"; };
|
||||||
|
5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackView.swift; sourceTree = "<group>"; };
|
||||||
|
5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackPreview.swift; sourceTree = "<group>"; };
|
||||||
|
5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowPackTimeline.swift; sourceTree = "<group>"; };
|
||||||
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
|
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
|
||||||
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
||||||
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = "<group>"; };
|
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
@@ -2808,6 +2828,8 @@
|
|||||||
4C0A3F8D280F63FF000448DE /* Models */ = {
|
4C0A3F8D280F63FF000448DE /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5C09FD112DF283D200823661 /* FollowPackModel.swift */,
|
||||||
|
5C4FA7EB2DC29AE900CE658C /* FollowPackEvent.swift */,
|
||||||
D73BDB122D71212600D69970 /* NostrNetworkManager */,
|
D73BDB122D71212600D69970 /* NostrNetworkManager */,
|
||||||
D74F43082B23F09300425B75 /* Purple */,
|
D74F43082B23F09300425B75 /* Purple */,
|
||||||
BA3759882ABCCDE30018D73B /* Camera */,
|
BA3759882ABCCDE30018D73B /* Camera */,
|
||||||
@@ -3614,6 +3636,7 @@
|
|||||||
4CC7AAEE297F11B300430951 /* Events */ = {
|
4CC7AAEE297F11B300430951 /* Events */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5C4FA7FA2DC29C3800CE658C /* FollowPack */,
|
||||||
5CC852A02BDED9970039FFC5 /* Highlight */,
|
5CC852A02BDED9970039FFC5 /* Highlight */,
|
||||||
4CA927682A290F8F0098A105 /* Components */,
|
4CA927682A290F8F0098A105 /* Components */,
|
||||||
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
||||||
@@ -3929,6 +3952,16 @@
|
|||||||
path = Images;
|
path = Images;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5C4FA7FA2DC29C3800CE658C /* FollowPack */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5C4FA8022DCAF80400CE658C /* FollowPackTimeline.swift */,
|
||||||
|
5C4FA7FE2DC5119300CE658C /* FollowPackPreview.swift */,
|
||||||
|
5C4FA7F92DC29C3800CE658C /* FollowPackView.swift */,
|
||||||
|
);
|
||||||
|
path = FollowPack;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
5CC852A02BDED9970039FFC5 /* Highlight */ = {
|
5CC852A02BDED9970039FFC5 /* Highlight */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -4655,6 +4688,7 @@
|
|||||||
BA3759972ABCCF360018D73B /* CameraPreview.swift in Sources */,
|
BA3759972ABCCF360018D73B /* CameraPreview.swift in Sources */,
|
||||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
||||||
4CA927672A290F8B0098A105 /* RelativeTime.swift in Sources */,
|
4CA927672A290F8B0098A105 /* RelativeTime.swift in Sources */,
|
||||||
|
5C4FA8032DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */,
|
||||||
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
||||||
D7CB5D4B2B11721600AD4105 /* ZapType.swift in Sources */,
|
D7CB5D4B2B11721600AD4105 /* ZapType.swift in Sources */,
|
||||||
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
|
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
|
||||||
@@ -4799,6 +4833,7 @@
|
|||||||
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
||||||
4C12535E2A76CA870004F4B8 /* SwitchedTimelineNotify.swift in Sources */,
|
4C12535E2A76CA870004F4B8 /* SwitchedTimelineNotify.swift in Sources */,
|
||||||
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */,
|
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */,
|
||||||
|
5C4FA7ED2DC29AE900CE658C /* FollowPackEvent.swift in Sources */,
|
||||||
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
|
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */,
|
||||||
3ACF94462DAA006500971A4E /* NIP05DomainEventsModel.swift in Sources */,
|
3ACF94462DAA006500971A4E /* NIP05DomainEventsModel.swift in Sources */,
|
||||||
D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */,
|
D734B1452CCC19B1000B5C97 /* DamusFullScreenCover.swift in Sources */,
|
||||||
@@ -4879,6 +4914,7 @@
|
|||||||
D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */,
|
D7373BAA2B68A65A00F7783D /* PurpleAccountUpdateNotify.swift in Sources */,
|
||||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
|
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
|
||||||
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */,
|
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */,
|
||||||
|
5C4FA7FF2DC5119300CE658C /* FollowPackPreview.swift in Sources */,
|
||||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
|
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
|
||||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
|
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
|
||||||
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */,
|
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */,
|
||||||
@@ -4909,6 +4945,7 @@
|
|||||||
3165648B295B70D500C64604 /* LinkView.swift in Sources */,
|
3165648B295B70D500C64604 /* LinkView.swift in Sources */,
|
||||||
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */,
|
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */,
|
||||||
D7CB5D5C2B1176B200AD4105 /* MediaUploader.swift in Sources */,
|
D7CB5D5C2B1176B200AD4105 /* MediaUploader.swift in Sources */,
|
||||||
|
5C4FA7FD2DC29C3800CE658C /* FollowPackView.swift in Sources */,
|
||||||
4C1253562A76C8C60004F4B8 /* BroadcastNotify.swift in Sources */,
|
4C1253562A76C8C60004F4B8 /* BroadcastNotify.swift in Sources */,
|
||||||
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */,
|
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */,
|
||||||
B533694E2B66D791008A805E /* MutelistManager.swift in Sources */,
|
B533694E2B66D791008A805E /* MutelistManager.swift in Sources */,
|
||||||
@@ -4948,6 +4985,7 @@
|
|||||||
4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */,
|
4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */,
|
||||||
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */,
|
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */,
|
||||||
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
|
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
|
||||||
|
5C09FD132DF283D700823661 /* FollowPackModel.swift in Sources */,
|
||||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||||
D71AD8FF2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
D71AD8FF2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||||
@@ -5109,6 +5147,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
5C4FA7FB2DC29C3800CE658C /* FollowPackView.swift in Sources */,
|
||||||
D7F360262CEBBD8B009D34DA /* PresentFullScreenItemNotify.swift in Sources */,
|
D7F360262CEBBD8B009D34DA /* PresentFullScreenItemNotify.swift in Sources */,
|
||||||
82D6FA9A2CD9820500C925F4 /* ShareViewController.swift in Sources */,
|
82D6FA9A2CD9820500C925F4 /* ShareViewController.swift in Sources */,
|
||||||
82D6FAA92CD99F7900C925F4 /* FbConstants.swift in Sources */,
|
82D6FAA92CD99F7900C925F4 /* FbConstants.swift in Sources */,
|
||||||
@@ -5175,6 +5214,7 @@
|
|||||||
82D6FAE42CD99F7900C925F4 /* FollowNotify.swift in Sources */,
|
82D6FAE42CD99F7900C925F4 /* FollowNotify.swift in Sources */,
|
||||||
82D6FAE52CD99F7900C925F4 /* LikedNotify.swift in Sources */,
|
82D6FAE52CD99F7900C925F4 /* LikedNotify.swift in Sources */,
|
||||||
82D6FAE62CD99F7900C925F4 /* LocalNotificationNotify.swift in Sources */,
|
82D6FAE62CD99F7900C925F4 /* LocalNotificationNotify.swift in Sources */,
|
||||||
|
5C4FA8012DC5119300CE658C /* FollowPackPreview.swift in Sources */,
|
||||||
82D6FAE72CD99F7900C925F4 /* LoginNotify.swift in Sources */,
|
82D6FAE72CD99F7900C925F4 /* LoginNotify.swift in Sources */,
|
||||||
82D6FAE82CD99F7900C925F4 /* LogoutNotify.swift in Sources */,
|
82D6FAE82CD99F7900C925F4 /* LogoutNotify.swift in Sources */,
|
||||||
D706C5B12D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
D706C5B12D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
||||||
@@ -5227,6 +5267,7 @@
|
|||||||
82D6FB0F2CD99F7900C925F4 /* DamusLogoGradient.swift in Sources */,
|
82D6FB0F2CD99F7900C925F4 /* DamusLogoGradient.swift in Sources */,
|
||||||
82D6FB102CD99F7900C925F4 /* DamusBackground.swift in Sources */,
|
82D6FB102CD99F7900C925F4 /* DamusBackground.swift in Sources */,
|
||||||
82D6FB112CD99F7900C925F4 /* DamusLightGradient.swift in Sources */,
|
82D6FB112CD99F7900C925F4 /* DamusLightGradient.swift in Sources */,
|
||||||
|
5C4FA8042DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */,
|
||||||
82D6FB132CD99F7900C925F4 /* Shimmer.swift in Sources */,
|
82D6FB132CD99F7900C925F4 /* Shimmer.swift in Sources */,
|
||||||
82D6FB142CD99F7900C925F4 /* EndBlock.swift in Sources */,
|
82D6FB142CD99F7900C925F4 /* EndBlock.swift in Sources */,
|
||||||
82D6FB152CD99F7900C925F4 /* ImageCarousel.swift in Sources */,
|
82D6FB152CD99F7900C925F4 /* ImageCarousel.swift in Sources */,
|
||||||
@@ -5373,6 +5414,7 @@
|
|||||||
82D6FB9B2CD99F7900C925F4 /* MutedThreadsManager.swift in Sources */,
|
82D6FB9B2CD99F7900C925F4 /* MutedThreadsManager.swift in Sources */,
|
||||||
82D6FB9C2CD99F7900C925F4 /* WalletModel.swift in Sources */,
|
82D6FB9C2CD99F7900C925F4 /* WalletModel.swift in Sources */,
|
||||||
82D6FB9D2CD99F7900C925F4 /* ZapButtonModel.swift in Sources */,
|
82D6FB9D2CD99F7900C925F4 /* ZapButtonModel.swift in Sources */,
|
||||||
|
5C09FD142DF283D700823661 /* FollowPackModel.swift in Sources */,
|
||||||
82D6FB9E2CD99F7900C925F4 /* ContentFilters.swift in Sources */,
|
82D6FB9E2CD99F7900C925F4 /* ContentFilters.swift in Sources */,
|
||||||
82D6FB9F2CD99F7900C925F4 /* DamusCacheManager.swift in Sources */,
|
82D6FB9F2CD99F7900C925F4 /* DamusCacheManager.swift in Sources */,
|
||||||
82D6FBA02CD99F7900C925F4 /* NotificationsManager.swift in Sources */,
|
82D6FBA02CD99F7900C925F4 /* NotificationsManager.swift in Sources */,
|
||||||
@@ -5510,6 +5552,7 @@
|
|||||||
82D6FC202CD99F7900C925F4 /* RelayType.swift in Sources */,
|
82D6FC202CD99F7900C925F4 /* RelayType.swift in Sources */,
|
||||||
82D6FC212CD99F7900C925F4 /* SignalView.swift in Sources */,
|
82D6FC212CD99F7900C925F4 /* SignalView.swift in Sources */,
|
||||||
82D6FC222CD99F7900C925F4 /* RelayPicView.swift in Sources */,
|
82D6FC222CD99F7900C925F4 /* RelayPicView.swift in Sources */,
|
||||||
|
5C4FA7EC2DC29AE900CE658C /* FollowPackEvent.swift in Sources */,
|
||||||
82D6FC232CD99F7900C925F4 /* UserSearch.swift in Sources */,
|
82D6FC232CD99F7900C925F4 /* UserSearch.swift in Sources */,
|
||||||
82D6FC242CD99F7900C925F4 /* AddMuteItemView.swift in Sources */,
|
82D6FC242CD99F7900C925F4 /* AddMuteItemView.swift in Sources */,
|
||||||
82D6FC252CD99F7900C925F4 /* MuteDurationMenu.swift in Sources */,
|
82D6FC252CD99F7900C925F4 /* MuteDurationMenu.swift in Sources */,
|
||||||
@@ -5680,6 +5723,7 @@
|
|||||||
D73E5E602C6A97F4007EB227 /* ImageMetadata.swift in Sources */,
|
D73E5E602C6A97F4007EB227 /* ImageMetadata.swift in Sources */,
|
||||||
D73E5E612C6A97F4007EB227 /* ImageProcessing.swift in Sources */,
|
D73E5E612C6A97F4007EB227 /* ImageProcessing.swift in Sources */,
|
||||||
D73E5E622C6A97F4007EB227 /* BlurHashEncode.swift in Sources */,
|
D73E5E622C6A97F4007EB227 /* BlurHashEncode.swift in Sources */,
|
||||||
|
5C09FD122DF283D700823661 /* FollowPackModel.swift in Sources */,
|
||||||
D73E5E632C6A97F4007EB227 /* BlurHashDecode.swift in Sources */,
|
D73E5E632C6A97F4007EB227 /* BlurHashDecode.swift in Sources */,
|
||||||
D73E5F952C6AA753007EB227 /* FullScreenCarouselView.swift in Sources */,
|
D73E5F952C6AA753007EB227 /* FullScreenCarouselView.swift in Sources */,
|
||||||
D73E5E642C6A97F4007EB227 /* PostBox.swift in Sources */,
|
D73E5E642C6A97F4007EB227 /* PostBox.swift in Sources */,
|
||||||
@@ -5699,6 +5743,7 @@
|
|||||||
D73E5E6F2C6A97F4007EB227 /* TimeAgo.swift in Sources */,
|
D73E5E6F2C6A97F4007EB227 /* TimeAgo.swift in Sources */,
|
||||||
D73E5E702C6A97F4007EB227 /* Parser.swift in Sources */,
|
D73E5E702C6A97F4007EB227 /* Parser.swift in Sources */,
|
||||||
D73E5E722C6A97F4007EB227 /* LinkView.swift in Sources */,
|
D73E5E722C6A97F4007EB227 /* LinkView.swift in Sources */,
|
||||||
|
5C4FA7EE2DC29AE900CE658C /* FollowPackEvent.swift in Sources */,
|
||||||
D73E5F922C6AA720007EB227 /* QRCodeView.swift in Sources */,
|
D73E5F922C6AA720007EB227 /* QRCodeView.swift in Sources */,
|
||||||
D73E5E742C6A97F4007EB227 /* Lists.swift in Sources */,
|
D73E5E742C6A97F4007EB227 /* Lists.swift in Sources */,
|
||||||
D73E5E752C6A97F4007EB227 /* CoreSVG.swift in Sources */,
|
D73E5E752C6A97F4007EB227 /* CoreSVG.swift in Sources */,
|
||||||
@@ -5716,6 +5761,7 @@
|
|||||||
D73E5E802C6A97F4007EB227 /* CredentialHandler.swift in Sources */,
|
D73E5E802C6A97F4007EB227 /* CredentialHandler.swift in Sources */,
|
||||||
D73E5E812C6A97F4007EB227 /* KeyboardVisible.swift in Sources */,
|
D73E5E812C6A97F4007EB227 /* KeyboardVisible.swift in Sources */,
|
||||||
D73E5E832C6A97F4007EB227 /* AVPlayer+Additions.swift in Sources */,
|
D73E5E832C6A97F4007EB227 /* AVPlayer+Additions.swift in Sources */,
|
||||||
|
5C4FA7FC2DC29C3800CE658C /* FollowPackView.swift in Sources */,
|
||||||
D73E5E842C6A97F4007EB227 /* Zaps+.swift in Sources */,
|
D73E5E842C6A97F4007EB227 /* Zaps+.swift in Sources */,
|
||||||
D73E5E852C6A97F4007EB227 /* WalletConnect+.swift in Sources */,
|
D73E5E852C6A97F4007EB227 /* WalletConnect+.swift in Sources */,
|
||||||
D73E5E862C6A97F4007EB227 /* DamusPurpleNotificationManagement.swift in Sources */,
|
D73E5E862C6A97F4007EB227 /* DamusPurpleNotificationManagement.swift in Sources */,
|
||||||
@@ -5852,6 +5898,7 @@
|
|||||||
D73E5EFF2C6A97F4007EB227 /* ZapsView.swift in Sources */,
|
D73E5EFF2C6A97F4007EB227 /* ZapsView.swift in Sources */,
|
||||||
D73E5F002C6A97F4007EB227 /* CustomizeZapView.swift in Sources */,
|
D73E5F002C6A97F4007EB227 /* CustomizeZapView.swift in Sources */,
|
||||||
D73E5F012C6A97F4007EB227 /* ZapTypePicker.swift in Sources */,
|
D73E5F012C6A97F4007EB227 /* ZapTypePicker.swift in Sources */,
|
||||||
|
5C4FA8052DCAF80E00CE658C /* FollowPackTimeline.swift in Sources */,
|
||||||
D73E5F022C6A97F4007EB227 /* ZapUserView.swift in Sources */,
|
D73E5F022C6A97F4007EB227 /* ZapUserView.swift in Sources */,
|
||||||
D73E5F032C6A97F4007EB227 /* ProfileZapLinkView.swift in Sources */,
|
D73E5F032C6A97F4007EB227 /* ProfileZapLinkView.swift in Sources */,
|
||||||
D7DB930C2D69486700DA1EE5 /* NIP65.swift in Sources */,
|
D7DB930C2D69486700DA1EE5 /* NIP65.swift in Sources */,
|
||||||
@@ -5990,6 +6037,7 @@
|
|||||||
D703D7552C670A3700A400EA /* DamusUserDefaults.swift in Sources */,
|
D703D7552C670A3700A400EA /* DamusUserDefaults.swift in Sources */,
|
||||||
D703D7A32C670E1D00A400EA /* nostr_bech32.c in Sources */,
|
D703D7A32C670E1D00A400EA /* nostr_bech32.c in Sources */,
|
||||||
D703D7992C670DF900A400EA /* sha256.c in Sources */,
|
D703D7992C670DF900A400EA /* sha256.c in Sources */,
|
||||||
|
5C4FA8002DC5119300CE658C /* FollowPackPreview.swift in Sources */,
|
||||||
D703D7972C670DED00A400EA /* wasm.c in Sources */,
|
D703D7972C670DED00A400EA /* wasm.c in Sources */,
|
||||||
5C8498042D5D150000F74FEB /* ZapExplainer.swift in Sources */,
|
5C8498042D5D150000F74FEB /* ZapExplainer.swift in Sources */,
|
||||||
D703D7842C670C4700A400EA /* SequenceUtils.swift in Sources */,
|
D703D7842C670C4700A400EA /* SequenceUtils.swift in Sources */,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ enum FilterState : Int {
|
|||||||
case posts = 0
|
case posts = 0
|
||||||
case posts_and_replies = 1
|
case posts_and_replies = 1
|
||||||
case conversations = 2
|
case conversations = 2
|
||||||
|
case follow_list = 3
|
||||||
|
|
||||||
func filter(ev: NostrEvent) -> Bool {
|
func filter(ev: NostrEvent) -> Bool {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -22,6 +23,8 @@ enum FilterState : Int {
|
|||||||
return true
|
return true
|
||||||
case .conversations:
|
case .conversations:
|
||||||
return true
|
return true
|
||||||
|
case .follow_list:
|
||||||
|
return ev.known_kind == .follow_list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
damus/Models/FollowPackEvent.swift
Normal file
39
damus/Models/FollowPackEvent.swift
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// FollowPackEvent.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/30/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct FollowPackEvent {
|
||||||
|
let event: NostrEvent
|
||||||
|
var title: String? = nil
|
||||||
|
var uuid: String? = nil
|
||||||
|
var image: URL? = nil
|
||||||
|
var description: String? = nil
|
||||||
|
var publicKeys: [Pubkey] = []
|
||||||
|
|
||||||
|
|
||||||
|
static func parse(from ev: NostrEvent) -> FollowPackEvent {
|
||||||
|
var followlist = FollowPackEvent(event: ev)
|
||||||
|
|
||||||
|
for tag in ev.tags {
|
||||||
|
guard tag.count >= 2 else { continue }
|
||||||
|
switch tag[0].string() {
|
||||||
|
case "title": followlist.title = tag[1].string()
|
||||||
|
case "d": followlist.uuid = tag[1].string()
|
||||||
|
case "image": followlist.image = URL(string: tag[1].string())
|
||||||
|
case "description": followlist.description = tag[1].string()
|
||||||
|
case "p":
|
||||||
|
followlist.publicKeys.append(Pubkey(Data(hex: tag[1].string())))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return followlist
|
||||||
|
}
|
||||||
|
}
|
||||||
77
damus/Models/FollowPackModel.swift
Normal file
77
damus/Models/FollowPackModel.swift
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// FollowPackModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 6/5/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
class FollowPackModel: ObservableObject {
|
||||||
|
var events: EventHolder
|
||||||
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
|
let damus_state: DamusState
|
||||||
|
let subid = UUID().description
|
||||||
|
let limit: UInt32 = 500
|
||||||
|
|
||||||
|
init(damus_state: DamusState) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self.events = EventHolder(on_queue: { ev in
|
||||||
|
preload_events(state: damus_state, events: [ev])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func subscribe(follow_pack_users: [Pubkey]) {
|
||||||
|
loading = true
|
||||||
|
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
|
||||||
|
var filter = NostrFilter(kinds: [.text, .chat])
|
||||||
|
filter.until = UInt32(Date.now.timeIntervalSince1970)
|
||||||
|
filter.authors = follow_pack_users
|
||||||
|
filter.limit = 500
|
||||||
|
|
||||||
|
damus_state.nostrNetwork.pool.subscribe(sub_id: subid, filters: [filter], handler: handle_event, to: to_relays)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unsubscribe(to: RelayURL? = nil) {
|
||||||
|
loading = false
|
||||||
|
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subid, to: to.map { [$0] })
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
||||||
|
guard case .nostr_event(let event) = conn_ev else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch event {
|
||||||
|
case .event(let sub_id, let ev):
|
||||||
|
guard sub_id == self.subid else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
||||||
|
{
|
||||||
|
if self.events.insert(ev) {
|
||||||
|
self.objectWillChange.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .notice(let msg):
|
||||||
|
print("follow pack notice: \(msg)")
|
||||||
|
case .ok:
|
||||||
|
break
|
||||||
|
case .eose(let sub_id):
|
||||||
|
loading = false
|
||||||
|
|
||||||
|
if sub_id == self.subid {
|
||||||
|
unsubscribe(to: relay_id)
|
||||||
|
|
||||||
|
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -227,6 +227,8 @@ class HomeModel: ContactsDelegate {
|
|||||||
break
|
break
|
||||||
case .relay_list:
|
case .relay_list:
|
||||||
break // This will be handled by `UserRelayListManager`
|
break // This will be handled by `UserRelayListManager`
|
||||||
|
case .follow_list:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
//
|
|
||||||
// SearchHomeModel.swift
|
// SearchHomeModel.swift
|
||||||
// damus
|
// damus
|
||||||
//
|
//
|
||||||
@@ -16,6 +15,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
var seen_pubkey: Set<Pubkey> = Set()
|
var seen_pubkey: Set<Pubkey> = Set()
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let base_subid = UUID().description
|
let base_subid = UUID().description
|
||||||
|
let follow_pack_subid = UUID().description
|
||||||
let profiles_subid = UUID().description
|
let profiles_subid = UUID().description
|
||||||
let limit: UInt32 = 500
|
let limit: UInt32 = 500
|
||||||
//let multiple_events_per_pubkey: Bool = false
|
//let multiple_events_per_pubkey: Bool = false
|
||||||
@@ -42,12 +42,18 @@ class SearchHomeModel: ObservableObject {
|
|||||||
func subscribe() {
|
func subscribe() {
|
||||||
loading = true
|
loading = true
|
||||||
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
|
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
|
||||||
|
|
||||||
|
var follow_list_filter = NostrFilter(kinds: [.follow_list])
|
||||||
|
follow_list_filter.until = UInt32(Date.now.timeIntervalSince1970)
|
||||||
|
|
||||||
damus_state.nostrNetwork.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays)
|
damus_state.nostrNetwork.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays)
|
||||||
|
damus_state.nostrNetwork.pool.subscribe(sub_id: follow_pack_subid, filters: [follow_list_filter], handler: handle_event, to: to_relays)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe(to: RelayURL? = nil) {
|
func unsubscribe(to: RelayURL? = nil) {
|
||||||
loading = false
|
loading = false
|
||||||
damus_state.nostrNetwork.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] })
|
damus_state.nostrNetwork.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] })
|
||||||
|
damus_state.nostrNetwork.pool.unsubscribe(sub_id: follow_pack_subid, to: to.map { [$0] })
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
||||||
@@ -57,7 +63,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
|
|
||||||
switch event {
|
switch event {
|
||||||
case .event(let sub_id, let ev):
|
case .event(let sub_id, let ev):
|
||||||
guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
|
guard sub_id == self.base_subid || sub_id == self.profiles_subid || sub_id == self.follow_pack_subid else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class SearchModel: ObservableObject {
|
|||||||
func subscribe() {
|
func subscribe() {
|
||||||
// since 1 month
|
// since 1 month
|
||||||
search.limit = self.limit
|
search.limit = self.limit
|
||||||
search.kinds = [.text, .like, .longform, .highlight]
|
search.kinds = [.text, .like, .longform, .highlight, .follow_list]
|
||||||
|
|
||||||
//likes_filter.ids = ref_events.referenced_ids!
|
//likes_filter.ids = ref_events.referenced_ids!
|
||||||
|
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ enum NostrKind: UInt32, Codable {
|
|||||||
case nwc_response = 23195
|
case nwc_response = 23195
|
||||||
case http_auth = 27235
|
case http_auth = 27235
|
||||||
case status = 30315
|
case status = 30315
|
||||||
|
case follow_list = 39089
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ enum Route: Hashable {
|
|||||||
case FollowersYouKnow(friendedFollowers: [Pubkey], followers: FollowersModel)
|
case FollowersYouKnow(friendedFollowers: [Pubkey], followers: FollowersModel)
|
||||||
case NIP05DomainEvents(events: NIP05DomainEventsModel, nip05_domain_favicon: FaviconURL?)
|
case NIP05DomainEvents(events: NIP05DomainEventsModel, nip05_domain_favicon: FaviconURL?)
|
||||||
case NIP05DomainPubkeys(domain: String, nip05_domain_favicon: FaviconURL?, pubkeys: [Pubkey])
|
case NIP05DomainPubkeys(domain: String, nip05_domain_favicon: FaviconURL?, pubkeys: [Pubkey])
|
||||||
|
case FollowPack(followPack: NostrEvent, model: FollowPackModel, blur_imgs: Bool)
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func view(navigationCoordinator: NavigationCoordinator, damusState: DamusState) -> some View {
|
func view(navigationCoordinator: NavigationCoordinator, damusState: DamusState) -> some View {
|
||||||
@@ -134,6 +135,8 @@ enum Route: Hashable {
|
|||||||
NIP05DomainTimelineView(damus_state: damusState, model: events, nip05_domain_favicon: nip05_domain_favicon)
|
NIP05DomainTimelineView(damus_state: damusState, model: events, nip05_domain_favicon: nip05_domain_favicon)
|
||||||
case .NIP05DomainPubkeys(let domain, let nip05_domain_favicon, let pubkeys):
|
case .NIP05DomainPubkeys(let domain, let nip05_domain_favicon, let pubkeys):
|
||||||
NIP05DomainPubkeysView(damus_state: damusState, domain: domain, nip05_domain_favicon: nip05_domain_favicon, pubkeys: pubkeys)
|
NIP05DomainPubkeysView(damus_state: damusState, domain: domain, nip05_domain_favicon: nip05_domain_favicon, pubkeys: pubkeys)
|
||||||
|
case .FollowPack(let followPack, let followPackModel, let blur_imgs):
|
||||||
|
FollowPackView(state: damusState, ev: followPack, model: followPackModel, blur_imgs: blur_imgs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +247,9 @@ enum Route: Hashable {
|
|||||||
case .NIP05DomainPubkeys(let domain, _, _):
|
case .NIP05DomainPubkeys(let domain, _, _):
|
||||||
hasher.combine("nip05DomainPubkeys")
|
hasher.combine("nip05DomainPubkeys")
|
||||||
hasher.combine(domain)
|
hasher.combine(domain)
|
||||||
|
case .FollowPack(let followPack, let followPackModel, let blur_imgs):
|
||||||
|
hasher.combine("followPack")
|
||||||
|
hasher.combine(followPack.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
242
damus/Views/Events/FollowPack/FollowPackPreview.swift
Normal file
242
damus/Views/Events/FollowPack/FollowPackPreview.swift
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
//
|
||||||
|
// FollowPackPreview.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/30/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct FollowPackUsers: View {
|
||||||
|
let state: DamusState
|
||||||
|
var publicKeys: [Pubkey]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
|
||||||
|
if !publicKeys.isEmpty {
|
||||||
|
CondensedProfilePicturesView(state: state, pubkeys: publicKeys, maxPictures: 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
let followPackUserCount = publicKeys.count
|
||||||
|
let nounString = pluralizedString(key: "follow_pack_user_count", count: followPackUserCount)
|
||||||
|
let nounText = Text(verbatim: nounString).font(.subheadline).foregroundColor(.gray)
|
||||||
|
Text("\(Text(verbatim: followPackUserCount.formatted()).font(.subheadline.weight(.medium))) \(nounText)", comment: "Sentence composed of 2 variables to describe how many people are in the follow pack. In source English, the first variable is the number of users, and the second variable is 'user' or 'users'.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FollowPackBannerImage: View {
|
||||||
|
let state: DamusState
|
||||||
|
let options: EventViewOptions
|
||||||
|
var image: URL? = nil
|
||||||
|
var preview: Bool
|
||||||
|
@State var blur_imgs: Bool
|
||||||
|
|
||||||
|
func Placeholder(url: URL, preview: Bool) -> some View {
|
||||||
|
Group {
|
||||||
|
if let meta = state.events.lookup_img_metadata(url: url),
|
||||||
|
case .processed(let blurhash) = meta.state {
|
||||||
|
Image(uiImage: blurhash)
|
||||||
|
.resizable()
|
||||||
|
.frame(maxWidth: preview ? 350 : UIScreen.main.bounds.width, minHeight: preview ? 180 : 200, maxHeight: preview ? 180 : 200)
|
||||||
|
} else {
|
||||||
|
DamusColors.adaptableWhite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func titleImage(url: URL, preview: Bool) -> some View {
|
||||||
|
KFAnimatedImage(url)
|
||||||
|
.callbackQueue(.dispatch(.global(qos:.background)))
|
||||||
|
.backgroundDecode(true)
|
||||||
|
.imageContext(.note, disable_animation: state.settings.disable_animation)
|
||||||
|
.image_fade(duration: 0.25)
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.configure { view in
|
||||||
|
view.framePreloadCount = 3
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
Placeholder(url: url, preview: preview)
|
||||||
|
}
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(maxWidth: preview ? 350 : UIScreen.main.bounds.width, minHeight: preview ? 180 : 200, maxHeight: preview ? 180 : 200)
|
||||||
|
.kfClickable()
|
||||||
|
.cornerRadius(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let url = image {
|
||||||
|
if (self.options.contains(.no_media)) {
|
||||||
|
EmptyView()
|
||||||
|
} else if !blur_imgs {
|
||||||
|
titleImage(url: url, preview: preview)
|
||||||
|
} else {
|
||||||
|
ZStack {
|
||||||
|
titleImage(url: url, preview: preview)
|
||||||
|
BlurOverlayView(blur_images: $blur_imgs, artifacts: nil, size: nil, damus_state: nil, parentView: .longFormView)
|
||||||
|
.frame(maxWidth: preview ? 350 : UIScreen.main.bounds.width, minHeight: preview ? 180 : 200, maxHeight: preview ? 180 : 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(NSLocalizedString("No cover image", comment: "Text letting user know there is no cover image."))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.frame(width: 350, height: 180)
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FollowPackPreviewBody: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: FollowPackEvent
|
||||||
|
let options: EventViewOptions
|
||||||
|
let header: Bool
|
||||||
|
@State var blur_imgs: Bool
|
||||||
|
|
||||||
|
@ObservedObject var artifacts: NoteArtifactsModel
|
||||||
|
|
||||||
|
init(state: DamusState, ev: FollowPackEvent, options: EventViewOptions, header: Bool, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = ev
|
||||||
|
self.options = options
|
||||||
|
self.header = header
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
|
||||||
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(state: DamusState, ev: NostrEvent, options: EventViewOptions, header: Bool, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = FollowPackEvent.parse(from: ev)
|
||||||
|
self.options = options
|
||||||
|
self.header = header
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
|
||||||
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if options.contains(.wide) {
|
||||||
|
Main.padding(.horizontal)
|
||||||
|
} else {
|
||||||
|
Main
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Main: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
||||||
|
if state.settings.media_previews {
|
||||||
|
FollowPackBannerImage(state: state, options: options, image: event.image, preview: true, blur_imgs: blur_imgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(event.title ?? NSLocalizedString("Untitled", comment: "Title of follow list event if it is untitled."))
|
||||||
|
.font(header ? .title : .headline)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
if let description = event.description {
|
||||||
|
Text(description)
|
||||||
|
.font(header ? .body : .caption)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
} else {
|
||||||
|
Text("")
|
||||||
|
.font(header ? .body : .caption)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
ProfilePicView(pubkey: event.event.pubkey, size: 25, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true)
|
||||||
|
.onTapGesture {
|
||||||
|
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
||||||
|
}
|
||||||
|
let profile_txn = state.profiles.lookup(id: event.event.pubkey)
|
||||||
|
let profile = profile_txn?.unsafeUnownedValue
|
||||||
|
let displayName = Profile.displayName(profile: profile, pubkey: event.event.pubkey)
|
||||||
|
switch displayName {
|
||||||
|
case .one(let one):
|
||||||
|
Text(one)
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
|
||||||
|
case .both(username: let username, displayName: let displayName):
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Text(verbatim: displayName)
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
|
||||||
|
Text(verbatim: "@\(username)")
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
|
||||||
|
FollowPackUsers(state: state, publicKeys: event.publicKeys)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
}
|
||||||
|
.frame(width: 350, height: state.settings.media_previews ? 330 : 150, alignment: .leading)
|
||||||
|
.background(DamusColors.neutral3)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.stroke(DamusColors.neutral1, lineWidth: 1)
|
||||||
|
)
|
||||||
|
.padding(.top, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FollowPackPreview: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: FollowPackEvent
|
||||||
|
let options: EventViewOptions
|
||||||
|
@State var blur_imgs: Bool
|
||||||
|
|
||||||
|
init(state: DamusState, ev: NostrEvent, options: EventViewOptions, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = FollowPackEvent.parse(from: ev)
|
||||||
|
self.options = options.union(.no_mentions)
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
FollowPackPreviewBody(state: state, ev: event, options: options, header: false, blur_imgs: blur_imgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let test_follow_list_event = FollowPackEvent.parse(from: NostrEvent(
|
||||||
|
content: "",
|
||||||
|
keypair: test_keypair,
|
||||||
|
kind: NostrKind.longform.rawValue,
|
||||||
|
tags: [
|
||||||
|
["title", "DAMUSES"],
|
||||||
|
["description", "Damus Team"],
|
||||||
|
["published_at", "1685638715"],
|
||||||
|
["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],
|
||||||
|
["p", "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"],
|
||||||
|
["p", "17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4"],
|
||||||
|
["p", "520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626"],
|
||||||
|
["p", "2779f3d9f42c7dee17f0e6bcdcf89a8f9d592d19e3b1bbd27ef1cffd1a7f98d1"],
|
||||||
|
["p", "bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91"],
|
||||||
|
["p", "e7424ad457e512fdf4764a56bf6d428a06a13a1006af1fb8e0fe32f6d03265c7"],
|
||||||
|
["p", "b88c7f007bbf3bc2fcaeff9e513f186bab33782c0baa6a6cc12add78b9110ba3"],
|
||||||
|
["p", "4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967"],
|
||||||
|
["image", "https://damus.io/img/logo.png"],
|
||||||
|
])!
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
struct FollowPackPreview_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
VStack {
|
||||||
|
FollowPackPreview(state: test_damus_state, ev: test_follow_list_event.event, options: [], blur_imgs: false)
|
||||||
|
}
|
||||||
|
.frame(height: 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
135
damus/Views/Events/FollowPack/FollowPackTimeline.swift
Normal file
135
damus/Views/Events/FollowPack/FollowPackTimeline.swift
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// FollowPackTimeline.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 5/6/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FollowPackTimelineView<Content: View>: View {
|
||||||
|
@ObservedObject var events: EventHolder
|
||||||
|
@Binding var loading: Bool
|
||||||
|
|
||||||
|
let damus: DamusState
|
||||||
|
let show_friend_icon: Bool
|
||||||
|
let filter: (NostrEvent) -> Bool
|
||||||
|
let content: Content?
|
||||||
|
let apply_mute_rules: Bool
|
||||||
|
|
||||||
|
init(events: EventHolder, loading: Binding<Bool>, headerHeight: Binding<CGFloat>, headerOffset: Binding<CGFloat>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
|
||||||
|
self.events = events
|
||||||
|
self._loading = loading
|
||||||
|
self.damus = damus
|
||||||
|
self.show_friend_icon = show_friend_icon
|
||||||
|
self.filter = filter
|
||||||
|
self.apply_mute_rules = apply_mute_rules
|
||||||
|
self.content = content?()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
|
||||||
|
self.events = events
|
||||||
|
self._loading = loading
|
||||||
|
self.damus = damus
|
||||||
|
self.show_friend_icon = show_friend_icon
|
||||||
|
self.filter = filter
|
||||||
|
self.apply_mute_rules = apply_mute_rules
|
||||||
|
self.content = content?()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
MainContent
|
||||||
|
}
|
||||||
|
|
||||||
|
var MainContent: some View {
|
||||||
|
ScrollViewReader { scroller in
|
||||||
|
ScrollView(.horizontal) {
|
||||||
|
if let content {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
Color.clear
|
||||||
|
.id("startblock")
|
||||||
|
.frame(height: 0)
|
||||||
|
|
||||||
|
FollowPackInnerView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules)
|
||||||
|
.redacted(reason: loading ? .placeholder : [])
|
||||||
|
.shimmer(loading)
|
||||||
|
.disabled(loading)
|
||||||
|
.background {
|
||||||
|
GeometryReader { proxy -> Color in
|
||||||
|
handle_scroll_queue(proxy, queue: self.events)
|
||||||
|
return Color.clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.coordinateSpace(name: "scroll")
|
||||||
|
.onReceive(handle_notify(.scroll_to_top)) { () in
|
||||||
|
events.flush()
|
||||||
|
self.events.should_queue = false
|
||||||
|
scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
events.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FollowPackInnerView: View {
|
||||||
|
@ObservedObject var events: EventHolder
|
||||||
|
let state: DamusState
|
||||||
|
let filter: (NostrEvent) -> Bool
|
||||||
|
|
||||||
|
init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) {
|
||||||
|
self.events = events
|
||||||
|
self.state = damus
|
||||||
|
self.filter = apply_mute_rules ? { filter($0) && !damus.mutelist_manager.is_event_muted($0) } : filter
|
||||||
|
}
|
||||||
|
|
||||||
|
var event_options: EventViewOptions {
|
||||||
|
if self.state.settings.truncate_timeline_text {
|
||||||
|
return [.wide, .truncate_content]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.wide]
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
LazyHStack(spacing: 0) {
|
||||||
|
let events = self.events.events
|
||||||
|
if events.isEmpty {
|
||||||
|
EmptyTimelineView()
|
||||||
|
} else {
|
||||||
|
let evs = events.filter(filter)
|
||||||
|
let indexed = Array(zip(evs, 0...))
|
||||||
|
ForEach(indexed, id: \.0.id) { tup in
|
||||||
|
let ev = tup.0
|
||||||
|
let ind = tup.1
|
||||||
|
let blur_imgs = should_blur_images(settings: state.settings, contacts: state.contacts, ev: ev, our_pubkey: state.pubkey)
|
||||||
|
if ev.kind == NostrKind.follow_list.rawValue {
|
||||||
|
FollowPackPreview(state: state, ev: ev, options: event_options, blur_imgs: blur_imgs)
|
||||||
|
.onTapGesture {
|
||||||
|
state.nav.push(route: Route.FollowPack(followPack: ev, model: FollowPackModel(damus_state: state), blur_imgs: blur_imgs))
|
||||||
|
}
|
||||||
|
.padding(.top, 7)
|
||||||
|
.onAppear {
|
||||||
|
let to_preload =
|
||||||
|
Array([indexed[safe: ind+1]?.0,
|
||||||
|
indexed[safe: ind+2]?.0,
|
||||||
|
indexed[safe: ind+3]?.0,
|
||||||
|
indexed[safe: ind+4]?.0,
|
||||||
|
indexed[safe: ind+5]?.0
|
||||||
|
].compactMap({ $0 }))
|
||||||
|
|
||||||
|
preload_events(state: state, events: to_preload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
176
damus/Views/Events/FollowPack/FollowPackView.swift
Normal file
176
damus/Views/Events/FollowPack/FollowPackView.swift
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
//
|
||||||
|
// FollowPackView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/30/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct FollowPackView: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: FollowPackEvent
|
||||||
|
@StateObject var model: FollowPackModel
|
||||||
|
@State var blur_imgs: Bool
|
||||||
|
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
@ObservedObject var artifacts: NoteArtifactsModel
|
||||||
|
|
||||||
|
init(state: DamusState, ev: FollowPackEvent, model: FollowPackModel, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = ev
|
||||||
|
self._model = StateObject(wrappedValue: model)
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
|
||||||
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(state: DamusState, ev: NostrEvent, model: FollowPackModel, blur_imgs: Bool) {
|
||||||
|
self.state = state
|
||||||
|
self.event = FollowPackEvent.parse(from: ev)
|
||||||
|
self._model = StateObject(wrappedValue: model)
|
||||||
|
self.blur_imgs = blur_imgs
|
||||||
|
|
||||||
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
func content_filter(_ pubkeys: [Pubkey]) -> ((NostrEvent) -> Bool) {
|
||||||
|
var filters = ContentFilters.defaults(damus_state: self.state)
|
||||||
|
filters.append({ pubkeys.contains($0.pubkey) })
|
||||||
|
return ContentFilters(filters: filters).filter
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FollowPackTabSelection: Int {
|
||||||
|
case people = 0
|
||||||
|
case posts = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@State var tab_selection: FollowPackTabSelection = .people
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
ScrollView {
|
||||||
|
FollowPackHeader
|
||||||
|
|
||||||
|
FollowPackTabs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
if model.events.events.isEmpty {
|
||||||
|
model.subscribe(follow_pack_users: event.publicKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
model.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tabs: [(String, FollowPackTabSelection)] {
|
||||||
|
let tabs = [
|
||||||
|
(NSLocalizedString("People", comment: "Label for filter for seeing the people in this follow pack."), FollowPackTabSelection.people),
|
||||||
|
(NSLocalizedString("Posts", comment: "Label for filter for seeing the posts from the people in this follow pack."), FollowPackTabSelection.posts)
|
||||||
|
]
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
var FollowPackTabs: some View {
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
CustomPicker(tabs: tabs, selection: $tab_selection)
|
||||||
|
Divider()
|
||||||
|
.frame(height: 1)
|
||||||
|
}
|
||||||
|
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||||
|
|
||||||
|
if tab_selection == FollowPackTabSelection.people {
|
||||||
|
LazyVStack(alignment: .leading) {
|
||||||
|
ForEach(event.publicKeys.reversed(), id: \.self) { pk in
|
||||||
|
FollowUserView(target: .pubkey(pk), damus_state: state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.padding(.bottom, 50)
|
||||||
|
.tag(FollowPackTabSelection.people)
|
||||||
|
.id(FollowPackTabSelection.people)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tab_selection == FollowPackTabSelection.posts {
|
||||||
|
InnerTimelineView(events: model.events, damus: state, filter: content_filter(event.publicKeys))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear() {
|
||||||
|
model.subscribe(follow_pack_users: event.publicKeys)
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
model.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var FollowPackHeader: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
||||||
|
if state.settings.media_previews {
|
||||||
|
FollowPackBannerImage(state: state, options: EventViewOptions(), image: event.image, preview: false, blur_imgs: blur_imgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(event.title ?? NSLocalizedString("Untitled", comment: "Title of follow list event if it is untitled."))
|
||||||
|
.font(.title)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.top, 5)
|
||||||
|
|
||||||
|
if let description = event.description {
|
||||||
|
Text(description)
|
||||||
|
.font(.body)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
ProfilePicView(pubkey: event.event.pubkey, size: 25, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true)
|
||||||
|
.onTapGesture {
|
||||||
|
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
||||||
|
}
|
||||||
|
let profile_txn = state.profiles.lookup(id: event.event.pubkey)
|
||||||
|
let profile = profile_txn?.unsafeUnownedValue
|
||||||
|
let displayName = Profile.displayName(profile: profile, pubkey: event.event.pubkey)
|
||||||
|
switch displayName {
|
||||||
|
case .one(let one):
|
||||||
|
Text(NSLocalizedString("Created by \(one)", comment: "Lets the user know who created this follow pack."))
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
|
||||||
|
case .both(username: let username, displayName: let displayName):
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Text(NSLocalizedString("Created by \(displayName)", comment: "Lets the user know who created this follow pack."))
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
|
||||||
|
Text(verbatim: "@\(username)")
|
||||||
|
.font(.subheadline).foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
FollowPackUsers(state: state, publicKeys: event.publicKeys)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct FollowPackView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
VStack {
|
||||||
|
FollowPackView(state: test_damus_state, ev: test_follow_list_event, model: FollowPackModel(damus_state: test_damus_state), blur_imgs: false)
|
||||||
|
}
|
||||||
|
.frame(height: 400)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ class LoadableNostrEventViewModel: ObservableObject {
|
|||||||
case .zap, .zap_request:
|
case .zap, .zap_request:
|
||||||
guard let zap = await get_zap(from: ev, state: damus_state) else { return .not_found }
|
guard let zap = await get_zap(from: ev, state: damus_state) else { return .not_found }
|
||||||
return .loaded(route: Route.Zaps(target: zap.target))
|
return .loaded(route: Route.Zaps(target: zap.target))
|
||||||
case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .longform, .nwc_request, .nwc_response, .http_auth, .status, .relay_list:
|
case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .longform, .nwc_request, .nwc_response, .http_auth, .status, .relay_list, .follow_list:
|
||||||
return .unknown_or_unsupported_kind
|
return .unknown_or_unsupported_kind
|
||||||
}
|
}
|
||||||
case .naddr(let naddr):
|
case .naddr(let naddr):
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ struct ProfileView: View {
|
|||||||
var filters = ContentFilters.defaults(damus_state: damus_state)
|
var filters = ContentFilters.defaults(damus_state: damus_state)
|
||||||
filters.append(fstate.filter)
|
filters.append(fstate.filter)
|
||||||
switch fstate {
|
switch fstate {
|
||||||
case .posts, .posts_and_replies:
|
case .posts, .posts_and_replies, .follow_list:
|
||||||
filters.append({ profile.pubkey == $0.pubkey })
|
filters.append({ profile.pubkey == $0.pubkey })
|
||||||
case .conversations:
|
case .conversations:
|
||||||
filters.append({ profile.conversation_events.contains($0.id) } )
|
filters.append({ profile.conversation_events.contains($0.id) } )
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ struct SearchHomeView: View {
|
|||||||
@State var search: String = ""
|
@State var search: String = ""
|
||||||
@FocusState private var isFocused: Bool
|
@FocusState private var isFocused: Bool
|
||||||
|
|
||||||
var content_filter: (NostrEvent) -> Bool {
|
func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
|
||||||
let filters = ContentFilters.defaults(damus_state: self.damus_state)
|
var filters = ContentFilters.defaults(damus_state: damus_state)
|
||||||
|
filters.append(fstate.filter)
|
||||||
return ContentFilters(filters: filters).filter
|
return ContentFilters(filters: filters).filter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,21 +53,20 @@ struct SearchHomeView: View {
|
|||||||
loading: $model.loading,
|
loading: $model.loading,
|
||||||
damus: damus_state,
|
damus: damus_state,
|
||||||
show_friend_icon: true,
|
show_friend_icon: true,
|
||||||
filter: { ev in
|
filter:content_filter(FilterState.posts),
|
||||||
if !content_filter(ev) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_muted = damus_state.mutelist_manager.is_event_muted(ev)
|
|
||||||
if event_muted {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
content: {
|
content: {
|
||||||
AnyView(VStack {
|
AnyView(VStack(alignment: .leading) {
|
||||||
SuggestedHashtagsView(damus_state: damus_state, max_items: 5, events: model.events)
|
HStack {
|
||||||
|
Image(systemName: "sparkles")
|
||||||
|
.foregroundStyle(PinkGradient)
|
||||||
|
Text("Follow Packs", comment: "A label indicating that the items below it are follow packs")
|
||||||
|
.foregroundStyle(PinkGradient)
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
FollowPackTimelineView<AnyView>(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true,filter:content_filter(FilterState.follow_list)
|
||||||
|
).padding(.bottom)
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
|
|||||||
@@ -2,6 +2,22 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>follow_pack_user_count</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
<string>%#@FOLLOW_PACK_USERS@</string>
|
||||||
|
<key>FOLLOW_PACK_USERS</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSStringFormatSpecTypeKey</key>
|
||||||
|
<string>NSStringPluralRuleType</string>
|
||||||
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
|
<string>d</string>
|
||||||
|
<key>one</key>
|
||||||
|
<string>user</string>
|
||||||
|
<key>other</key>
|
||||||
|
<string>users</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>followed_by_three_and_others</key>
|
<key>followed_by_three_and_others</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
<key>NSStringLocalizedFormatKey</key>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ final class LocalizationUtilTests: XCTestCase {
|
|||||||
|
|
||||||
// Test cases of the localization string key, and the expected en-US strings for a count of 0, 1, and 2.
|
// Test cases of the localization string key, and the expected en-US strings for a count of 0, 1, and 2.
|
||||||
let keys = [
|
let keys = [
|
||||||
|
["follow_pack_user_count", "users", "user", "users"],
|
||||||
["followers_count", "Followers", "Follower", "Followers"],
|
["followers_count", "Followers", "Follower", "Followers"],
|
||||||
["following_count", "Following", "Following", "Following"],
|
["following_count", "Following", "Following", "Following"],
|
||||||
["hellthread_notifications_disabled", "Hide notifications that tag more than 0 profiles", "Hide notifications that tag more than 1 profile", "Hide notifications that tag more than 2 profiles"],
|
["hellthread_notifications_disabled", "Hide notifications that tag more than 0 profiles", "Hide notifications that tag more than 1 profile", "Hide notifications that tag more than 2 profiles"],
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ class NdbNote: Codable, Equatable, Hashable {
|
|||||||
// Extension to make NdbNote compatible with NostrEvent's original API
|
// Extension to make NdbNote compatible with NostrEvent's original API
|
||||||
extension NdbNote {
|
extension NdbNote {
|
||||||
var is_textlike: Bool {
|
var is_textlike: Bool {
|
||||||
return kind == 1 || kind == 42 || kind == 30023 || kind == 9802
|
return kind == 1 || kind == 42 || kind == 30023 || kind == 9802 || kind == 39089
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_quote_repost: NoteId? {
|
var is_quote_repost: NoteId? {
|
||||||
|
|||||||
Reference in New Issue
Block a user