Compare commits
21 Commits
remove-unu
...
reactions-
| Author | SHA1 | Date | |
|---|---|---|---|
|
847f31f5a6
|
|||
|
|
fd130b78e7 | ||
|
|
0be0273121 | ||
|
|
b349de22b7 | ||
|
|
cc2d196705 | ||
|
|
53be29efc2 | ||
|
|
529ee63f29 | ||
|
|
490e8ec1fb | ||
|
|
df267ffd04 | ||
|
|
b771e8f49a | ||
|
|
a88e80a346 | ||
|
8ac9863765
|
|||
|
|
4a851501a1 | ||
|
|
4ccfe81558 | ||
|
|
e7ed9dfe86 | ||
|
|
0dce7aea45 | ||
|
|
6376c61bad | ||
|
bdd1403a7d
|
|||
|
|
23c3130a82 | ||
|
9172102f4d
|
|||
|
|
8bcd8317f1 |
@@ -12,6 +12,7 @@
|
|||||||
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
|
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
|
||||||
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
|
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
|
||||||
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
|
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
|
||||||
|
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3A0A30BA2C21397A00F8C9BC /* EmojiPicker */; };
|
||||||
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; };
|
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; };
|
||||||
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */; };
|
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */; };
|
||||||
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; };
|
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; };
|
||||||
@@ -32,8 +33,10 @@
|
|||||||
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
|
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3ACB685D297633BC00C46468 /* Localizable.strings */; };
|
||||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
|
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */; };
|
||||||
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
||||||
3AFE89C32BD4156F00AD31EF /* MCEmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3AFE89C22BD4156F00AD31EF /* MCEmojiPicker */; };
|
|
||||||
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */; };
|
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */; };
|
||||||
|
4C011B5E2BD0A56A002F2F9B /* ChatEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */; };
|
||||||
|
4C011B5F2BD0A56A002F2F9B /* ChatroomThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5D2BD0A56A002F2F9B /* ChatroomThreadView.swift */; };
|
||||||
|
4C011B612BD0B25C002F2F9B /* ReplyQuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B602BD0B25C002F2F9B /* ReplyQuoteView.swift */; };
|
||||||
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
|
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
|
||||||
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
|
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
|
||||||
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; };
|
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; };
|
||||||
@@ -396,6 +399,7 @@
|
|||||||
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 /* HighlightPostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightPostView.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 */; };
|
||||||
@@ -403,6 +407,11 @@
|
|||||||
5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; };
|
5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; };
|
||||||
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
|
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
|
||||||
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
|
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
|
||||||
|
5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */; };
|
||||||
|
5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529E2BD744F60039FFC5 /* HighlightView.swift */; };
|
||||||
|
5CC852A22BDED9B90039FFC5 /* HighlightDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */; };
|
||||||
|
5CC852A42BDF3CA10039FFC5 /* HighlightLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */; };
|
||||||
|
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */; };
|
||||||
5CC868DD2AA29B3200FB22BA /* NeutralButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */; };
|
5CC868DD2AA29B3200FB22BA /* NeutralButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */; };
|
||||||
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */; };
|
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */; };
|
||||||
5CF2DCCE2AABE1A500984B8D /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; };
|
5CF2DCCE2AABE1A500984B8D /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; };
|
||||||
@@ -494,6 +503,9 @@
|
|||||||
D7870BC32AC47EBC0080BA88 /* EventLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */; };
|
D7870BC32AC47EBC0080BA88 /* EventLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */; };
|
||||||
D789D1202AFEFBF20083A7AB /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = D789D11F2AFEFBF20083A7AB /* secp256k1 */; };
|
D789D1202AFEFBF20083A7AB /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = D789D11F2AFEFBF20083A7AB /* secp256k1 */; };
|
||||||
D78CD5982B8990300014D539 /* DamusAppNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78CD5972B8990300014D539 /* DamusAppNotificationView.swift */; };
|
D78CD5982B8990300014D539 /* DamusAppNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78CD5972B8990300014D539 /* DamusAppNotificationView.swift */; };
|
||||||
|
D78DB8592C1CE9CA00F0AB12 /* SwipeActions in Frameworks */ = {isa = PBXBuildFile; productRef = D78DB8582C1CE9CA00F0AB12 /* SwipeActions */; };
|
||||||
|
D78DB85B2C20FE5000F0AB12 /* VectorMath.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78DB85A2C20FE4F00F0AB12 /* VectorMath.swift */; };
|
||||||
|
D78DB85F2C20FED300F0AB12 /* ChatBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78DB85E2C20FED300F0AB12 /* ChatBubbleView.swift */; };
|
||||||
D798D21A2B0856CC00234419 /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
D798D21A2B0856CC00234419 /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
||||||
D798D21B2B0856F200234419 /* NdbTagsIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDD1AE12A6B3074001CD4DF /* NdbTagsIterator.swift */; };
|
D798D21B2B0856F200234419 /* NdbTagsIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDD1AE12A6B3074001CD4DF /* NdbTagsIterator.swift */; };
|
||||||
D798D21C2B0857E400234419 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; };
|
D798D21C2B0857E400234419 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; };
|
||||||
@@ -822,6 +834,9 @@
|
|||||||
3AF6336929884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3AF6336929884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3AF6336A29884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-PT"; path = "pt-PT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3AF6336A29884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-PT"; path = "pt-PT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nip98HTTPAuth.swift; sourceTree = "<group>"; };
|
3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nip98HTTPAuth.swift; sourceTree = "<group>"; };
|
||||||
|
4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEventView.swift; sourceTree = "<group>"; };
|
||||||
|
4C011B5D2BD0A56A002F2F9B /* ChatroomThreadView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatroomThreadView.swift; sourceTree = "<group>"; };
|
||||||
|
4C011B602BD0B25C002F2F9B /* ReplyQuoteView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyQuoteView.swift; sourceTree = "<group>"; };
|
||||||
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
|
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
|
||||||
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
|
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
|
||||||
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; };
|
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
@@ -1325,6 +1340,7 @@
|
|||||||
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 /* HighlightPostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightPostView.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>"; };
|
||||||
@@ -1332,6 +1348,11 @@
|
|||||||
5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; };
|
5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; };
|
||||||
5C7389B62B9E692E00781E0A /* MutinyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyButton.swift; sourceTree = "<group>"; };
|
5C7389B62B9E692E00781E0A /* MutinyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyButton.swift; sourceTree = "<group>"; };
|
||||||
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyGradient.swift; sourceTree = "<group>"; };
|
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyGradient.swift; sourceTree = "<group>"; };
|
||||||
|
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightEvent.swift; sourceTree = "<group>"; };
|
||||||
|
5CC8529E2BD744F60039FFC5 /* HighlightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightView.swift; sourceTree = "<group>"; };
|
||||||
|
5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDescription.swift; sourceTree = "<group>"; };
|
||||||
|
5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightLink.swift; sourceTree = "<group>"; };
|
||||||
|
5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightEventRef.swift; sourceTree = "<group>"; };
|
||||||
5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeutralButtonStyle.swift; sourceTree = "<group>"; };
|
5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeutralButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPicView.swift; sourceTree = "<group>"; };
|
5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPicView.swift; sourceTree = "<group>"; };
|
||||||
5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLightGradient.swift; sourceTree = "<group>"; };
|
5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLightGradient.swift; sourceTree = "<group>"; };
|
||||||
@@ -1410,6 +1431,8 @@
|
|||||||
D7870BC02AC4750B0080BA88 /* MentionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionView.swift; sourceTree = "<group>"; };
|
D7870BC02AC4750B0080BA88 /* MentionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionView.swift; sourceTree = "<group>"; };
|
||||||
D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLoaderView.swift; sourceTree = "<group>"; };
|
D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLoaderView.swift; sourceTree = "<group>"; };
|
||||||
D78CD5972B8990300014D539 /* DamusAppNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusAppNotificationView.swift; sourceTree = "<group>"; };
|
D78CD5972B8990300014D539 /* DamusAppNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusAppNotificationView.swift; sourceTree = "<group>"; };
|
||||||
|
D78DB85A2C20FE4F00F0AB12 /* VectorMath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VectorMath.swift; sourceTree = "<group>"; };
|
||||||
|
D78DB85E2C20FED300F0AB12 /* ChatBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatBubbleView.swift; sourceTree = "<group>"; };
|
||||||
D798D21D2B0858BB00234419 /* MigratedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigratedTypes.swift; sourceTree = "<group>"; };
|
D798D21D2B0858BB00234419 /* MigratedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigratedTypes.swift; sourceTree = "<group>"; };
|
||||||
D798D2272B085CDA00234419 /* NdbNote+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NdbNote+.swift"; sourceTree = "<group>"; };
|
D798D2272B085CDA00234419 /* NdbNote+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NdbNote+.swift"; sourceTree = "<group>"; };
|
||||||
D798D22B2B086C7400234419 /* NostrEvent+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NostrEvent+.swift"; sourceTree = "<group>"; };
|
D798D22B2B086C7400234419 /* NostrEvent+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NostrEvent+.swift"; sourceTree = "<group>"; };
|
||||||
@@ -1474,9 +1497,10 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
|
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
|
||||||
|
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */,
|
||||||
|
D78DB8592C1CE9CA00F0AB12 /* SwipeActions in Frameworks */,
|
||||||
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */,
|
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */,
|
||||||
4C27C9322A64766F007DBC75 /* MarkdownUI in Frameworks */,
|
4C27C9322A64766F007DBC75 /* MarkdownUI in Frameworks */,
|
||||||
3AFE89C32BD4156F00AD31EF /* MCEmojiPicker in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -1656,6 +1680,7 @@
|
|||||||
B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */,
|
B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */,
|
||||||
B533694D2B66D791008A805E /* MutelistManager.swift */,
|
B533694D2B66D791008A805E /* MutelistManager.swift */,
|
||||||
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */,
|
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */,
|
||||||
|
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1996,6 +2021,7 @@
|
|||||||
4C75EFA227FA576C0006080F /* Views */ = {
|
4C75EFA227FA576C0006080F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D78DB85D2C20FE9E00F0AB12 /* Chat */,
|
||||||
D71AC4CA2BA8E3320076268E /* Extensions */,
|
D71AC4CA2BA8E3320076268E /* Extensions */,
|
||||||
BA3759952ABCCF360018D73B /* Camera */,
|
BA3759952ABCCF360018D73B /* Camera */,
|
||||||
F71694E82A66221E001F4053 /* Onboarding */,
|
F71694E82A66221E001F4053 /* Onboarding */,
|
||||||
@@ -2389,6 +2415,7 @@
|
|||||||
4CC7AAEE297F11B300430951 /* Events */ = {
|
4CC7AAEE297F11B300430951 /* Events */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5CC852A02BDED9970039FFC5 /* Highlight */,
|
||||||
4CA927682A290F8F0098A105 /* Components */,
|
4CA927682A290F8F0098A105 /* Components */,
|
||||||
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
||||||
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
|
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
|
||||||
@@ -2686,12 +2713,25 @@
|
|||||||
path = Images;
|
path = Images;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5CC852A02BDED9970039FFC5 /* Highlight */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5CC8529E2BD744F60039FFC5 /* HighlightView.swift */,
|
||||||
|
5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */,
|
||||||
|
5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */,
|
||||||
|
5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */,
|
||||||
|
5C4D9EA62C042FA5005EA0F7 /* HighlightPostView.swift */,
|
||||||
|
);
|
||||||
|
path = Highlight;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
7C0F392D29B57C8F0039859C /* Extensions */ = {
|
7C0F392D29B57C8F0039859C /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
||||||
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
|
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
|
||||||
D72E12772BEED22400F4F781 /* Array.swift */,
|
D72E12772BEED22400F4F781 /* Array.swift */,
|
||||||
|
D78DB85A2C20FE4F00F0AB12 /* VectorMath.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2760,6 +2800,17 @@
|
|||||||
path = Purple;
|
path = Purple;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D78DB85D2C20FE9E00F0AB12 /* Chat */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4C011B5C2BD0A56A002F2F9B /* ChatEventView.swift */,
|
||||||
|
4C011B602BD0B25C002F2F9B /* ReplyQuoteView.swift */,
|
||||||
|
4C011B5D2BD0A56A002F2F9B /* ChatroomThreadView.swift */,
|
||||||
|
D78DB85E2C20FED300F0AB12 /* ChatBubbleView.swift */,
|
||||||
|
);
|
||||||
|
path = Chat;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D79C4C152AFEB061003A41B4 /* DamusNotificationService */ = {
|
D79C4C152AFEB061003A41B4 /* DamusNotificationService */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2841,7 +2892,8 @@
|
|||||||
4C649880286E0EE300EAE2B3 /* secp256k1 */,
|
4C649880286E0EE300EAE2B3 /* secp256k1 */,
|
||||||
4C06670328FC7EC500038D2A /* Kingfisher */,
|
4C06670328FC7EC500038D2A /* Kingfisher */,
|
||||||
4C27C9312A64766F007DBC75 /* MarkdownUI */,
|
4C27C9312A64766F007DBC75 /* MarkdownUI */,
|
||||||
3AFE89C22BD4156F00AD31EF /* MCEmojiPicker */,
|
3A0A30BA2C21397A00F8C9BC /* EmojiPicker */,
|
||||||
|
D78DB8582C1CE9CA00F0AB12 /* SwipeActions */,
|
||||||
);
|
);
|
||||||
productName = damus;
|
productName = damus;
|
||||||
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
|
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
|
||||||
@@ -2981,7 +3033,8 @@
|
|||||||
4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */,
|
4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */,
|
||||||
4C27C9302A64766F007DBC75 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */,
|
4C27C9302A64766F007DBC75 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */,
|
||||||
D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */,
|
D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */,
|
||||||
3AFE89C12BD4156F00AD31EF /* XCRemoteSwiftPackageReference "MCEmojiPicker" */,
|
3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */,
|
||||||
|
D78DB8572C1CE9CA00F0AB12 /* XCRemoteSwiftPackageReference "SwipeActions" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
|
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -3126,6 +3179,7 @@
|
|||||||
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
|
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
|
||||||
D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */,
|
D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */,
|
||||||
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
|
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
|
||||||
|
5C4D9EA72C042FA5005EA0F7 /* HighlightPostView.swift in Sources */,
|
||||||
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */,
|
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */,
|
||||||
D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */,
|
D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */,
|
||||||
D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */,
|
D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */,
|
||||||
@@ -3156,6 +3210,7 @@
|
|||||||
4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */,
|
4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */,
|
||||||
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */,
|
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */,
|
||||||
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */,
|
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */,
|
||||||
|
5CC852A42BDF3CA10039FFC5 /* HighlightLink.swift in Sources */,
|
||||||
4C32B9552A9AD44700DC3548 /* ByteBuffer.swift in Sources */,
|
4C32B9552A9AD44700DC3548 /* ByteBuffer.swift in Sources */,
|
||||||
4C32B95B2A9AD44700DC3548 /* NativeObject.swift in Sources */,
|
4C32B95B2A9AD44700DC3548 /* NativeObject.swift in Sources */,
|
||||||
3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */,
|
3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */,
|
||||||
@@ -3235,6 +3290,7 @@
|
|||||||
E02429952B7E97740088B16C /* CameraController.swift in Sources */,
|
E02429952B7E97740088B16C /* CameraController.swift in Sources */,
|
||||||
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */,
|
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */,
|
||||||
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */,
|
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */,
|
||||||
|
D78DB85B2C20FE5000F0AB12 /* VectorMath.swift in Sources */,
|
||||||
D7CB5D3E2B116DAD00AD4105 /* NotificationsManager.swift in Sources */,
|
D7CB5D3E2B116DAD00AD4105 /* NotificationsManager.swift in Sources */,
|
||||||
50A16FFF2AA76A0900DFEC1F /* VideoController.swift in Sources */,
|
50A16FFF2AA76A0900DFEC1F /* VideoController.swift in Sources */,
|
||||||
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */,
|
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */,
|
||||||
@@ -3243,6 +3299,7 @@
|
|||||||
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */,
|
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */,
|
||||||
4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */,
|
4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */,
|
||||||
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
|
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
|
||||||
|
4C011B612BD0B25C002F2F9B /* ReplyQuoteView.swift in Sources */,
|
||||||
D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */,
|
D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */,
|
||||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
||||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
||||||
@@ -3293,6 +3350,7 @@
|
|||||||
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
|
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
|
||||||
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
|
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
|
||||||
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
||||||
|
5CC852A22BDED9B90039FFC5 /* HighlightDescription.swift in Sources */,
|
||||||
4C94D6432BA5AEFE00C26EFF /* QuoteRepostsView.swift in Sources */,
|
4C94D6432BA5AEFE00C26EFF /* QuoteRepostsView.swift in Sources */,
|
||||||
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */,
|
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */,
|
||||||
4CA352AE2A76C1AC003BB08B /* FollowedNotify.swift in Sources */,
|
4CA352AE2A76C1AC003BB08B /* FollowedNotify.swift in Sources */,
|
||||||
@@ -3331,11 +3389,14 @@
|
|||||||
4C64305C2A945AFF00B0C0E9 /* MusicController.swift in Sources */,
|
4C64305C2A945AFF00B0C0E9 /* MusicController.swift in Sources */,
|
||||||
5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */,
|
5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */,
|
||||||
F79C7FAD29D5E9620000F946 /* EditPictureControl.swift in Sources */,
|
F79C7FAD29D5E9620000F946 /* EditPictureControl.swift in Sources */,
|
||||||
|
4C011B5F2BD0A56A002F2F9B /* ChatroomThreadView.swift in Sources */,
|
||||||
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
|
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
|
||||||
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
|
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
|
||||||
4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */,
|
4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */,
|
||||||
4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */,
|
4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */,
|
||||||
|
5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */,
|
||||||
BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */,
|
BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */,
|
||||||
|
5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */,
|
||||||
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */,
|
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */,
|
||||||
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
||||||
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
|
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
|
||||||
@@ -3390,6 +3451,7 @@
|
|||||||
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 */,
|
||||||
|
D78DB85F2C20FED300F0AB12 /* ChatBubbleView.swift in Sources */,
|
||||||
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
|
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
|
||||||
4C9D6D162B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift in Sources */,
|
4C9D6D162B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift in Sources */,
|
||||||
4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */,
|
4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */,
|
||||||
@@ -3446,6 +3508,7 @@
|
|||||||
B51C1CEB2B55A60A00E312A9 /* MuteDurationMenu.swift in Sources */,
|
B51C1CEB2B55A60A00E312A9 /* MuteDurationMenu.swift in Sources */,
|
||||||
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
|
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
|
||||||
4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */,
|
4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */,
|
||||||
|
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */,
|
||||||
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
|
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
|
||||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||||
@@ -3499,6 +3562,7 @@
|
|||||||
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
|
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
|
||||||
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
|
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
|
||||||
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */,
|
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */,
|
||||||
|
4C011B5E2BD0A56A002F2F9B /* ChatEventView.swift in Sources */,
|
||||||
4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */,
|
4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */,
|
||||||
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */,
|
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */,
|
||||||
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,
|
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,
|
||||||
@@ -4005,6 +4069,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 = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -4031,6 +4096,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)",
|
"$(PROJECT_DIR)",
|
||||||
);
|
);
|
||||||
|
MARKETING_VERSION = 1.10;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
@@ -4054,6 +4120,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 = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -4080,6 +4147,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)",
|
"$(PROJECT_DIR)",
|
||||||
);
|
);
|
||||||
|
MARKETING_VERSION = 1.10;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
@@ -4284,12 +4352,12 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
3AFE89C12BD4156F00AD31EF /* XCRemoteSwiftPackageReference "MCEmojiPicker" */ = {
|
3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/izyumkin/MCEmojiPicker";
|
repositoryURL = "https://github.com/tyiu/EmojiPicker.git";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 1.2.3;
|
minimumVersion = 0.1.1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
||||||
@@ -4324,6 +4392,14 @@
|
|||||||
minimumVersion = 0.2.26;
|
minimumVersion = 0.2.26;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
D78DB8572C1CE9CA00F0AB12 /* XCRemoteSwiftPackageReference "SwipeActions" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/aheze/SwipeActions";
|
||||||
|
requirement = {
|
||||||
|
kind = exactVersion;
|
||||||
|
version = 1.1.0;
|
||||||
|
};
|
||||||
|
};
|
||||||
D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = {
|
D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing";
|
repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing";
|
||||||
@@ -4335,10 +4411,10 @@
|
|||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
3AFE89C22BD4156F00AD31EF /* MCEmojiPicker */ = {
|
3A0A30BA2C21397A00F8C9BC /* EmojiPicker */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 3AFE89C12BD4156F00AD31EF /* XCRemoteSwiftPackageReference "MCEmojiPicker" */;
|
package = 3A0A30B92C21397A00F8C9BC /* XCRemoteSwiftPackageReference "EmojiPicker" */;
|
||||||
productName = MCEmojiPicker;
|
productName = EmojiPicker;
|
||||||
};
|
};
|
||||||
4C06670328FC7EC500038D2A /* Kingfisher */ = {
|
4C06670328FC7EC500038D2A /* Kingfisher */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
@@ -4360,6 +4436,11 @@
|
|||||||
package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */;
|
package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */;
|
||||||
productName = secp256k1;
|
productName = secp256k1;
|
||||||
};
|
};
|
||||||
|
D78DB8582C1CE9CA00F0AB12 /* SwipeActions */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = D78DB8572C1CE9CA00F0AB12 /* XCRemoteSwiftPackageReference "SwipeActions" */;
|
||||||
|
productName = SwipeActions;
|
||||||
|
};
|
||||||
D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */ = {
|
D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
|
package = D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */;
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
{
|
{
|
||||||
|
"originHash" : "babaf4d5748afecf49bbb702530d8e9576460692f478b0a50ee43195dd4440e2",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "emojikit",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/tyiu/EmojiKit",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "05805f72d63a6d6a2d7dc7fe14abd37c1317b11a",
|
||||||
|
"version" : "0.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "emojipicker",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/tyiu/EmojiPicker.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "0c28b4a1a6b8840cf2580bda59517f6d0a733dc8",
|
||||||
|
"version" : "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "gsplayer",
|
"identity" : "gsplayer",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -18,15 +37,6 @@
|
|||||||
"version" : "7.6.1"
|
"version" : "7.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"identity" : "mcemojipicker",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/izyumkin/MCEmojiPicker",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "e0b4903b75ae1cc418d276d84d1cb946b8a1d73c",
|
|
||||||
"version" : "1.2.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identity" : "secp256k1.swift",
|
"identity" : "secp256k1.swift",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -35,6 +45,15 @@
|
|||||||
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
|
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-collections",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-collections.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
|
||||||
|
"version" : "1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-markdown-ui",
|
"identity" : "swift-markdown-ui",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -60,7 +79,25 @@
|
|||||||
"revision" : "74203046135342e4a4a627476dd6caf8b28fe11b",
|
"revision" : "74203046135342e4a4a627476dd6caf8b28fe11b",
|
||||||
"version" : "509.0.0"
|
"version" : "509.0.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-trie",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/tyiu/swift-trie",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "4c50bff6c168f74425f70476be62a072980d2da7",
|
||||||
|
"version" : "0.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swipeactions",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/aheze/SwipeActions",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "41e6f6dce02d8cfa164f8c5461a41340850ca3ab",
|
||||||
|
"version" : "1.1.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xD7",
|
||||||
|
"green" : "0xD1",
|
||||||
|
"red" : "0xD1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x13",
|
||||||
|
"green" : "0x11",
|
||||||
|
"red" : "0x11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xF9",
|
||||||
|
"green" : "0xF3",
|
||||||
|
"red" : "0xF3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x25",
|
||||||
|
"green" : "0x22",
|
||||||
|
"red" : "0x22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "244",
|
||||||
|
"green" : "218",
|
||||||
|
"red" : "244"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "92",
|
||||||
|
"green" : "45",
|
||||||
|
"red" : "93"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "236",
|
||||||
|
"green" : "194",
|
||||||
|
"red" : "238"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "109",
|
||||||
|
"green" : "49",
|
||||||
|
"red" : "111"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "197",
|
||||||
|
"green" : "67",
|
||||||
|
"red" : "204"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "255",
|
||||||
|
"green" : "194",
|
||||||
|
"red" : "255"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xF2",
|
||||||
|
"green" : "0xD8",
|
||||||
|
"red" : "0xF4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x45",
|
||||||
|
"green" : "0x17",
|
||||||
|
"red" : "0x47"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,11 @@ import SwiftUI
|
|||||||
|
|
||||||
class DamusColors {
|
class DamusColors {
|
||||||
static let adaptableGrey = Color("DamusAdaptableGrey")
|
static let adaptableGrey = Color("DamusAdaptableGrey")
|
||||||
|
static let adaptableGrey2 = Color("DamusAdaptableGrey 2")
|
||||||
|
static let adaptableLighterGrey = Color("DamusAdaptableLighterGrey")
|
||||||
|
static let adaptablePurpleBackground = Color("DamusAdaptablePurpleBackground 1")
|
||||||
|
static let adaptablePurpleBackground2 = Color("DamusAdaptablePurpleBackground 2")
|
||||||
|
static let adaptablePurpleForeground = Color("DamusAdaptablePurpleForeground")
|
||||||
static let adaptableBlack = Color("DamusAdaptableBlack")
|
static let adaptableBlack = Color("DamusAdaptableBlack")
|
||||||
static let adaptableWhite = Color("DamusAdaptableWhite")
|
static let adaptableWhite = Color("DamusAdaptableWhite")
|
||||||
static let white = Color("DamusWhite")
|
static let white = Color("DamusWhite")
|
||||||
@@ -23,6 +28,7 @@ class DamusColors {
|
|||||||
static let green = Color("DamusGreen")
|
static let green = Color("DamusGreen")
|
||||||
static let purple = Color("DamusPurple")
|
static let purple = Color("DamusPurple")
|
||||||
static let deepPurple = Color("DamusDeepPurple")
|
static let deepPurple = Color("DamusDeepPurple")
|
||||||
|
static let highlight = Color("DamusHighlight")
|
||||||
static let blue = Color("DamusBlue")
|
static let blue = Color("DamusBlue")
|
||||||
static let bitcoin = Color("Bitcoin")
|
static let bitcoin = Color("Bitcoin")
|
||||||
static let success = Color("DamusSuccessPrimary")
|
static let success = Color("DamusSuccessPrimary")
|
||||||
|
|||||||
@@ -9,16 +9,20 @@ import UIKit
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SelectableText: View {
|
struct SelectableText: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
let event: NostrEvent?
|
||||||
let attributedString: AttributedString
|
let attributedString: AttributedString
|
||||||
let textAlignment: NSTextAlignment
|
let textAlignment: NSTextAlignment
|
||||||
|
@State private var showHighlightPost = false
|
||||||
|
@State private var selectedText = ""
|
||||||
@State private var selectedTextHeight: CGFloat = .zero
|
@State private var selectedTextHeight: CGFloat = .zero
|
||||||
@State private var selectedTextWidth: CGFloat = .zero
|
@State private var selectedTextWidth: CGFloat = .zero
|
||||||
|
|
||||||
let size: EventViewKind
|
let size: EventViewKind
|
||||||
|
|
||||||
init(attributedString: AttributedString, textAlignment: NSTextAlignment? = nil, size: EventViewKind) {
|
init(damus_state: DamusState, event: NostrEvent?, attributedString: AttributedString, textAlignment: NSTextAlignment? = nil, size: EventViewKind) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self.event = event
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
self.textAlignment = textAlignment ?? NSTextAlignment.natural
|
self.textAlignment = textAlignment ?? NSTextAlignment.natural
|
||||||
self.size = size
|
self.size = size
|
||||||
@@ -32,6 +36,9 @@ struct SelectableText: View {
|
|||||||
font: eventviewsize_to_uifont(size),
|
font: eventviewsize_to_uifont(size),
|
||||||
fixedWidth: selectedTextWidth,
|
fixedWidth: selectedTextWidth,
|
||||||
textAlignment: self.textAlignment,
|
textAlignment: self.textAlignment,
|
||||||
|
enableHighlighting: self.enableHighlighting(),
|
||||||
|
showHighlightPost: $showHighlightPost,
|
||||||
|
selectedText: $selectedText,
|
||||||
height: $selectedTextHeight
|
height: $selectedTextHeight
|
||||||
)
|
)
|
||||||
.padding([.leading, .trailing], -1.0)
|
.padding([.leading, .trailing], -1.0)
|
||||||
@@ -46,8 +53,48 @@ struct SelectableText: View {
|
|||||||
self.selectedTextWidth = newSize.width
|
self.selectedTextWidth = newSize.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showHighlightPost) {
|
||||||
|
if let event {
|
||||||
|
HighlightPostView(damus_state: damus_state, event: event, selectedText: $selectedText)
|
||||||
|
.presentationDragIndicator(.visible)
|
||||||
|
.presentationDetents([.height(selectedTextHeight + 150), .medium, .large])
|
||||||
|
}
|
||||||
|
}
|
||||||
.frame(height: selectedTextHeight)
|
.frame(height: selectedTextHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func enableHighlighting() -> Bool {
|
||||||
|
self.event != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate class TextView: UITextView {
|
||||||
|
@Binding var showHighlightPost: Bool
|
||||||
|
@Binding var selectedText: String
|
||||||
|
|
||||||
|
init(frame: CGRect, textContainer: NSTextContainer?, showHighlightPost: Binding<Bool>, selectedText: Binding<String>) {
|
||||||
|
self._showHighlightPost = showHighlightPost
|
||||||
|
self._selectedText = selectedText
|
||||||
|
super.init(frame: frame, textContainer: textContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||||
|
if action == #selector(highlightText(_:)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.canPerformAction(action, withSender: sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func highlightText(_ sender: Any?) {
|
||||||
|
guard let selectedRange = self.selectedTextRange else { return }
|
||||||
|
selectedText = self.text(in: selectedRange) ?? ""
|
||||||
|
showHighlightPost.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate struct TextViewRepresentable: UIViewRepresentable {
|
fileprivate struct TextViewRepresentable: UIViewRepresentable {
|
||||||
@@ -57,11 +104,13 @@ struct SelectableText: View {
|
|||||||
let font: UIFont
|
let font: UIFont
|
||||||
let fixedWidth: CGFloat
|
let fixedWidth: CGFloat
|
||||||
let textAlignment: NSTextAlignment
|
let textAlignment: NSTextAlignment
|
||||||
|
let enableHighlighting: Bool
|
||||||
|
@Binding var showHighlightPost: Bool
|
||||||
|
@Binding var selectedText: String
|
||||||
@Binding var height: CGFloat
|
@Binding var height: CGFloat
|
||||||
|
|
||||||
func makeUIView(context: UIViewRepresentableContext<Self>) -> UITextView {
|
func makeUIView(context: UIViewRepresentableContext<Self>) -> TextView {
|
||||||
let view = UITextView()
|
let view = TextView(frame: .zero, textContainer: nil, showHighlightPost: $showHighlightPost, selectedText: $selectedText)
|
||||||
view.isEditable = false
|
view.isEditable = false
|
||||||
view.dataDetectorTypes = .all
|
view.dataDetectorTypes = .all
|
||||||
view.isSelectable = true
|
view.isSelectable = true
|
||||||
@@ -71,10 +120,15 @@ struct SelectableText: View {
|
|||||||
view.textContainerInset.left = 1.0
|
view.textContainerInset.left = 1.0
|
||||||
view.textContainerInset.right = 1.0
|
view.textContainerInset.right = 1.0
|
||||||
view.textAlignment = textAlignment
|
view.textAlignment = textAlignment
|
||||||
|
|
||||||
|
let menuController = UIMenuController.shared
|
||||||
|
let highlightItem = UIMenuItem(title: "Highlight", action: #selector(view.highlightText(_:)))
|
||||||
|
menuController.menuItems = self.enableHighlighting ? [highlightItem] : []
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<Self>) {
|
func updateUIView(_ uiView: TextView, context: UIViewRepresentableContext<Self>) {
|
||||||
let mutableAttributedString = createNSAttributedString()
|
let mutableAttributedString = createNSAttributedString()
|
||||||
uiView.attributedText = mutableAttributedString
|
uiView.attributedText = mutableAttributedString
|
||||||
uiView.textAlignment = self.textAlignment
|
uiView.textAlignment = self.textAlignment
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ struct TranslateView: View {
|
|||||||
.padding([.top, .bottom], 10)
|
.padding([.top, .bottom], 10)
|
||||||
|
|
||||||
if self.size == .selected {
|
if self.size == .selected {
|
||||||
SelectableText(attributedString: artifacts.content.attributed, size: self.size)
|
SelectableText(damus_state: damus_state, event: event, attributedString: artifacts.content.attributed, size: self.size)
|
||||||
} else {
|
} else {
|
||||||
artifacts.content.text
|
artifacts.content.text
|
||||||
.font(eventviewsize_to_font(self.size, font_size: font_size))
|
.font(eventviewsize_to_font(self.size, font_size: font_size))
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import SwiftUI
|
|||||||
struct TruncatedText: View {
|
struct TruncatedText: View {
|
||||||
let text: CompatibleText
|
let text: CompatibleText
|
||||||
let maxChars: Int
|
let maxChars: Int
|
||||||
|
let show_show_more_button: Bool
|
||||||
|
|
||||||
init(text: CompatibleText, maxChars: Int = 280) {
|
init(text: CompatibleText, maxChars: Int = 280, show_show_more_button: Bool) {
|
||||||
self.text = text
|
self.text = text
|
||||||
self.maxChars = maxChars
|
self.maxChars = maxChars
|
||||||
|
self.show_show_more_button = show_show_more_button
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -29,19 +31,21 @@ struct TruncatedText: View {
|
|||||||
|
|
||||||
if truncatedAttributedString != nil {
|
if truncatedAttributedString != nil {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
if self.show_show_more_button {
|
||||||
Button(NSLocalizedString("Show more", comment: "Button to show entire note.")) { }
|
Button(NSLocalizedString("Show more", comment: "Button to show entire note.")) { }
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TruncatedText_Previews: PreviewProvider {
|
struct TruncatedText_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VStack(spacing: 100) {
|
VStack(spacing: 100) {
|
||||||
TruncatedText(text: CompatibleText(stringLiteral: "hello\nthere\none\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven"))
|
TruncatedText(text: CompatibleText(stringLiteral: "hello\nthere\none\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven"), show_show_more_button: true)
|
||||||
.frame(width: 200, height: 200)
|
.frame(width: 200, height: 200)
|
||||||
|
|
||||||
TruncatedText(text: CompatibleText(stringLiteral: "hello\nthere\none\ntwo\nthree\nfour"))
|
TruncatedText(text: CompatibleText(stringLiteral: "hello\nthere\none\ntwo\nthree\nfour"), show_show_more_button: true)
|
||||||
.frame(width: 200, height: 200)
|
.frame(width: 200, height: 200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AVKit
|
import AVKit
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
|
import EmojiPicker
|
||||||
|
|
||||||
struct ZapSheet {
|
struct ZapSheet {
|
||||||
let target: ZapTarget
|
let target: ZapTarget
|
||||||
@@ -719,7 +720,8 @@ struct ContentView: View {
|
|||||||
music: MusicController(onChange: music_changed),
|
music: MusicController(onChange: music_changed),
|
||||||
video: VideoController(),
|
video: VideoController(),
|
||||||
ndb: ndb,
|
ndb: ndb,
|
||||||
quote_reposts: .init(our_pubkey: pubkey)
|
quote_reposts: .init(our_pubkey: pubkey),
|
||||||
|
emoji_provider: DefaultEmojiProvider(showAllVariations: true)
|
||||||
)
|
)
|
||||||
|
|
||||||
home.damus_state = self.damus_state!
|
home.damus_state = self.damus_state!
|
||||||
|
|||||||
@@ -9,31 +9,31 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
class CreateAccountModel: ObservableObject {
|
class CreateAccountModel: ObservableObject {
|
||||||
@Published var real_name: String = ""
|
@Published var display_name: String = ""
|
||||||
@Published var nick_name: String = ""
|
@Published var name: String = ""
|
||||||
@Published var about: String = ""
|
@Published var about: String = ""
|
||||||
@Published var pubkey: Pubkey = .empty
|
@Published var pubkey: Pubkey = .empty
|
||||||
@Published var privkey: Privkey = .empty
|
@Published var privkey: Privkey = .empty
|
||||||
@Published var profile_image: URL? = nil
|
@Published var profile_image: URL? = nil
|
||||||
|
|
||||||
var rendered_name: String {
|
var rendered_name: String {
|
||||||
if real_name.isEmpty {
|
if display_name.isEmpty {
|
||||||
return nick_name
|
return name
|
||||||
}
|
}
|
||||||
return real_name
|
return display_name
|
||||||
}
|
}
|
||||||
|
|
||||||
var keypair: Keypair {
|
var keypair: Keypair {
|
||||||
return Keypair(pubkey: self.pubkey, privkey: self.privkey)
|
return Keypair(pubkey: self.pubkey, privkey: self.privkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(real: String = "", nick: String = "", about: String = "") {
|
init(display_name: String = "", name: String = "", about: String = "") {
|
||||||
let keypair = generate_new_keypair()
|
let keypair = generate_new_keypair()
|
||||||
self.pubkey = keypair.pubkey
|
self.pubkey = keypair.pubkey
|
||||||
self.privkey = keypair.privkey
|
self.privkey = keypair.privkey
|
||||||
|
|
||||||
self.real_name = real
|
self.display_name = display_name
|
||||||
self.nick_name = nick
|
self.name = name
|
||||||
self.about = about
|
self.about = about
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import LinkPresentation
|
import LinkPresentation
|
||||||
|
import EmojiPicker
|
||||||
|
|
||||||
class DamusState: HeadlessDamusState {
|
class DamusState: HeadlessDamusState {
|
||||||
let pool: RelayPool
|
let pool: RelayPool
|
||||||
@@ -37,8 +38,9 @@ class DamusState: HeadlessDamusState {
|
|||||||
let ndb: Ndb
|
let ndb: Ndb
|
||||||
var purple: DamusPurple
|
var purple: DamusPurple
|
||||||
var push_notification_client: PushNotificationClient
|
var push_notification_client: PushNotificationClient
|
||||||
|
let emoji_provider: EmojiProvider
|
||||||
|
|
||||||
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
|
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter, emoji_provider: EmojiProvider) {
|
||||||
self.pool = pool
|
self.pool = pool
|
||||||
self.keypair = keypair
|
self.keypair = keypair
|
||||||
self.likes = likes
|
self.likes = likes
|
||||||
@@ -70,6 +72,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
)
|
)
|
||||||
self.quote_reposts = quote_reposts
|
self.quote_reposts = quote_reposts
|
||||||
self.push_notification_client = PushNotificationClient(keypair: keypair, settings: settings)
|
self.push_notification_client = PushNotificationClient(keypair: keypair, settings: settings)
|
||||||
|
self.emoji_provider = emoji_provider
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@@ -99,6 +102,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
|
|
||||||
func close() {
|
func close() {
|
||||||
print("txn: damus close")
|
print("txn: damus close")
|
||||||
|
wallet.disconnect()
|
||||||
pool.close()
|
pool.close()
|
||||||
ndb.close()
|
ndb.close()
|
||||||
}
|
}
|
||||||
@@ -134,7 +138,8 @@ class DamusState: HeadlessDamusState {
|
|||||||
music: nil,
|
music: nil,
|
||||||
video: VideoController(),
|
video: VideoController(),
|
||||||
ndb: .empty,
|
ndb: .empty,
|
||||||
quote_reposts: .init(our_pubkey: empty_pub)
|
quote_reposts: .init(our_pubkey: empty_pub),
|
||||||
|
emoji_provider: DefaultEmojiProvider(showAllVariations: true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
damus/Models/HighlightEvent.swift
Normal file
34
damus/Models/HighlightEvent.swift
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// HighlightEvent.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/22/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct HighlightEvent {
|
||||||
|
let event: NostrEvent
|
||||||
|
|
||||||
|
var event_ref: String? = nil
|
||||||
|
var url_ref: URL? = nil
|
||||||
|
var context: String? = nil
|
||||||
|
|
||||||
|
static func parse(from ev: NostrEvent) -> HighlightEvent {
|
||||||
|
var highlight = HighlightEvent(event: ev)
|
||||||
|
|
||||||
|
for tag in ev.tags {
|
||||||
|
guard tag.count >= 2 else { continue }
|
||||||
|
switch tag[0].string() {
|
||||||
|
case "e": highlight.event_ref = tag[1].string()
|
||||||
|
case "a": highlight.event_ref = tag[1].string()
|
||||||
|
case "r": highlight.url_ref = URL(string: tag[1].string())
|
||||||
|
case "context": highlight.context = tag[1].string()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return highlight
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -184,7 +184,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
case .chat, .longform, .text:
|
case .chat, .longform, .text, .highlight:
|
||||||
handle_text_event(sub_id: sub_id, ev)
|
handle_text_event(sub_id: sub_id, ev)
|
||||||
case .contacts:
|
case .contacts:
|
||||||
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
||||||
@@ -586,7 +586,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: RelayURL? = nil) {
|
func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: RelayURL? = nil) {
|
||||||
// TODO: separate likes?
|
// TODO: separate likes?
|
||||||
var home_filter_kinds: [NostrKind] = [
|
var home_filter_kinds: [NostrKind] = [
|
||||||
.text, .longform, .boost
|
.text, .longform, .boost, .highlight
|
||||||
]
|
]
|
||||||
if !damus_state.settings.onlyzaps_mode {
|
if !damus_state.settings.onlyzaps_mode {
|
||||||
home_filter_kinds.append(.like)
|
home_filter_kinds.append(.like)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
var text_filter = NostrFilter(kinds: [.text, .longform])
|
var text_filter = NostrFilter(kinds: [.text, .longform, .highlight])
|
||||||
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])
|
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])
|
||||||
|
|
||||||
profile_filter.authors = [pubkey]
|
profile_filter.authors = [pubkey]
|
||||||
|
|||||||
@@ -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]
|
search.kinds = [.text, .like, .longform, .highlight]
|
||||||
|
|
||||||
//likes_filter.ids = ref_events.referenced_ids!
|
//likes_filter.ids = ref_events.referenced_ids!
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ class ThreadModel: ObservableObject {
|
|||||||
add_event(event, keypair: damus_state.keypair)
|
add_event(event, keypair: damus_state.keypair)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func events() -> [NostrEvent] {
|
||||||
|
return Array(event_map).sorted(by: { a, b in
|
||||||
|
return a.created_at < b.created_at
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var is_original: Bool {
|
var is_original: Bool {
|
||||||
return original_event.id == event.id
|
return original_event.id == event.id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ enum NostrKind: UInt32, Codable {
|
|||||||
case longform = 30023
|
case longform = 30023
|
||||||
case zap = 9735
|
case zap = 9735
|
||||||
case zap_request = 9734
|
case zap_request = 9734
|
||||||
|
case highlight = 9802
|
||||||
case nwc_request = 23194
|
case nwc_request = 23194
|
||||||
case nwc_response = 23195
|
case nwc_response = 23195
|
||||||
case http_auth = 27235
|
case http_auth = 27235
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -10,9 +10,9 @@ import Foundation
|
|||||||
class Constants {
|
class Constants {
|
||||||
//static let EXAMPLE_DEMOS: DamusState = .empty
|
//static let EXAMPLE_DEMOS: DamusState = .empty
|
||||||
static let DAMUS_APP_GROUP_IDENTIFIER: String = "group.com.damus"
|
static let DAMUS_APP_GROUP_IDENTIFIER: String = "group.com.damus"
|
||||||
static let DEVICE_TOKEN_RECEIVER_PRODUCTION_URL: URL = URL(string: "https://notify.damus.io:8000/user-info")!
|
static let DEVICE_TOKEN_RECEIVER_PRODUCTION_URL: URL = URL(string: "http://45.33.32.5:8000/user-info")!
|
||||||
static let DEVICE_TOKEN_RECEIVER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info")!
|
static let DEVICE_TOKEN_RECEIVER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info")!
|
||||||
static let DEVICE_TOKEN_REVOKER_PRODUCTION_URL: URL = URL(string: "https://notify.damus.io:8000/user-info/remove")!
|
static let DEVICE_TOKEN_REVOKER_PRODUCTION_URL: URL = URL(string: "http://45.33.32.5:8000/user-info/remove")!
|
||||||
static let DEVICE_TOKEN_REVOKER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info/remove")!
|
static let DEVICE_TOKEN_REVOKER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info/remove")!
|
||||||
static let MAIN_APP_BUNDLE_IDENTIFIER: String = "com.jb55.damus2"
|
static let MAIN_APP_BUNDLE_IDENTIFIER: String = "com.jb55.damus2"
|
||||||
static let NOTIFICATION_EXTENSION_BUNDLE_IDENTIFIER: String = "com.jb55.damus2.DamusNotificationService"
|
static let NOTIFICATION_EXTENSION_BUNDLE_IDENTIFIER: String = "com.jb55.damus2.DamusNotificationService"
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ class EventCache {
|
|||||||
// TODO: remove me and change code to use ndb directly
|
// TODO: remove me and change code to use ndb directly
|
||||||
private let ndb: Ndb
|
private let ndb: Ndb
|
||||||
private var events: [NoteId: NostrEvent] = [:]
|
private var events: [NoteId: NostrEvent] = [:]
|
||||||
private var replies = ReplyMap()
|
|
||||||
private var cancellable: AnyCancellable?
|
private var cancellable: AnyCancellable?
|
||||||
private var image_metadata: [String: ImageMetadataState] = [:] // lowercased URL key
|
private var image_metadata: [String: ImageMetadataState] = [:] // lowercased URL key
|
||||||
private var event_data: [NoteId: EventData] = [:]
|
private var event_data: [NoteId: EventData] = [:]
|
||||||
|
var replies = ReplyMap()
|
||||||
|
|
||||||
//private var thread_latest: [String: Int64]
|
//private var thread_latest: [String: Int64]
|
||||||
|
|
||||||
|
|||||||
27
damus/Util/Extensions/VectorMath.swift
Normal file
27
damus/Util/Extensions/VectorMath.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// VectorMath.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2024-06-17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension CGPoint {
|
||||||
|
/// Summing a vector to a point
|
||||||
|
static func +(lhs: CGPoint, rhs: CGVector) -> CGPoint {
|
||||||
|
return CGPoint(x: lhs.x + rhs.dx, y: lhs.y + rhs.dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtracting a vector from a point
|
||||||
|
static func -(lhs: CGPoint, rhs: CGVector) -> CGPoint {
|
||||||
|
return CGPoint(x: lhs.x - rhs.dx, y: lhs.y - rhs.dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CGVector {
|
||||||
|
/// Multiplying a vector by a scalar
|
||||||
|
static func *(lhs: CGVector, rhs: CGFloat) -> CGVector {
|
||||||
|
return CGVector(dx: lhs.dx * rhs, dy: lhs.dy * rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,7 +85,7 @@ enum Route: Hashable {
|
|||||||
case .TranslationSettings(let settings):
|
case .TranslationSettings(let settings):
|
||||||
TranslationSettingsView(settings: settings, damus_state: damusState)
|
TranslationSettingsView(settings: settings, damus_state: damusState)
|
||||||
case .ReactionsSettings(let settings):
|
case .ReactionsSettings(let settings):
|
||||||
ReactionsSettingsView(settings: settings)
|
ReactionsSettingsView(settings: settings, damus_state: damusState)
|
||||||
case .SearchSettings(let settings):
|
case .SearchSettings(let settings):
|
||||||
SearchSettingsView(settings: settings)
|
SearchSettingsView(settings: settings)
|
||||||
case .DeveloperSettings(let settings):
|
case .DeveloperSettings(let settings):
|
||||||
@@ -93,7 +93,8 @@ enum Route: Hashable {
|
|||||||
case .FirstAidSettings(settings: let settings):
|
case .FirstAidSettings(settings: let settings):
|
||||||
FirstAidSettingsView(damus_state: damusState, settings: settings)
|
FirstAidSettingsView(damus_state: damusState, settings: settings)
|
||||||
case .Thread(let thread):
|
case .Thread(let thread):
|
||||||
ThreadView(state: damusState, thread: thread)
|
ChatroomThreadView(damus: damusState, thread: thread)
|
||||||
|
//ThreadView(state: damusState, thread: thread)
|
||||||
case .Reposts(let reposts):
|
case .Reposts(let reposts):
|
||||||
RepostsView(damus_state: damusState, model: reposts)
|
RepostsView(damus_state: damusState, model: reposts)
|
||||||
case .QuoteReposts(let quote_reposts):
|
case .QuoteReposts(let quote_reposts):
|
||||||
|
|||||||
@@ -6,28 +6,34 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import MCEmojiPicker
|
import EmojiPicker
|
||||||
|
import EmojiKit
|
||||||
|
import SwipeActions
|
||||||
|
|
||||||
struct EventActionBar: View {
|
struct EventActionBar: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let generator = UIImpactFeedbackGenerator(style: .medium)
|
let generator = UIImpactFeedbackGenerator(style: .medium)
|
||||||
let userProfile : ProfileModel
|
let userProfile : ProfileModel
|
||||||
|
let swipe_context: SwipeContext?
|
||||||
|
let options: Options
|
||||||
|
|
||||||
// just used for previews
|
// just used for previews
|
||||||
@State var show_share_sheet: Bool = false
|
@State var show_share_sheet: Bool = false
|
||||||
@State var show_share_action: Bool = false
|
@State var show_share_action: Bool = false
|
||||||
@State var show_repost_action: Bool = false
|
@State var show_repost_action: Bool = false
|
||||||
|
|
||||||
@State private var isOnTopHalfOfScreen: Bool = false
|
@State private var selectedEmoji: Emoji? = nil
|
||||||
|
|
||||||
@ObservedObject var bar: ActionBarModel
|
@ObservedObject var bar: ActionBarModel
|
||||||
|
|
||||||
init(damus_state: DamusState, event: NostrEvent, bar: ActionBarModel? = nil) {
|
init(damus_state: DamusState, event: NostrEvent, bar: ActionBarModel? = nil, options: Options = [], swipe_context: SwipeContext? = nil) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.event = event
|
self.event = event
|
||||||
_bar = ObservedObject(wrappedValue: bar ?? make_actionbar_model(ev: event.id, damus: damus_state))
|
_bar = ObservedObject(wrappedValue: bar ?? make_actionbar_model(ev: event.id, damus: damus_state))
|
||||||
self.userProfile = ProfileModel(pubkey: event.pubkey, damus: damus_state)
|
self.userProfile = ProfileModel(pubkey: event.pubkey, damus: damus_state)
|
||||||
|
self.options = options
|
||||||
|
self.swipe_context = swipe_context
|
||||||
}
|
}
|
||||||
|
|
||||||
var lnurl: String? {
|
var lnurl: String? {
|
||||||
@@ -44,9 +50,57 @@ struct EventActionBar: View {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var space_if_spread: AnyView {
|
||||||
HStack {
|
if options.contains(.no_spread) {
|
||||||
if damus_state.keypair.privkey != nil {
|
return AnyView(EmptyView())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return AnyView(Spacer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Swipe action menu buttons
|
||||||
|
|
||||||
|
var reply_swipe_button: some View {
|
||||||
|
SwipeAction(systemImage: "arrowshape.turn.up.left.fill", backgroundColor: DamusColors.adaptableGrey) {
|
||||||
|
notify(.compose(.replying_to(event)))
|
||||||
|
self.swipe_context?.state.wrappedValue = .closed
|
||||||
|
}
|
||||||
|
.allowSwipeToTrigger()
|
||||||
|
.swipeButtonStyle()
|
||||||
|
.accessibilityLabel(NSLocalizedString("Reply", comment: "Accessibility label for reply button"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var repost_swipe_button: some View {
|
||||||
|
SwipeAction(image: "repost", backgroundColor: DamusColors.adaptableGrey) {
|
||||||
|
self.show_repost_action = true
|
||||||
|
self.swipe_context?.state.wrappedValue = .closed
|
||||||
|
}
|
||||||
|
.swipeButtonStyle()
|
||||||
|
.accessibilityLabel(NSLocalizedString("Repost or quote this note", comment: "Accessibility label for repost/quote button"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var like_swipe_button: some View {
|
||||||
|
SwipeAction(image: "shaka", backgroundColor: DamusColors.adaptableGrey) {
|
||||||
|
send_like(emoji: damus_state.settings.default_emoji_reaction)
|
||||||
|
self.swipe_context?.state.wrappedValue = .closed
|
||||||
|
}
|
||||||
|
.swipeButtonStyle()
|
||||||
|
.accessibilityLabel(NSLocalizedString("React with default reaction emoji", comment: "Accessibility label for react button"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var share_swipe_button: some View {
|
||||||
|
SwipeAction(image: "upload", backgroundColor: DamusColors.adaptableGrey) {
|
||||||
|
show_share_action = true
|
||||||
|
self.swipe_context?.state.wrappedValue = .closed
|
||||||
|
}
|
||||||
|
.swipeButtonStyle()
|
||||||
|
.accessibilityLabel(NSLocalizedString("Share externally", comment: "Accessibility label for external share button"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Bar buttons
|
||||||
|
|
||||||
|
var reply_button: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
EventActionButton(img: "bubble2", col: bar.replied ? DamusColors.purple : Color.gray) {
|
EventActionButton(img: "bubble2", col: bar.replied ? DamusColors.purple : Color.gray) {
|
||||||
notify(.compose(.replying_to(event)))
|
notify(.compose(.replying_to(event)))
|
||||||
@@ -57,7 +111,8 @@ struct EventActionBar: View {
|
|||||||
.foregroundColor(bar.replied ? DamusColors.purple : Color.gray)
|
.foregroundColor(bar.replied ? DamusColors.purple : Color.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer()
|
|
||||||
|
var repost_button: some View {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
|
|
||||||
EventActionButton(img: "repost", col: bar.boosted ? Color.green : nil) {
|
EventActionButton(img: "repost", col: bar.boosted ? Color.green : nil) {
|
||||||
@@ -68,12 +123,11 @@ struct EventActionBar: View {
|
|||||||
.font(.footnote.weight(.medium))
|
.font(.footnote.weight(.medium))
|
||||||
.foregroundColor(bar.boosted ? Color.green : Color.gray)
|
.foregroundColor(bar.boosted ? Color.green : Color.gray)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if show_like {
|
var like_button: some View {
|
||||||
Spacer()
|
|
||||||
|
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
LikeButton(damus_state: damus_state, liked: bar.liked, liked_emoji: bar.our_like != nil ? to_reaction_emoji(ev: bar.our_like!) : nil, isOnTopHalfOfScreen: $isOnTopHalfOfScreen) { emoji in
|
LikeButton(damus_state: damus_state, liked: bar.liked, liked_emoji: bar.our_like != nil ? to_reaction_emoji(ev: bar.our_like!) : nil) { emoji in
|
||||||
if bar.liked {
|
if bar.liked {
|
||||||
//notify(.delete, bar.our_like)
|
//notify(.delete, bar.our_like)
|
||||||
} else {
|
} else {
|
||||||
@@ -87,17 +141,85 @@ struct EventActionBar: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let lnurl = self.lnurl {
|
var share_button: some View {
|
||||||
Spacer()
|
|
||||||
NoteZapButton(damus_state: damus_state, target: ZapTarget.note(id: event.id, author: event.pubkey), lnurl: lnurl, zaps: self.damus_state.events.get_cache_data(self.event.id).zaps_model)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
EventActionButton(img: "upload", col: Color.gray) {
|
EventActionButton(img: "upload", col: Color.gray) {
|
||||||
show_share_action = true
|
show_share_action = true
|
||||||
}
|
}
|
||||||
.accessibilityLabel(NSLocalizedString("Share", comment: "Button to share a note"))
|
.accessibilityLabel(NSLocalizedString("Share", comment: "Button to share a note"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Main views
|
||||||
|
|
||||||
|
var swipe_action_menu_content: some View {
|
||||||
|
Group {
|
||||||
|
self.reply_swipe_button
|
||||||
|
self.repost_swipe_button
|
||||||
|
if show_like {
|
||||||
|
self.like_swipe_button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var swipe_action_menu_reverse_content: some View {
|
||||||
|
Group {
|
||||||
|
if show_like {
|
||||||
|
self.like_swipe_button
|
||||||
|
}
|
||||||
|
self.repost_swipe_button
|
||||||
|
self.reply_swipe_button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var action_bar_content: some View {
|
||||||
|
let hide_items_without_activity = options.contains(.hide_items_without_activity)
|
||||||
|
let should_hide_chat_bubble = hide_items_without_activity && bar.replies == 0
|
||||||
|
let should_hide_repost = hide_items_without_activity && bar.boosts == 0
|
||||||
|
let should_hide_reactions = hide_items_without_activity && bar.likes == 0
|
||||||
|
let zap_model = self.damus_state.events.get_cache_data(self.event.id).zaps_model
|
||||||
|
let should_hide_zap = hide_items_without_activity && zap_model.zap_total > 0
|
||||||
|
let should_hide_share_button = hide_items_without_activity
|
||||||
|
|
||||||
|
return HStack(spacing: options.contains(.no_spread) ? 10 : 0) {
|
||||||
|
if damus_state.keypair.privkey != nil && !should_hide_chat_bubble {
|
||||||
|
self.reply_button
|
||||||
|
}
|
||||||
|
|
||||||
|
if !should_hide_repost {
|
||||||
|
self.space_if_spread
|
||||||
|
self.repost_button
|
||||||
|
}
|
||||||
|
|
||||||
|
if show_like && !should_hide_reactions {
|
||||||
|
self.space_if_spread
|
||||||
|
self.like_button
|
||||||
|
}
|
||||||
|
|
||||||
|
if let lnurl = self.lnurl, !should_hide_zap {
|
||||||
|
self.space_if_spread
|
||||||
|
NoteZapButton(damus_state: damus_state, target: ZapTarget.note(id: event.id, author: event.pubkey), lnurl: lnurl, zaps: zap_model)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !should_hide_share_button {
|
||||||
|
self.space_if_spread
|
||||||
|
self.share_button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var content: some View {
|
||||||
|
if options.contains(.swipe_action_menu) {
|
||||||
|
AnyView(self.swipe_action_menu_content)
|
||||||
|
}
|
||||||
|
else if options.contains(.swipe_action_menu_reverse) {
|
||||||
|
AnyView(self.swipe_action_menu_reverse_content)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
AnyView(self.action_bar_content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
self.content
|
||||||
.onAppear {
|
.onAppear {
|
||||||
self.bar.update(damus: damus_state, evid: self.event.id)
|
self.bar.update(damus: damus_state, evid: self.event.id)
|
||||||
}
|
}
|
||||||
@@ -136,20 +258,6 @@ struct EventActionBar: View {
|
|||||||
self.bar.our_like = liked.event
|
self.bar.our_like = liked.event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(
|
|
||||||
GeometryReader { geometry in
|
|
||||||
EmptyView()
|
|
||||||
.onAppear {
|
|
||||||
let eventActionBarY = geometry.frame(in: .global).midY
|
|
||||||
let screenMidY = UIScreen.main.bounds.midY
|
|
||||||
self.isOnTopHalfOfScreen = eventActionBarY > screenMidY
|
|
||||||
}
|
|
||||||
.onChange(of: geometry.frame(in: .global).midY) { newY in
|
|
||||||
let screenMidY = UIScreen.main.bounds.midY
|
|
||||||
self.isOnTopHalfOfScreen = newY > screenMidY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func send_like(emoji: String) {
|
func send_like(emoji: String) {
|
||||||
@@ -164,6 +272,17 @@ struct EventActionBar: View {
|
|||||||
|
|
||||||
damus_state.postbox.send(like_ev)
|
damus_state.postbox.send(like_ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Helper structures
|
||||||
|
|
||||||
|
struct Options: OptionSet {
|
||||||
|
let rawValue: UInt32
|
||||||
|
|
||||||
|
static let no_spread = Options(rawValue: 1 << 0)
|
||||||
|
static let hide_items_without_activity = Options(rawValue: 1 << 1)
|
||||||
|
static let swipe_action_menu = Options(rawValue: 1 << 2)
|
||||||
|
static let swipe_action_menu_reverse = Options(rawValue: 1 << 3)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -183,7 +302,6 @@ struct LikeButton: View {
|
|||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let liked: Bool
|
let liked: Bool
|
||||||
let liked_emoji: String?
|
let liked_emoji: String?
|
||||||
@Binding var isOnTopHalfOfScreen: Bool
|
|
||||||
let action: (_ emoji: String) -> Void
|
let action: (_ emoji: String) -> Void
|
||||||
|
|
||||||
// For reactions background
|
// For reactions background
|
||||||
@@ -192,7 +310,7 @@ struct LikeButton: View {
|
|||||||
|
|
||||||
@State private var isReactionsVisible = false
|
@State private var isReactionsVisible = false
|
||||||
|
|
||||||
@State private var selectedEmoji: String = ""
|
@State private var selectedEmoji: Emoji?
|
||||||
|
|
||||||
// Following four are Shaka animation properties
|
// Following four are Shaka animation properties
|
||||||
let timer = Timer.publish(every: 0.10, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 0.10, on: .main, in: .common).autoconnect()
|
||||||
@@ -231,6 +349,11 @@ struct LikeButton: View {
|
|||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $isReactionsVisible) {
|
||||||
|
NavigationView {
|
||||||
|
EmojiPickerView(selectedEmoji: $selectedEmoji, emojiProvider: damus_state.emoji_provider)
|
||||||
|
}.presentationDetents([.medium, .large])
|
||||||
|
}
|
||||||
.accessibilityLabel(NSLocalizedString("Like", comment: "Accessibility Label for Like button"))
|
.accessibilityLabel(NSLocalizedString("Like", comment: "Accessibility Label for Like button"))
|
||||||
.rotationEffect(Angle(degrees: shouldAnimate ? rotationAngle : 0))
|
.rotationEffect(Angle(degrees: shouldAnimate ? rotationAngle : 0))
|
||||||
.onReceive(self.timer) { _ in
|
.onReceive(self.timer) { _ in
|
||||||
@@ -245,14 +368,10 @@ struct LikeButton: View {
|
|||||||
amountOfAngleIncrease = 20.0
|
amountOfAngleIncrease = 20.0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.emojiPicker(
|
|
||||||
isPresented: $isReactionsVisible,
|
|
||||||
selectedEmoji: $selectedEmoji,
|
|
||||||
arrowDirection: isOnTopHalfOfScreen ? .down : .up,
|
|
||||||
isDismissAfterChoosing: true
|
|
||||||
)
|
|
||||||
.onChange(of: selectedEmoji) { newSelectedEmoji in
|
.onChange(of: selectedEmoji) { newSelectedEmoji in
|
||||||
self.action(newSelectedEmoji)
|
if let newSelectedEmoji {
|
||||||
|
self.action(newSelectedEmoji.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +418,6 @@ struct LikeButton: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct EventActionBar_Previews: PreviewProvider {
|
struct EventActionBar_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let ds = test_damus_state
|
let ds = test_damus_state
|
||||||
@@ -324,7 +442,44 @@ struct EventActionBar_Previews: PreviewProvider {
|
|||||||
EventActionBar(damus_state: ds, event: ev, bar: extra_max_bar)
|
EventActionBar(damus_state: ds, event: ev, bar: extra_max_bar)
|
||||||
|
|
||||||
EventActionBar(damus_state: ds, event: ev, bar: mega_max_bar)
|
EventActionBar(damus_state: ds, event: ev, bar: mega_max_bar)
|
||||||
|
|
||||||
|
EventActionBar(damus_state: ds, event: ev, bar: bar, options: [.no_spread])
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Helpers
|
||||||
|
|
||||||
|
fileprivate struct SwipeButtonStyle: ViewModifier {
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.frame(width: 50, height: 50)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.overlay(Circle().stroke(Color.damusAdaptableGrey2, lineWidth: 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension View {
|
||||||
|
func swipeButtonStyle() -> some View {
|
||||||
|
modifier(SwipeButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Needed extensions for SwipeAction
|
||||||
|
|
||||||
|
public extension SwipeAction where Label == Image, Background == Color {
|
||||||
|
init(
|
||||||
|
image: String,
|
||||||
|
backgroundColor: Color = Color.primary.opacity(0.1),
|
||||||
|
highlightOpacity: Double = 0.5,
|
||||||
|
action: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self.init(action: action) { highlight in
|
||||||
|
Image(image)
|
||||||
|
} background: { highlight in
|
||||||
|
backgroundColor
|
||||||
|
.opacity(highlight ? highlightOpacity : 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
184
damus/Views/Chat/ChatBubbleView.swift
Normal file
184
damus/Views/Chat/ChatBubbleView.swift
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
//
|
||||||
|
// ChatBubbleView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2024-06-17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Use this view to display content inside of a custom-designed chat bubble shape.
|
||||||
|
struct ChatBubble<T: View, U: ShapeStyle, V: View>: View {
|
||||||
|
/// The direction at which the chat bubble tip will be pointing towards
|
||||||
|
let direction: Direction
|
||||||
|
let stroke_content: U
|
||||||
|
let stroke_style: StrokeStyle
|
||||||
|
let background_style: V
|
||||||
|
@ViewBuilder let content: T
|
||||||
|
|
||||||
|
// Constants, which are loosely tied to `OFFSET_X` and `OFFSET_Y`
|
||||||
|
let OFFSET_X_PADDING: CGFloat = 6
|
||||||
|
let OFFSET_Y_BOTTOM_PADDING: CGFloat = 3
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
self.content
|
||||||
|
.padding(direction == .left ? .leading : .trailing, OFFSET_X_PADDING)
|
||||||
|
.padding(.bottom, OFFSET_Y_BOTTOM_PADDING)
|
||||||
|
.background(self.background_style)
|
||||||
|
.clipShape(
|
||||||
|
BubbleShape(direction: self.direction)
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
BubbleShape(direction: self.direction)
|
||||||
|
.stroke(self.stroke_content, style: self.stroke_style)
|
||||||
|
)
|
||||||
|
.padding(direction == .left ? .leading : .trailing, -OFFSET_X_PADDING)
|
||||||
|
.padding(.bottom, -OFFSET_Y_BOTTOM_PADDING)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Direction {
|
||||||
|
case right
|
||||||
|
case left
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BubbleShape: Shape {
|
||||||
|
/// The direction at which the chat bubble tip will be pointing towards
|
||||||
|
let direction: Direction
|
||||||
|
|
||||||
|
// MARK: Constant parameters that defines the shape and look of the chat bubbles
|
||||||
|
|
||||||
|
/// The corner radius of the round edges
|
||||||
|
let CORNER_RADIUS: CGFloat = 10
|
||||||
|
/// The height of the chat bubble tip detail
|
||||||
|
let DETAIL_HEIGHT: CGFloat = 10
|
||||||
|
/// The horizontal distance between the chat bubble tip and the vertical edge of the bubble
|
||||||
|
let OFFSET_X: CGFloat = 7
|
||||||
|
/// The vertical distance between the chat bubble tip and the bottom edge of the bubble
|
||||||
|
let OFFSET_Y: CGFloat = 5
|
||||||
|
/// Value between 0 and 1 that determines curvature of the upper chat bubble curve detail
|
||||||
|
let DETAIL_CURVE_FACTOR: CGFloat = 0.75
|
||||||
|
/// Value between 0 and 1 that determines curvature of the lower chat bubble curve detail
|
||||||
|
let LOWER_DETAIL_CURVE_FACTOR: CGFloat = 0.4
|
||||||
|
/// The horizontal distance between the chat bubble tip and the point at which the lower chat bubble curve detail attaches to the bottom of the chat bubble
|
||||||
|
let LOWER_DETAIL_ATTACHMENT_OFFSET_X: CGFloat = 20
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
return self.direction == .left ? self.draw_left_bubble(in: rect) : self.draw_right_bubble(in: rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func draw_left_bubble(in rect: CGRect) -> Path {
|
||||||
|
return Path { p in
|
||||||
|
// Start at the top left, just below the end of the corner radius
|
||||||
|
let start = CGPoint(x: OFFSET_X, y: CORNER_RADIUS)
|
||||||
|
// Left edge
|
||||||
|
p.move(to: start)
|
||||||
|
p.addLine(to: CGPoint(x: OFFSET_X, y: rect.height - DETAIL_HEIGHT))
|
||||||
|
// Draw the chat bubble tip
|
||||||
|
p.addLine(to: CGPoint(x: OFFSET_X, y: rect.height - DETAIL_HEIGHT))
|
||||||
|
let tip_of_bubble = CGPoint(x: 0, y: rect.height)
|
||||||
|
p.addQuadCurve(
|
||||||
|
to: tip_of_bubble,
|
||||||
|
control: CGPoint(x: 0, y: rect.height - DETAIL_HEIGHT) + CGVector(dx: OFFSET_X, dy: DETAIL_HEIGHT) * DETAIL_CURVE_FACTOR
|
||||||
|
)
|
||||||
|
let lower_detail_attachment = CGPoint(x: LOWER_DETAIL_ATTACHMENT_OFFSET_X, y: rect.height - OFFSET_Y)
|
||||||
|
p.addCurve(
|
||||||
|
to: lower_detail_attachment,
|
||||||
|
control1: tip_of_bubble + CGVector(dx: LOWER_DETAIL_ATTACHMENT_OFFSET_X, dy: 0) * LOWER_DETAIL_CURVE_FACTOR,
|
||||||
|
control2: lower_detail_attachment - CGVector(dx: LOWER_DETAIL_ATTACHMENT_OFFSET_X, dy: 0) * LOWER_DETAIL_CURVE_FACTOR
|
||||||
|
)
|
||||||
|
// Draw the bottom edge
|
||||||
|
p.addLine(to: CGPoint(x: rect.width - CORNER_RADIUS, y: rect.height - OFFSET_Y))
|
||||||
|
// Draw the bottom right round corner
|
||||||
|
p.addQuadCurve(
|
||||||
|
to: CGPoint(x: rect.width, y: rect.height - OFFSET_Y - CORNER_RADIUS),
|
||||||
|
control: CGPoint(x: rect.width, y: rect.height - OFFSET_Y)
|
||||||
|
)
|
||||||
|
// Draw right edge
|
||||||
|
p.addLine(to: CGPoint(x: rect.width, y: CORNER_RADIUS))
|
||||||
|
// Draw top right round corner
|
||||||
|
p.addQuadCurve(
|
||||||
|
to: CGPoint(x: rect.width - CORNER_RADIUS, y: 0),
|
||||||
|
control: CGPoint(x: rect.width, y: 0)
|
||||||
|
)
|
||||||
|
// Draw top edge
|
||||||
|
p.addLine(to: CGPoint(x: CORNER_RADIUS + OFFSET_X, y: 0))
|
||||||
|
// Draw top left round corner
|
||||||
|
p.addQuadCurve(
|
||||||
|
to: start,
|
||||||
|
control: CGPoint(x: OFFSET_X, y: 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func draw_right_bubble(in rect: CGRect) -> Path {
|
||||||
|
return Path { p in
|
||||||
|
// Start at the top right, just below the end of the corner radius
|
||||||
|
let right_edge = rect.width - OFFSET_X
|
||||||
|
let start = CGPoint(x: right_edge, y: CORNER_RADIUS)
|
||||||
|
p.move(to: start)
|
||||||
|
// Right edge
|
||||||
|
p.addLine(to: CGPoint(x: right_edge, y: rect.height - DETAIL_HEIGHT))
|
||||||
|
// Draw the chat bubble tip
|
||||||
|
let tip_of_bubble = CGPoint(x: rect.width, y: rect.height)
|
||||||
|
p.addQuadCurve(
|
||||||
|
to: tip_of_bubble,
|
||||||
|
control: CGPoint(x: rect.width, y: rect.height - DETAIL_HEIGHT) + CGVector(dx: -OFFSET_X, dy: DETAIL_HEIGHT) * DETAIL_CURVE_FACTOR
|
||||||
|
)
|
||||||
|
let lower_detail_attachment = CGPoint(x: rect.width - LOWER_DETAIL_ATTACHMENT_OFFSET_X, y: rect.height - OFFSET_Y)
|
||||||
|
p.addCurve(
|
||||||
|
to: lower_detail_attachment,
|
||||||
|
control1: tip_of_bubble - CGVector(dx: LOWER_DETAIL_ATTACHMENT_OFFSET_X, dy: 0) * LOWER_DETAIL_CURVE_FACTOR,
|
||||||
|
control2: lower_detail_attachment + CGVector(dx: LOWER_DETAIL_ATTACHMENT_OFFSET_X, dy: 0) * LOWER_DETAIL_CURVE_FACTOR
|
||||||
|
)
|
||||||
|
// Draw the bottom edge
|
||||||
|
p.addLine(to: CGPoint(x: CORNER_RADIUS, y: rect.height - OFFSET_Y))
|
||||||
|
// Draw the bottom left round corner
|
||||||
|
p.addQuadCurve(
|
||||||
|
to: CGPoint(x: 0, y: rect.height - OFFSET_Y - CORNER_RADIUS),
|
||||||
|
control: CGPoint(x: 0, y: rect.height - OFFSET_Y)
|
||||||
|
)
|
||||||
|
// Draw left edge
|
||||||
|
p.addLine(to: CGPoint(x: 0, y: CORNER_RADIUS))
|
||||||
|
// Draw top right round corner
|
||||||
|
p.addQuadCurve(
|
||||||
|
to: CGPoint(x: CORNER_RADIUS, y: 0),
|
||||||
|
control: CGPoint(x: 0, y: 0)
|
||||||
|
)
|
||||||
|
// Draw top edge
|
||||||
|
p.addLine(to: CGPoint(x: rect.width - CORNER_RADIUS - OFFSET_X, y: 0))
|
||||||
|
// Draw top left round corner
|
||||||
|
p.addQuadCurve(
|
||||||
|
to: start,
|
||||||
|
control: CGPoint(x: rect.width - OFFSET_X, y: 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
VStack {
|
||||||
|
ChatBubble(
|
||||||
|
direction: .left,
|
||||||
|
stroke_content: Color.accentColor.opacity(0),
|
||||||
|
stroke_style: .init(lineWidth: 4),
|
||||||
|
background_style: Color.accentColor
|
||||||
|
) {
|
||||||
|
Text("Hello there")
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
ChatBubble(
|
||||||
|
direction: .right,
|
||||||
|
stroke_content: Color.accentColor.opacity(0),
|
||||||
|
stroke_style: .init(lineWidth: 4),
|
||||||
|
background_style: Color.accentColor
|
||||||
|
) {
|
||||||
|
Text("Hello there")
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.foregroundColor(.white)
|
||||||
|
}
|
||||||
|
}
|
||||||
324
damus/Views/Chat/ChatEventView.swift
Normal file
324
damus/Views/Chat/ChatEventView.swift
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
//
|
||||||
|
// ChatView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-04-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import EmojiKit
|
||||||
|
import EmojiPicker
|
||||||
|
import SwipeActions
|
||||||
|
|
||||||
|
fileprivate let CORNER_RADIUS: CGFloat = 10
|
||||||
|
|
||||||
|
struct ChatEventView: View {
|
||||||
|
// MARK: Parameters
|
||||||
|
let event: NostrEvent
|
||||||
|
let selected_event: NostrEvent
|
||||||
|
let prev_ev: NostrEvent?
|
||||||
|
let next_ev: NostrEvent?
|
||||||
|
let damus_state: DamusState
|
||||||
|
var thread: ThreadModel
|
||||||
|
let scroll_to_event: ((_ id: NoteId) -> Void)?
|
||||||
|
let focus_event: (() -> Void)?
|
||||||
|
let highlight_bubble: Bool
|
||||||
|
|
||||||
|
// MARK: long-press reaction control objects
|
||||||
|
/// Whether the user is actively pressing the view
|
||||||
|
@State var is_pressing = false
|
||||||
|
/// The dispatched work item scheduled by a timer to bounce the event bubble and show the emoji selector
|
||||||
|
@State var long_press_bounce_work_item: DispatchWorkItem?
|
||||||
|
@State var popover_state: PopoverState = .closed {
|
||||||
|
didSet {
|
||||||
|
let generator = UIImpactFeedbackGenerator(style: popover_state == .open_emoji_selector ? .heavy : .light)
|
||||||
|
generator.impactOccurred()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@State var selected_emoji: Emoji?
|
||||||
|
|
||||||
|
@State private var isOnTopHalfOfScreen: Bool = false
|
||||||
|
@ObservedObject var bar: ActionBarModel
|
||||||
|
|
||||||
|
enum PopoverState: String {
|
||||||
|
case closed
|
||||||
|
case open_emoji_selector
|
||||||
|
}
|
||||||
|
|
||||||
|
var just_started: Bool {
|
||||||
|
return prev_ev == nil || prev_ev!.pubkey != event.pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
func next_replies_to_this() -> Bool {
|
||||||
|
guard let next = next_ev else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return damus_state.events.replies.lookup(next.id) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func is_reply_to_prev(ref_id: NoteId) -> Bool {
|
||||||
|
guard let prev = prev_ev else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if let rep = damus_state.events.replies.lookup(event.id) {
|
||||||
|
return rep.contains(prev.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var disable_animation: Bool {
|
||||||
|
self.damus_state.settings.disable_animation
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply_quote_options: EventViewOptions {
|
||||||
|
return [.no_previews, .no_action_bar, .truncate_content_very_short, .no_show_more, .no_translate, .no_media]
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile_picture_view: some View {
|
||||||
|
VStack {
|
||||||
|
ProfilePicView(pubkey: event.pubkey, size: 32, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation)
|
||||||
|
.onTapGesture {
|
||||||
|
show_profile_action_sheet_if_enabled(damus_state: damus_state, pubkey: event.pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
var by_other_user: Bool {
|
||||||
|
return event.pubkey != damus_state.pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_ours: Bool { return !by_other_user }
|
||||||
|
|
||||||
|
var event_bubble: some View {
|
||||||
|
ChatBubble(
|
||||||
|
direction: is_ours ? .right : .left,
|
||||||
|
stroke_content: Color.accentColor.opacity(highlight_bubble ? 1 : 0),
|
||||||
|
stroke_style: .init(lineWidth: 4),
|
||||||
|
background_style: by_other_user ? DamusColors.adaptableGrey : DamusColors.adaptablePurpleBackground
|
||||||
|
) {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
if by_other_user {
|
||||||
|
HStack {
|
||||||
|
ProfileName(pubkey: event.pubkey, damus: damus_state)
|
||||||
|
.onTapGesture {
|
||||||
|
show_profile_action_sheet_if_enabled(damus_state: damus_state, pubkey: event.pubkey)
|
||||||
|
}
|
||||||
|
Text(verbatim: "\(format_relative_time(event.created_at))")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let replying_to = event.direct_replies(),
|
||||||
|
replying_to != selected_event.id {
|
||||||
|
ReplyQuoteView(keypair: damus_state.keypair, quoter: event, event_id: replying_to, state: damus_state, thread: thread, options: reply_quote_options)
|
||||||
|
.background(is_ours ? DamusColors.adaptablePurpleBackground2 : DamusColors.adaptableGrey2)
|
||||||
|
.foregroundColor(is_ours ? Color.damusAdaptablePurpleForeground : Color.damusAdaptableBlack)
|
||||||
|
.cornerRadius(5)
|
||||||
|
.onTapGesture {
|
||||||
|
self.scroll_to_event?(replying_to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let blur_images = should_blur_images(settings: damus_state.settings, contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
||||||
|
NoteContentView(damus_state: damus_state, event: event, blur_images: blur_images, size: .normal, options: [])
|
||||||
|
.padding(2)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 150, alignment: is_ours ? .trailing : .leading)
|
||||||
|
.padding(10)
|
||||||
|
}
|
||||||
|
.tint(is_ours ? Color.white : Color.accentColor)
|
||||||
|
.overlay(
|
||||||
|
ZStack(alignment: is_ours ? .bottomLeading : .bottomTrailing) {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
self.action_bar
|
||||||
|
.padding(.horizontal, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
if popover_state == .closed {
|
||||||
|
focus_event?()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
popover_state = .closed
|
||||||
|
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||||
|
generator.impactOccurred()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var event_bubble_with_long_press_interaction: some View {
|
||||||
|
ZStack(alignment: is_ours ? .bottomLeading : .bottomTrailing) {
|
||||||
|
self.event_bubble
|
||||||
|
.sheet(isPresented: Binding(get: { popover_state == .open_emoji_selector }, set: { new_state in
|
||||||
|
withAnimation(new_state == true ? .easeIn(duration: 0.5) : .easeOut(duration: 0.1)) {
|
||||||
|
popover_state = new_state == true ? .open_emoji_selector : .closed
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
NavigationView {
|
||||||
|
EmojiPickerView(selectedEmoji: $selected_emoji, emojiProvider: damus_state.emoji_provider)
|
||||||
|
}.presentationDetents([.medium, .large])
|
||||||
|
}
|
||||||
|
.onChange(of: selected_emoji) { newSelectedEmoji in
|
||||||
|
if let newSelectedEmoji {
|
||||||
|
send_like(emoji: newSelectedEmoji.value)
|
||||||
|
popover_state = .closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.scaleEffect(self.popover_state == .open_emoji_selector ? 1.08 : is_pressing ? 1.02 : 1)
|
||||||
|
.shadow(color: (is_pressing || self.popover_state == .open_emoji_selector) ? .black.opacity(0.1) : .black.opacity(0.3), radius: (is_pressing || self.popover_state == .open_emoji_selector) ? 8 : 0, y: (is_pressing || self.popover_state == .open_emoji_selector) ? 15 : 0)
|
||||||
|
.onLongPressGesture(minimumDuration: 0.5, maximumDistance: 10, perform: {
|
||||||
|
long_press_bounce_work_item?.cancel()
|
||||||
|
}, onPressingChanged: { is_pressing in
|
||||||
|
withAnimation(is_pressing ? .easeIn(duration: 0.5) : .easeOut(duration: 0.1)) {
|
||||||
|
self.is_pressing = is_pressing
|
||||||
|
if popover_state != .closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.is_pressing {
|
||||||
|
let item = DispatchWorkItem {
|
||||||
|
// Ensure the action is performed only if the condition is still valid
|
||||||
|
if self.is_pressing {
|
||||||
|
withAnimation(.bouncy(duration: 0.2, extraBounce: 0.35)) {
|
||||||
|
popover_state = .open_emoji_selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long_press_bounce_work_item = item
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.background(
|
||||||
|
GeometryReader { geometry in
|
||||||
|
EmptyView()
|
||||||
|
.onAppear {
|
||||||
|
let eventActionBarY = geometry.frame(in: .global).midY
|
||||||
|
let screenMidY = UIScreen.main.bounds.midY
|
||||||
|
self.isOnTopHalfOfScreen = eventActionBarY > screenMidY
|
||||||
|
}
|
||||||
|
.onChange(of: geometry.frame(in: .global).midY) { newY in
|
||||||
|
let screenMidY = UIScreen.main.bounds.midY
|
||||||
|
self.isOnTopHalfOfScreen = newY > screenMidY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func send_like(emoji: String) {
|
||||||
|
guard let keypair = damus_state.keypair.to_full(),
|
||||||
|
let like_ev = make_like_event(keypair: keypair, liked: event, content: emoji) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bar.our_like = like_ev
|
||||||
|
|
||||||
|
let generator = UIImpactFeedbackGenerator(style: .medium)
|
||||||
|
generator.impactOccurred()
|
||||||
|
|
||||||
|
damus_state.postbox.send(like_ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
var action_bar: some View {
|
||||||
|
return Group {
|
||||||
|
if !bar.is_empty {
|
||||||
|
HStack {
|
||||||
|
if by_other_user {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
EventActionBar(damus_state: damus_state, event: event, bar: bar, options: [.no_spread, .hide_items_without_activity])
|
||||||
|
.padding(10)
|
||||||
|
.background(DamusColors.adaptableLighterGrey)
|
||||||
|
.disabled(true)
|
||||||
|
.cornerRadius(100)
|
||||||
|
.overlay(RoundedRectangle(cornerSize: CGSize(width: 100, height: 100)).stroke(DamusColors.adaptableWhite, lineWidth: 1))
|
||||||
|
.shadow(color: Color.black.opacity(0.05),radius: 3, y: 3)
|
||||||
|
.scaleEffect(0.7, anchor: is_ours ? .leading : .trailing)
|
||||||
|
if !by_other_user {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, -20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var event_bubble_with_long_press_and_swipe_interactions: some View {
|
||||||
|
Group {
|
||||||
|
SwipeView {
|
||||||
|
self.event_bubble_with_long_press_interaction
|
||||||
|
} leadingActions: { context in
|
||||||
|
EventActionBar(
|
||||||
|
damus_state: damus_state,
|
||||||
|
event: event,
|
||||||
|
bar: bar,
|
||||||
|
options: is_ours ? [.swipe_action_menu_reverse] : [.swipe_action_menu],
|
||||||
|
swipe_context: context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.swipeSpacing(-20)
|
||||||
|
.swipeActionsStyle(.mask)
|
||||||
|
.swipeMinimumDistance(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var content: some View {
|
||||||
|
return VStack {
|
||||||
|
HStack(alignment: .bottom, spacing: 4) {
|
||||||
|
if by_other_user {
|
||||||
|
self.profile_picture_view
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.event_bubble_with_long_press_and_swipe_interactions
|
||||||
|
|
||||||
|
if !by_other_user {
|
||||||
|
self.profile_picture_view
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.id(event.id)
|
||||||
|
.padding([.bottom], bar.is_empty ? 6 : 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if [.boost, .zap, .longform].contains(where: { event.known_kind == $0 }) {
|
||||||
|
EmptyView()
|
||||||
|
} else {
|
||||||
|
self.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Notification.Name {
|
||||||
|
static var toggle_thread_view: Notification.Name {
|
||||||
|
return Notification.Name("convert_to_thread")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
let bar = make_actionbar_model(ev: test_note.id, damus: test_damus_state)
|
||||||
|
return ChatEventView(event: test_note, selected_event: test_note, prev_ev: nil, next_ev: nil, damus_state: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state), scroll_to_event: nil, focus_event: nil, highlight_bubble: false, bar: bar)
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
let bar = make_actionbar_model(ev: test_note.id, damus: test_damus_state)
|
||||||
|
return ChatEventView(event: test_short_note, selected_event: test_note, prev_ev: nil, next_ev: nil, damus_state: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state), scroll_to_event: nil, focus_event: nil, highlight_bubble: false, bar: bar)
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
let bar = make_actionbar_model(ev: test_note.id, damus: test_damus_state)
|
||||||
|
return ChatEventView(event: test_short_note, selected_event: test_note, prev_ev: nil, next_ev: nil, damus_state: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state), scroll_to_event: nil, focus_event: nil, highlight_bubble: true, bar: bar)
|
||||||
|
}
|
||||||
195
damus/Views/Chat/ChatroomThreadView.swift
Normal file
195
damus/Views/Chat/ChatroomThreadView.swift
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
//
|
||||||
|
// ChatroomView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-04-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwipeActions
|
||||||
|
|
||||||
|
struct ChatroomThreadView: View {
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
@State var once: Bool = false
|
||||||
|
let damus: DamusState
|
||||||
|
@ObservedObject var thread: ThreadModel
|
||||||
|
@State var selected_note_id: NoteId? = nil
|
||||||
|
@State var user_just_posted_flag: Bool = false
|
||||||
|
@Namespace private var animation
|
||||||
|
|
||||||
|
@State var parent_events: [NostrEvent] = []
|
||||||
|
@State var sorted_child_events: [NostrEvent] = []
|
||||||
|
|
||||||
|
func compute_events(selected_event: NostrEvent? = nil) {
|
||||||
|
let selected_event = selected_event ?? thread.event
|
||||||
|
self.parent_events = damus.events.parent_events(event: selected_event, keypair: damus.keypair)
|
||||||
|
let all_recursive_child_events = self.recursive_child_events(event: selected_event)
|
||||||
|
self.sorted_child_events = all_recursive_child_events.filter({
|
||||||
|
should_show_event(event: $0, damus_state: damus) // Hide muted events from chatroom conversation
|
||||||
|
}).sorted(by: { a, b in
|
||||||
|
return a.created_at < b.created_at
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func recursive_child_events(event: NdbNote) -> [NdbNote] {
|
||||||
|
let immediate_children = damus.events.child_events(event: event)
|
||||||
|
var indirect_children: [NdbNote] = []
|
||||||
|
for immediate_child in immediate_children {
|
||||||
|
indirect_children.append(contentsOf: self.recursive_child_events(event: immediate_child))
|
||||||
|
}
|
||||||
|
return immediate_children + indirect_children
|
||||||
|
}
|
||||||
|
|
||||||
|
func go_to_event(scroller: ScrollViewProxy, note_id: NoteId) {
|
||||||
|
scroll_to_event(scroller: scroller, id: note_id, delay: 0, animate: true, anchor: .top)
|
||||||
|
selected_note_id = note_id
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
|
||||||
|
withAnimation {
|
||||||
|
selected_note_id = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func set_active_event(scroller: ScrollViewProxy, ev: NdbNote) {
|
||||||
|
withAnimation {
|
||||||
|
self.compute_events(selected_event: ev)
|
||||||
|
thread.set_active_event(ev, keypair: self.damus.keypair)
|
||||||
|
self.go_to_event(scroller: scroller, note_id: ev.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollViewReader { scroller in
|
||||||
|
ScrollView(.vertical) {
|
||||||
|
LazyVStack(alignment: .leading, spacing: 8) {
|
||||||
|
// MARK: - Parents events view
|
||||||
|
ForEach(parent_events, id: \.id) { parent_event in
|
||||||
|
EventMutingContainerView(damus_state: damus, event: parent_event) {
|
||||||
|
EventView(damus: damus, event: parent_event)
|
||||||
|
.matchedGeometryEffect(id: parent_event.id.hex(), in: animation, anchor: .center)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.onTapGesture {
|
||||||
|
self.set_active_event(scroller: scroller, ev: parent_event)
|
||||||
|
}
|
||||||
|
.id(parent_event.id)
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding(.top, 4)
|
||||||
|
.padding(.leading, 25 * 2)
|
||||||
|
|
||||||
|
}.background(GeometryReader { geometry in
|
||||||
|
// get the height and width of the EventView view
|
||||||
|
let eventHeight = geometry.frame(in: .global).height
|
||||||
|
// let eventWidth = geometry.frame(in: .global).width
|
||||||
|
|
||||||
|
// vertical gray line in the background
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.gray.opacity(0.25))
|
||||||
|
.frame(width: 2, height: eventHeight)
|
||||||
|
.offset(x: 40, y: 40)
|
||||||
|
})
|
||||||
|
|
||||||
|
// MARK: - Actual event view
|
||||||
|
EventMutingContainerView(
|
||||||
|
damus_state: damus,
|
||||||
|
event: self.thread.event,
|
||||||
|
muteBox: { event_shown, muted_reason in
|
||||||
|
AnyView(
|
||||||
|
EventMutedBoxView(shown: event_shown, reason: muted_reason)
|
||||||
|
.padding(5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
SelectedEventView(damus: damus, event: self.thread.event, size: .selected)
|
||||||
|
.matchedGeometryEffect(id: self.thread.event.id.hex(), in: animation, anchor: .center)
|
||||||
|
}
|
||||||
|
.id(self.thread.event.id)
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Children view
|
||||||
|
let events = sorted_child_events
|
||||||
|
let count = events.count
|
||||||
|
SwipeViewGroup {
|
||||||
|
ForEach(Array(zip(events, events.indices)), id: \.0.id) { (ev, ind) in
|
||||||
|
ChatEventView(event: events[ind],
|
||||||
|
selected_event: self.thread.event,
|
||||||
|
prev_ev: ind > 0 ? events[ind-1] : nil,
|
||||||
|
next_ev: ind == count-1 ? nil : events[ind+1],
|
||||||
|
damus_state: damus,
|
||||||
|
thread: thread,
|
||||||
|
scroll_to_event: { note_id in
|
||||||
|
self.go_to_event(scroller: scroller, note_id: note_id)
|
||||||
|
},
|
||||||
|
focus_event: {
|
||||||
|
self.set_active_event(scroller: scroller, ev: ev)
|
||||||
|
},
|
||||||
|
highlight_bubble: selected_note_id == ev.id,
|
||||||
|
bar: make_actionbar_model(ev: ev.id, damus: damus)
|
||||||
|
)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.id(ev.id)
|
||||||
|
.matchedGeometryEffect(id: ev.id.hex(), in: animation, anchor: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top)
|
||||||
|
EndBlock()
|
||||||
|
}
|
||||||
|
.onReceive(handle_notify(.post), perform: { notify in
|
||||||
|
switch notify {
|
||||||
|
case .post(_):
|
||||||
|
user_just_posted_flag = true
|
||||||
|
case .cancel:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onReceive(thread.objectWillChange) {
|
||||||
|
self.compute_events()
|
||||||
|
if let last_event = thread.events().last, last_event.pubkey == damus.pubkey, user_just_posted_flag {
|
||||||
|
self.go_to_event(scroller: scroller, note_id: last_event.id)
|
||||||
|
user_just_posted_flag = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear() {
|
||||||
|
thread.subscribe()
|
||||||
|
self.compute_events()
|
||||||
|
scroll_to_event(scroller: scroller, id: thread.event.id, delay: 0.1, animate: false)
|
||||||
|
}
|
||||||
|
.onDisappear() {
|
||||||
|
thread.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toggle_thread_view() {
|
||||||
|
NotificationCenter.default.post(name: .toggle_thread_view, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct ChatroomView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
Group {
|
||||||
|
ChatroomThreadView(damus: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state))
|
||||||
|
.previewDisplayName("Test note")
|
||||||
|
|
||||||
|
let test_thread = ThreadModel(event: test_thread_note_1, damus_state: test_damus_state)
|
||||||
|
ChatroomThreadView(damus: test_damus_state, thread: test_thread)
|
||||||
|
.onAppear {
|
||||||
|
test_thread.add_event(test_thread_note_2, keypair: test_keypair)
|
||||||
|
test_thread.add_event(test_thread_note_3, keypair: test_keypair)
|
||||||
|
test_thread.add_event(test_thread_note_4, keypair: test_keypair)
|
||||||
|
test_thread.add_event(test_thread_note_5, keypair: test_keypair)
|
||||||
|
test_thread.add_event(test_thread_note_6, keypair: test_keypair)
|
||||||
|
test_thread.add_event(test_thread_note_7, keypair: test_keypair)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scroll_after_load(thread: ThreadModel, proxy: ScrollViewProxy) {
|
||||||
|
scroll_to_event(scroller: proxy, id: thread.event.id, delay: 0.1, animate: false)
|
||||||
|
}
|
||||||
70
damus/Views/Chat/ReplyQuoteView.swift
Normal file
70
damus/Views/Chat/ReplyQuoteView.swift
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//
|
||||||
|
// ReplyQuoteView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-04-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ReplyQuoteView: View {
|
||||||
|
let keypair: Keypair
|
||||||
|
let quoter: NostrEvent
|
||||||
|
let event_id: NoteId
|
||||||
|
let state: DamusState
|
||||||
|
@ObservedObject var thread: ThreadModel
|
||||||
|
let options: EventViewOptions
|
||||||
|
|
||||||
|
func content(event: NdbNote) -> some View {
|
||||||
|
ZStack(alignment: .leading) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
if should_show_event(event: event, damus_state: state) {
|
||||||
|
ProfilePicView(pubkey: event.pubkey, size: 14, highlight: .reply, profiles: state.profiles, disable_animation: false)
|
||||||
|
let blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event, our_pubkey: state.pubkey)
|
||||||
|
NoteContentView(damus_state: state, event: event, blur_images: blur_images, size: .small, options: options)
|
||||||
|
.font(.callout)
|
||||||
|
.lineLimit(1)
|
||||||
|
.padding(.bottom, -7)
|
||||||
|
.padding(.top, -5)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.frame(height: 20)
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Text("Note you've muted", comment: "Label indicating note has been muted")
|
||||||
|
.italic()
|
||||||
|
.font(.caption)
|
||||||
|
.opacity(0.5)
|
||||||
|
.padding(.bottom, -7)
|
||||||
|
.padding(.top, -5)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.frame(height: 20)
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading, 5+3)
|
||||||
|
Rectangle()
|
||||||
|
.foregroundStyle(.accent)
|
||||||
|
.frame(width: 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if let event = state.events.lookup(event_id) {
|
||||||
|
self.content(event: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReplyQuoteView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
let s = test_damus_state
|
||||||
|
let quoter = test_note
|
||||||
|
ReplyQuoteView(keypair: s.keypair, quoter: quoter, event_id: test_note.id, state: s, thread: ThreadModel(event: quoter, damus_state: s), options: [.no_previews, .no_action_bar, .truncate_content_very_short, .no_show_more, .no_translate])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,68 +25,44 @@ struct CreateAccountView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
VStack {
|
VStack {
|
||||||
|
Spacer()
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
EditPictureControl(uploader: .nostrBuild, pubkey: account.pubkey, image_url: $account.profile_image , uploadObserver: profileUploadObserver, callback: uploadedProfilePicture)
|
|
||||||
|
|
||||||
Text("Public Key", comment: "Label to indicate the public key of the account.")
|
EditPictureControl(uploader: .nostrBuild, pubkey: account.pubkey, size: 75, setup: true, image_url: $account.profile_image , uploadObserver: profileUploadObserver, callback: uploadedProfilePicture)
|
||||||
|
.shadow(radius: 2)
|
||||||
|
.padding(.top, 100)
|
||||||
|
|
||||||
|
Text("Add Photo", comment: "Label to indicate user can add a photo.")
|
||||||
.bold()
|
.bold()
|
||||||
.padding()
|
.foregroundColor(DamusColors.neutral6)
|
||||||
.onTapGesture {
|
|
||||||
regen_key()
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyText($account.pubkey)
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
.onTapGesture {
|
|
||||||
regen_key()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 250, alignment: .center)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.fill(DamusColors.adaptableGrey, strokeBorder: .gray.opacity(0.5), lineWidth: 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SignupForm {
|
SignupForm {
|
||||||
FormLabel(NSLocalizedString("Display name", comment: "Label to prompt display name entry."), optional: true)
|
FormLabel(NSLocalizedString("Name", comment: "Label to prompt name entry."), optional: false)
|
||||||
FormTextInput(NSLocalizedString("Satoshi Nakamoto", comment: "Name of Bitcoin creator(s)."), text: $account.real_name)
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
FormTextInput(NSLocalizedString("Satoshi Nakamoto", comment: "Name of Bitcoin creator(s)."), text: $account.name)
|
||||||
.textInputAutocapitalization(.words)
|
.textInputAutocapitalization(.words)
|
||||||
|
|
||||||
FormLabel(NSLocalizedString("About", comment: "Label to prompt for about text entry for user to describe about themself."), optional: true)
|
FormLabel(NSLocalizedString("Bio", comment: "Label to prompt bio entry for user to describe themself."), optional: true)
|
||||||
FormTextInput(NSLocalizedString("Creator(s) of Bitcoin. Absolute legend.", comment: "Example description about Bitcoin creator(s), Satoshi Nakamoto."), text: $account.about)
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
FormTextInput(NSLocalizedString("Absolute legend.", comment: "Example Bio"), text: $account.about)
|
||||||
}
|
}
|
||||||
.padding(.top, 10)
|
.padding(.top, 25)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
nav.push(route: Route.SaveKeys(account: account))
|
nav.push(route: Route.SaveKeys(account: account))
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Create account now", comment: "Button to create account.")
|
Text("Next", comment: "Button to continue with account creation.")
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
}
|
}
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
.disabled(profileUploadObserver.isLoading)
|
.disabled(profileUploadObserver.isLoading || account.name.isEmpty)
|
||||||
.opacity(profileUploadObserver.isLoading ? 0.5 : 1)
|
.opacity(profileUploadObserver.isLoading || account.name.isEmpty ? 0.5 : 1)
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Text("By signing up, you agree to our ", comment: "Ask the user if they already have an account on Nostr")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(Color("DamusMediumGrey"))
|
|
||||||
|
|
||||||
Button(action: {
|
|
||||||
nav.push(route: Route.EULA)
|
|
||||||
}, label: {
|
|
||||||
Text("EULA")
|
|
||||||
.font(.subheadline)
|
|
||||||
})
|
|
||||||
.padding(.vertical, 5)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
LoginPrompt()
|
LoginPrompt()
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
|
||||||
@@ -94,8 +70,8 @@ struct CreateAccountView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
.background(DamusBackground(maxHeight: UIScreen.main.bounds.size.height/2), alignment: .top)
|
||||||
.dismissKeyboardOnTap()
|
.dismissKeyboardOnTap()
|
||||||
.navigationTitle("Create account")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.navigationBarItems(leading: BackNav())
|
.navigationBarItems(leading: BackNav())
|
||||||
@@ -111,7 +87,7 @@ struct LoginPrompt: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Already on Nostr?", comment: "Ask the user if they already have an account on Nostr")
|
Text("Already on Nostr?", comment: "Ask the user if they already have an account on Nostr")
|
||||||
.foregroundColor(Color("DamusMediumGrey"))
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
|
||||||
Button(NSLocalizedString("Login", comment: "Button to navigate to login view.")) {
|
Button(NSLocalizedString("Login", comment: "Button to navigate to login view.")) {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
@@ -148,20 +124,11 @@ extension View {
|
|||||||
|
|
||||||
struct CreateAccountView_Previews: PreviewProvider {
|
struct CreateAccountView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let model = CreateAccountModel(real: "", nick: "jb55", about: "")
|
let model = CreateAccountModel(display_name: "", name: "jb55", about: "")
|
||||||
return CreateAccountView(account: model, nav: .init())
|
return CreateAccountView(account: model, nav: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeyText(_ pubkey: Binding<Pubkey>) -> some View {
|
|
||||||
let bechkey = bech32_encode(hrp: PUBKEY_HRP, pubkey.wrappedValue.bytes)
|
|
||||||
return Text(bechkey)
|
|
||||||
.textSelection(.enabled)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.font(.callout.monospaced())
|
|
||||||
.foregroundStyle(DamusLogoGradient.gradient)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormTextInput(_ title: String, text: Binding<String>) -> some View {
|
func FormTextInput(_ title: String, text: Binding<String>) -> some View {
|
||||||
return TextField("", text: text)
|
return TextField("", text: text)
|
||||||
.placeholder(when: text.wrappedValue.isEmpty) {
|
.placeholder(when: text.wrappedValue.isEmpty) {
|
||||||
@@ -171,6 +138,10 @@ func FormTextInput(_ title: String, text: Binding<String>) -> some View {
|
|||||||
.background {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: 12)
|
||||||
.stroke(.gray.opacity(0.5), lineWidth: 1)
|
.stroke(.gray.opacity(0.5), lineWidth: 1)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.foregroundColor(.damusAdaptableWhite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.font(.body.bold())
|
.font(.body.bold())
|
||||||
}
|
}
|
||||||
@@ -183,6 +154,10 @@ func FormLabel(_ title: String, optional: Bool = false) -> some View {
|
|||||||
Text("optional", comment: "Label indicating that a form input is optional.")
|
Text("optional", comment: "Label indicating that a form input is optional.")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
|
} else {
|
||||||
|
Text("required", comment: "Label indicating that a form input is required.")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ struct EventView: View {
|
|||||||
}
|
}
|
||||||
} else if event.known_kind == .longform {
|
} else if event.known_kind == .longform {
|
||||||
LongformPreview(state: damus, ev: event, options: options)
|
LongformPreview(state: damus, ev: event, options: options)
|
||||||
|
} else if event.known_kind == .highlight {
|
||||||
|
HighlightView(state: damus, event: event, options: options)
|
||||||
} else {
|
} else {
|
||||||
TextEvent(damus: damus, event: event, pubkey: pubkey, options: options)
|
TextEvent(damus: damus, event: event, pubkey: pubkey, options: options)
|
||||||
//.padding([.top], 6)
|
//.padding([.top], 6)
|
||||||
|
|||||||
@@ -16,7 +16,15 @@ struct ReplyPart: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let reply_ref = event.thread_reply()?.reply {
|
if let reply_ref = event.thread_reply()?.reply {
|
||||||
ReplyDescription(event: event, replying_to: events.lookup(reply_ref.note_id), ndb: ndb)
|
let replying_to = events.lookup(reply_ref.note_id)
|
||||||
|
if event.known_kind != .highlight {
|
||||||
|
ReplyDescription(event: event, replying_to: replying_to, ndb: ndb)
|
||||||
|
} else if event.known_kind == .highlight {
|
||||||
|
HighlightDescription(event: event, highlighted_event: replying_to, ndb: ndb)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ struct EventBody: View {
|
|||||||
if !options.contains(.truncate_content) {
|
if !options.contains(.truncate_content) {
|
||||||
note_content
|
note_content
|
||||||
}
|
}
|
||||||
|
} else if event.known_kind == .highlight {
|
||||||
|
HighlightBodyView(state: damus_state, ev: event, options: options)
|
||||||
} else {
|
} else {
|
||||||
note_content
|
note_content
|
||||||
}
|
}
|
||||||
|
|||||||
53
damus/Views/Events/Highlight/HighlightDescription.swift
Normal file
53
damus/Views/Events/Highlight/HighlightDescription.swift
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// HighlightDescription.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/28/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// Modified from Reply Description
|
||||||
|
struct HighlightDescription: View {
|
||||||
|
let event: NostrEvent
|
||||||
|
let highlighted_event: NostrEvent?
|
||||||
|
let ndb: Ndb
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
(Text(Image(systemName: "highlighter")) + Text(verbatim: " \(highlight_desc(ndb: ndb, event: event, highlighted_event: highlighted_event))"))
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HighlightDescription_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
HighlightDescription(event: test_note, highlighted_event: test_note, ndb: test_damus_state.ndb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func highlight_desc(ndb: Ndb, event: NostrEvent, highlighted_event: NostrEvent?, locale: Locale = Locale.current) -> String {
|
||||||
|
let desc = make_reply_description(event, replying_to: highlighted_event)
|
||||||
|
let pubkeys = desc.pubkeys
|
||||||
|
|
||||||
|
let bundle = bundleForLocale(locale: locale)
|
||||||
|
|
||||||
|
if pubkeys.count == 0 {
|
||||||
|
return NSLocalizedString("Highlighted", bundle: bundle, comment: "Label to indicate that the user is highlighting their own post.")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let profile_txn = NdbTxn(ndb: ndb) else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
let names: [String] = pubkeys.map { pk in
|
||||||
|
let prof = ndb.lookup_profile_with_txn(pk, txn: profile_txn)
|
||||||
|
|
||||||
|
return Profile.displayName(profile: prof?.profile, pubkey: pk).username.truncate(maxLength: 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
let uniqueNames: [String] = Array(Set(names))
|
||||||
|
return String(format: NSLocalizedString("Highlighted %@", bundle: bundle, comment: "Label to indicate that the user is highlighting 1 user."), locale: locale, uniqueNames.first ?? "")
|
||||||
|
}
|
||||||
92
damus/Views/Events/Highlight/HighlightEventRef.swift
Normal file
92
damus/Views/Events/Highlight/HighlightEventRef.swift
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
//
|
||||||
|
// HighlightEventRef.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct HighlightEventRef: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
let event_ref: NoteId
|
||||||
|
|
||||||
|
init(damus_state: DamusState, event_ref: NoteId) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
self.event_ref = event_ref
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FailedImage: View {
|
||||||
|
var body: some View {
|
||||||
|
Image("markdown")
|
||||||
|
.resizable()
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
.background(DamusColors.neutral3)
|
||||||
|
.frame(width: 35, height: 35)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.overlay(RoundedRectangle(cornerRadius: 10).stroke(.gray.opacity(0.5), lineWidth: 0.5))
|
||||||
|
.scaledToFit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
EventLoaderView(damus_state: damus_state, event_id: event_ref) { event in
|
||||||
|
EventMutingContainerView(damus_state: damus_state, event: event) {
|
||||||
|
if event.known_kind == .longform {
|
||||||
|
HStack(alignment: .top, spacing: 10) {
|
||||||
|
let longform_event = LongformEvent.parse(from: event)
|
||||||
|
if let url = longform_event.image {
|
||||||
|
KFAnimatedImage(url)
|
||||||
|
.callbackQueue(.dispatch(.global(qos:.background)))
|
||||||
|
.backgroundDecode(true)
|
||||||
|
.imageContext(.note, disable_animation: true)
|
||||||
|
.image_fade(duration: 0.25)
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.configure { view in
|
||||||
|
view.framePreloadCount = 3
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
FailedImage()
|
||||||
|
}
|
||||||
|
.frame(width: 35, height: 35)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.overlay(RoundedRectangle(cornerRadius: 10).stroke(.gray.opacity(0.5), lineWidth: 0.5))
|
||||||
|
.scaledToFit()
|
||||||
|
} else {
|
||||||
|
FailedImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text(longform_event.title ?? "Untitled")
|
||||||
|
.font(.system(size: 14, weight: .bold))
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
let profile_txn = damus_state.profiles.lookup(id: longform_event.event.pubkey, txn_name: "highlight-profile")
|
||||||
|
let profile = profile_txn?.unsafeUnownedValue
|
||||||
|
|
||||||
|
if let display_name = profile?.display_name {
|
||||||
|
Text(display_name)
|
||||||
|
.font(.system(size: 12))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
} else if let name = profile?.name {
|
||||||
|
Text(name)
|
||||||
|
.font(.system(size: 12))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding([.leading, .vertical], 7)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.stroke(DamusColors.neutral3, lineWidth: 2)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
damus/Views/Events/Highlight/HighlightLink.swift
Normal file
101
damus/Views/Events/Highlight/HighlightLink.swift
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// HighlightLink.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/28/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct HighlightLink: View {
|
||||||
|
let state: DamusState
|
||||||
|
let url: URL
|
||||||
|
let content: String
|
||||||
|
@Environment(\.openURL) var openURL
|
||||||
|
|
||||||
|
func text_fragment_url() -> URL? {
|
||||||
|
let fragmentDirective = "#:~:"
|
||||||
|
let textDirective = "text="
|
||||||
|
let separator = ","
|
||||||
|
var text = ""
|
||||||
|
|
||||||
|
let components = content.components(separatedBy: " ")
|
||||||
|
if components.count <= 10 {
|
||||||
|
text = content
|
||||||
|
} else {
|
||||||
|
let textStart = Array(components.prefix(5)).joined(separator: " ")
|
||||||
|
let textEnd = Array(components.suffix(2)).joined(separator: " ")
|
||||||
|
text = textStart + separator + textEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
let url_with_fragments = url.absoluteString + fragmentDirective + textDirective + text
|
||||||
|
return URL(string: url_with_fragments)
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_url_icon() -> URL? {
|
||||||
|
var icon = URL(string: url.absoluteString + "/favicon.ico")
|
||||||
|
if let url_host = url.host() {
|
||||||
|
icon = URL(string: "https://" + url_host + "/favicon.ico")
|
||||||
|
}
|
||||||
|
return icon
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: {
|
||||||
|
openURL(text_fragment_url() ?? url)
|
||||||
|
}, label: {
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
if let url = get_url_icon() {
|
||||||
|
KFAnimatedImage(url)
|
||||||
|
.imageContext(.pfp, disable_animation: true)
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.configure { view in
|
||||||
|
view.framePreloadCount = 3
|
||||||
|
}
|
||||||
|
.placeholder { _ in
|
||||||
|
Image("link")
|
||||||
|
.resizable()
|
||||||
|
.padding(5)
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
.background(DamusColors.adaptableWhite)
|
||||||
|
}
|
||||||
|
.frame(width: 35, height: 35)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.scaledToFit()
|
||||||
|
} else {
|
||||||
|
Image("link")
|
||||||
|
.resizable()
|
||||||
|
.padding(5)
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
.background(DamusColors.adaptableWhite)
|
||||||
|
.frame(width: 35, height: 35)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(url.absoluteString)
|
||||||
|
.font(eventviewsize_to_font(.normal, font_size: state.settings.font_size))
|
||||||
|
.foregroundColor(DamusColors.adaptableBlack)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.padding([.leading, .vertical], 7)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.background(DamusColors.neutral3)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.stroke(DamusColors.neutral3, lineWidth: 2)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HighlightLink_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
let url = URL(string: "https://damus.io")!
|
||||||
|
VStack {
|
||||||
|
HighlightLink(state: test_damus_state, url: url, content: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
damus/Views/Events/Highlight/HighlightPostView.swift
Normal file
78
damus/Views/Events/Highlight/HighlightPostView.swift
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// HighlightPostView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 5/26/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct HighlightPostView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
let event: NostrEvent
|
||||||
|
@Binding var selectedText: String
|
||||||
|
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
VStack {
|
||||||
|
HStack(spacing: 5.0) {
|
||||||
|
Button(action: {
|
||||||
|
dismiss()
|
||||||
|
}, label: {
|
||||||
|
Text("Cancel", comment: "Button to cancel out of highlighting a note.")
|
||||||
|
.padding(10)
|
||||||
|
})
|
||||||
|
.buttonStyle(NeutralButtonStyle())
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button(NSLocalizedString("Post", comment: "Button to post a highlight.")) {
|
||||||
|
var tags: [[String]] = [ ["e", "\(self.event.id)"] ]
|
||||||
|
tags.append(["context", self.event.content])
|
||||||
|
|
||||||
|
let kind = NostrKind.highlight.rawValue
|
||||||
|
guard let ev = NostrEvent(content: selectedText, keypair: damus_state.keypair, kind: kind, tags: tags) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
damus_state.postbox.send(ev)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
.bold()
|
||||||
|
.buttonStyle(GradientButtonStyle(padding: 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.foregroundColor(DamusColors.neutral3)
|
||||||
|
.padding(.top, 5)
|
||||||
|
}
|
||||||
|
.frame(height: 30)
|
||||||
|
.padding()
|
||||||
|
.padding(.top, 15)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
var attributedString: AttributedString {
|
||||||
|
var attributedString = AttributedString(self.event.content)
|
||||||
|
|
||||||
|
if let range = attributedString.range(of: selectedText) {
|
||||||
|
attributedString[range].backgroundColor = DamusColors.highlight
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributedString
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(attributedString)
|
||||||
|
.lineSpacing(5)
|
||||||
|
.padding(10)
|
||||||
|
}
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 25).fill(DamusColors.highlight).frame(width: 4),
|
||||||
|
alignment: .leading
|
||||||
|
)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
192
damus/Views/Events/Highlight/HighlightView.swift
Normal file
192
damus/Views/Events/Highlight/HighlightView.swift
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
//
|
||||||
|
// HighlightView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by eric on 4/22/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct HighlightTruncatedText: View {
|
||||||
|
let attributedString: AttributedString
|
||||||
|
let maxChars: Int
|
||||||
|
|
||||||
|
init(attributedString: AttributedString, maxChars: Int = 360) {
|
||||||
|
self.attributedString = attributedString
|
||||||
|
self.maxChars = maxChars
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
|
||||||
|
let truncatedAttributedString: AttributedString? = attributedString.truncateOrNil(maxLength: maxChars)
|
||||||
|
|
||||||
|
if let truncatedAttributedString {
|
||||||
|
Text(truncatedAttributedString)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
} else {
|
||||||
|
Text(attributedString)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if truncatedAttributedString != nil {
|
||||||
|
Spacer()
|
||||||
|
Button(NSLocalizedString("Show more", comment: "Button to show entire note.")) { }
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HighlightBodyView: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: HighlightEvent
|
||||||
|
let options: EventViewOptions
|
||||||
|
|
||||||
|
init(state: DamusState, ev: HighlightEvent, options: EventViewOptions) {
|
||||||
|
self.state = state
|
||||||
|
self.event = ev
|
||||||
|
self.options = options
|
||||||
|
}
|
||||||
|
|
||||||
|
init(state: DamusState, ev: NostrEvent, options: EventViewOptions) {
|
||||||
|
self.state = state
|
||||||
|
self.event = HighlightEvent.parse(from: ev)
|
||||||
|
self.options = options
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if options.contains(.wide) {
|
||||||
|
Main.padding(.horizontal)
|
||||||
|
} else {
|
||||||
|
Main
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var truncate: Bool {
|
||||||
|
return options.contains(.truncate_content)
|
||||||
|
}
|
||||||
|
|
||||||
|
var truncate_very_short: Bool {
|
||||||
|
return options.contains(.truncate_content_very_short)
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncatedText(attributedString: AttributedString) -> some View {
|
||||||
|
Group {
|
||||||
|
if truncate_very_short {
|
||||||
|
HighlightTruncatedText(attributedString: attributedString, maxChars: 140)
|
||||||
|
.font(eventviewsize_to_font(.normal, font_size: state.settings.font_size))
|
||||||
|
}
|
||||||
|
else if truncate {
|
||||||
|
HighlightTruncatedText(attributedString: attributedString)
|
||||||
|
.font(eventviewsize_to_font(.normal, font_size: state.settings.font_size))
|
||||||
|
} else {
|
||||||
|
Text(attributedString)
|
||||||
|
.font(eventviewsize_to_font(.normal, font_size: state.settings.font_size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Main: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
HStack {
|
||||||
|
var attributedString: AttributedString {
|
||||||
|
var attributedString: AttributedString = ""
|
||||||
|
if let context = event.context {
|
||||||
|
if context.count < event.event.content.count {
|
||||||
|
attributedString = AttributedString(event.event.content)
|
||||||
|
} else {
|
||||||
|
attributedString = AttributedString(context)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attributedString = AttributedString(event.event.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let range = attributedString.range(of: event.event.content) {
|
||||||
|
attributedString[range].backgroundColor = DamusColors.highlight
|
||||||
|
}
|
||||||
|
return attributedString
|
||||||
|
}
|
||||||
|
|
||||||
|
truncatedText(attributedString: attributedString)
|
||||||
|
.lineSpacing(5)
|
||||||
|
.padding(10)
|
||||||
|
}
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 25).fill(DamusColors.highlight).frame(width: 4),
|
||||||
|
alignment: .leading
|
||||||
|
)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
|
if let url = event.url_ref {
|
||||||
|
HighlightLink(state: state, url: url, content: event.event.content)
|
||||||
|
} else {
|
||||||
|
if let evRef = event.event_ref {
|
||||||
|
if let eventHex = hex_decode_id(evRef) {
|
||||||
|
HighlightEventRef(damus_state: state, event_ref: NoteId(eventHex))
|
||||||
|
.padding(.top, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HighlightView: View {
|
||||||
|
let state: DamusState
|
||||||
|
let event: HighlightEvent
|
||||||
|
let options: EventViewOptions
|
||||||
|
|
||||||
|
init(state: DamusState, event: NostrEvent, options: EventViewOptions) {
|
||||||
|
self.state = state
|
||||||
|
self.event = HighlightEvent.parse(from: event)
|
||||||
|
self.options = options.union(.no_mentions)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
EventShell(state: state, event: event.event, options: options) {
|
||||||
|
HighlightBodyView(state: state, ev: event, options: options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HighlightView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
|
||||||
|
let content = "Nostr, a decentralized and open social network protocol. Without ads, toxic algorithms, or censorship"
|
||||||
|
let context = "Damus is built on Nostr, a decentralized and open social network protocol. Without ads, toxic algorithms, or censorship, Damus gives you access to the social network that a truly free and healthy society needs — and deserves."
|
||||||
|
|
||||||
|
let test_highlight_event = HighlightEvent.parse(from: NostrEvent(
|
||||||
|
content: content,
|
||||||
|
keypair: test_keypair,
|
||||||
|
kind: NostrKind.highlight.rawValue,
|
||||||
|
tags: [
|
||||||
|
["context", context],
|
||||||
|
["r", "https://damus.io"],
|
||||||
|
["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],
|
||||||
|
])!
|
||||||
|
)
|
||||||
|
|
||||||
|
let test_highlight_event2 = HighlightEvent.parse(from: NostrEvent(
|
||||||
|
content: content,
|
||||||
|
keypair: test_keypair,
|
||||||
|
kind: NostrKind.highlight.rawValue,
|
||||||
|
tags: [
|
||||||
|
["context", context],
|
||||||
|
["e", "36017b098859d62e1dbd802290d59c9de9f18bb0ca00ba4b875c2930dd5891ae"],
|
||||||
|
["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],
|
||||||
|
])!
|
||||||
|
)
|
||||||
|
VStack {
|
||||||
|
HighlightView(state: test_damus_state, event: test_highlight_event.event, options: [])
|
||||||
|
|
||||||
|
HighlightView(state: test_damus_state, event: test_highlight_event2.event, options: [.wide])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,13 +51,13 @@ struct LongformPreviewBody: View {
|
|||||||
func truncatedText(content: CompatibleText) -> some View {
|
func truncatedText(content: CompatibleText) -> some View {
|
||||||
Group {
|
Group {
|
||||||
if truncate_very_short {
|
if truncate_very_short {
|
||||||
TruncatedText(text: content, maxChars: 140)
|
TruncatedText(text: content, maxChars: 140, show_show_more_button: !options.contains(.no_show_more))
|
||||||
.font(header ? .body : .caption)
|
.font(header ? .body : .caption)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
}
|
}
|
||||||
else if truncate {
|
else if truncate {
|
||||||
TruncatedText(text: content)
|
TruncatedText(text: content, show_show_more_button: !options.contains(.no_show_more))
|
||||||
.font(header ? .body : .caption)
|
.font(header ? .body : .caption)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ struct LongformView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
EventShell(state: state, event: event.event, options: options) {
|
EventShell(state: state, event: event.event, options: options) {
|
||||||
SelectableText(attributedString: AttributedString(stringLiteral: event.title ?? "Untitled"), size: .title)
|
SelectableText(damus_state: state, event: event.event, attributedString: AttributedString(stringLiteral: event.title ?? "Untitled"), size: .title)
|
||||||
|
|
||||||
NoteContentView(damus_state: state, event: event.event, blur_images: false, size: .selected, options: options)
|
NoteContentView(damus_state: state, event: event.event, blur_images: false, size: .selected, options: options)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,16 @@ struct SelectedEventView: View {
|
|||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|
||||||
if let reply_ref = event.thread_reply()?.reply {
|
if let reply_ref = event.thread_reply()?.reply {
|
||||||
ReplyDescription(event: event, replying_to: damus.events.lookup(reply_ref.note_id), ndb: damus.ndb)
|
let replying_to = damus.events.lookup(reply_ref.note_id)
|
||||||
|
if event.known_kind == .highlight {
|
||||||
|
HighlightDescription(event: event, highlighted_event: replying_to, ndb: damus.ndb)
|
||||||
|
.padding(.horizontal)
|
||||||
|
} else {
|
||||||
|
ReplyDescription(event: event, replying_to: replying_to, ndb: damus.ndb)
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
} else if event.known_kind == .highlight {
|
||||||
|
HighlightDescription(event: event, highlighted_event: nil, ndb: damus.ndb)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ struct EventViewOptions: OptionSet {
|
|||||||
static let no_mentions = EventViewOptions(rawValue: 1 << 9)
|
static let no_mentions = EventViewOptions(rawValue: 1 << 9)
|
||||||
static let no_media = EventViewOptions(rawValue: 1 << 10)
|
static let no_media = EventViewOptions(rawValue: 1 << 10)
|
||||||
static let truncate_content_very_short = EventViewOptions(rawValue: 1 << 11)
|
static let truncate_content_very_short = EventViewOptions(rawValue: 1 << 11)
|
||||||
|
static let no_previews = EventViewOptions(rawValue: 1 << 12)
|
||||||
|
static let no_show_more = EventViewOptions(rawValue: 1 << 13)
|
||||||
|
|
||||||
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
|
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
|
||||||
static let embedded_text_only: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested, .no_media, .truncate_content_very_short]
|
static let embedded_text_only: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested, .no_media, .truncate_content_very_short, .no_previews]
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TextEvent: View {
|
struct TextEvent: View {
|
||||||
|
|||||||
@@ -62,8 +62,9 @@ struct LoginView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
VStack {
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
SignInHeader()
|
SignInHeader()
|
||||||
.padding(.top, 100)
|
|
||||||
|
|
||||||
SignInEntry(key: $key, shouldSaveKey: $shouldSaveKey)
|
SignInEntry(key: $key, shouldSaveKey: $shouldSaveKey)
|
||||||
|
|
||||||
@@ -112,8 +113,9 @@ struct LoginView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.padding(.bottom, 50)
|
||||||
}
|
}
|
||||||
.background(DamusBackground(maxHeight: 350), alignment: .top)
|
.background(DamusBackground(maxHeight: UIScreen.main.bounds.size.height/2), alignment: .top)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
credential_handler.check_credentials()
|
credential_handler.check_credentials()
|
||||||
}
|
}
|
||||||
@@ -320,9 +322,13 @@ struct KeyInput: View {
|
|||||||
}
|
}
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.overlay {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: 12)
|
||||||
.stroke(.gray, lineWidth: 1)
|
.stroke(.gray, lineWidth: 1)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 12)
|
||||||
|
.foregroundColor(.damusAdaptableWhite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,11 +343,12 @@ struct SignInHeader: View {
|
|||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
|
|
||||||
Text("Sign in", comment: "Title of view to log into an account.")
|
Text("Sign in", comment: "Title of view to log into an account.")
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
.font(.system(size: 32, weight: .bold))
|
.font(.system(size: 32, weight: .bold))
|
||||||
.padding(.bottom, 5)
|
.padding(.bottom, 5)
|
||||||
|
|
||||||
Text("Welcome to the social network you control", comment: "Welcome text")
|
Text("Welcome to the social network you control", comment: "Welcome text")
|
||||||
.foregroundColor(Color("DamusMediumGrey"))
|
.foregroundColor(DamusColors.neutral6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,6 +360,7 @@ struct SignInEntry: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Enter your account key", comment: "Prompt for user to enter an account key to login.")
|
Text("Enter your account key", comment: "Prompt for user to enter an account key to login.")
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
|
|
||||||
@@ -444,7 +452,9 @@ struct LoginView_Previews: PreviewProvider {
|
|||||||
let bech32_pubkey = "KeyInput"
|
let bech32_pubkey = "KeyInput"
|
||||||
Group {
|
Group {
|
||||||
LoginView(key: pubkey, nav: .init())
|
LoginView(key: pubkey, nav: .init())
|
||||||
|
.previewDevice(PreviewDevice(rawValue: "iPhone SE (3rd generation)"))
|
||||||
LoginView(key: bech32_pubkey, nav: .init())
|
LoginView(key: bech32_pubkey, nav: .init())
|
||||||
|
.previewDevice(PreviewDevice(rawValue: "iPhone 15 Pro Max"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,11 +78,11 @@ struct NoteContentView: View {
|
|||||||
func truncatedText(content: CompatibleText) -> some View {
|
func truncatedText(content: CompatibleText) -> some View {
|
||||||
Group {
|
Group {
|
||||||
if truncate_very_short {
|
if truncate_very_short {
|
||||||
TruncatedText(text: content, maxChars: 140)
|
TruncatedText(text: content, maxChars: 140, show_show_more_button: !options.contains(.no_show_more))
|
||||||
.font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size))
|
.font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size))
|
||||||
}
|
}
|
||||||
else if truncate {
|
else if truncate {
|
||||||
TruncatedText(text: content)
|
TruncatedText(text: content, show_show_more_button: !options.contains(.no_show_more))
|
||||||
.font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size))
|
.font(eventviewsize_to_font(size, font_size: damus_state.settings.font_size))
|
||||||
} else {
|
} else {
|
||||||
content.text
|
content.text
|
||||||
@@ -132,10 +132,10 @@ struct NoteContentView: View {
|
|||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if size == .selected {
|
if size == .selected {
|
||||||
if with_padding {
|
if with_padding {
|
||||||
SelectableText(attributedString: artifacts.content.attributed, size: self.size)
|
SelectableText(damus_state: damus_state, event: self.event, attributedString: artifacts.content.attributed, size: self.size)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
} else {
|
} else {
|
||||||
SelectableText(attributedString: artifacts.content.attributed, size: self.size)
|
SelectableText(damus_state: damus_state, event: self.event, attributedString: artifacts.content.attributed, size: self.size)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if with_padding {
|
if with_padding {
|
||||||
@@ -186,7 +186,7 @@ struct NoteContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if damus_state.settings.media_previews {
|
if damus_state.settings.media_previews, has_previews {
|
||||||
if with_padding {
|
if with_padding {
|
||||||
previewView(links: artifacts.links).padding(.horizontal)
|
previewView(links: artifacts.links).padding(.horizontal)
|
||||||
} else {
|
} else {
|
||||||
@@ -197,6 +197,10 @@ struct NoteContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var has_previews: Bool {
|
||||||
|
!options.contains(.no_previews)
|
||||||
|
}
|
||||||
|
|
||||||
func loadMediaButton(artifacts: NoteArtifactsSeparated) -> some View {
|
func loadMediaButton(artifacts: NoteArtifactsSeparated) -> some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
load_media = true
|
load_media = true
|
||||||
@@ -397,6 +401,14 @@ struct NoteContentView_Previews: PreviewProvider {
|
|||||||
.border(Color.red)
|
.border(Color.red)
|
||||||
}
|
}
|
||||||
.previewDisplayName("Long-form note")
|
.previewDisplayName("Long-form note")
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
NoteContentView(damus_state: state, event: test_note, blur_images: false, size: .small, options: [.no_previews, .no_action_bar, .truncate_content_very_short, .no_show_more])
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.previewDisplayName("Small single-line note")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ struct AboutView: View {
|
|||||||
Group {
|
Group {
|
||||||
if let about_string {
|
if let about_string {
|
||||||
let truncated_about = show_full_about ? about_string : about_string.truncateOrNil(maxLength: max_about_length)
|
let truncated_about = show_full_about ? about_string : about_string.truncateOrNil(maxLength: max_about_length)
|
||||||
SelectableText(attributedString: truncated_about ?? about_string, textAlignment: self.text_alignment, size: .subheadline)
|
SelectableText(damus_state: state, event: nil, attributedString: truncated_about ?? about_string, textAlignment: self.text_alignment, size: .subheadline)
|
||||||
|
|
||||||
if truncated_about != nil {
|
if truncated_about != nil {
|
||||||
if show_full_about {
|
if show_full_about {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
class ImageUploadingObserver: ObservableObject {
|
class ImageUploadingObserver: ObservableObject {
|
||||||
@Published var isLoading: Bool = false
|
@Published var isLoading: Bool = false
|
||||||
@@ -14,6 +15,8 @@ class ImageUploadingObserver: ObservableObject {
|
|||||||
struct EditPictureControl: View {
|
struct EditPictureControl: View {
|
||||||
let uploader: MediaUploader
|
let uploader: MediaUploader
|
||||||
let pubkey: Pubkey
|
let pubkey: Pubkey
|
||||||
|
var size: CGFloat? = 25
|
||||||
|
var setup: Bool? = false
|
||||||
@Binding var image_url: URL?
|
@Binding var image_url: URL?
|
||||||
@ObservedObject var uploadObserver: ImageUploadingObserver
|
@ObservedObject var uploadObserver: ImageUploadingObserver
|
||||||
let callback: (URL?) -> Void
|
let callback: (URL?) -> Void
|
||||||
@@ -43,20 +46,53 @@ struct EditPictureControl: View {
|
|||||||
if uploadObserver.isLoading {
|
if uploadObserver.isLoading {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(CircularProgressViewStyle(tint: DamusColors.purple))
|
.progressViewStyle(CircularProgressViewStyle(tint: DamusColors.purple))
|
||||||
|
.frame(width: size, height: size)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(DamusColors.white.opacity(0.7))
|
.background(DamusColors.white.opacity(0.7))
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.shadow(color: DamusColors.purple, radius: 15, x: 0, y: 0)
|
.shadow(color: DamusColors.purple, radius: 15, x: 0, y: 0)
|
||||||
|
} else if let url = image_url {
|
||||||
|
KFAnimatedImage(url)
|
||||||
|
.imageContext(.pfp, disable_animation: false)
|
||||||
|
.onFailure(fallbackUrl: URL(string: robohash(pubkey)), cacheKey: url.absoluteString)
|
||||||
|
.cancelOnDisappear(true)
|
||||||
|
.configure { view in
|
||||||
|
view.framePreloadCount = 3
|
||||||
|
}
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: (size ?? 25) + 10, height: (size ?? 25) + 10)
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.overlay(Circle().stroke(.white, lineWidth: 4))
|
||||||
|
} else {
|
||||||
|
if setup ?? false {
|
||||||
|
Image(systemName: "person")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
|
.padding(20)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.background {
|
||||||
|
Circle()
|
||||||
|
.fill(PinkGradient, strokeBorder: .white, lineWidth: 4)
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Image("camera")
|
Image("camera")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 25, height: 25)
|
.frame(width: size, height: size)
|
||||||
.foregroundColor(DamusColors.purple)
|
.foregroundColor(DamusColors.purple)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(DamusColors.white.opacity(0.7))
|
.background(DamusColors.white.opacity(0.7))
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.shadow(color: DamusColors.purple, radius: 15, x: 0, y: 0)
|
.background {
|
||||||
|
Circle()
|
||||||
|
.fill(DamusColors.purple, strokeBorder: .white, lineWidth: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $show_camera) {
|
.sheet(isPresented: $show_camera) {
|
||||||
@@ -110,7 +146,7 @@ struct EditPictureControl_Previews: PreviewProvider {
|
|||||||
let observer = ImageUploadingObserver()
|
let observer = ImageUploadingObserver()
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.gray
|
Color.gray
|
||||||
EditPictureControl(uploader: .nostrBuild, pubkey: test_pubkey, image_url: url, uploadObserver: observer) { _ in
|
EditPictureControl(uploader: .nostrBuild, pubkey: test_pubkey, size: 100, setup: false, image_url: url, uploadObserver: observer) { _ in
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ struct ProfileView: View {
|
|||||||
@State var show_share_sheet: Bool = false
|
@State var show_share_sheet: Bool = false
|
||||||
@State var show_qr_code: Bool = false
|
@State var show_qr_code: Bool = false
|
||||||
@State var action_sheet_presented: Bool = false
|
@State var action_sheet_presented: Bool = false
|
||||||
|
@State var mute_dialog_presented: Bool = false
|
||||||
@State var filter_state : FilterState = .posts
|
@State var filter_state : FilterState = .posts
|
||||||
@State var yOffset: CGFloat = 0
|
@State var yOffset: CGFloat = 0
|
||||||
|
|
||||||
@@ -162,7 +163,10 @@ struct ProfileView: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
action_sheet_presented = true
|
action_sheet_presented = true
|
||||||
}) {
|
}) {
|
||||||
navImage(img: "share3")
|
Image(systemName: "ellipsis")
|
||||||
|
.frame(width: 33, height: 33)
|
||||||
|
.background(Color.black.opacity(0.6))
|
||||||
|
.clipShape(Circle())
|
||||||
}
|
}
|
||||||
.confirmationDialog(NSLocalizedString("Actions", comment: "Title for confirmation dialog to either share, report, or mute a profile."), isPresented: $action_sheet_presented) {
|
.confirmationDialog(NSLocalizedString("Actions", comment: "Title for confirmation dialog to either share, report, or mute a profile."), isPresented: $action_sheet_presented) {
|
||||||
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
||||||
@@ -196,15 +200,21 @@ struct ProfileView: View {
|
|||||||
damus_state.postbox.send(new_ev)
|
damus_state.postbox.send(new_ev)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MuteDurationMenu { duration in
|
Button(NSLocalizedString("Mute", comment: "Button to mute a profile"), role: .destructive) {
|
||||||
notify(.mute(.user(profile.pubkey, duration?.date_from_now)))
|
mute_dialog_presented = true
|
||||||
} label: {
|
|
||||||
Text("Mute", comment: "Button to mute a profile.")
|
|
||||||
.foregroundStyle(.red)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.confirmationDialog(NSLocalizedString("Mute", comment: "Title for confirmation dialog to mute a profile."), isPresented: $mute_dialog_presented) {
|
||||||
|
ForEach(DamusDuration.allCases, id: \.self) { duration in
|
||||||
|
Button {
|
||||||
|
notify(.mute(.user(profile.pubkey, duration.date_from_now)))
|
||||||
|
} label: {
|
||||||
|
Text(duration.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var customNavbar: some View {
|
var customNavbar: some View {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct ReactionsView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack {
|
LazyVStack {
|
||||||
ForEach(model.events.events, id: \.id) { ev in
|
ForEach(model.events.events.filter { $0.last_refid() == model.target }, id: \.id) { ev in
|
||||||
ReactionView(damus_state: damus_state, reaction: ev)
|
ReactionView(damus_state: damus_state, reaction: ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import Security
|
|||||||
struct SaveKeysView: View {
|
struct SaveKeysView: View {
|
||||||
let account: CreateAccountModel
|
let account: CreateAccountModel
|
||||||
let pool: RelayPool = RelayPool(ndb: Ndb()!)
|
let pool: RelayPool = RelayPool(ndb: Ndb()!)
|
||||||
@State var pub_copied: Bool = false
|
|
||||||
@State var priv_copied: Bool = false
|
|
||||||
@State var loading: Bool = false
|
@State var loading: Bool = false
|
||||||
@State var error: String? = nil
|
@State var error: String? = nil
|
||||||
|
|
||||||
@@ -31,30 +29,42 @@ struct SaveKeysView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image("logo-nobg")
|
||||||
|
.resizable()
|
||||||
|
.shadow(color: DamusColors.purple, radius: 2)
|
||||||
|
.frame(width: 56, height: 56, alignment: .center)
|
||||||
|
.padding(.top, 20.0)
|
||||||
|
|
||||||
if account.rendered_name.isEmpty {
|
if account.rendered_name.isEmpty {
|
||||||
Text("Welcome!", comment: "Text to welcome user.")
|
Text("Welcome!", comment: "Text to welcome user.")
|
||||||
.font(.title.bold())
|
.font(.title)
|
||||||
.padding(.bottom, 10)
|
.fontWeight(.heavy)
|
||||||
|
.foregroundStyle(DamusLogoGradient.gradient)
|
||||||
} else {
|
} else {
|
||||||
Text("Welcome, \(account.rendered_name)!", comment: "Text to welcome user.")
|
Text("Welcome, \(account.rendered_name)!", comment: "Text to welcome user.")
|
||||||
.font(.title.bold())
|
.font(.title)
|
||||||
.padding(.bottom, 10)
|
.fontWeight(.heavy)
|
||||||
|
.foregroundStyle(DamusLogoGradient.gradient)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus.", comment: "Reminder to user that they should save their account information.")
|
Text("Save your login info?", comment: "Ask user if they want to save their account information.")
|
||||||
.padding(.bottom, 10)
|
.font(.title)
|
||||||
|
.fontWeight(.heavy)
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
.padding(.top, 5)
|
||||||
|
|
||||||
Text("Private Key", comment: "Label to indicate that the text below is the user's private key used by only the user themself as a secret to login to access their account.")
|
Text("We'll save your account key, so you won't need to enter it manually next time you log in.", comment: "Reminder to user that they should save their account information.")
|
||||||
.font(.title2.bold())
|
.font(.system(size: 14))
|
||||||
.padding(.bottom, 10)
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
.padding(.top, 2)
|
||||||
|
.padding(.bottom, 100)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
Text("This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!", comment: "Label to describe that a private key is the user's secret account key and what they should do with it.")
|
Spacer()
|
||||||
.padding(.bottom, 10)
|
|
||||||
|
|
||||||
SaveKeyView(text: account.privkey.nsec, textContentType: .newPassword, is_copied: $priv_copied, focus: $privkey_focused)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
|
|
||||||
if priv_copied {
|
|
||||||
if loading {
|
if loading {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(.circular)
|
.progressViewStyle(.circular)
|
||||||
@@ -74,38 +84,43 @@ struct SaveKeysView: View {
|
|||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
save_key(account)
|
||||||
complete_account_creation(account)
|
complete_account_creation(account)
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Let's go!", comment: "Button to complete account creation and start using the app.")
|
Text("Save", comment: "Button to save key, complete account creation, and start using the app.")
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
}
|
}
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
complete_account_creation(account)
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("Not now", comment: "Button to not save key, complete account creation, and start using the app.")
|
||||||
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
|
}
|
||||||
|
.buttonStyle(NeutralButtonStyle(padding: EdgeInsets(top: 15, leading: 15, bottom: 15, trailing: 15), cornerRadius: 12))
|
||||||
|
.padding(.top, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
}
|
}
|
||||||
.background(
|
.background(DamusBackground(maxHeight: UIScreen.main.bounds.size.height/2), alignment: .top)
|
||||||
Image("eula-bg")
|
|
||||||
.resizable()
|
|
||||||
.blur(radius: 70)
|
|
||||||
.ignoresSafeArea(),
|
|
||||||
alignment: .top
|
|
||||||
)
|
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.navigationBarItems(leading: BackNav())
|
.navigationBarItems(leading: BackNav())
|
||||||
.onAppear {
|
|
||||||
// Hack to force keyboard to show up for a short moment and then hiding it to register password autofill flow.
|
|
||||||
pubkey_focused = true
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
||||||
pubkey_focused = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func save_key(_ account: CreateAccountModel) {
|
||||||
|
credential_handler.save_credential(pubkey: account.pubkey, privkey: account.privkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func complete_account_creation(_ account: CreateAccountModel) {
|
func complete_account_creation(_ account: CreateAccountModel) {
|
||||||
@@ -123,8 +138,6 @@ struct SaveKeysView: View {
|
|||||||
|
|
||||||
self.pool.register_handler(sub_id: "signup", handler: handle_event)
|
self.pool.register_handler(sub_id: "signup", handler: handle_event)
|
||||||
|
|
||||||
credential_handler.save_credential(pubkey: account.pubkey, privkey: account.privkey)
|
|
||||||
|
|
||||||
self.loading = true
|
self.loading = true
|
||||||
|
|
||||||
self.pool.connect()
|
self.pool.connect()
|
||||||
@@ -188,74 +201,13 @@ struct SaveKeysView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SaveKeyView: View {
|
|
||||||
let text: String
|
|
||||||
let textContentType: UITextContentType
|
|
||||||
@Binding var is_copied: Bool
|
|
||||||
var focus: FocusState<Bool>.Binding
|
|
||||||
|
|
||||||
func copy_text() {
|
|
||||||
UIPasteboard.general.string = text
|
|
||||||
is_copied = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
spacerBlock(width: 0, height: 0)
|
|
||||||
Button(action: copy_text) {
|
|
||||||
Label("", image: is_copied ? "check-circle.fill" : "copy2")
|
|
||||||
.foregroundColor(is_copied ? .green : .gray)
|
|
||||||
.background {
|
|
||||||
if is_copied {
|
|
||||||
Circle()
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.frame(width: 25, height: 25, alignment: .center)
|
|
||||||
.padding(.leading, -8)
|
|
||||||
.padding(.top, 1)
|
|
||||||
} else {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField("", text: .constant(text))
|
|
||||||
.padding(5)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 4.0).opacity(0.1)
|
|
||||||
}
|
|
||||||
.textSelection(.enabled)
|
|
||||||
.font(.callout.monospaced())
|
|
||||||
.onTapGesture {
|
|
||||||
copy_text()
|
|
||||||
// Hack to force keyboard to hide. Showing keyboard on text field is necessary to register password autofill flow but the text itself should not be modified.
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
end_editing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.textContentType(textContentType)
|
|
||||||
.deleteDisabled(true)
|
|
||||||
.focused(focus)
|
|
||||||
|
|
||||||
spacerBlock(width: 0, height: 0) /// set a 'width' > 0 here to vary key Text's aspect ratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder private func spacerBlock(width: CGFloat, height: CGFloat) -> some View {
|
|
||||||
Color.orange.opacity(1)
|
|
||||||
.frame(width: width, height: height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SaveKeysView_Previews: PreviewProvider {
|
struct SaveKeysView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let model = CreateAccountModel(real: "William", nick: "jb55", about: "I'm me")
|
let model = CreateAccountModel(display_name: "William", name: "jb55", about: "I'm me")
|
||||||
SaveKeysView(account: model)
|
SaveKeysView(account: model)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func create_account_to_metadata(_ model: CreateAccountModel) -> Profile {
|
func create_account_to_metadata(_ model: CreateAccountModel) -> Profile {
|
||||||
return Profile(name: model.nick_name, display_name: model.real_name, about: model.about, picture: model.profile_image?.absoluteString, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nil, damus_donation: nil)
|
return Profile(name: model.name, display_name: model.display_name, about: model.about, picture: model.profile_image?.absoluteString, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nil, damus_donation: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,20 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import MCEmojiPicker
|
import EmojiPicker
|
||||||
|
import EmojiKit
|
||||||
|
|
||||||
struct ReactionsSettingsView: View {
|
struct ReactionsSettingsView: View {
|
||||||
@ObservedObject var settings: UserSettingsStore
|
@ObservedObject var settings: UserSettingsStore
|
||||||
|
let damus_state: DamusState
|
||||||
@State private var isReactionsVisible: Bool = false
|
@State private var isReactionsVisible: Bool = false
|
||||||
|
|
||||||
|
@State private var selectedEmoji: Emoji? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
Text(settings.default_emoji_reaction)
|
Text(settings.default_emoji_reaction)
|
||||||
.emojiPicker(
|
|
||||||
isPresented: $isReactionsVisible,
|
|
||||||
selectedEmoji: $settings.default_emoji_reaction,
|
|
||||||
arrowDirection: .up,
|
|
||||||
isDismissAfterChoosing: true
|
|
||||||
)
|
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isReactionsVisible = true
|
isReactionsVisible = true
|
||||||
}
|
}
|
||||||
@@ -31,43 +29,23 @@ struct ReactionsSettingsView: View {
|
|||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("Reactions", comment: "Title of emoji reactions view"))
|
.navigationTitle(NSLocalizedString("Reactions", comment: "Title of emoji reactions view"))
|
||||||
.navigationBarTitleDisplayMode(.large)
|
.navigationBarTitleDisplayMode(.large)
|
||||||
|
.sheet(isPresented: $isReactionsVisible) {
|
||||||
|
NavigationView {
|
||||||
|
EmojiPickerView(selectedEmoji: $selectedEmoji, emojiProvider: damus_state.emoji_provider)
|
||||||
|
}
|
||||||
|
.presentationDetents([.medium, .large])
|
||||||
|
}
|
||||||
|
.onChange(of: selectedEmoji) { newEmoji in
|
||||||
|
guard let newEmoji else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settings.default_emoji_reaction = newEmoji.value
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// From: https://stackoverflow.com/a/39425959
|
|
||||||
extension Character {
|
|
||||||
/// A simple emoji is one scalar and presented to the user as an Emoji
|
|
||||||
var isSimpleEmoji: Bool {
|
|
||||||
guard let firstScalar = unicodeScalars.first else { return false }
|
|
||||||
return firstScalar.properties.isEmoji && firstScalar.value > 0x238C
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the scalars will be merged into an emoji
|
|
||||||
var isCombinedIntoEmoji: Bool { unicodeScalars.count > 1 && unicodeScalars.first?.properties.isEmoji ?? false }
|
|
||||||
|
|
||||||
var isEmoji: Bool { isSimpleEmoji || isCombinedIntoEmoji }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
var isSingleEmoji: Bool { count == 1 && containsEmoji }
|
|
||||||
|
|
||||||
var containsEmoji: Bool { contains { $0.isEmoji } }
|
|
||||||
|
|
||||||
var containsOnlyEmoji: Bool { !isEmpty && !contains { !$0.isEmoji } }
|
|
||||||
|
|
||||||
var emojiString: String { emojis.map { String($0) }.reduce("", +) }
|
|
||||||
|
|
||||||
var emojis: [Character] { filter { $0.isEmoji } }
|
|
||||||
|
|
||||||
var emojiScalars: [UnicodeScalar] { filter { $0.isEmoji }.flatMap { $0.unicodeScalars } }
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidEmoji(_ string: String) -> Bool {
|
|
||||||
return string.isSingleEmoji
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ReactionsSettingsView_Previews: PreviewProvider {
|
struct ReactionsSettingsView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ReactionsSettingsView(settings: UserSettingsStore())
|
ReactionsSettingsView(settings: UserSettingsStore(), damus_state: test_damus_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,32 +28,53 @@ struct SetupView: View {
|
|||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.foregroundStyle(DamusLogoGradient.gradient)
|
.foregroundStyle(DamusLogoGradient.gradient)
|
||||||
|
|
||||||
Text("The go-to iOS Nostr client", comment: "Quick description of what Damus is")
|
Text("The social network you control", comment: "Quick description of what Damus is")
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.neutral6)
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
|
|
||||||
WhatIsNostr()
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
WhyWeNeedNostr()
|
|
||||||
.padding()
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
navigationCoordinator.push(route: Route.CreateAccount)
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("Create Account", comment: "Button to continue to the create account page.")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
|
}
|
||||||
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
navigationCoordinator.push(route: Route.Login)
|
navigationCoordinator.push(route: Route.Login)
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Let's get started!", comment: "Button to continue to login page.")
|
Text("Sign In", comment: "Button to continue to login page.")
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
|
||||||
}
|
}
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("By continuing you agree to our ")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(DamusColors.neutral6)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
navigationCoordinator.push(route: Route.EULA)
|
||||||
|
}, label: {
|
||||||
|
Text("EULA", comment: "End User License Agreement")
|
||||||
|
.font(.subheadline)
|
||||||
|
})
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
}
|
||||||
|
.padding(.bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(DamusBackground(maxHeight: 300), alignment: .top)
|
.background(DamusBackground(maxHeight: UIScreen.main.bounds.size.height/2), alignment: .top)
|
||||||
.navigationDestination(for: Route.self) { route in
|
.navigationDestination(for: Route.self) { route in
|
||||||
route.view(navigationCoordinator: navigationCoordinator, damusState: DamusState.empty)
|
route.view(navigationCoordinator: navigationCoordinator, damusState: DamusState.empty)
|
||||||
}
|
}
|
||||||
@@ -63,61 +84,6 @@ struct SetupView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LearnAboutNostrLink: View {
|
|
||||||
@Environment(\.openURL) var openURL
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Button(action: {
|
|
||||||
openURL(URL(string: "https://nostr.com")!)
|
|
||||||
}, label: {
|
|
||||||
Text("Learn more about Nostr", comment: "Button that opens up a webpage where the user can learn more about Nostr.")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
})
|
|
||||||
|
|
||||||
Image(systemName: "arrow.up.right")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WhatIsNostr: View {
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Image("nostr-logo")
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("What is Nostr?", comment: "Heading text for section describing what is Nostr.")
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.padding(.vertical, 10)
|
|
||||||
|
|
||||||
Text("Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network", comment: "Description about what is Nostr.")
|
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
|
||||||
|
|
||||||
LearnAboutNostrLink()
|
|
||||||
.padding(.top, 10)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WhyWeNeedNostr: View {
|
|
||||||
var body: some View {
|
|
||||||
HStack(alignment: .top) {
|
|
||||||
Image("lightbulb")
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("Why we need Nostr?", comment: "Heading text for section describing why Nostr is needed.")
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.padding(.vertical, 10)
|
|
||||||
|
|
||||||
Text("Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken", comment: "Description about why Nostr is needed.")
|
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SetupView_Previews: PreviewProvider {
|
struct SetupView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
Group {
|
Group {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@testable import damus
|
@testable import damus
|
||||||
|
import EmojiPicker
|
||||||
|
|
||||||
// Generates a test damus state with configurable mock parameters
|
// Generates a test damus state with configurable mock parameters
|
||||||
func generate_test_damus_state(
|
func generate_test_damus_state(
|
||||||
@@ -50,7 +51,9 @@ func generate_test_damus_state(
|
|||||||
music: .init(onChange: {_ in }),
|
music: .init(onChange: {_ in }),
|
||||||
video: .init(),
|
video: .init(),
|
||||||
ndb: ndb,
|
ndb: ndb,
|
||||||
quote_reposts: .init(our_pubkey: our_pubkey) )
|
quote_reposts: .init(our_pubkey: our_pubkey),
|
||||||
|
emoji_provider: DefaultEmojiProvider(showAllVariations: false)
|
||||||
|
)
|
||||||
|
|
||||||
return damus
|
return damus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ extension NdbNote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func get_cached_inner_event(cache: EventCache) -> NdbNote? {
|
func get_cached_inner_event(cache: EventCache) -> NdbNote? {
|
||||||
guard self.known_kind == .boost else {
|
guard self.known_kind == .boost || self.known_kind == .highlight else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ class NdbNote: Encodable, 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
|
return kind == 1 || kind == 42 || kind == 30023 || kind == 9802
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_quote_repost: NoteId? {
|
var is_quote_repost: NoteId? {
|
||||||
|
|||||||
Reference in New Issue
Block a user