Notifications
Changelog-Added: Add new Notifications View
This commit is contained in:
@@ -47,6 +47,11 @@
|
|||||||
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; };
|
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; };
|
||||||
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; };
|
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; };
|
||||||
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
|
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
|
||||||
|
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; };
|
||||||
|
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */; };
|
||||||
|
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */; };
|
||||||
|
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7729A577AB00E2BD5A /* EventCache.swift */; };
|
||||||
|
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */; };
|
||||||
4C363A8428233689006E126D /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8328233689006E126D /* Parser.swift */; };
|
4C363A8428233689006E126D /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8328233689006E126D /* Parser.swift */; };
|
||||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8728236948006E126D /* BlocksView.swift */; };
|
4C363A8828236948006E126D /* BlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8728236948006E126D /* BlocksView.swift */; };
|
||||||
4C363A8A28236B57006E126D /* MentionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8928236B57006E126D /* MentionView.swift */; };
|
4C363A8A28236B57006E126D /* MentionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8928236B57006E126D /* MentionView.swift */; };
|
||||||
@@ -97,6 +102,9 @@
|
|||||||
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */; };
|
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */; };
|
||||||
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C42812B298C848200DBF26F /* TranslateView.swift */; };
|
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C42812B298C848200DBF26F /* TranslateView.swift */; };
|
||||||
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C477C9D282C3A4800033AA3 /* TipCounter.swift */; };
|
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C477C9D282C3A4800033AA3 /* TipCounter.swift */; };
|
||||||
|
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0629A540BA003E4487 /* NotificationsModel.swift */; };
|
||||||
|
4C54AA0A29A55429003E4487 /* EventGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0929A55429003E4487 /* EventGroup.swift */; };
|
||||||
|
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0B29A5543C003E4487 /* ZapGroup.swift */; };
|
||||||
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E67284ED36500A22DF5 /* SearchHomeModel.swift */; };
|
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E67284ED36500A22DF5 /* SearchHomeModel.swift */; };
|
||||||
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */; };
|
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */; };
|
||||||
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9113283D694D0052CD1C /* FollowTarget.swift */; };
|
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9113283D694D0052CD1C /* FollowTarget.swift */; };
|
||||||
@@ -345,6 +353,11 @@
|
|||||||
4C285C8B28398BC6008A31F1 /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = "<group>"; };
|
4C285C8B28398BC6008A31F1 /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = "<group>"; };
|
||||||
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveKeysView.swift; sourceTree = "<group>"; };
|
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveKeysView.swift; sourceTree = "<group>"; };
|
||||||
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
|
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
|
||||||
|
4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||||
|
4C30AC7329A5680900E2BD5A /* EventGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupView.swift; sourceTree = "<group>"; };
|
||||||
|
4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemView.swift; sourceTree = "<group>"; };
|
||||||
|
4C30AC7729A577AB00E2BD5A /* EventCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCache.swift; sourceTree = "<group>"; };
|
||||||
|
4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicturesView.swift; sourceTree = "<group>"; };
|
||||||
4C363A8328233689006E126D /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
|
4C363A8328233689006E126D /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
|
||||||
4C363A8728236948006E126D /* BlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksView.swift; sourceTree = "<group>"; };
|
4C363A8728236948006E126D /* BlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksView.swift; sourceTree = "<group>"; };
|
||||||
4C363A8928236B57006E126D /* MentionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionView.swift; sourceTree = "<group>"; };
|
4C363A8928236B57006E126D /* MentionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionView.swift; sourceTree = "<group>"; };
|
||||||
@@ -425,6 +438,9 @@
|
|||||||
4C42812B298C848200DBF26F /* TranslateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslateView.swift; sourceTree = "<group>"; };
|
4C42812B298C848200DBF26F /* TranslateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslateView.swift; sourceTree = "<group>"; };
|
||||||
4C477C9D282C3A4800033AA3 /* TipCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipCounter.swift; sourceTree = "<group>"; };
|
4C477C9D282C3A4800033AA3 /* TipCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipCounter.swift; sourceTree = "<group>"; };
|
||||||
4C4A3A5A288A1B2200453788 /* damus.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = damus.entitlements; sourceTree = "<group>"; };
|
4C4A3A5A288A1B2200453788 /* damus.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = damus.entitlements; sourceTree = "<group>"; };
|
||||||
|
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsModel.swift; sourceTree = "<group>"; };
|
||||||
|
4C54AA0929A55429003E4487 /* EventGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroup.swift; sourceTree = "<group>"; };
|
||||||
|
4C54AA0B29A5543C003E4487 /* ZapGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapGroup.swift; sourceTree = "<group>"; };
|
||||||
4C5C7E67284ED36500A22DF5 /* SearchHomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHomeModel.swift; sourceTree = "<group>"; };
|
4C5C7E67284ED36500A22DF5 /* SearchHomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHomeModel.swift; sourceTree = "<group>"; };
|
||||||
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = "<group>"; };
|
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = "<group>"; };
|
||||||
4C5F9113283D694D0052CD1C /* FollowTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowTarget.swift; sourceTree = "<group>"; };
|
4C5F9113283D694D0052CD1C /* FollowTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowTarget.swift; sourceTree = "<group>"; };
|
||||||
@@ -665,6 +681,7 @@
|
|||||||
4C0A3F8D280F63FF000448DE /* Models */ = {
|
4C0A3F8D280F63FF000448DE /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C54AA0829A55416003E4487 /* Notifications */,
|
||||||
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
|
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
|
||||||
4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
|
4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
|
||||||
4C0A3F92280F66F5000448DE /* ReplyMap.swift */,
|
4C0A3F92280F66F5000448DE /* ReplyMap.swift */,
|
||||||
@@ -704,13 +721,35 @@
|
|||||||
3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */,
|
3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */,
|
||||||
4CE8795A2996C47A00F758CC /* ZapsModel.swift */,
|
4CE8795A2996C47A00F758CC /* ZapsModel.swift */,
|
||||||
3AA59D1C2999B0400061C48E /* DraftsModel.swift */,
|
3AA59D1C2999B0400061C48E /* DraftsModel.swift */,
|
||||||
|
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4C30AC7029A5676F00E2BD5A /* Notifications */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */,
|
||||||
|
4C30AC7329A5680900E2BD5A /* EventGroupView.swift */,
|
||||||
|
4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */,
|
||||||
|
4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */,
|
||||||
|
);
|
||||||
|
path = Notifications;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
4C54AA0829A55416003E4487 /* Notifications */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4C54AA0929A55429003E4487 /* EventGroup.swift */,
|
||||||
|
4C54AA0B29A5543C003E4487 /* ZapGroup.swift */,
|
||||||
|
);
|
||||||
|
path = Notifications;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4C75EFA227FA576C0006080F /* Views */ = {
|
4C75EFA227FA576C0006080F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C30AC7029A5676F00E2BD5A /* Notifications */,
|
||||||
4CE0E2B029A3DF4700DB4CA2 /* Timeline */,
|
4CE0E2B029A3DF4700DB4CA2 /* Timeline */,
|
||||||
4CE879562996C44A00F758CC /* Zaps */,
|
4CE879562996C44A00F758CC /* Zaps */,
|
||||||
4CB9D4A52992D01900A9A7E4 /* Profile */,
|
4CB9D4A52992D01900A9A7E4 /* Profile */,
|
||||||
@@ -828,6 +867,7 @@
|
|||||||
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
||||||
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */,
|
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */,
|
||||||
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */,
|
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */,
|
||||||
|
4C30AC7729A577AB00E2BD5A /* EventCache.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1239,6 +1279,7 @@
|
|||||||
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
|
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
|
||||||
4C363A8A28236B57006E126D /* MentionView.swift in Sources */,
|
4C363A8A28236B57006E126D /* MentionView.swift in Sources */,
|
||||||
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
|
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
|
||||||
|
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */,
|
||||||
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
|
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
|
||||||
4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
|
4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
|
||||||
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
|
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
|
||||||
@@ -1251,6 +1292,7 @@
|
|||||||
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */,
|
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */,
|
||||||
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
|
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
|
||||||
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
||||||
|
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */,
|
||||||
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
||||||
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
||||||
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
||||||
@@ -1293,6 +1335,7 @@
|
|||||||
4C649844285A952100EAE2B3 /* LocalUserConfig.swift in Sources */,
|
4C649844285A952100EAE2B3 /* LocalUserConfig.swift in Sources */,
|
||||||
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
|
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
|
||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
||||||
|
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */,
|
||||||
4C363A8428233689006E126D /* Parser.swift in Sources */,
|
4C363A8428233689006E126D /* Parser.swift in Sources */,
|
||||||
3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */,
|
3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */,
|
||||||
4CE4F9E328528C5200C00DD9 /* AddRelayView.swift in Sources */,
|
4CE4F9E328528C5200C00DD9 /* AddRelayView.swift in Sources */,
|
||||||
@@ -1343,11 +1386,13 @@
|
|||||||
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
||||||
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
|
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
|
||||||
4CE879582996C45300F758CC /* ZapsView.swift in Sources */,
|
4CE879582996C45300F758CC /* ZapsView.swift in Sources */,
|
||||||
|
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */,
|
||||||
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
|
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
|
||||||
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
|
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
|
||||||
4C363A94282704FA006E126D /* Post.swift in Sources */,
|
4C363A94282704FA006E126D /* Post.swift in Sources */,
|
||||||
4C216F32286E388800040376 /* DMChatView.swift in Sources */,
|
4C216F32286E388800040376 /* DMChatView.swift in Sources */,
|
||||||
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */,
|
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */,
|
||||||
|
4C54AA0A29A55429003E4487 /* EventGroup.swift in Sources */,
|
||||||
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
|
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
|
||||||
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
||||||
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
|
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
|
||||||
@@ -1373,6 +1418,7 @@
|
|||||||
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
|
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
|
||||||
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
||||||
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
||||||
|
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
||||||
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
||||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
||||||
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */,
|
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */,
|
||||||
@@ -1402,6 +1448,7 @@
|
|||||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||||
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */,
|
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */,
|
||||||
|
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */,
|
||||||
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
||||||
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
||||||
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
||||||
@@ -1426,6 +1473,7 @@
|
|||||||
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */,
|
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */,
|
||||||
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */,
|
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */,
|
||||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
||||||
|
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
|
||||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||||
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
||||||
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
||||||
@@ -1691,7 +1739,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1733,7 +1781,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 7;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|||||||
@@ -12,11 +12,7 @@ struct UserView: View {
|
|||||||
let pubkey: String
|
let pubkey: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
|
||||||
let followers = FollowersModel(damus_state: damus_state, target: pubkey)
|
|
||||||
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: followers)
|
|
||||||
|
|
||||||
NavigationLink(destination: pv) {
|
|
||||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ struct ContentView: View {
|
|||||||
case .notifications:
|
case .notifications:
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Divider()
|
Divider()
|
||||||
TimelineView(events: home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true })
|
NotificationsView(state: damus, notifications: home.notifications)
|
||||||
}
|
}
|
||||||
case .dms:
|
case .dms:
|
||||||
DirectMessagesView(damus_state: damus_state!)
|
DirectMessagesView(damus_state: damus_state!)
|
||||||
@@ -615,7 +615,8 @@ struct ContentView: View {
|
|||||||
settings: UserSettingsStore(),
|
settings: UserSettingsStore(),
|
||||||
relay_filters: relay_filters,
|
relay_filters: relay_filters,
|
||||||
relay_metadata: metadatas,
|
relay_metadata: metadatas,
|
||||||
drafts: Drafts()
|
drafts: Drafts(),
|
||||||
|
events: EventCache()
|
||||||
)
|
)
|
||||||
home.damus_state = self.damus_state!
|
home.damus_state = self.damus_state!
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ struct DamusState {
|
|||||||
let relay_filters: RelayFilters
|
let relay_filters: RelayFilters
|
||||||
let relay_metadata: RelayMetadatas
|
let relay_metadata: RelayMetadatas
|
||||||
let drafts: Drafts
|
let drafts: Drafts
|
||||||
|
let events: EventCache
|
||||||
|
|
||||||
var pubkey: String {
|
var pubkey: String {
|
||||||
return keypair.pubkey
|
return keypair.pubkey
|
||||||
@@ -32,9 +33,8 @@ struct DamusState {
|
|||||||
var is_privkey_user: Bool {
|
var is_privkey_user: Bool {
|
||||||
keypair.privkey != nil
|
keypair.privkey != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static var empty: DamusState {
|
static var empty: DamusState {
|
||||||
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts())
|
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class EventsModel: ObservableObject {
|
|||||||
case .notice(_):
|
case .notice(_):
|
||||||
break
|
break
|
||||||
case .eose(_):
|
case .eose(_):
|
||||||
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, events: events, damus_state: state)
|
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,22 +50,18 @@ class HomeModel: ObservableObject {
|
|||||||
let profiles_subid = UUID().description
|
let profiles_subid = UUID().description
|
||||||
|
|
||||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||||
@Published var notifications: EventHolder
|
@Published var notifications = NotificationsModel()
|
||||||
@Published var dms: DirectMessagesModel
|
@Published var dms: DirectMessagesModel
|
||||||
@Published var events: EventHolder
|
@Published var events = EventHolder()
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
@Published var signal: SignalModel = SignalModel()
|
@Published var signal: SignalModel = SignalModel()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.events = EventHolder()
|
|
||||||
self.notifications = EventHolder()
|
|
||||||
self.damus_state = DamusState.empty
|
self.damus_state = DamusState.empty
|
||||||
self.dms = DirectMessagesModel(our_pubkey: "")
|
self.dms = DirectMessagesModel(our_pubkey: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
init(damus_state: DamusState) {
|
init(damus_state: DamusState) {
|
||||||
self.events = EventHolder()
|
|
||||||
self.notifications = EventHolder()
|
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
||||||
self.setup_debouncer()
|
self.setup_debouncer()
|
||||||
@@ -143,7 +139,7 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !notifications.insert(ev) {
|
if !notifications.insert_zap(zap) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +225,7 @@ class HomeModel: ObservableObject {
|
|||||||
guard inner_ev.is_valid else {
|
guard inner_ev.is_valid else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if inner_ev.is_textlike {
|
if inner_ev.is_textlike {
|
||||||
handle_text_event(sub_id: sub_id, ev)
|
handle_text_event(sub_id: sub_id, ev)
|
||||||
}
|
}
|
||||||
@@ -255,12 +251,11 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK SIGS ON THESE
|
|
||||||
|
|
||||||
switch damus_state.likes.add_event(ev, target: e.ref_id) {
|
switch damus_state.likes.add_event(ev, target: e.ref_id) {
|
||||||
case .already_counted:
|
case .already_counted:
|
||||||
break
|
break
|
||||||
case .success(let n):
|
case .success(let n):
|
||||||
|
handle_notification(ev: ev)
|
||||||
let liked = Counted(event: ev, id: e.ref_id, total: n)
|
let liked = Counted(event: ev, id: e.ref_id, total: n)
|
||||||
notify(.liked, liked)
|
notify(.liked, liked)
|
||||||
notify(.update_stats, e.ref_id)
|
notify(.update_stats, e.ref_id)
|
||||||
@@ -320,9 +315,9 @@ class HomeModel: ObservableObject {
|
|||||||
if sub_id == dms_subid {
|
if sub_id == dms_subid {
|
||||||
var dms = dms.dms.flatMap { $0.1.events }
|
var dms = dms.dms.flatMap { $0.1.events }
|
||||||
dms.append(contentsOf: incoming_dms)
|
dms.append(contentsOf: incoming_dms)
|
||||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: dms, damus_state: damus_state)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state)
|
||||||
} else if sub_id == notifications_subid {
|
} else if sub_id == notifications_subid {
|
||||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: notifications.all_events, damus_state: damus_state)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.loading = false
|
self.loading = false
|
||||||
@@ -375,7 +370,6 @@ class HomeModel: ObservableObject {
|
|||||||
// TODO: separate likes?
|
// TODO: separate likes?
|
||||||
var home_filter = NostrFilter.filter_kinds([
|
var home_filter = NostrFilter.filter_kinds([
|
||||||
NostrKind.text.rawValue,
|
NostrKind.text.rawValue,
|
||||||
NostrKind.chat.rawValue,
|
|
||||||
NostrKind.like.rawValue,
|
NostrKind.like.rawValue,
|
||||||
NostrKind.boost.rawValue,
|
NostrKind.boost.rawValue,
|
||||||
])
|
])
|
||||||
@@ -385,7 +379,6 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
var notifications_filter = NostrFilter.filter_kinds([
|
var notifications_filter = NostrFilter.filter_kinds([
|
||||||
NostrKind.text.rawValue,
|
NostrKind.text.rawValue,
|
||||||
NostrKind.chat.rawValue,
|
|
||||||
NostrKind.like.rawValue,
|
NostrKind.like.rawValue,
|
||||||
NostrKind.boost.rawValue,
|
NostrKind.boost.rawValue,
|
||||||
NostrKind.zap.rawValue,
|
NostrKind.zap.rawValue,
|
||||||
@@ -461,7 +454,12 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !notifications.insert(ev) {
|
damus_state.events.insert(ev)
|
||||||
|
if let inner_ev = ev.inner_event {
|
||||||
|
damus_state.events.insert(inner_ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !notifications.insert_event(ev) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +482,8 @@ class HomeModel: ObservableObject {
|
|||||||
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
damus_state.events.insert(ev)
|
||||||
|
|
||||||
if sub_id == home_subid {
|
if sub_id == home_subid {
|
||||||
insert_home_event(ev)
|
insert_home_event(ev)
|
||||||
|
|||||||
32
damus/Models/Notifications/EventGroup.swift
Normal file
32
damus/Models/Notifications/EventGroup.swift
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// ReactionGroup.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class EventGroup {
|
||||||
|
var events: [NostrEvent]
|
||||||
|
|
||||||
|
var last_event_at: Int64 {
|
||||||
|
guard let first = self.events.first else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return first.created_at
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.events = []
|
||||||
|
}
|
||||||
|
|
||||||
|
init(events: [NostrEvent]) {
|
||||||
|
self.events = events
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert(_ ev: NostrEvent) -> Bool {
|
||||||
|
return insert_uniq_sorted_event_created(events: &events, new_ev: ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
53
damus/Models/Notifications/ZapGroup.swift
Normal file
53
damus/Models/Notifications/ZapGroup.swift
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// ZapGroup.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class ZapGroup {
|
||||||
|
var zaps: [Zap]
|
||||||
|
var msat_total: Int64
|
||||||
|
var zappers: Set<String>
|
||||||
|
|
||||||
|
var last_event_at: Int64 {
|
||||||
|
guard let first = zaps.first else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return first.event.created_at
|
||||||
|
}
|
||||||
|
|
||||||
|
func zap_requests() -> [NostrEvent] {
|
||||||
|
zaps.map { z in z.request.ev }
|
||||||
|
}
|
||||||
|
|
||||||
|
init(zaps: [Zap]) {
|
||||||
|
self.zaps = zaps
|
||||||
|
self.msat_total = 0
|
||||||
|
self.zappers = Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.zaps = []
|
||||||
|
self.msat_total = 0
|
||||||
|
self.zappers = Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert(_ zap: Zap) -> Bool {
|
||||||
|
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
msat_total += zap.invoice.amount
|
||||||
|
|
||||||
|
if !zappers.contains(zap.request.ev.pubkey) {
|
||||||
|
zappers.insert(zap.request.ev.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
294
damus/Models/NotificationsModel.swift
Normal file
294
damus/Models/NotificationsModel.swift
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
//
|
||||||
|
// NotificationsModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum NotificationItem {
|
||||||
|
case repost(String, EventGroup)
|
||||||
|
case reaction(String, EventGroup)
|
||||||
|
case profile_zap(ZapGroup)
|
||||||
|
case event_zap(String, ZapGroup)
|
||||||
|
case reply(NostrEvent)
|
||||||
|
|
||||||
|
var id: String {
|
||||||
|
switch self {
|
||||||
|
case .repost(let evid, _):
|
||||||
|
return "repost_" + evid
|
||||||
|
case .reaction(let evid, _):
|
||||||
|
return "reaction_" + evid
|
||||||
|
case .profile_zap:
|
||||||
|
return "profile_zap"
|
||||||
|
case .event_zap(let evid, _):
|
||||||
|
return "event_zap_" + evid
|
||||||
|
case .reply(let ev):
|
||||||
|
return "reply_" + ev.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var last_event_at: Int64 {
|
||||||
|
switch self {
|
||||||
|
case .reaction(_, let evgrp):
|
||||||
|
return evgrp.last_event_at
|
||||||
|
case .repost(_, let evgrp):
|
||||||
|
return evgrp.last_event_at
|
||||||
|
case .profile_zap(let zapgrp):
|
||||||
|
return zapgrp.last_event_at
|
||||||
|
case .event_zap(_, let zapgrp):
|
||||||
|
return zapgrp.last_event_at
|
||||||
|
case .reply(let reply):
|
||||||
|
return reply.created_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationsModel: ObservableObject {
|
||||||
|
var incoming_zaps: [Zap]
|
||||||
|
var incoming_events: [NostrEvent]
|
||||||
|
var should_queue: Bool
|
||||||
|
|
||||||
|
// mappings from events to
|
||||||
|
var zaps: [String: ZapGroup]
|
||||||
|
var profile_zaps: ZapGroup
|
||||||
|
var reactions: [String: EventGroup]
|
||||||
|
var reposts: [String: EventGroup]
|
||||||
|
var replies: [NostrEvent]
|
||||||
|
var has_reply: Set<String>
|
||||||
|
|
||||||
|
@Published var notifications: [NotificationItem]
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.zaps = [:]
|
||||||
|
self.reactions = [:]
|
||||||
|
self.reposts = [:]
|
||||||
|
self.replies = []
|
||||||
|
self.has_reply = Set()
|
||||||
|
self.should_queue = true
|
||||||
|
self.incoming_zaps = []
|
||||||
|
self.incoming_events = []
|
||||||
|
self.profile_zaps = ZapGroup()
|
||||||
|
self.notifications = []
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniq_pubkeys() -> [String] {
|
||||||
|
var pks = Set<String>()
|
||||||
|
|
||||||
|
for ev in incoming_events {
|
||||||
|
pks.insert(ev.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
for grp in reposts {
|
||||||
|
for ev in grp.value.events {
|
||||||
|
pks.insert(ev.pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ev in replies {
|
||||||
|
pks.insert(ev.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
for zap in incoming_zaps {
|
||||||
|
pks.insert(zap.request.ev.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array(pks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func build_notifications() -> [NotificationItem] {
|
||||||
|
var notifs: [NotificationItem] = []
|
||||||
|
|
||||||
|
for el in zaps {
|
||||||
|
let evid = el.key
|
||||||
|
let zapgrp = el.value
|
||||||
|
|
||||||
|
let notif: NotificationItem = .event_zap(evid, zapgrp)
|
||||||
|
notifs.append(notif)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !profile_zaps.zaps.isEmpty {
|
||||||
|
notifs.append(.profile_zap(profile_zaps))
|
||||||
|
}
|
||||||
|
|
||||||
|
for el in reposts {
|
||||||
|
let evid = el.key
|
||||||
|
let evgrp = el.value
|
||||||
|
|
||||||
|
notifs.append(.repost(evid, evgrp))
|
||||||
|
}
|
||||||
|
|
||||||
|
for el in reactions {
|
||||||
|
let evid = el.key
|
||||||
|
let evgrp = el.value
|
||||||
|
|
||||||
|
notifs.append(.reaction(evid, evgrp))
|
||||||
|
}
|
||||||
|
|
||||||
|
for reply in replies {
|
||||||
|
notifs.append(.reply(reply))
|
||||||
|
}
|
||||||
|
|
||||||
|
notifs.sort { $0.last_event_at > $1.last_event_at }
|
||||||
|
return notifs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func insert_repost(_ ev: NostrEvent) -> Bool {
|
||||||
|
guard let reposted_ev = ev.inner_event else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = reposted_ev.id
|
||||||
|
|
||||||
|
if let evgrp = self.reposts[id] {
|
||||||
|
return evgrp.insert(ev)
|
||||||
|
} else {
|
||||||
|
let evgrp = EventGroup()
|
||||||
|
self.reposts[id] = evgrp
|
||||||
|
return evgrp.insert(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func insert_text(_ ev: NostrEvent) -> Bool {
|
||||||
|
guard !has_reply.contains(ev.id) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
has_reply.insert(ev.id)
|
||||||
|
replies.append(ev)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func insert_reaction(_ ev: NostrEvent) -> Bool {
|
||||||
|
guard let ref_id = ev.referenced_ids.last else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = ref_id.id
|
||||||
|
|
||||||
|
if let evgrp = self.reactions[id] {
|
||||||
|
return evgrp.insert(ev)
|
||||||
|
} else {
|
||||||
|
let evgrp = EventGroup()
|
||||||
|
self.reactions[id] = evgrp
|
||||||
|
return evgrp.insert(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func insert_event_immediate(_ ev: NostrEvent) -> Bool {
|
||||||
|
if ev.known_kind == .boost {
|
||||||
|
return insert_repost(ev)
|
||||||
|
} else if ev.known_kind == .like {
|
||||||
|
return insert_reaction(ev)
|
||||||
|
} else if ev.known_kind == .text {
|
||||||
|
return insert_text(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func insert_zap_immediate(_ zap: Zap) -> Bool {
|
||||||
|
switch zap.target {
|
||||||
|
case .note(let notezt):
|
||||||
|
let id = notezt.note_id
|
||||||
|
if let zapgrp = self.zaps[notezt.note_id] {
|
||||||
|
return zapgrp.insert(zap)
|
||||||
|
} else {
|
||||||
|
let zapgrp = ZapGroup()
|
||||||
|
self.zaps[id] = zapgrp
|
||||||
|
return zapgrp.insert(zap)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .profile:
|
||||||
|
return profile_zaps.insert(zap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert_event(_ ev: NostrEvent) -> Bool {
|
||||||
|
if should_queue {
|
||||||
|
return insert_uniq_sorted_event_created(events: &incoming_events, new_ev: ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if insert_event_immediate(ev) {
|
||||||
|
self.notifications = build_notifications()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert_zap(_ zap: Zap) -> Bool {
|
||||||
|
if should_queue {
|
||||||
|
return insert_uniq_sorted_zap_by_created(zaps: &incoming_zaps, new_zap: zap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if insert_zap_immediate(zap) {
|
||||||
|
self.notifications = build_notifications()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func filter(_ isIncluded: (NostrEvent) -> Bool) {
|
||||||
|
var changed = false
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
count = incoming_events.count
|
||||||
|
incoming_events = incoming_events.filter(isIncluded)
|
||||||
|
changed = changed || incoming_events.count != count
|
||||||
|
|
||||||
|
count = profile_zaps.zaps.count
|
||||||
|
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request.ev) }
|
||||||
|
changed = changed || profile_zaps.zaps.count != count
|
||||||
|
|
||||||
|
for el in reactions {
|
||||||
|
count = el.value.events.count
|
||||||
|
el.value.events = el.value.events.filter(isIncluded)
|
||||||
|
changed = changed || el.value.events.count != count
|
||||||
|
}
|
||||||
|
|
||||||
|
for el in reposts {
|
||||||
|
count = el.value.events.count
|
||||||
|
el.value.events = el.value.events.filter(isIncluded)
|
||||||
|
changed = changed || el.value.events.count != count
|
||||||
|
}
|
||||||
|
|
||||||
|
for el in zaps {
|
||||||
|
count = el.value.zaps.count
|
||||||
|
el.value.zaps = el.value.zaps.filter {
|
||||||
|
isIncluded($0.request.ev)
|
||||||
|
}
|
||||||
|
changed = changed || el.value.zaps.count != count
|
||||||
|
}
|
||||||
|
|
||||||
|
count = replies.count
|
||||||
|
replies = replies.filter(isIncluded)
|
||||||
|
changed = changed || replies.count != count
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
self.notifications = build_notifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func flush() -> Bool {
|
||||||
|
var inserted = false
|
||||||
|
|
||||||
|
for zap in incoming_zaps {
|
||||||
|
inserted = insert_zap_immediate(zap) || inserted
|
||||||
|
}
|
||||||
|
|
||||||
|
for event in incoming_events {
|
||||||
|
inserted = insert_event_immediate(event) || inserted
|
||||||
|
}
|
||||||
|
|
||||||
|
if inserted {
|
||||||
|
self.notifications = build_notifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
return inserted
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
// global events are not realtime
|
// global events are not realtime
|
||||||
unsubscribe(to: relay_id)
|
unsubscribe(to: relay_id)
|
||||||
|
|
||||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events.all_events, damus_state: damus_state)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -98,8 +98,31 @@ func find_profiles_to_fetch_pk(profiles: Profiles, event_pubkeys: [String]) -> [
|
|||||||
|
|
||||||
return Array(pubkeys)
|
return Array(pubkeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad) -> [String] {
|
||||||
|
switch load {
|
||||||
|
case .from_events(let events):
|
||||||
|
return find_profiles_to_fetch_from_events(profiles: profiles, events: events)
|
||||||
|
case .from_keys(let pks):
|
||||||
|
return find_profiles_to_fetch_from_keys(profiles: profiles, pks: pks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [String]) -> [String] {
|
||||||
|
var pubkeys = Set<String>()
|
||||||
|
|
||||||
func find_profiles_to_fetch(profiles: Profiles, events: [NostrEvent]) -> [String] {
|
for pk in pks {
|
||||||
|
if profiles.lookup(id: pk) != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkeys.insert(pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array(pubkeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]) -> [String] {
|
||||||
var pubkeys = Set<String>()
|
var pubkeys = Set<String>()
|
||||||
|
|
||||||
for ev in events {
|
for ev in events {
|
||||||
@@ -113,9 +136,14 @@ func find_profiles_to_fetch(profiles: Profiles, events: [NostrEvent]) -> [String
|
|||||||
return Array(pubkeys)
|
return Array(pubkeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent], damus_state: DamusState) {
|
enum PubkeysToLoad {
|
||||||
|
case from_events([NostrEvent])
|
||||||
|
case from_keys([String])
|
||||||
|
}
|
||||||
|
|
||||||
|
func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad, damus_state: DamusState) {
|
||||||
var filter = NostrFilter.filter_profiles
|
var filter = NostrFilter.filter_profiles
|
||||||
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events)
|
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load)
|
||||||
filter.authors = authors
|
filter.authors = authors
|
||||||
|
|
||||||
guard !authors.isEmpty else {
|
guard !authors.isEmpty else {
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ class ThreadModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sub_id == self.base_subid {
|
if sub_id == self.base_subid {
|
||||||
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, events: events, damus_state: damus_state)
|
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: damus_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,14 +50,14 @@ class ZapsModel: ObservableObject {
|
|||||||
break
|
break
|
||||||
case .eose:
|
case .eose:
|
||||||
let events = self.zaps.map { $0.request.ev }
|
let events = self.zaps.map { $0.request.ev }
|
||||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events, damus_state: state)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
||||||
case .event(_, let ev):
|
case .event(_, let ev):
|
||||||
guard ev.kind == 9735 else {
|
guard ev.kind == 9735 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let zap = state.zaps.zaps[ev.id] {
|
if let zap = state.zaps.zaps[ev.id] {
|
||||||
if insert_uniq_sorted_zap(zaps: &zaps, new_zap: zap) {
|
if insert_uniq_sorted_zap_by_amount(zaps: &zaps, new_zap: zap) {
|
||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -71,7 +71,7 @@ class ZapsModel: ObservableObject {
|
|||||||
|
|
||||||
state.zaps.add_zap(zap: zap)
|
state.zaps.add_zap(zap: zap)
|
||||||
|
|
||||||
if insert_uniq_sorted_zap(zaps: &zaps, new_zap: zap) {
|
if insert_uniq_sorted_zap_by_amount(zaps: &zaps, new_zap: zap) {
|
||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,9 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
|||||||
return decrypted(privkey: privkey) ?? "*failed to decrypt content*"
|
return decrypted(privkey: privkey) ?? "*failed to decrypt content*"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
/*
|
||||||
switch validity {
|
switch validity {
|
||||||
case .ok:
|
case .ok:
|
||||||
return content
|
return content
|
||||||
@@ -176,6 +179,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
|||||||
case .bad_sig:
|
case .bad_sig:
|
||||||
return content + "\n\n*WARNING: invalid signature, could be forged!*"
|
return content + "\n\n*WARNING: invalid signature, could be forged!*"
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
|
|||||||
27
damus/Util/EventCache.swift
Normal file
27
damus/Util/EventCache.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// EventCache.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class EventCache {
|
||||||
|
private var events: [String: NostrEvent]
|
||||||
|
|
||||||
|
func lookup(_ evid: String) -> NostrEvent? {
|
||||||
|
return events[evid]
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert(_ ev: NostrEvent) {
|
||||||
|
guard events[ev.id] == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
events[ev.id] = ev
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.events = [:]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ class EventHolder: ObservableObject {
|
|||||||
private var has_event: Set<String>
|
private var has_event: Set<String>
|
||||||
@Published var events: [NostrEvent]
|
@Published var events: [NostrEvent]
|
||||||
@Published var incoming: [NostrEvent]
|
@Published var incoming: [NostrEvent]
|
||||||
@Published var should_queue: Bool
|
var should_queue: Bool
|
||||||
|
|
||||||
var queued: Int {
|
var queued: Int {
|
||||||
return incoming.count
|
return incoming.count
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ func insert_uniq_by_pubkey(events: inout [NostrEvent], new_ev: NostrEvent, cmp:
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap, cmp: (Zap, Zap) -> Bool) -> Bool {
|
||||||
func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
|
||||||
var i: Int = 0
|
var i: Int = 0
|
||||||
|
|
||||||
for zap in zaps {
|
for zap in zaps {
|
||||||
@@ -48,7 +47,7 @@ func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_zap.invoice.amount > zap.invoice.amount {
|
if cmp(new_zap, zap) {
|
||||||
zaps.insert(new_zap, at: i)
|
zaps.insert(new_zap, at: i)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -59,6 +58,19 @@ func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func insert_uniq_sorted_zap_by_created(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||||
|
return insert_uniq_sorted_zap(zaps: &zaps, new_zap: new_zap) { (a, b) in
|
||||||
|
a.event.created_at > b.event.created_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func insert_uniq_sorted_zap_by_amount(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||||
|
return insert_uniq_sorted_zap(zaps: &zaps, new_zap: new_zap) { (a, b) in
|
||||||
|
a.invoice.amount > b.invoice.amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func insert_uniq_sorted_event_created(events: inout [NostrEvent], new_ev: NostrEvent) -> Bool {
|
func insert_uniq_sorted_event_created(events: inout [NostrEvent], new_ev: NostrEvent) -> Bool {
|
||||||
return insert_uniq_sorted_event(events: &events, new_ev: new_ev) {
|
return insert_uniq_sorted_event(events: &events, new_ev: new_ev) {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class Zaps {
|
|||||||
if our_zaps[note_target.note_id] == nil {
|
if our_zaps[note_target.note_id] == nil {
|
||||||
our_zaps[note_target.note_id] = [zap]
|
our_zaps[note_target.note_id] = [zap]
|
||||||
} else {
|
} else {
|
||||||
insert_uniq_sorted_zap(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
|
insert_uniq_sorted_zap_by_amount(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
|
||||||
}
|
}
|
||||||
case .profile(_):
|
case .profile(_):
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ struct DMChatView: View {
|
|||||||
|
|
||||||
var Header: some View {
|
var Header: some View {
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
let profile_page = ProfileView(damus_state: damus_state, pubkey: pubkey)
|
||||||
let fmodel = FollowersModel(damus_state: damus_state, target: pubkey)
|
|
||||||
let profile_page = ProfileView(damus_state: damus_state, profile: pmodel, followers: fmodel)
|
|
||||||
return NavigationLink(destination: profile_page) {
|
return NavigationLink(destination: profile_page) {
|
||||||
HStack {
|
HStack {
|
||||||
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles)
|
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles)
|
||||||
|
|||||||
@@ -61,10 +61,8 @@ struct EventView: View {
|
|||||||
if event.known_kind == .boost {
|
if event.known_kind == .boost {
|
||||||
if let inner_ev = event.inner_event {
|
if let inner_ev = event.inner_event {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
let prof_model = ProfileModel(pubkey: event.pubkey, damus: damus)
|
|
||||||
let follow_model = FollowersModel(damus_state: damus, target: event.pubkey)
|
|
||||||
let prof = damus.profiles.lookup(id: event.pubkey)
|
let prof = damus.profiles.lookup(id: event.pubkey)
|
||||||
let booster_profile = ProfileView(damus_state: damus, profile: prof_model, followers: follow_model)
|
let booster_profile = ProfileView(damus_state: damus, pubkey: event.pubkey)
|
||||||
|
|
||||||
NavigationLink(destination: booster_profile) {
|
NavigationLink(destination: booster_profile) {
|
||||||
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)
|
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ struct EventBody: View {
|
|||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let size: EventViewKind
|
let size: EventViewKind
|
||||||
|
let should_show_img: Bool
|
||||||
|
|
||||||
|
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_show_img: Bool? = nil) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self.event = event
|
||||||
|
self.size = size
|
||||||
|
self.should_show_img = should_show_img ?? should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
var content: String {
|
var content: String {
|
||||||
event.get_content(damus_state.keypair.privkey)
|
event.get_content(damus_state.keypair.privkey)
|
||||||
@@ -21,8 +29,6 @@ struct EventBody: View {
|
|||||||
ReplyDescription(event: event, profiles: damus_state.profiles)
|
ReplyDescription(event: event, profiles: damus_state.profiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey, booster_pubkey: nil)
|
|
||||||
|
|
||||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content))
|
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content))
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ struct EventProfile: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
VStack {
|
VStack {
|
||||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
|
||||||
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: FollowersModel(damus_state: damus_state, target: pubkey))
|
|
||||||
|
|
||||||
NavigationLink(destination: pv) {
|
|
||||||
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles)
|
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ struct TextEvent: View {
|
|||||||
let profile = damus.profiles.lookup(id: pubkey)
|
let profile = damus.profiles.lookup(id: pubkey)
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
|
NavigationLink(destination: ProfileView(damus_state: damus, pubkey: pubkey)) {
|
||||||
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
|
|
||||||
|
|
||||||
NavigationLink(destination: pv) {
|
|
||||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus.profiles)
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus.profiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
189
damus/Views/Notifications/EventGroupView.swift
Normal file
189
damus/Views/Notifications/EventGroupView.swift
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
//
|
||||||
|
// RepostGroupView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
|
enum EventGroupType {
|
||||||
|
case repost(EventGroup)
|
||||||
|
case reaction(EventGroup)
|
||||||
|
case zap(ZapGroup)
|
||||||
|
case profile_zap(ZapGroup)
|
||||||
|
|
||||||
|
var events: [NostrEvent] {
|
||||||
|
switch self {
|
||||||
|
case .repost(let grp):
|
||||||
|
return grp.events
|
||||||
|
case .reaction(let grp):
|
||||||
|
return grp.events
|
||||||
|
case .zap(let zapgrp):
|
||||||
|
return zapgrp.zap_requests()
|
||||||
|
case .profile_zap(let zapgrp):
|
||||||
|
return zapgrp.zap_requests()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ReactingTo {
|
||||||
|
case your_post
|
||||||
|
case tagged_in
|
||||||
|
case your_profile
|
||||||
|
}
|
||||||
|
|
||||||
|
func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo {
|
||||||
|
guard let ev else {
|
||||||
|
return .your_profile
|
||||||
|
}
|
||||||
|
|
||||||
|
if ev.pubkey == our_pubkey {
|
||||||
|
return .your_post
|
||||||
|
}
|
||||||
|
|
||||||
|
return .tagged_in
|
||||||
|
}
|
||||||
|
|
||||||
|
func determine_reacting_to_text(_ r: ReactingTo) -> String {
|
||||||
|
switch r {
|
||||||
|
case .tagged_in:
|
||||||
|
return "a post you were tagged in"
|
||||||
|
case .your_post:
|
||||||
|
return "your post"
|
||||||
|
case .your_profile:
|
||||||
|
return "your profile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func event_author_name(profiles: Profiles, _ ev: NostrEvent) -> String {
|
||||||
|
let alice_pk = ev.pubkey
|
||||||
|
let alice_prof = profiles.lookup(id: alice_pk)
|
||||||
|
return Profile.displayName(profile: alice_prof, pubkey: alice_pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?) -> String {
|
||||||
|
let verb = reacting_to_verb(group: group)
|
||||||
|
|
||||||
|
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
|
||||||
|
let target = determine_reacting_to_text(reacting_to)
|
||||||
|
|
||||||
|
if group.events.count == 1 {
|
||||||
|
let ev = group.events.first!
|
||||||
|
let profile = profiles.lookup(id: ev.pubkey)
|
||||||
|
let display_name = Profile.displayName(profile: profile, pubkey: ev.pubkey)
|
||||||
|
return String(format: "%@ is %@ %@", display_name, verb, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.events.count == 2 {
|
||||||
|
let alice_name = event_author_name(profiles: profiles, group.events[0])
|
||||||
|
let bob_name = event_author_name(profiles: profiles, group.events[1])
|
||||||
|
|
||||||
|
return String(format: "%@ and %@ are %@ %@", alice_name, bob_name, verb, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.events.count > 2 {
|
||||||
|
let alice_name = event_author_name(profiles: profiles, group.events.first!)
|
||||||
|
let count = group.events.count - 1
|
||||||
|
|
||||||
|
return String(format: "%@ and %d other people are %@ %@", alice_name, count, verb, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "??"
|
||||||
|
}
|
||||||
|
|
||||||
|
func reacting_to_verb(group: EventGroupType) -> String {
|
||||||
|
switch group {
|
||||||
|
case .reaction:
|
||||||
|
return "reacting"
|
||||||
|
case .repost:
|
||||||
|
return "reposting"
|
||||||
|
case .zap: fallthrough
|
||||||
|
case .profile_zap:
|
||||||
|
return "zapping"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EventGroupView: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: NostrEvent?
|
||||||
|
let group: EventGroupType
|
||||||
|
|
||||||
|
var GroupDescription: some View {
|
||||||
|
Text(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
|
||||||
|
let fmt = format_msats_abbrev(zapgrp.msat_total)
|
||||||
|
return VStack(alignment: .center) {
|
||||||
|
Image(systemName: "bolt.fill")
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
Text("\(fmt)")
|
||||||
|
.foregroundColor(Color.orange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var GroupIcon: some View {
|
||||||
|
Group {
|
||||||
|
switch group {
|
||||||
|
case .repost:
|
||||||
|
Image(systemName: "arrow.2.squarepath")
|
||||||
|
.foregroundColor(Color("DamusGreen"))
|
||||||
|
case .reaction:
|
||||||
|
Image("shaka-full")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
case .profile_zap(let zapgrp):
|
||||||
|
ZapIcon(zapgrp)
|
||||||
|
case .zap(let zapgrp):
|
||||||
|
ZapIcon(zapgrp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .top) {
|
||||||
|
GroupIcon
|
||||||
|
.frame(width: PFP_SIZE + 10)
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
ProfilePicturesView(state: state, events: group.events)
|
||||||
|
|
||||||
|
GroupDescription
|
||||||
|
|
||||||
|
if let event {
|
||||||
|
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: event.id)) {
|
||||||
|
Text(event.content)
|
||||||
|
.padding([.top], 1)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding([.top], 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let test_encoded_post = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
|
||||||
|
let test_repost = NostrEvent(id: "", content: test_encoded_post, pubkey: "", kind: 6, tags: [], createdAt: 1)
|
||||||
|
let test_reposts = [test_repost, test_repost]
|
||||||
|
let test_event_group = EventGroup(events: test_reposts)
|
||||||
|
|
||||||
|
struct EventGroupView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
VStack {
|
||||||
|
EventGroupView(state: test_damus_state(), event: test_event, group: .repost(test_event_group))
|
||||||
|
.frame(height: 200)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
EventGroupView(state: test_damus_state(), event: test_event, group: .reaction(test_event_group))
|
||||||
|
.frame(height: 200)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
86
damus/Views/Notifications/NotificationItemView.swift
Normal file
86
damus/Views/Notifications/NotificationItemView.swift
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// NotificationItemView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
enum ShowItem {
|
||||||
|
case show(NostrEvent?)
|
||||||
|
case dontshow(NostrEvent?)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notification_item_event(events: EventCache, notif: NotificationItem) -> ShowItem {
|
||||||
|
switch notif {
|
||||||
|
case .repost(let evid, _):
|
||||||
|
return .dontshow(events.lookup(evid))
|
||||||
|
case .reply(let ev):
|
||||||
|
return .show(ev)
|
||||||
|
case .reaction(let evid, _):
|
||||||
|
return .dontshow(events.lookup(evid))
|
||||||
|
case .event_zap(let evid, _):
|
||||||
|
return .dontshow(events.lookup(evid))
|
||||||
|
case .profile_zap:
|
||||||
|
return .show(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NotificationItemView: View {
|
||||||
|
let state: DamusState
|
||||||
|
let item: NotificationItem
|
||||||
|
|
||||||
|
var show_item: ShowItem {
|
||||||
|
notification_item_event(events: state.events, notif: item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item(_ ev: NostrEvent?) -> some View {
|
||||||
|
Group {
|
||||||
|
switch item {
|
||||||
|
case .repost(_, let evgrp):
|
||||||
|
EventGroupView(state: state, event: ev, group: .repost(evgrp))
|
||||||
|
|
||||||
|
case .event_zap(_, let zapgrp):
|
||||||
|
EventGroupView(state: state, event: ev, group: .zap(zapgrp))
|
||||||
|
|
||||||
|
case .profile_zap(let grp):
|
||||||
|
EventGroupView(state: state, event: nil, group: .profile_zap(grp))
|
||||||
|
|
||||||
|
case .reaction(_, let evgrp):
|
||||||
|
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
|
||||||
|
|
||||||
|
case .reply(let ev):
|
||||||
|
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: ev.id)) {
|
||||||
|
EventView(damus: state, event: ev, has_action_bar: true)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding([.top,.bottom], 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
switch show_item {
|
||||||
|
case .show(let ev):
|
||||||
|
Item(ev)
|
||||||
|
|
||||||
|
case .dontshow(let ev):
|
||||||
|
if let ev {
|
||||||
|
Item(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let test_notification_item: NotificationItem = .repost("evid", test_event_group)
|
||||||
|
|
||||||
|
struct NotificationItemView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
NotificationItemView(state: test_damus_state(), item: test_notification_item)
|
||||||
|
}
|
||||||
|
}
|
||||||
43
damus/Views/Notifications/NotificationsView.swift
Normal file
43
damus/Views/Notifications/NotificationsView.swift
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// NotificationsView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct NotificationsView: View {
|
||||||
|
let state: DamusState
|
||||||
|
@ObservedObject var notifications: NotificationsModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollViewReader { scroller in
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack(alignment: .leading) {
|
||||||
|
Color.white.opacity(0)
|
||||||
|
.id("startblock")
|
||||||
|
.frame(height: 5)
|
||||||
|
ForEach(notifications.notifications, id: \.id) { item in
|
||||||
|
NotificationItemView(state: state, item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
.onReceive(handle_notify(.scroll_to_top)) { notif in
|
||||||
|
let _ = notifications.flush()
|
||||||
|
self.notifications.should_queue = false
|
||||||
|
scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
let _ = notifications.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NotificationsView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
NotificationsView(state: test_damus_state(), notifications: NotificationsModel())
|
||||||
|
}
|
||||||
|
}
|
||||||
37
damus/Views/Notifications/ProfilePicturesView.swift
Normal file
37
damus/Views/Notifications/ProfilePicturesView.swift
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// ProfilePicturesView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ProfilePicturesView: View {
|
||||||
|
let state: DamusState
|
||||||
|
let events: [NostrEvent]
|
||||||
|
|
||||||
|
@State var nav_target: String? = nil
|
||||||
|
@State var navigating: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationLink(destination: ProfileView(damus_state: state, pubkey: nav_target ?? ""), isActive: $navigating) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
ForEach(events.prefix(8)) { ev in
|
||||||
|
ProfilePicView(pubkey: ev.pubkey, size: 32.0, highlight: .none, profiles: state.profiles)
|
||||||
|
.onTapGesture {
|
||||||
|
nav_target = ev.pubkey
|
||||||
|
navigating = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProfilePicturesView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
ProfilePicturesView(state: test_damus_state(), events: [test_event, test_event])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -110,8 +110,6 @@ struct ProfileView: View {
|
|||||||
static let markdown = Markdown()
|
static let markdown = Markdown()
|
||||||
|
|
||||||
@State private var selected_tab: ProfileTab = .posts
|
@State private var selected_tab: ProfileTab = .posts
|
||||||
@StateObject var profile: ProfileModel
|
|
||||||
@StateObject var followers: FollowersModel
|
|
||||||
@State private var showingEditProfile = false
|
@State private var showingEditProfile = false
|
||||||
@State var showing_select_wallet: Bool = false
|
@State var showing_select_wallet: Bool = false
|
||||||
@State var is_zoomed: Bool = false
|
@State var is_zoomed: Bool = false
|
||||||
@@ -120,6 +118,21 @@ struct ProfileView: View {
|
|||||||
@State var filter_state : FilterState = .posts
|
@State var filter_state : FilterState = .posts
|
||||||
@State var yOffset: CGFloat = 0
|
@State var yOffset: CGFloat = 0
|
||||||
|
|
||||||
|
@StateObject var profile: ProfileModel
|
||||||
|
@StateObject var followers: FollowersModel
|
||||||
|
|
||||||
|
init(damus_state: DamusState, profile: ProfileModel, followers: FollowersModel) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self._profile = StateObject(wrappedValue: profile)
|
||||||
|
self._followers = StateObject(wrappedValue: followers)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(damus_state: DamusState, pubkey: String) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self._profile = StateObject(wrappedValue: ProfileModel(pubkey: pubkey, damus: damus_state))
|
||||||
|
self._followers = StateObject(wrappedValue: FollowersModel(damus_state: damus_state, target: pubkey))
|
||||||
|
}
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Environment(\.openURL) var openURL
|
@Environment(\.openURL) var openURL
|
||||||
@@ -459,9 +472,7 @@ struct ProfileView: View {
|
|||||||
struct ProfileView_Previews: PreviewProvider {
|
struct ProfileView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let ds = test_damus_state()
|
let ds = test_damus_state()
|
||||||
let followers = FollowersModel(damus_state: ds, target: ds.pubkey)
|
ProfileView(damus_state: ds, pubkey: ds.pubkey)
|
||||||
let profile_model = ProfileModel(pubkey: ds.pubkey, damus: ds)
|
|
||||||
ProfileView(damus_state: ds, profile: profile_model, followers: followers)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user