damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 23c3130a8267c6bfaa37a392c515ac261813d1bb
parent 8bcd8317f1c4376608a17339e1ac11d37317a592
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Mon, 17 Jun 2024 17:04:36 -0700

New chat thread view

This commit changes the thread view to a new UX concept where children views of the selected view are now presented as chat bubbles, and the entire tree of conversation is shown flattened. New interactions, layout, and design changes have been introduced to revamp the user experience.

Testing
-------

Device: A mix of iPhone physical devices and simulator
iOS: A mix of iOS 17 versions
Damus: A mix of versions leading up to this one.
Coverage:
1. Unit tests are passing
2. A select few users have been using prototypes versions of this as their daily driver
3. Layout tested with an eclectic mix of threads
4. Posting new notes to the thread works
5. Clicking on reply quote view takes user to the mentioned message with a momentary visible highlight
6. Swipe actions work
7. Long press on chat bubbles works and shows emoji selector. Adding emoji sends the reaction
8. Clicking on notes selects them with an easy to follow transition

Known issues:
1. The text on the reply quote view occasionally appears to be off-center (in about 10% of occurrences). The cause is still unknown
2. Long press will still show the emoji keyboard even if user is on "onlyzaps" mode
3. Quoted events are not rendered on chat bubbles. When user posts a quoted event with no text, that could lead to confusion

Closes: https://github.com/damus-io/damus/issues/1126
Changelog-Added: Completely new threads experience that is easier and more pleasant to use
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 45+++++++++++++++++++++++++++++++++++++++++++++
Mdamus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 12+++++++++++-
Adamus/Assets.xcassets/Colors/DamusAdaptableGrey 2.colorset/Contents.json | 38++++++++++++++++++++++++++++++++++++++
Adamus/Assets.xcassets/Colors/DamusAdaptableLighterGrey.colorset/Contents.json | 38++++++++++++++++++++++++++++++++++++++
Adamus/Assets.xcassets/Colors/DamusAdaptablePurpleBackground 1.colorset/Contents.json | 38++++++++++++++++++++++++++++++++++++++
Adamus/Assets.xcassets/Colors/DamusAdaptablePurpleBackground 2.colorset/Contents.json | 38++++++++++++++++++++++++++++++++++++++
Adamus/Assets.xcassets/Colors/DamusAdaptablePurpleForeground.colorset/Contents.json | 38++++++++++++++++++++++++++++++++++++++
Mdamus/Components/DamusColors.swift | 5+++++
Mdamus/Components/TruncatedText.swift | 14+++++++++-----
Mdamus/Models/ThreadModel.swift | 8+++++++-
Mdamus/TestData.swift | 16++++++++++++++++
Mdamus/Util/EventCache.swift | 6+++---
Adamus/Util/Extensions/VectorMath.swift | 27+++++++++++++++++++++++++++
Mdamus/Util/Router.swift | 3++-
Mdamus/Views/ActionBar/EventActionBar.swift | 262+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Adamus/Views/Chat/ChatBubbleView.swift | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adamus/Views/Chat/ChatEventView.swift | 324+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adamus/Views/Chat/ChatroomThreadView.swift | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adamus/Views/Chat/ReplyQuoteView.swift | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/Events/Longform/LongformPreview.swift | 4++--
Mdamus/Views/Events/TextEvent.swift | 4+++-
Mdamus/Views/NoteContentView.swift | 24++++++++++++++++++------
22 files changed, 1326 insertions(+), 67 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -34,6 +34,9 @@ 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 */; }; + 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 */; }; 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; }; 4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; }; @@ -494,6 +497,9 @@ D7870BC32AC47EBC0080BA88 /* EventLoaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7870BC22AC47EBC0080BA88 /* EventLoaderView.swift */; }; D789D1202AFEFBF20083A7AB /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = D789D11F2AFEFBF20083A7AB /* secp256k1 */; }; 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 */; }; D798D21B2B0856F200234419 /* NdbTagsIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDD1AE12A6B3074001CD4DF /* NdbTagsIterator.swift */; }; D798D21C2B0857E400234419 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; }; @@ -822,6 +828,9 @@ 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>"; }; 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>"; }; 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>"; }; @@ -1410,6 +1419,8 @@ 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>"; }; 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>"; }; 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>"; }; @@ -1474,6 +1485,7 @@ buildActionMask = 2147483647; files = ( 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */, + D78DB8592C1CE9CA00F0AB12 /* SwipeActions in Frameworks */, 4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */, 4C27C9322A64766F007DBC75 /* MarkdownUI in Frameworks */, 3AFE89C32BD4156F00AD31EF /* MCEmojiPicker in Frameworks */, @@ -1996,6 +2008,7 @@ 4C75EFA227FA576C0006080F /* Views */ = { isa = PBXGroup; children = ( + D78DB85D2C20FE9E00F0AB12 /* Chat */, D71AC4CA2BA8E3320076268E /* Extensions */, BA3759952ABCCF360018D73B /* Camera */, F71694E82A66221E001F4053 /* Onboarding */, @@ -2692,6 +2705,7 @@ 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */, 4C7D09752A0AF19E00943473 /* FillAndStroke.swift */, D72E12772BEED22400F4F781 /* Array.swift */, + D78DB85A2C20FE4F00F0AB12 /* VectorMath.swift */, ); path = Extensions; sourceTree = "<group>"; @@ -2760,6 +2774,17 @@ path = Purple; 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 */ = { isa = PBXGroup; children = ( @@ -2842,6 +2867,7 @@ 4C06670328FC7EC500038D2A /* Kingfisher */, 4C27C9312A64766F007DBC75 /* MarkdownUI */, 3AFE89C22BD4156F00AD31EF /* MCEmojiPicker */, + D78DB8582C1CE9CA00F0AB12 /* SwipeActions */, ); productName = damus; productReference = 4CE6DEE327F7A08100C66700 /* damus.app */; @@ -2982,6 +3008,7 @@ 4C27C9302A64766F007DBC75 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */, D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, 3AFE89C12BD4156F00AD31EF /* XCRemoteSwiftPackageReference "MCEmojiPicker" */, + D78DB8572C1CE9CA00F0AB12 /* XCRemoteSwiftPackageReference "SwipeActions" */, ); productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */; projectDirPath = ""; @@ -3235,6 +3262,7 @@ E02429952B7E97740088B16C /* CameraController.swift in Sources */, 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */, 5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */, + D78DB85B2C20FE5000F0AB12 /* VectorMath.swift in Sources */, D7CB5D3E2B116DAD00AD4105 /* NotificationsManager.swift in Sources */, 50A16FFF2AA76A0900DFEC1F /* VideoController.swift in Sources */, F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */, @@ -3243,6 +3271,7 @@ F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */, 4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */, 4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */, + 4C011B612BD0B25C002F2F9B /* ReplyQuoteView.swift in Sources */, D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */, 4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */, 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */, @@ -3331,6 +3360,7 @@ 4C64305C2A945AFF00B0C0E9 /* MusicController.swift in Sources */, 5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */, F79C7FAD29D5E9620000F946 /* EditPictureControl.swift in Sources */, + 4C011B5F2BD0A56A002F2F9B /* ChatroomThreadView.swift in Sources */, 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */, 4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */, 4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */, @@ -3390,6 +3420,7 @@ 4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */, 3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */, 5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */, + D78DB85F2C20FED300F0AB12 /* ChatBubbleView.swift in Sources */, D2277EEA2A089BD5006C3807 /* Router.swift in Sources */, 4C9D6D162B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift in Sources */, 4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */, @@ -3499,6 +3530,7 @@ 4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */, 4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */, D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */, + 4C011B5E2BD0A56A002F2F9B /* ChatEventView.swift in Sources */, 4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */, 4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */, 501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */, @@ -4324,6 +4356,14 @@ 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" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; @@ -4360,6 +4400,11 @@ package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */; productName = secp256k1; }; + D78DB8582C1CE9CA00F0AB12 /* SwipeActions */ = { + isa = XCSwiftPackageProductDependency; + package = D78DB8572C1CE9CA00F0AB12 /* XCRemoteSwiftPackageReference "SwipeActions" */; + productName = SwipeActions; + }; D7A343ED2AD0D77C00CED48B /* InlineSnapshotTesting */ = { isa = XCSwiftPackageProductDependency; package = D7A343EC2AD0D77C00CED48B /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; diff --git a/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "babaf4d5748afecf49bbb702530d8e9576460692f478b0a50ee43195dd4440e2", "pins" : [ { "identity" : "gsplayer", @@ -60,7 +61,16 @@ "revision" : "74203046135342e4a4a627476dd6caf8b28fe11b", "version" : "509.0.0" } + }, + { + "identity" : "swipeactions", + "kind" : "remoteSourceControl", + "location" : "https://github.com/aheze/SwipeActions", + "state" : { + "revision" : "41e6f6dce02d8cfa164f8c5461a41340850ca3ab", + "version" : "1.1.0" + } } ], - "version" : 2 + "version" : 3 } diff --git a/damus/Assets.xcassets/Colors/DamusAdaptableGrey 2.colorset/Contents.json b/damus/Assets.xcassets/Colors/DamusAdaptableGrey 2.colorset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/Colors/DamusAdaptableLighterGrey.colorset/Contents.json b/damus/Assets.xcassets/Colors/DamusAdaptableLighterGrey.colorset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/Colors/DamusAdaptablePurpleBackground 1.colorset/Contents.json b/damus/Assets.xcassets/Colors/DamusAdaptablePurpleBackground 1.colorset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/Colors/DamusAdaptablePurpleBackground 2.colorset/Contents.json b/damus/Assets.xcassets/Colors/DamusAdaptablePurpleBackground 2.colorset/Contents.json @@ -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 + } +} diff --git a/damus/Assets.xcassets/Colors/DamusAdaptablePurpleForeground.colorset/Contents.json b/damus/Assets.xcassets/Colors/DamusAdaptablePurpleForeground.colorset/Contents.json @@ -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 + } +} diff --git a/damus/Components/DamusColors.swift b/damus/Components/DamusColors.swift @@ -10,6 +10,11 @@ import SwiftUI class DamusColors { 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 adaptableWhite = Color("DamusAdaptableWhite") static let white = Color("DamusWhite") diff --git a/damus/Components/TruncatedText.swift b/damus/Components/TruncatedText.swift @@ -10,10 +10,12 @@ import SwiftUI struct TruncatedText: View { let text: CompatibleText 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.maxChars = maxChars + self.show_show_more_button = show_show_more_button } var body: some View { @@ -29,8 +31,10 @@ struct TruncatedText: View { if truncatedAttributedString != nil { Spacer() - Button(NSLocalizedString("Show more", comment: "Button to show entire note.")) { } - .allowsHitTesting(false) + if self.show_show_more_button { + Button(NSLocalizedString("Show more", comment: "Button to show entire note.")) { } + .allowsHitTesting(false) + } } } } @@ -38,10 +42,10 @@ struct TruncatedText: View { struct TruncatedText_Previews: PreviewProvider { static var previews: some View { 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) - 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) } } diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift @@ -20,7 +20,13 @@ class ThreadModel: ObservableObject { self.original_event = event 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 { return original_event.id == event.id } diff --git a/damus/TestData.swift b/damus/TestData.swift @@ -28,6 +28,13 @@ let test_note = createdAt: UInt32(Date().timeIntervalSince1970 - 100) )! +let test_short_note = + NostrEvent( + content: "Nostr is the super app.", + keypair: jack_keypair, + createdAt: UInt32(Date().timeIntervalSince1970 - 100) + )! + let test_note_json_with_escaped_slash = "{\"tags\":[],\"pubkey\":\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\",\"content\":\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\",\"created_at\":1691864981,\"kind\":1,\"sig\":\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\",\"id\":\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\"}" let test_encoded_note_with_image = NostrEvent.owned_from_json(json: test_note_json_with_escaped_slash) @@ -427,3 +434,12 @@ let test_contact_list_json = """ {"content":"{\\\"wss://nos.lol\\\":{\\\"write\\\":true,\\\"read\\\":true},\\\"wss://relay.damus.io\\\":{\\\"write\\\":true,\\\"read\\\":true},\\\"ws://monad.jb55.com:8080\\\":{\\\"write\\\":true,\\\"read\\\":true},\\\"wss://nostr.wine\\\":{\\\"write\\\":true,\\\"read\\\":true},\\\"wss://welcome.nostr.wine\\\":{\\\"write\\\":true,\\\"read\\\":true},\\\"wss://eden.nostr.land\\\":{\\\"write\\\":true,\\\"read\\\":true},\\\"wss://relay.mostr.pub\\\":{\\\"write\\\":true,\\\"read\\\":true},\\\"wss://nostr-pub.wellorder.net\\\":{\\\"write\\\":true,\\\"read\\\":true}}","created_at":1689904312,"id":"20d0ff27d6fcb13de8366328c5b1a7af26bcac07f2e558fbebd5e9242e608c09","kind":3,"pubkey":"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245","sig":"f388e9b45d2872e9320e2d092f52d3d9ccee7e83ef3376b48a705c22099d27736fcc750800e0b6cd4a2a6b7a44c4b0b435c7242392c7ceeb809da40c1fa1ee6c","tags":[["p","6cad545430904b84a8101c5783b65043f19ae29d2da1076b8fc3e64892736f03","wss://nostr-pub.wellorder.net"],["p","2ef93f01cd2493e04235a6b87b10d3c4a74e2a7eb7c3caf168268f6af73314b5","wss://nostr-pub.wellorder.net"],["p","9ec7a778167afb1d30c4833de9322da0c08ba71a69e1911d5578d3144bb56437","wss://nostr.bitcoiner.social"],["p","b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a","wss://nostr-relay.untethr.me"],["p","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","wss://nostr-relay.freeberty.net"],["p","8c0da4862130283ff9e67d889df264177a508974e2feb96de139804ea66d6168","wss://nostr-pub.wellorder.net"],["p","21763c71793764a2661eb10ede32a8f2312c9f8db08bc539c888bafa38dcf368"],["p","4570d7a0b49b5524797120810116a2a5c18281423b173a557056f08f15c5382d","wss://nostr-pub.wellorder.net"],["p","04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9","wss://nostr-relay.freeberty.net"],["p","d3646691ba5b1d796c1e1b3430df00fe1189ec9c232877adde18c8f656af18f0"],["p","34af16f1787b261331a67fa519e9edb11f0a851c0b10a15901720b0fe0a06d1d"],["p","b7c66ce6f7bbe034e96be54c2ffc0adf631a889abc0834ba1431171b67c489aa"],["p","8355095016fddbe31fcf1453b26f613553e9758cf2263e190eac8fd96a3d3de9"],["p","3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"],["p","35d26e4690cbe1a898af61cc3515661eb5fa763b57bd0b42e45099c8b32fd50f"],["p","0810b5bc4cddc3e7624a1f6acbdccdc95c6e9409c144ce83365ee04a3a63314e"],["p","51fc7209201b1414f721c3d2d2b3430699b1e6317716c5182cc1d7945072e358"],["p","e6a92d8b6c20426f78bba8510ccdc73df5122814a3bac1d553adebac67a92b27"],["p","619d0c0483a311a16767b0a7999ce9f28e58e79eff66f0edafae9ca2d9054f08"],["p","d7b76d02c758a62a505e03bd5f5049aaee4e7e36283d273c7f6798912692df2b"],["p","d4d4fdde8ab4924b1e452e896709a3bd236da4c0576274b52af5992d4d34762c"],["p","3878d95db7b854c3a0d3b2d6b7bf9bf28b36162be64326f5521ba71cf3b45a69"],["p","ac9ec020170155f0feb347f0d777ee5fc38dd1f36353093046323646cff5169f"],["p","d91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075"],["p","146bda4ec6932830503ee4f8e8b626bd7b3a5784232b8240ba15c8cbff9a07cd"],["p","ea75802dd1c86933c1e20c582541bb283d44c88e3445ed90d4375fc3d973f3a0"],["p","41108126409bf99cb77ff16fd53f4da2e53010b0dca04b0a53ebdf46eade37aa"],["p","9682c33f9024dadb1bffdf762c3156e26b4aa340de8d06c91ca537fcc0fdb3a9"],["p","a8f14f05c64f9e62bdada89c21a52f09aa5d7948b47ccf52da1be16b0de9efac"],["p","80482e60178c2ce996da6d67577f56a2b2c47ccb1c84c81f2b7960637cb71b78"],["p","b10c0000079a83cf26815dc7538818d8d56a2983e374e30a4143e50060978457"],["p","547fcc5c7e655fe7c83da5a812e6332f0a4779c87bf540d8e75a4edbbf36fe4a"],["p","dd81a8bacbab0b5c3007d1672fb8301383b4e9583d431835985057223eb298a5"],["p","bb1cf5250435ff475cd8b32acb23e3ee7bbe8fc38f6951704b4798513947672c"],["p","1fa91680ebfc68069ec13423fc8b9b0a746e9265584e16cf7d80be7ad721de6e"],["p","52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd"],["p","d8139f130cc1dec5d92e3a4dc49fb11f064bf5b32c65d96da107ba2389547dc7"],["p","9ac12013d20fae4f8829ba4e5ba6343e410288d3a0752d6143386d2c1af1f57e"],["p","46fcbe3065eaf1ae7811465924e48923363ff3f526bd6f73d7c184b16bd8ce4d"],["p","4b0572ab1f9a575415326b599f1179c20134bc23040d207156e71800b4ed33fe"],["p","2fa4b9ba71b6dab17c4723745bb7850dfdafcb6ae1a8642f76f9c64fa5f43436"],["p","3235036bd0957dfb27ccda02d452d7c763be40c91a1ac082ba6983b25238388c"],["p","954aaf69c2e7c9fb3f9998f61944ab8ab08ce3c8679ecd985e4486a6eb696217"],["p","d7f0e3917c466f1e2233e9624fbd6d4bd1392dbcfcaf3574f457569d496cb731"],["p","08b80da85ba68ac031885ea555ab42bb42231fde9b690bbd0f48c128dfbf8009"],["p","104749bc9151a0e54b9845ee50fc4b559439dd1ada006e36a6c49ad3ea16a55c"],["p","c47daa0cd21a70797fe9404f8fe0c3f679c2b46148788d1295e6424232064f1d"],["p","d987084c48390a290f5d2a34603ae64f55137d9b4affced8c0eae030eb222a25"],["p","e1ff3bfdd4e40315959b08b4fcc8245eaa514637e1d4ec2ae166b743341be1af"],["p","cf9413eb6bbe55c8a3c10119ec0635e134fa266f2c50f825d7225da9b92ecc4e"],["p","bae77874946ec111f94be59aef282de092dc4baf213f8ecb8c9e15cb7ed7304e"],["p","c7eda660a6bc8270530e82b4a7712acdea2e31dc0a56f8dc955ac009efd97c86"],["p","2590201e2919a8aa6568c88900192aa54ef00e6c0974a5b0432f52614a841ec8"],["p","38b07a31f3b23dbeb9f59deb7bec5b993173fb4022206980f3809d0b68abf959"],["p","23a2cf63ec81e65561acafc655898d2fd0ef190084653fa452413f75e5a3d5bc"],["p","0d6c8388dcb049b8dd4fc8d3d8c3bb93de3da90ba828e4f09c8ad0f346488a33"],["p","11b9a89404dbf3034e7e1886ba9dc4c6d376f239a118271bd2ec567a889850ce"],["p","4d5ce768123563bc583697db5e84841fb528f7b708d966f2e546286ce3c72077"],["p","9a29ee8c3771573e5306bb7701182e970b188ce3552713ca68a157ebc3c0bf75"],["p","57225e0adcbad1fddf8d9ba1f5f36d657f134b7e0ea7aed6c0eb7013e4ef45f1"],["p","b175db709771d32bbe7d8599e0c41f3f8768cc3a8333603d93c6d72d41c42f76"],["p","9b9f5f1ec13105c8d1c2ea16aa952e98640b170b871420980ea11b18eb1f1e03"],["p","3ed35796636aa19b7327de00b1192fbb985e8bf05d71604237bcd9df9b8bc73c"],["p","234d39919c1bd120766c4d874e8f34df4c80981236d76cdd95e246b1d01ae10b"],["p","a4cb51f4618cfcd16b2d3171c466179bed8e197c43b8598823b04de266cef110"],["p","2b36fb6ae1022d0d4eac2a9f13fc2638f3350acc9b07bdca1de43a7c63429644"],["p","472f440f29ef996e92a186b8d320ff180c855903882e59d50de1b8bd5669301e"],["p","3fde182cc7e6efa69a393b16ef41b10c03928df3b96acf4f0eb03f9fca63a09a"],["p","11674b2d321fc24239b02afdf966c32e36594a6282bd7f3d4bcd12438558ab51"],["p","42a0825e980b9f97943d2501d99c3a3859d4e68cd6028c02afe58f96ba661a9d"],["p","76c71aae3a491f1d9eec47cba17e229cda4113a0bbb6e6ae1776d7643e29cafa"],["p","8606b7b82a2f278c290e8dc53d853a5fa92ed0f270564da7012e7a0bfd1b464d"],["p","4c96d763eb2fe01910f7e7220b7c7ecdbe1a70057f344b9f79c28af080c3ee30"],["p","d12feb34b3ee120423b818cd8dda47000639bbec9a6ee6d3317ea886ec5b084f"],["p","645681b9d067b1a362c4bee8ddff987d2466d49905c26cb8fec5e6fb73af5c84"],["p","b1576eb99a4774158a32fc5e190afa3ded4da19f51fbfa0b1a1bf6421ea5733a"],["p","00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700"],["p","88a2c3b420b4a027706a98600d1fd744ac6cfd12e201b74189be5ef4b2b3aa45"],["p","00000e48d2029e90401d9bfc393dc6eedb7d834e31b163a027c6adb4d9df4e7b"],["p","65594f279a789982b55c02a38c92a99b986f891d2814c5f553d1bbfe3e23853d"],["p","8c5ec8b33956dac9aa616d81cb755c0efe3fc02f6fd58d593313ae1069acb490"],["p","77b504e58f206c2aa1b2ae6acc1eb11321f4061eafc8e5b531015dbca536b4e4"],["p","388a27f5a9583465a41718253c4a62406499111949692c1cc7bbd40fcbcd97a7"],["p","c4f5e7a75a8ce3683d529cff06368439c529e5243c6b125ba68789198856cac7"],["p","566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8"],["p","e25a8b2051022a08f97d267d4b99ddfc500a0bfe149a5f671e46f72e9ea36ec9"],["p","44189afbf0d25716de6af793dc61c113ecf56166481912ce14c2052db62f396c"],["p","149bc0d45eb549e0a94e2a8b0ef0f7304a422b52616fd05d264689471e3ffa69"],["p","af30bf9b7c8843a1f323699a8320a879dfee4b17512696fec39969db284af14a"],["p","11c1839049c616fba0bc48c3de85e593323194426f4d1208063216eb6aa2533a"],["p","960c4ed4a211426a918338cd94a147f06f17d7b437787a4cbd4e2b251879c7fa"],["p","e6a9a4f853e4b1d426eb44d0c5db09fdc415ce513e664118f46f5ffbea304cbc"],["p","cedab81be42ef47dbde653f4ba7ab25ac3aa32cfc2b672ee0f89c0faf882f13e"],["p","50d94fc2d8580c682b071a542f8b1e31a200b0508bab95a33bef0855df281d63"],["p","e22f6c280855623bbf4f720eed52213c9c298968344dcfd22d15173d46ce767b"],["p","8230e5ce9829717e7ea90dfa3fa9b8c6696f36fe2cbdfaa7f1bf8738d4507c8a"],["p","a67e98faf32f2520ae574d84262534e7b94625ce0d4e14a50c97e362c06b770e"],["p","52c674b42ab0fa0d016bb571c7f038fb9939ad72fb9370e7dfae71060c2f8c9b"],["p","93a90fae706f1463658c6bb5030c633c707c7e2e8fa17919499d97240925e367"],["p","e8e7fddd6837ebf922918fee8e81b343e71c9a86888dec2d4778807cece22195"],["p","bc52210b20d3fb89326463a3518674c7edde65794a7765c7f3a9119b20bfc6de"],["p","c5cfda98d01f152b3493d995eed4cdb4d9e55a973925f6f9ea24769a5a21e778"],["p","607f56cd27897f4c7b2e0fbf17a07c14d6ea737792559f0f43679e684080bb2f"],["p","3946adbb2fc7c95f75356d8f3952c8e2705ee2431f8bd33f5cae0f9ede0298e2"],["p","de35d1b04affcfb5693ea3a556a9c729100528ab3dfdbd6029e3fe0076bfa5fb"],["p","0000007039d4f19ab07896d8790fb43b50c9ffbc455079518f90f8626de780d1"],["p","79edc0ac14a1866060eb5a64073af6bf87ce0fcd8b47b510813f3e559f4e2a1f"],["p","46d0dfd3a724a302ca9175163bdf788f3606b3fd1bb12d5fe055d1e418cb60ea"],["p","cef0418909e32c2f00d40ee3d60b50086c3881039dc4817316eb1c9ca7e0cb30"],["p","000000000652e452ee68a01187fb08c899496cb46cb51d1aa0803d063acedba7"],["p","01fff557077a55b464c519011e5d5e7d2c484c3a8def59009b62b4c0d00c67a0"],["p","c1e9ab3a56a2ab6ca4bebf44ea64b2fda40ac6311e886ba86b4652169cb56b43"],["p","2df69cd0c6ab95e08f466abe7b39bb64e744ee31ffc3041f270bdfec2a37ec06"],["p","a8e76c3ace7829f9ee44cf9293309e21a1824bf1e57631d00685a1ed0b0bd8a2"],["p","fd0bcf8cd1aee83fe75e6c0fdfc543845e5bc3f50d26d2d5e5c6d3fa521f98c0"],["p","68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272"],["p","28463e26e8d36de7599b1d1d9c4c1cc9d69b33d8628e443455eed8b02929be4f"],["p","cf7ad05f8e99de8eadbbfbd5ca1c0f9b75499bce07074966b277688ca5e1d726"],["p","b2dd40097e4d04b1a56fb3b65fc1d1aaf2929ad30fd842c74d68b9908744495b"],["p","884704bd421721e292edbff42eb77547fe115c6ff9825b08fc366be4cd69e9f6"],["p","52155da703585f25053830ac39863c80ea6adc57b360472c16c566a412d2bc38"],["p","d0debf9fb12def81f43d7c69429bb784812ac1e4d2d53a202db6aac7ea4b466c"],["p","274611b4728b0c40be1cf180d8f3427d7d3eebc55645d869a002e8b657f8cd61"],["p","2ce6eab2e7fc0e533b3f4d3e39feb6b57fc4aec8215c7306461c59f3f9171bbd"],["p","a96ebe87ae15e47f4f1e79a0c26c07587b49488bd22e7f3789e06c23892753af"],["p","436456869bdd7fcb3aaaa91bed05173ea1510879004250b9f69b2c4370d58cf7"],["p","cbc5ef6b01cbd1ffa2cb95a954f04c385a936c1a86e1bb9ccdf2cf0f4ebeaccb"],["p","b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc"],["p","9156e62c7d2f49a91b55effec6c111d3fb343e9de6ff05650e7fd89a039a9dce"],["p","ea09f3038a61d7af9bb59ae821ef80957fb2b9f3cb94ed4a6e2460cd51b90893"],["p","06b7819d7f1c7f5472118266ed7bca8785dceae09e36ea3a4af665c6d1d8327c"],["p","417e77a2516cdbf270b0ae3089c424340961086d22191ee372e51298fa8fd7b0"],["p","1027fd5bc3b5e50c9800d48bc8acfbc290d89b857c7ce15572a57048c4c0558e"],["p","00000000c63158350be797e6b4801083cf25e985dd0a277fe9d7c7b94c99780f"],["p","6846296c017f8e16ce3656d24548cac49eba4ff2e38d261862f056a0fe36e514"],["p","bf8eb2aa2515fbfa8b385ab9e2e8aea1817ec6d929614c082390ac13983171f5"],["p","f7dbfb13eec4c938ba4ddae192c54f307bf420f3106d2a6d1402ba6fe1fc26bb"],["p","5c4bf3e548683d61fb72be5f48c2dff0cf51901b9dd98ee8db178efe522e325f"],["p","aac07d95089ce6adf08b9156d43c1a4ab594c6130b7dcb12ec199008c5819a2f"],["p","dc08d8aa9e088f9ece4bdf1e86607ba7aad2036b43084d465a0dfb5f2e3ba9c4"],["p","0418ca2d6cd6c7fbc4e0391bb745027023a7edbc38f2a60fc3b68f006efb85eb"],["p","dc062cf626179203b6baada5fd05d0a57e29b8504a7a848097282daba63309a8"],["p","ae3909314fa3e56926ef5cc4434a442440142a107644d11a2e51cbdee728c2a6"],["p","e509e5b7a598b9cbea63d99375546b8bfa2dcecad759bc4f593e22504d204cef"],["p","e668a111aa647e63ef587c17fb0e2513d5c2859cd8d389563c7640ffea1fc216"],["p","5192759b0e0c91d936115ca57b7fedea4116258f9d99cc3933ec3f1c3e81e2ed"],["p","d86a593d49a036b3460c6dbd62228d077f82f41f0216f4f87d21bd09477627d5"],["p","f4a466ce15c1a2cf09a4f75d27a010eb80bc8af47efdd4202c60c9db9f7a0a16"],["p","a124b08e6515de9a9672037bc4adc385a94e2bd37b5aa8a047083b25f431accc"],["p","000000000332c7831d9c5a99f183afc2813a6f69a16edda7f6fc0ed8110566e6"],["p","8ec86ac9e10979998652068ee6b00223b8e3265aabb3fe28fb6b3b6e294adc96"],["p","f0293508f3eb9e6fe99c2fd8ba69ff446216872a2d9f67979bfa4db8b3155806"],["p","f2076355aded9d556fca9c318033b3b95cf9aec909e9e9002554ac44a63ea086"],["p","287f1c9eb5c3faa88f9920626da4b0492c91d6ee5e2da71b3e9e0702e790c154"],["p","91dbab9f62660e95258480d2f2cff6dcfdb513f28a85fa4fb55ee993a5b46809"],["p","97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"],["p","c48e29f04b482cc01ca1f9ef8c86ef8318c059e0e9353235162f080f26e14c11"],["p","82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2"],["p","4690087445ad65f6d8d8adfee9cf51cfc2f0b765c9651c86e5c2079d04cf5ea5"],["p","5f3cbfd9d44acb9c947a81d1f978f62b740803276c24079de325cb58f85c71aa"],["p","38609f8bb73a240a557511257c8917120a160657d9cb54d499315c1ff8ab8f0c"],["p","fde9cf3d26a2aaa023e1ce6ac00aa033fef31489dea958e4657bd7dea24e388c"],["p","b9003833fabff271d0782e030be61b7ec38ce7d45a1b9a869fbdb34b9e2d2000"],["p","ec8f72ff2937c197cb0d032dae27bae073ae6a4e1bd2a8e2ef1578636b3595cb"],["p","35f25abceda5f71685dd378f02167cc51dd19313660951c40266a5dc3b8ad0f5"],["p","5e7ae588d7d11eac4c25906e6da807e68c6498f49a38e4692be5a089616ceb18"],["p","803a613997a26e8714116f99aa1f98e8589cb6116e1aaa1fc9c389984fcd9bb8"],["p","ab54780cfb99ef3f61e65c9e4c12ed6b96ed182554c76260bb098e4d0884884e"],["p","58ee1ae59943750475900a48b5a9ba929f07b786004cc0d8703fe59d796968c1"],["p","bf2376e17ba4ec269d10fcc996a4746b451152be9031fa48e74553dde5526bce"],["p","06b0d07a0c1c319da1dc573fd566069e59c705f8216bb9f43cb7b3c885da82eb"],["p","c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0"],["p","9b00d7352b5f42ed1980fc69bfab51c3f415bde617800b44cee235a227934c3b"],["p","6681268ace4748d41a4cfcc1e64006fb935bbc359782b3d9611f64d51c6752d9"],["p","47fca8cf73434bd69ae7b81041867dd9efd3ece1e3eac4f6c3d20f62e6a48eeb"],["p","20d88bae0c38e6407279e6a83350a931e714f0135e013ea4a1b14f936b7fead5"],["p","602712ce1c96299264acf77444fa9f2ce8f2148e1c5ae37b7e922ddcf6bb68db"],["p","e1ed06b418d48648ea288b6ea5c0ca852dfe0144e5f9b639b4475cc50e5d7c44"],["p","7a5745db0eb4dc0b40262e0d73114461002df592b73c88fa6ab58b2e948da0dd"],["p","c1651ae9616f87373ba00af0d97dc46902564e1fc1e5b6c4083383d91e83506b"],["p","f728d9e6e7048358e70930f5ca64b097770d989ccd86854fe618eda9c8a38106"],["p","46b65b1a8787b6420b882e0934ad4db85e207757db8eceb606238378cd1e7197"],["p","ad46db12ee250a108756ab4f0f3007b04d7e699f45eac3ab696077296219d207"],["p","24b3a5d7761fa4abcd66d3d2725509e224eef23adb15f9d6c4535e030423dda2"],["p","a341f45ff9758f570a21b000c17d4e53a3a497c8397f26c0e6d61e5acffc7a98"],["p","b5db1aacc067a056350c4fcaaa0f445c8f2acbb3efc2079c51aaba1f35cd8465"],["p","e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411"],["p","090254801a7e8e5085b02e711622f0dfa1a85503493af246aa42af08f5e4d2df"],["p","92de68b21302fa2137b1cbba7259b8ba967b535a05c6d2b0847d9f35ff3cf56a"],["p","c49d52a573366792b9a6e4851587c28042fb24fa5625c6d67b8c95c8751aca15"],["p","4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0"],["p","5b0e8da6fdfba663038690b37d216d8345a623cc33e111afd0f738ed7792bc54"],["p","1306edd66f1da374adc417cf884bbcff57c6399656236c1f872ee10403c01b2d"],["p","be1d89794bf92de5dd64c1e60f6a2c70c140abac9932418fee30c5c637fe9479"],["p","e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42"],["p","34d2f5274f1958fcd2cb2463dabeaddf8a21f84ace4241da888023bf05cc8095"],["p","169916bf398cad4533a602b9930170f2abebf67fb8469bf19929f4edbbcdbbf6"],["p","311b497635856767ff5c1cefa2b8c5c875ce184ae4876da9279e829ba01dd129"],["p","883fea4c071fda4406d2b66be21cb1edaf45a3e058050d6201ecf1d3596bbc39"],["p","7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805"],["p","83e818dfbeccea56b0f551576b3fd39a7a50e1d8159343500368fa085ccd964b"],["p","d9db47840379c206ebc3d164ea758efae18a6dd756a079b718aafdc699056611"],["p","99894d7779521334cb49913e23381e196a1bb10e5be3eded8e1e9e0803fd866d"],["p","27797bd4e5ee52db0a197668c92b9a3e7e237e1f9fa73a10c38d731c294cfc9a"],["p","b0a7265070ed2f660777145c3fb84b69eceefbbd7bfa97731c314ed587ed1307"],["p","9348d77597fdfd430581a70b6fa176c06d01ea5f518760fa5df3e64b7f3c3b0b"],["p","85080d3bad70ccdcd7f74c29a44f55bb85cbcd3dd0cbb957da1d215bdb931204"],["p","c037a6897df86bfd4df5496ca7e2318992b4766897fb18fbd1d347a4f4459f5e"],["p","efc83f01c8fb309df2c8866b8c7924cc8b6f0580afdde1d6e16e2b6107c2862c"],["p","1a4a8f79cae0979ea385c58ab9f954fdaccd6f245e6fa4750a79eb7510ae754e"],["p","b83a28b7e4e5d20bd960c5faeb6625f95529166b8bdb045d42634a2f35919450"],["p","31942ee52c0609a6c625a52f9d7e29b1f36dbecac266cfb26ef9bb686cc99cc5"],["p","47f7163bed3bdb80dc8b514693293d588710607018855cb5a53f4bb6ddba8377"],["p","dedf91f5c5eee3f3864eec34b28fc99c6a8cc44b250888ccf4d0d8d854f48d54"],["p","18730a5ef6d6cdc9d1ad5d2d9c193b3922668df7a49ddfc55bc6b66ea4753dcd"],["p","2eab634b27a78107c98599a982849b4f71c605316c8f4994861f83dc565df5c8"],["p","51375471a553d9048f8d5f324d1b052f7bb41979da50fa6a08321a7a53858b02"],["p","6849675eb1938e3e45abae5db145d6aadb2b045b43ec52bf1b851f243bbf2743"],["p","6c237d8b3b120251c38c230c06d9e48f0d3017657c5b65c8c36112eb15c52aeb"],["p","9c9ecd7c8a8c3144ae48bf425b6592c8e53c385fd83376d4ffb7f6ac1a17bfab"],["p","bdbe1bdbc9b25a8d89d8fdaf0be1a0dcd837bac9691f597892903a5fdd86e27f"],["p","8c7c631279785d45090d29ea60020a078170057e0def3f183a5948babf4c1b33"],["p","5de8858dcb693cccc8692157d63bb952ebbc18b7a99e97684a4ea64a45cd1676"],["p","aa55a479ad6934d0fd78f3dbd88515cd1ca0d7a110812e711380d59df7598935"],["p","edcd20558f17d99327d841e4582f9b006331ac4010806efa020ef0d40078e6da"],["p","c48b5cced5ada74db078df6b00fa53fc1139d73bf0ed16de325d52220211dbd5"],["p","e8c73171ccb039e66fb25af2061371a0fbd811a1d38d34e2ceef1132d93b97cf"],["p","a9b9525992a486aa16b3c1d3f9d3604bca08f3c15b712d70711b9aecd8c3dc44"],["p","6f35047caf7432fc0ab54a28fed6c82e7b58230bf98302bf18350ff71e10430a"],["p","246ca9df2cea8a42e647ea9597fb21e18737d46c4114773214a929e9799031b8"],["p","0497384b57b43c107a778870462901bf68e0e8583b32e2816563543c059784a4"],["p","021d7ef7aafc034a8fefba4de07622d78fd369df1e5f9dd7d41dc2cffa74ae02"],["p","1afe0c74e3d7784eba93a5e3fa554a6eeb01928d12739ae8ba4832786808e36d"],["p","29fbc05acee671fb579182ca33b0e41b455bb1f9564b90a3d8f2f39dee3f2779"],["p","f87653e8abe5be8790ff38a89108518380ba0cdc079b2a16dbcc97406a3019ff"],["p","b154080cb49639bb079a6a53c1d98e7130eeab3c61aa95dd9e38f9e400027cc7"],["p","5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e"],["p","47b38f4d3721390d5b6bef78dae3f3e3888ecdbf1844fbb33b88721d366d5c88"],["p","b66be78da89991544a05c3a2b63da1d15eefe8e9a1bb6a4369f8616865bd6b7c"],["p","1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b"],["p","c161a5ba8ddb3bcca6cb59ee184305d9387fe91fd8fcc5efbbf600336858fb1d"],["p","dc4d0dded8db14f723a472739fed86cb9d56ebcd3c30ba619f4a449ec7f63079"],["p","020f2d21ae09bf35fcdfb65decf1478b846f5f728ab30c5eaabcd6d081a81c3e"],["p","8967f290cc7749fd3d232fb7110c05db746a31fce0635aeec4e111ad8bfc810d"],["p","885bfc2076f182973b045024459552332f6747772d95e1320f93126ebf1739c5"],["p","1439abd42981165eacccd046bd565aad19f2314a93ad9bd09ad83e3342bec99f"],["p","c2622c916d9b90e10a81b2ba67b19bdfc5d6be26c25756d1f990d3785ce1361b"],["p","95361a2b42a26c22bac3b6b6ba4c5cac4d36906eb0cfb98268681c45a301c518"],["p","1b11ed41e815234599a52050a6a40c79bdd3bfa3d65e5d4a2c8d626698835d6d"],["p","e4c2fc0097894052668570909f37ae359488a7177f6a7f4b196cb7c11cbc552d"],["p","e76450df94f84c1c0b71677a45d75b7918f0b786113c2d038e6ab8841b99f276"],["p","98315132d6ab8cfe404f3a8046b8336d545f1494b163b6ee6a6391c5aec248c9"],["p","04918dfc36c93e7db6cc0d60f37e1522f1c36b64d3f4b424c532d7c595febbc5"],["p","a3eb29554bd27fca7f53f66272e4bb59d066f2f31708cf341540cb4729fbd841"],["p","482754a056b1e7b51a2e6e9ef99634c1578f19082caf6a353d7db8c1a190233d"],["p","a1808558470389142e297d4729e081ab8bdff1ab50d0ebe22ffa78958f7a6ab7"],["p","62fe02416353e9ac019c21f99b8288f53d1d29ea2d860653a67690d747d6e4ec"],["p","7688053a111e466966111d8498368e817dd548da2fca3e2d941e76e43442d338"],["p","23177fe189f2054f62b055b225f8fee5d0d4ad3ef82bf2b19d5b2ce446121827"],["p","cd868bf553cabfeef5ff89f9d34875210cdeda276ca4c6a3e7ffb7a31ab96447"],["p","eaf27aa104833bcd16f671488b01d65f6da30163b5848aea99677cc947dd00aa"],["p","58c741aa630c2da35a56a77c1d05381908bd10504fdd2d8b43f725efa6d23196"],["p","f7bdef7bcb088a65370c67dd1f4116a23614a726fbf917a252461b42ac855307"],["p","182fbb5f031ff231ee32071f1873e69ddc1daced03a15c5c863bf40b5b451f57"],["p","572aa88414c6b1443082fe6d6a70b0a5a132355e2677c6723f2a0200e266a569"],["p","171ddd43dab1af0d1fb14029287152a4c89296890e0607cf5e7ba73c73fdf1a5"],["p","9989500413fb756d8437912cc32be0730dbe1bfc6b5d2eef759e1456c239f905"],["p","c5e99670da6e4364cf28440872685dbe11fe82c9e63d2851b6cbb6eaac4c611d"],["p","d4843f4c280abba3d43d84ed7924b2567d7c166f5e72985b9f06d355601b5d78"],["p","f2c96c97f6419a538f84cf3fa72e2194605e1848096e6e5170cce5b76799d400"],["p","2779f3d9f42c7dee17f0e6bcdcf89a8f9d592d19e3b1bbd27ef1cffd1a7f98d1"],["p","4564d670cc2b516c0173a27814abe5d8ca60abc8f883ac82b47b5c980877484b"],["p","dbe0605a9c73172bad7523a327b236d55ea4b634e80e78a9013db791f8fd5b2c"],["p","dcdd29d5f71beaa1c362e6aa384dd073fdcca1155caccd29f82d43acf8be7598"],["p","a5e93aef8e820cbc7ab7b6205f854b87aed4b48c5f6b30fbbeba5c99e40dcf3f"],["p","8b0a2beaf6ebef925e8e78f8f0ada41f7898b8da72a8971b89988bf7857d369f"],["p","e41e883f1ef62485a074c1a1fa1d0a092a5d678ad49bedc2f955ab5e305ba94e"],["p","32a69aeda5455e5f592c45faf01bc29eb031c30da580b6d84bad537cfd507cd0"],["p","aaa799c0c8ce5a63c0ac9f237e8186e748d27c2e23077ec5eb8c090b440c1707"],["p","9168772564e66c07a776a3e2849b02d1a0ac88a7f8e621600c54493ca0de48ea"],["p","ce41c1698a8c042218bc586f0b9ec8d5bffa3dcbcea09bd59db9d0d92c3fc0b4"],["p","1833ee04459feb2ca4ae690d5f31269ad488c69e5fe903a42b532c677c4a8170"],["p","50a25300cc08675d90d834475405a7f16668c0f2f1c2238b2ce9fc43d13b6646"],["p","2f4fa408d85b962d1fe717daae148a4c98424ab2e10c7dd11927e101ed3257b2"],["p","971615b70ad9ec896f8d5ba0f2d01652f1dfe5f9ced81ac9469ca7facefad68b"],["p","c1e7fc21b4f9c199e6086e095639f0f16a4e4884544547ce8a653ed7b5b6c4a7"],["p","000000001c5c45196786e79f83d21fe801549fdc98e2c26f96dcef068a5dbcd7"],["p","d8bcfacfcd875d196251b0e9fcd6932f960e22e45d3e6cc48c898917aa97645b"],["p","18e63deafbc5b455559a430368764d672212f7dc4b4b01a7e6dc5594ba9db85f"],["p","6414874c1db3df7edd58bd66c51abc8bb01baabc9405c8c48e589578c5c6d4a3"],["p","6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93"],["p","9349d012686caab46f6bfefd2f4c361c52e14b1cde1cd027476e0ae6d3e98946"],["p","330fb1431ff9d8c250706bbcdc016d5495a3f744e047a408173e92ae7ee42dac"],["p","15e91efac36e0dc30139a351a1a0930276526b2b49eede15ee3d3bbc38a087de"],["p","4adb4ff2dc72bbf1f6da19fc109008a25013c837cf712016972fad015b19513f"],["p","e20f8a383ac5e15366101c1608ee4f33fa8b2d79250ceb2b5a8abaa4394a6e7c"],["p","e75692ec71174e698df1f3d1f5771855bcc4e6e568489d2eaad489d81064ace6"],["p","fda0d1933d7e3f4120e4aeb9a27f96db2f28cc2724ef15a2c504866e45f68d39"],["p","d699260e979c481d95e11456c06a1a78469cb7bed8c3425124ca6a963f0e3095"],["p","5c89facf55a1e2b44d4697e098b34d6632ff666d9eb819ca8b25ac2fbcb74ddb"],["p","decaf1c5361563a0d6485db00692bc667e8344c3e6b3255556599e5d27fbdde5"],["p","5f498ff809e02c5685e3bda193fcd7147a22e7b3971079549b0bb37643f3cacc"],["p","826e9f895b81ab41a4522268b249e68d02ca81608def562a493cee35ffc5c759"],["p","338ef72e3deebda385aedea5e89b87ec35a7d296d4a9b642bb2c1ad926007db7"],["p","15973e4d0b61e82a711df3b37fda2e84059e065f1a1b666a8a9ce1fe6fbe6786"],["p","dee3ff75c49087c3ab9cf50844cea6f7db305089c399a91eedbea8d50e80174d"],["p","baa529237d2f5b19c3ae06064d57fcf6bbc2f6cbe84c1e89f57bb146454f8d40"],["p","af70b1b1c0b5d28f24a13dd31bfb3da0e8f2e0e1b7d76f90c467e964fde74078"],["p","05933d8782d155d10cf8a06f37962f329855188063903d332714fbd881bac46e"],["p","fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"],["p","d0a1ffb8761b974cec4a3be8cbcb2e96a7090dcf465ffeac839aa4ca20c9a59e"],["p","63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"],["p","218238431393959d6c8617a3bd899303a96609b44a644e973891038a7de8622d"],["p","ade7a0c6acca095c5b36f88f20163bccda4d97b071c4acc8fe329dc724eec8fb"],["p","8366029071b385def2e4fb964d2d73e6f4246131ac1ff7608bbcb1971c5081d2"],["p","25de661e94f8ca6098ae6cacc35fd1634c091d7c1ac2924e935fbc5eb2c6ffec"],["p","738ea36ef74b2ac80bfb3887b40637c7dcdf74ea6eed73c718b7193313b90f9b"],["p","e4f695f05bb05b231255ccce3d471b8d79c64a65bccc014662d27f0f7e921092"],["p","de7ecd1e2976a6adb2ffa5f4db81a7d812c8bb6698aa00dcf1e76adb55efd645"],["p","3335d373e6c1b5bc669b4b1220c08728ea8ce622e5a7cfeeb4c0001d91ded1de"],["p","1989034e56b8f606c724f45a12ce84a11841621aaf7182a1f6564380b9c4276b"],["p","045a6fa0da5d278ac1c3aee79df23b7372ea03ee4da04ad4b8db9a5967f32334"],["p","7fb2a29bd1a41d9a8ca43a19a7dcf3a8522f1bc09b4086253539190e9c29c51a"],["p","27c4d775bedfaf861452eb366e5db3d9957eb2d4a226cd8856dd5e83760abcae"],["p","f8e480f4bb537facc1bdbd020f6defa136ebd51f057b95fb4050be2524f1cc13"],["p","6871d8df0d425a2b07ecdc30a3b53ffaef14d9ad2573fc1542694d654a9396c1"],["p","5954fa9ae6d133f9e9fe3630daad55ef0c24388ed461486a5ca03f7bda31c933"],["p","961044f97885d36c54eeabd0a847da2ddfc778bb99bd1bc02ec05a823ea51ee9"],["p","3356de61b39647931ce8b2140b2bab837e0810c0ef515bbe92de0248040b8bdd"],["p","7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194"],["p","2a2c0f22aac6fe3b557e5354d643598b2635a82ccd63c342d541fa571456b2da"],["p","6827ef2b75ee652dcc83958b83aea0bc6580705b56041a9ee70a4178e1046cdb"],["p","eaf1a13a032ce649bc60f290a000531c4d525f6a7a28f74326972c4438682f56"],["p","e8ed3798c6ffebffa08501ac39e271662bfd160f688f94c45d692d8767dd345a"],["p","c9b19ffcd43e6a5f23b3d27106ce19e4ad2df89ba1031dd4617f1b591e108965"],["p","9d065f84c0cba7b0ef86f5d2d155e6ce01178a8a33e194f9999b7497b1b2201b"],["p","f1b911af1c7a56073e3b83ba7eaa681467040e0fbbdd265445aa80e65c274c22"],["p","b88c7f007bbf3bc2fcaeff9e513f186bab33782c0baa6a6cc12add78b9110ba3"],["p","3fc5f8553abd753ac47967c4c468cfd08e8cb9dee71b79e12d5adab205bc04d3"],["p","e7424ad457e512fdf4764a56bf6d428a06a13a1006af1fb8e0fe32f6d03265c7"],["p","8685ebef665338dd6931e2ccdf3c19d9f0e5a1067c918f22e7081c2558f8faf8"],["p","91c9a5e1a9744114c6fe2d61ae4de82629eaaa0fb52f48288093c7e7e036f832"],["p","9c163c7351f8832b08b56cbb2e095960d1c5060dd6b0e461e813f0f07459119e"],["p","0000000025a7ccbf6bd0c0a5a1856d78f2e9f08c778b779d35e81b5ea3f77edf"],["p","84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240"],["p","3d2e51508699f98f0f2bdbe7a45b673c687fe6420f466dc296d90b908d51d594"],["p","eda6845cc2269bea10f010744ad79409acb7129d96857d4bf19e027696299292"],["p","064de2497ce621aee2a5b4b926a08b1ca01bce9da85b0c714e883e119375140c"],["p","c571b73bb05a98a3062b838434aea45b07dae15ee657c4f333de814473a7c61a"],["p","a44dbc9aaa357176a7d4f5c3106846ea096b66de0b50ee39aff54baab6c4bf4b"],["p","eab0e756d32b80bcd464f3d844b8040303075a13eabc3599a762c9ac7ab91f4f"],["p","3292075675a330c63f4f278dda4609da959872c30c04301569190ae9761f314e"],["p","d307643547703537dfdef811c3dea96f1f9e84c8249e200353425924a9908cf8"],["p","c7dccba4fe4426a7b1ea239a5637ba40fab9862c8c86b3330fe65e9f667435f6"],["p","520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626"],["p","148d1366a5e4672b1321adf00321778f86a2371a4bdbe99133f28df0b3d32fa1"],["p","bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["p","e771af0b05c8e95fcdf6feb3500544d2fb1ccd384788e9f490bb3ee28e8ed66f"],["p","59fbee7369df7713dbbfa9bbdb0892c62eba929232615c6ff2787da384cb770f"],["p","8766a54ef9a170b3860bc66fd655abb24b5fda75d7d7ff362f44442fbdeb47b9"],["p","16b8676587c1ddde60b23b27205112a4d5f0ce7bd0414f67476d5eea1502af36"],["p","4408c00c1e9358c05ad11dbe2d3a1d4acb8c2b6dd6386e4e3da9e14bb06df42b"],["p","35b23cd02d2d75e55cee38fdee26bc82f1d15d3c9580800b04b0da2edb7517ea"],["p","b9ceaeeb4178a549e8b0570f348b2caa4bef8933fe3323d45e3875c01919a2c2"],["p","e08b8d662bc4dbf60f257786cc49b3afaadfe340233235fe7f661829a4631e80"],["p","9579444852221038dcba34512257b66a1c6e5bdb4339b6794826d4024b3e4ce9"],["p","e0f59d89047b868a188c5efd6b93dd8c16b65643b8718884dad8542386c60ddd"],["p","387519cafd325668ecffe59577f37238638da4cf2d985b82f932fc81d33da1e8"],["p","b7996c183e036df27802945b80bbdc8b0bf5971b6621a86bf3569c332117f07d"],["p","2bda4f03446bc1c6ff00594e350a1e7ec57fb9cdc4a7b52694223d56ce0599e9"],["p","597b42de56a9e0c19ee2d0cde5797dd58d48ce8dd25c732b4c873af11161f9fd"],["p","a3b0ce5d70d0db22885706b2b1f144c6864a7e4828acff3f8f01ca6b3f54ad15"],["p","a79aeaccc6b3c25f33c0c02e183627c6d459a285423d104ac7f435e15d7b62d2"],["p","dbe6f9f78b0cecf94d7b41cb644b984b8881c021c0525d479ba6416966e07661"],["p","4ffb87a974bbb52fcac737b79c295c047d91aced8923b0b858df7cad2281157f"],["p","b601a97815bc7cfa8b346f8eebf447842e6711df7107c81cf3a379f044cbf9a9"],["p","3004d45a0ab6352c61a62586a57c50f11591416c29db1143367a4f0623b491ca"],["p","974d0f476f175adf26ceddb29f460368536343561b9b76582fe9859483f01878"],["p","a7fa4b91d1913a262d1c0b19991b80104d471e139f42464138fb900ddc495cb0"],["p","ae8111c3a6cd81fc2aa45146894803bb66fa0d28446c0361cdeb00036b15249b"],["p","2888961a564e080dfe35ad8fc6517b920d2fcd2b7830c73f7c3f9f2abae90ea9"],["p","737154efe5377075346566977c6496df88213b85e6253acf485b897b58842616"],["p","84de35e2584d2b144aae823c9ed0b0f3deda09648530b93d1a2a146d1dea9864"],["p","ab2bb23ddda009492ffc89bda608b3d09173e97d46f2be6b3d40678822de4e30"],["p","1e4c4fafe7b6627283e6061fe7f862f793ce004d835ba2b3e29e7a2504ac9dba"],["p","0a722ca20e1ccff0adfdc8c2abb097957f0e0bf32db18c4281f031756d50eb8d"],["p","bad9867380f6f2b8f50d3ff869aaf75dc998797204a7c85a4bf6f8bb9fc07078"],["p","7560e065bdfe91872a336b4b15dacd2445257f429364c10efc38e6e7d8ffc1ff"],["p","36f7bc3a3f40b11095f546a86b11ff1babc7ca7111c8498d6b6950cfc7663694"],["p","bc9c5405dc6eef2e4081db9aa050e4e2adc655de5df5ebc4d4242c47d453bd35"],["p","ec99edc5567e02815fb15020285e2fa8390931cedf59c83d6bb2c5f6ee1530b9"],["p","b2722dd1e13ff9b82ff2f432186019045fee39911d5652d6b4263562061af908"],["p","9e1e498420bc7c35f3e3a78d20045b4c8343986dae48739759bccb2a27e88c53"],["p","ea2e3c814d08a378f8a5b8faecb2884d05855975c5ca4b5c25e2d6f936286f14"],["p","094ed88c9671bf1e8dd26501bf8a12ed013ad34c0deb35f0e607a89eff43e366"],["p","eaf41f497cf085ab9f2c435ee4c4a512c1f42e085bfd6f7584d450d6c2811850"],["p","4df7b43b3a4db4b99e3dbad6bd0f84226726efd63ae7e027f91acbd91b4dba48"],["p","74dcec31fd3b8cfd960bc5a35ecbeeb8b9cee8eb81f6e8da4c8067553709248d"],["p","b708f7392f588406212c3882e7b3bc0d9b08d62f95fa170d099127ece2770e5e"],["p","81db8ce1438a899dcdf1d2c207074efc8aebdb1026efca7c3105d896901b5b16"],["p","875705d034963b8487a8246468612a854ed2b5af66be936e1e48c25ce96a750d"],["p","58dece9ff68d021afe76d549b9e48e6cb7b06a5c14cdf45332c8ed7321d6f211"],["p","d68af8d1bdcef7162c8ff0b33078ac0c49721f5d0f33687643241b1eabae60c1"],["p","8a044409cee04f124a49db9411bc183519573f1beb31c82980867d1232125ee7"],["p","ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49"],["p","69a0a0910b49a1dbfbc4e4f10df22b5806af5403a228267638f2e908c968228d"],["p","b72022ca648be35220e9dc545580bad787e81cd301ab58d0ab26fc63f496a0af"],["p","54a43756097aae2bf19009747c03ce9a707f842f94931d6daf931d14b4fcda50"],["p","679ba2f9608cee6e5d7e9f7cca3b6cd55352c1ba5a8d7f5f30a89ccd4ff72f41"],["p","5c508c34f58866ec7341aaf10cc1af52e9232bb9f859c8103ca5ecf2aa93bf78"],["p","9ba8c688f091ca48de2b0f9bc998e3bc36a0092149f9201767da592849777f1c"],["p","c93406ed82c231019cf1d96700884fdedf1f7d5a32fa368b10b260cc6918f4a1"],["p","6f2347c6fc4cbcc26d66e74247abadd4151592277b3048331f52aa3a5c244af9"],["p","ca4ef0d885f25c5b1aa6d424f591b1b7a7d37af1cf58eaaa21a6e0d584e65287"],["p","56a6b75373c8f7b93c53bcae86d8ffbaba9f2a1b38122054fcdb7f3bf645b727"],["p","ee32690bd984faff7b7496e3239da93d36d541efddaa7e5035451c4236be52c4"],["p","7b3f7803750746f455413a221f80965eecb69ef308f2ead1da89cc2c8912e968"],["p","307e1c3e3d90dbf197297832445ba5405fe0243b4c0c9e627f0069d7a76a9422"],["p","82aa6958505fd4a7ecedf8df4009291044ee6f1c8c4a8c39e1099d69c94d0851"],["p","71e82481364ae0ced88b237a79cdcd3ee89bdbdeb288259c2d1eb9b8563603ff"],["p","63ee602bdb417251e180ca2189e6df10902ca64e16f9b16d3a8bb83fd0cad077"],["p","79224236c9b2d22281ad5b5b4ccb94bc983f4628a029ced8e9f7c0afafb28b8a"],["p","0114bb11dd8eb89bfb40669509b2a5a473d27126e27acae58257f2fd7cd95776"],["p","dd363393ce4331c4f5958dd0dec7da3436f5d003c8f838bf6a8e6da71d1b99ce"],["p","a903b72b9566a0425d48030421587e2e78e50807f6b3524243f2223371eaf903"],["p","5133a0947abe801445c34e2f56151d8690f4789ab4c763275b14166e74c5830f"],["p","c83723d33fa86c8f01b254b1dcaaa025b2ca659320950d044d22c41b5d1daf29"],["p","c5fb6ecc876e0458e3eca9918e370cbcd376901c58460512fe537a46e58c38bb"],["p","aef0d6b212827f3ba1de6189613e6d4824f181f567b1205273c16895fdaf0b23"],["p","f7380c11f6a40c7681d2661ef601b9305991141b6bf45ad70585da5cd5c75df4"],["p","72936ba9f4f21f1563e2e5001aade6cce3acc162a4d99a823d231dc641b9e3b4"],["p","07eced8b63b883cedbd8520bdb3303bf9c2b37c2c7921ca5c59f64e0f79ad2a6"],["p","096a1d36a17d77792d57686d7bb2df2c59e97baded5c5f8972791e91215b2d0b"],["p","f68c1fc5bf7f5125f992ce30ed32881fcc749777c7a9de89cc601326029df9f9"],["p","1c31ccda2709fc6cf5db0a0b0873613e25646c4a944779dfb5e8d6cbbcd2ee1c"],["p","3e6d6eea7129430b4852e418de4319dd3052b72e42a0ecc79a3b1b8c9558d991"],["p","adfe27560472d5168c79e82071f12f7b6039cd94ba664e54a855c37c80f1d737"],["p","e1055729d51e037b3c14e8c56e2c79c22183385d94aadb32e5dc88092cd0fef4"],["p","3d842afecd5e293f28b6627933704a3fb8ce153aa91d790ab11f6a752d44a42d"],["p","baf27a4cc4da49913e7fdecc951fd3b971c9279959af62b02b761a043c33384c"],["p","c7617e84337c611c7d5f941b35b1ec51f2ae6e9f41aac9616092d510e1c295e0"],["p","c20b4972b72b1d40cc6b519a4c2d039581c73a1a9d5e642ddb9142b64ea28c23"],["p","0000006a13e10fb648049b5e78632a0c2bf09eaf6a9d55d081b82baf86c951be"],["p","6a6dd705c4dfe4bae00ed62d9bb80ce94eb1075ef7444e3a0aa5acf6abb929a7"],["p","3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"],["p","473e67022c1db877b27d39a81aa35d3a00faf1eb1e664840674544561ddc8f76"],["p","269289d6a004d7180a00672a6b7d3baefd042bee55935067e706dcd500fea7df"],["p","9a39bf837c868d61ed8cce6a4c7a0eb96f5e5bcc082ad6afdd5496cb614a23fb"],["p","c9c7b0ec19af3bae3197cd1d61e24341bd68273787008aa0c8cf71ee5c24b46c"],["p","045745ac0e90a436141a3addd95575c2ead47b613f45287283e5802ff7fd99fd"],["p","4e7e4e3e3bb9ccd808c557b0462089290278a72427115a3e9de9f1bed17a4158"],["p","2250f69694c2a43929e77e5de0f6a61ae5e37a1ee6d6a3baef1706ed9901248b"],["p","b945e8537bfd2ca3d36acc393e6ce948ad08471a44e5bc2f7eb1409cf5046619"],["p","f37b511f945a08813b945317ba5537c4250f790d6090020eded52fd7939c8ea3"],["p","bbd2da1b871a6ee6dad21f8f0836fbf0db2224bebde40197b8733dee19fc624e"],["p","104a9e01bfa9fd7d89920636bf25bb28f1fa5ee4a12201fad462fb79c9b5b2e9"],["p","040d2c77769e68440da407d6a524f7ee51b5f31cc97617464cc1b67066ba6bd2"],["p","e8795f9f4821f63116572ed4998924c6f0e01682945bf7a3d9d6132f1c7dace7"],["p","f9a9b8fa74e42bea1b6d2da13242018f7a8ddeb0a97dd45dc4b53617dc55c6e5"],["p","0b118e40d6f3dfabb17f21a94a647701f140d8b063a9e84fe6e483644edc09cb"],["p","af551accea482000bdccb34bd3c521558e1f353773a3caed83a147921c369ea1"],["p","cbb2f023b6aa09626d51d2f4ea99fa9138ea80ec7d5ffdce9feef8dcd6352031"],["p","84580515242fc91b6bec5988b6f43e46f05c2de55612e0ec41cecdb4a2059f18"],["p","cb8c024ba2c439831b3094e52116ffdb61fa0cf804135b6ee1e8338709617fa3"],["p","b99dbca0184a32ce55904cb267b22e434823c97f418f36daf5d2dff0dd7b5c27"],["p","1dc5587be56fbc4d4bd5534b2672429913a6ec99a42c64fd309ccc3f3a2e4d6b"],["p","1e56c1cd4dbcdc520af1e0bcea259ffd06b5fcd08033cecac10afa1e6623655d"],["p","111d96ee6d34878d95afbfe50b1f80e81256d766276b8a204de346185256242e"],["p","c89cf36deea286da912d4145f7140c73495d77e2cfedfb652158daa7c771f2f8"],["p","ddff07845a831e9c5e08cad7571e484268926c220013f5bbab12ed5bcbe0ea05"],["p","90b9bec74789688e515125596ab6350bfe646176ac75742275063922c5fea010"],["p","470be0e81485a5ff4d430dab3c7b26c5c74fa5223370a63d7710907a619c49d7"],["p","3f1f611ebf95162da08e73795ddfbd3a2ebfe5b3edc0495356032af3fd251aa2"],["p","36c24dafa66fc420000bb3c1b5380eee010b642316a65e6bf8a5aba30edd3ce5"],["p","48244ab49f7f48cf6a469fec4ffd0c2f078d65d212723e454dd8ae8e67608090"],["p","79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6"],["p","f3f45fdd6ecabc5e6dd6c8dfa2ff018c7a06a32ef3d73a00f8e2bacdfe303060"],["p","a3505d470b88d5b1202ddced532d8469d599972052dec2a920611993ca5a0e60"],["p","5a6440553acacb4f820127802f1ca1b0a66e70783ad70a9f7ba81c392107e5d5"],["p","25a2192dcf34c3be326988b5c9f942aa96789899d15b59412602854a8723e9e8"],["p","098894e6fb0b0c3a5a62585bed8806909ae9bb9458a246220bed0a723296a5ca"],["p","84d535055542132100ea22e96e33349844422e6e698cc98bd8fb5eae08d76752"],["p","a4dbfdc6e7e27e33b04e8009cf15dd1df35d62a9b258e70c38166871a577c47a"],["p","26bd32c67232bdf16d05e763ec67d883015eb99fd1269025224c20c6cfdb0158"],["p","efa6abd09142caf23dfb70ed3b9bd549042901caa66f686259a1cc55a4970369"],["p","dbf3d7c79a92995ccfb135997ac1612f41637c8a805be393204b3d1c2769d127"],["p","d1b13fd9d860fb0fe4ba2082023804a5c04b72a78fdd63d63d78fcf44363a8cb"],["p","9279276bffb83cee33946e564c3600a32840269a8206d01ddf40c6432baa0bcb"],["p","73923fd4c8d2a590fcadb3feb691cd6a80915872e947093993d1ff10452b3614"],["p","95a163f3d0d1ae9dc995032807ffbb94788c8b131a644c6c92dd3db56df0a379"],["p","9e93fb0012a6177faddf2fd324fb61eafbe8b142b31c5e89fd85bfafd12483b6"],["p","6fda5bce2882176bfbabfab503a1b5281329582d71c4be84bbb567e65c1a791f"],["p","958b754a1d3de5b5eca0fe31d2d555f451325f8498a83da1997b7fcd5c39e88c"],["p","5db3341ed17a539af4d33af843d3121737541dd4c8a2e03a9c812eed51479791"],["p","f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9"],["p","17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4"],["p","bfc058c9abb250a2f4f0f240210ae750221b614f19b9872ea8cdf59a69d68914"],["p","66bd8fed3590f2299ef0128f58d67879289e6a99a660e83ead94feab7606fd17"],["p","3a66ab5ba2e12bf067430029c62baca27b25b53dc4c9e4c2ccd4289faee5b65f"],["p","1c4490bf32cefa822127fad5ed42b60ffdff3d154821d77ddeaab665f67624da"],["p","b7b1382ea9bd7b2420eed4db8d8333aa664c7bb7bee2208554a31a6074635e6c"],["p","8f124a840f7d480bf28866c6de8b8eb68ae525ed674f851bb2d15b0ac2425d8c"],["p","4657dfe8965be8980a93072bcfb5e59a65124406db0f819215ee78ba47934b3e"],["p","12377ea24f32869fe24dfcacf581b33a1ab53bb279292b765413a2a07333e755"],["p","fe7f6bc6f7338b76bbf80db402ade65953e20b2f23e66e898204b63cc42539a3"],["p","d3c1f02c6c8766aaf762bbf4c1d00d204461b65f68fc1b67c42203a9ba36ad05"],["p","41eeaea1075c8ea7f041e0370f96cdc4b03ae84b73c2e5925bc6878d0d9532e7"],["p","f47d575f2c441f579acda9d032950f36c266961302e9e6c12c585f2c496724b2"],["p","ae60eb949586948b516c8221ed7500e3b4f134ae0806e3fdffdeacf96b358117"],["p","874a2f71ce6541dc93307518b1ec31cc9773fa6dcb1f8d00f2b8f2f8446bc4a7"],["p","47223dd1b3f1d917620b8a3df72f4986e49a06568eab8f1180062393d34d488a"],["p","a530f7f75b62f86073078fd0d82302508c9273ec846d19766ef41417eb820644"],["p","5a8e581f16a012e24d2a640152ad562058cb065e1df28e907c1bfa82c150c8ba"],["p","391819e2f2f13b90cac7209419eb574ef7c0d1f4e81867fc24c47a3ce5e8a248"],["p","f45c21c24f0bff1e77e3dc628b7db0f0f86e8daec6770245e23ecbae1838145d"],["p","0f22c06eac1002684efcc68f568540e8342d1609d508bcd4312c038e6194f8b6"],["p","234c45ff85a31c19bf7108a747fa7be9cd4af95c7d621e07080ca2d663bb47d2"],["p","1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef"],["p","5144fe88ff4253c6408ee89ce7fae6f501d84599bc5bd14014d08e489587d5af"],["p","649eefe468ddb107c05eba6d0511d2a5298540fe4d5f0072b00636008fc72f92"],["p","c6b554646377f111ff7a9cf7e8f30ab488d7a7c2ee7ff85cc44b47fe357bc26a"],["p","53a8392e971b46326e3d0f8967db17c4f7cca4d42be979b1664124c8f69af528"],["p","43658ae91382bee7dfa3c7c360b13a5ec8c222635f2b2aad3de75e4bb20da906"],["p","f1479c160e576934586a7424195dc155a04448d3d71d4090adec95915dd1eba9"],["p","75f457569d7027f819de92e8bb13795c0febe9750dc3fb1b5c42aeb502d0841d"],["p","94e268f4aca4cc14613e1a6d50dab40882b9f08a31d7f6ba81604429b1bbba0e"],["p","d7a7476b1253a1902f765685ffe3d351f8c2e2ac728f655aeb53f4c9a2f9a77d"],["p","1d4cdded7bfef0968a48cdb72b1c6a8311c0ae60a86323ca79263bec80994472"],["p","20d154d9be251e5c66698e51ad0c739aa094ffcb97a9995a4ebec8c3d950895d"],["p","af73cc2f70c4440d9e7e0c531840c873be0639fa5964a1bc568e29e522bf4513"],["p","17b209d34f8fd7c30fde779eb8c3b0c84f724d021ebe6007a5ba70093b2576da"],["p","c3426c7ac9fc92f7bf35ba2b0688962e4ac754fb6b046d2634f88612ee5d5dad"],["p","45b5c4ca911630ff356760e6e743e38881050a2539de71151d73c3f50a7bee06"],["p","a305cc8926861bdde5c71bbb6fd394bb4cea6ef5f5f86402b249fc5ceb0ce220"],["p","0f51985097dcf1bda4dc174a92a4da3a65c7ccd3cb97f4a443e861c4f4d4db1f"],["p","489ac583fc30cfbee0095dd736ec46468faa8b187e311fda6269c4e18284ed0c"],["p","1d60adedb34c051b5b0f032758a6241a45ea42b6cdd916b0a4625ada1f12986d"],["p","4379e76bfa76a80b8db9ea759211d90bb3e67b2202f8880cc4f5ffe2065061ad"],["p","c9bdb692cef1f403336be7e0a79f8436e6fbc325a0d2e8746c4b7342234e27a4"],["p","a80455732d5bfa792f279011a8c871853182971994752b9cf1169611ff91a578"],["p","a3e30369152056c53e9d1d724a8ea5df2a6a8113495ab007bf89e4942b34ce21"],["p","c7d32972e398d4d20cd69b1a8451956cc14a2e9065ad1a8fda185c202698937b"],["p","df173277182f3155d37b330211ba1de4a81500c02d195e964f91be774ec96708"],["p","ef151c7a380f40a75d7d1493ac347b6777a9d9b5fa0aa3cddb47fc78fab69a8b"],["p","b6dcdddf86675287d1a4e8620d92aa905c258d850bf8cc923d39df1edfee5ee7"],["p","6aaed493c0de0cd56a123df8da36474fe254fcfc27549a43e2cb71c57007355e"],["p","44313b79dfc3303e3bd0c4aee0c872e96a84f23a2a45624b3ab630f24f43012f"],["p","6ad08392d1baa3f6ff7a9409e2ac5e5443587265d8b4a581c6067d88ea301584"],["p","dd663577f420383b0477d8117c709099904b45787bfe873ac1d366ee3f4cd75a"],["p","f81d7cbdfe99ff2b11932fb4cdcd94f18e629e3fedafcd25ee0a4ddc0967f0f9"],["p","92cbe5861cfc5213dd89f0a6f6084486f85e6f03cfeb70a13f455938116433b8"],["p","372da077d6353430f343d5853d85311b3fd27018d5a83b8c1b397b92518ec7ac"],["p","7ebc276072383091afa5700a526291e5329ccfdb119548665b92e88e3710c19c"],["p","3801b810302319202a3ded5474ee8fc484a0f56dc182a4e8ee9e30c7c6c14915"],["p","fce95231cd584e791f1f5d977ceac1ef6edb3d3a7a29ada5a657979836cbcb1f"],["p","5fd8c6a375c431729a3b78e2080ffff0a1dc63f52e2a868a801151190a31f955"],["p","82f3b82c7f855340fc1905b20ac50b95d64c700d2b9546507415088e81535425"],["p","134ad90e8553d8b0e1f4c03850fefc5bdf0a71df39a516dd628ba0e3b0375b2a"],["p","67acce177065a0de48d2f7e7aa01d618e8543e8332e7731947f8f94af7855e25"],["p","e40bcc3e12921ec232fe66528e2ba5d5cd4e0688e4bfa083a486a97fdaadceaf"],["p","d376c4df7ee3ac69dcc88bedaee04e545c6ba190d2a710f05fa2c960f6bde9f3"],["p","90f09238f3514f249e2b333e6119eef49697020f956fd7b6732ce118dd1b53cb"],["p","fee697b6486636a037ae730ae40d75c6e2d94f2b87b7679af61105c5f8770703"],["p","4f4cf7bec78b72b679320efcc5114d914155ed0a222ec8a6f5af18aa55ebebe0"],["p","a536ab1f7f3c0133baadbdf472b1ac7ad4b774ed432c1989284193572788bca0"],["p","175f568d77fb0cb7400f0ddd8aed1738cd797532b314ef053a1669d4dba7433a"],["p","481e54980f1ae08f609ca0412072b130f3623e018f7449adf772908f6d1ce20d"],["p","aa1aa6af6be3a2903e2fb18690d7df128a10eec0f3a015157daf371c688b4cff"],["p","6a0d83d316d380ff4902cf36d959b983fde7553a7c257268ef48b04b5a23b11b"],["p","55765e6f376f0f5c26be7e4bfd6e227500ed1e35276e9a7a6be073f4ce99cde7"],["p","0ffb0df6e0193519592e8fdc4e638bd560308c56dd1b3b3f83eea09f24d39020"],["p","a5bf66a4c585e247975da49272d07865b76133e4527656a0c0d5f80b84b4f6a5"],["p","6bbb7d71eaa2544215a877e136cd7f490f4625eb56459a0da856cc8296d5df30"],["p","8888888890493e0c6a6e4a24ae3319a0d7fc595ca3d8e5cae19954e1139008d3"],["p","8047df981a97dd41b48f554ac00e90bd62348fe65384c88ef29032d752857143"],["p","ab2726006d376205469146dffff63781bf1a567b35c56dda475f47383f670c0c"],["p","385eacfa42fc0831b4975983c485e0c7c55ed0f5e4f56d79fa7a7151fb0a06d7"],["p","aeb94d7b1476b54610e38b7fbe64358783ea733a7ef973763339b1d6f17cffb2"],["p","976713246c36db1a4364f917b98633cbe0805d46af880f6b50a505d4eb32ed47"],["p","40b9c85fffeafc1cadf8c30a4e5c88660ff6e4971a0dc723d5ab674b5e61b451"],["p","178d29ef6224d44b3b1264f48e4138f8e1c22e6fcdf6a16d7c27f488887b3b9d"],["p","da0b0d286a21e5ab449694530a2ae481fa242393144681f134bdcf0e88a426b9"],["p","cc76679480a4504b963a3809cba60b458ebf068c62713621dda94b527860447d"],["p","a9b0d19649c37932932d527d1de6f386b6f8616e7b9f3e0a7ccb8228ed62c5fd"],["p","df5b6a8e3b10687a934ff9f92ba8d7240091cfd125d81816c119644c2fb17caf"],["p","91e7934e16be971bbe215e235538253841bb37bfb17e82ee00cfa8c091cf198c"],["p","532d830dffe09c13e75e8b145c825718fc12b0003f61d61e9077721c7fff93cb"],["p","4f260791d78a93d13e09f1965f4ba1e1f96d1fcb812123a26d95737c9d54802b"],["p","2d9873b25bf2dda6141684d44d5eb76af59f167788a58e363ab1671fefee87f2"],["p","1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411"],["p","f9256bff3f5fc549d27d397939e4b1571c3611ac4d750a138e7cbc0406558a55"],["p","922945779f93fd0b3759f1157e3d9fa20f3fd24c4b8f2bcf520cacf649af776d"],["p","805b34f708837dfb3e7f05815ac5760564628b58d5a0ce839ccbb6ef3620fac3"],["p","d61f3bc5b3eb4400efdae6169a5c17cabf3246b514361de939ce4a1a0da6ef4a"],["p","d608b512d657371eef261a25f6a66a0ebf056798ac880c55814aeac67fbc0c80"],["p","b9cfebd0043778453f8cc5ec017f250c46d3056b460fc8c7f8a3b02e9312461f"],["p","6beb9b9791362595b2c39b8102253eae2b1e19a71d03a510104ad25c324a0939"],["p","e03cfe011d81424bb60a12e9eb0cb0c9c688c34712c3794c0752e0718b369ef2"],["p","9be0be0e64d38a29a9cec9a5c8ef5d873c2bfa5362a4b558da5ff69bc3cbb81e"],["p","787338757fc25d65cd929394d5e7713cf43638e8d259e8dcf5c73b834eb851f2"],["p","8c3b267e9db6b0115498cc3efcd187d1474864940ae8ff977826b9d83d205877"],["p","07ecf9838136fe430fac43fa0860dbc62a0aac0729c5a33df1192ce75e330c9f"],["p","ec168fd4e0685481fe7bf1495b60528dc5d55106a8046cbe3263e277f58e12ea"],["p","edf16b1dd61eab353a83af470cc13557029bff6827b4cb9b7fc9bdb632a2b8e6"],["p","27f186204dff66a612f584cc5e1ed20d8f43bd7b56706db19e56c59dc4d962ca"],["p","ccaa58e37c99c85bc5e754028a718bd46485e5d3cb3345691ecab83c755d48cc"],["p","2865a4be78283461ab40cc507de8d98ddd97b741d0b0c4d3d8dc6dd222f26c9f"],["p","2c7ebfed8dd2a7a0a4e7c7fcf950c092242ed1baaab0d7f99e070f043ec12798"],["p","4dc2e570c54fef8313fa304f52974044ed6c128510052600a9b84d837b8126f9"],["p","7bdef7bdebb8721f77927d0e77c66059360fa62371fdf15f3add93923a613229"],["p","358c96619656c7483a477e9830daca2fbf590deee9575e1b667206a4f03f0f8c"],["p","fd094058797ea357c5572d0dbd01bc8f962af25bb8375aeb164e6af429eb6635"],["p","b81f6b275ebd27a8f04ffd05dc16bc9fa329cb8d9c464bc7bdbf5068818e03c0"],["p","813fce4c4e76f1e7b4f4697bf1030a90f1a0b783f187d329800a4dd8697f9759"],["p","de8823cdc979bf4c753223edc19a7abc35ecff2959ef50ca9e9d573ac0f83fd0"],["p","67451d740975fdce8fce70307ca0e93eac885ce3f1d1fcfb378366ef9705381c"],["p","0f52b01a82649e892f9175f8dcc8cd432ac6cd5e0f5d5f8bbde62b1b21263741"],["p","f99c62e39e0f5d737fd96e9a67e8bb46e0e50c60822c53c879e8a1eb3e0f6c07"],["p","e45a0e649b7583610352ee14c946929a074da58fe3d9e38cc8132fe78366af21"],["p","d2e6028f99deb2c76c8ebf34531bef058ef525f1624f74ad96e31cf9fb7b11e3"],["p","a2a3dbf56b7eb956d86ed7d7ecb60aeabc979641fb45e675728e687ec4262ce6"],["p","28c64522edc6f3555c8abc6df7992c354fac4894885900518307b2d4cfb90206"],["p","fff19947841c84c567401f2fe5728b19a76d7cc5d46d0e3cd029df37ff20545e"],["p","14efaa56300e9d9a8d3d1432bb313e7b31a396ed7425676a75eee7f0db6cab98"],["p","33fa37c83657053b4eab0bae11a02c17a3b43a8453c243d63e62268c70963c46"],["p","3ef3be9db1e3f268f84e937ad73c68772a58c6ffcec1d42feeef5f214ad1eaf9"],["p","dec2cf60b0782891ead59f39c006eaf7f20c4590a0d46b0bc3cfc6499a9b0e20"],["p","d3052ca3e3d523b1ec80671eb1bba0517a2f522e195778dc83dd03a8d84a170e"],["p","a6f9769131dc2ae6b7f6d50fbc8c8a27069ab2340e2756e85355f9bbf1adbb41"],["p","2560ec95b699e92e01083b30afdeba44d6f1201358d7bba2877d35d76fd44029"],["p","4d1963c2574215c355ed2357af85b2a2a870ab0d883ab297134764af40d478b8"],["p","88dd3d492446e6df1a8837222e0f5248b39dfcf4733863b9586a97ac9346aacb"],["p","690af9eed15cc3a7439c39b228bf194da134f75d64f40114a41d77bff6a60699"],["p","c1aa0a2f0e1211dd3c46e285d64a411aca4f250bd372a0e85b98f7d6d03c9251"],["p","f4954ee05fc0725683b886c065da9b82966f6c5f2414901f83ab9d3c89172128"],["p","deba271e547767bd6d8eec75eece5615db317a03b07f459134b03e7236005655"],["p","11b2d93b26d7e56fb57f0afce0d33bfa7fb35b913e4c0aeb7706464befb9ca97"],["p","f5352d5eb9c8b2ecfd5ec74e8de6f109f2029d44b2de1ee0dea9bfdf646b26b7"],["p","19e358b8011f5f4fc653c565c6d4c2f33f32661f4f90982c9eedc292a8774ec3"],["p","47e734e8b2e19d35cf6e9142b39c375084b8b2e2d2bc9ee60b70f879d706ab5f"],["p","9c9bc3a22f645c18c3fdb299627ae6d97ea1f0bc2b8e4d9ad43b9fb7480f6d37"],["p","733c5427f55ceba01a0f6607ab0fd11832bbb27d7db17b570e7eb7b68a081d9a"],["p","b0b8fbd9578ac23e782d97a32b7b3a72cda0760761359bd65661d42752b4090a"],["p","51faaa771741ad55150ee389e5a06ebd6a2a42aa9414ad3f066c176f2c26615b"],["p","94e19ed8532ac7bb6ea2d268795819544c35aba232c4018745d9272d23e614ae"],["p","5d9fedd35634b28cc5132aa79ba0e97cee7d215168cf8160f648001b7bef8113"],["p","d2f3633e2e7e28468763f283f3ef57f1b6c9e72c04b9f18e091bb61ca6c9ac22"],["p","95a69326449931adda32e7e0f6275bec0e387abeee4bb56b3e94f46a6ac402e2"],["p","c5c84d7b28bc93ee011dfc8f22915ba6e82e967782a6617ec1063748ecb10643"],["p","3f4d4ed0186c86cc7de9f66ff49ae4551c312a742fb885a00ef93b657d4c5717"],["p","93518f91dfa51d8acf39217cdcd3d2ccd178433cb9e72368544aacd7412cb50c"],["p","20eff5826c53d172b6d2cb5087cc784240fc1298da89cce56ff2bee0521b94c9"],["p","35f1e09a7408b87ca877df12fe34360115fef2dda438082714916181c6243f1f"],["p","6bb36e4f65717a8e8acec00e726cdb93e51020d8e576e482720309c874b8e187"],["p","a1f66e86a8864c0cf172a41d0505369a0808fefe5691931dddf27eb51d62b916"],["p","08cd52a46ab37a9894b3333785c2ff50e068d1b01fb03d702608da83e9817d82"],["p","93e174736c4719f80627854aca8b67efd0b59558c8ece267a8eccbbd2e7c5535"],["p","a8d77e2cb4ee92a280321e96856c0d113dad3ff5d68f5595be85bf11113a0a8c"],["p","62bf13d3dd4e882a36be4406c08247b70cee7b523282d8bccb597110054579bb"],["p","813a2cfda49a8801e21bed3c2380e786fa4cad753dd15b1af19a37a815579579"],["p","d6018f72afc9a131d085ce458297375ad6f2fc9095cfea797328e7bd0ac5976b"],["p","cc4fb93863ceca1327a58addce145f953a4ba5c2ac88e6970086e02e7bd65d10"],["p","8d0d521dde92c8aaa10c3276fc5760eda765438f4885b70d096a49f969628fca"],["p","38ccd6fad4e93e4a3cbaa74a361c7e00c1b34e2b941a18d4e6429f4dc06e260b"],["p","b5ba65fbb0221a32b6c14400f505cfdd3651d43938a248a9265a516ec0c54240"],["p","30ceb64e73197a05958c8bd92ab079c815bb44fbfbb3eb5d9766c5207f08bdf5"],["p","d5abca3791ce35c65c8c28d25fd82b0eca676556af838f2d9471ca097fe4e3c4"],["p","f0810d5d7942500b78dedd7f35f88d51f790c215f8cbcc5a2e3181dc135a8f06"],["p","515d7a746745d9ddc3344f595cc845e9e9f8511bf3508bfa20058ca6e55033cb"],["p","3d70ec1ea586650a0474d6858454209d222158f4079e8db806f017ef5e30e767"],["p","7ed30679d22a30fee23107658ee2f0b77b3bcd8a6a68adbc009d28ad207442a9"],["p","106194f1ef1f2088ab0129b341779243b3c17ec5375591c860b2cb7b57be3537"],["p","1f2c17bd3bcaf12f9c7e78fe798eeea59c1b22e1ee036694d5dc2886ddfa35d7"],["p","c43bbb58e2e6bc2f9455758257f6ba5329107bd4e8274068c2936c69d9980b7d"],["p","e4c822745ac83d6e2534803d185579ed26f80c45d15a307a3b9e716a0260edb9"],["p","eccf506b0f8bb7fa7d2fe3b00c0d4f235cc7adff631e6b2ff84c0b0cf56bca4f"],["p","7cb13cde0670e590f02cbe9ea0fcf1e05edbc5cc8a409731fa5436440181cf1d"],["p","a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d"],["p","8fe3f243e91121818107875d51bca4f3fcf543437aa9715150ec8036358939c5"],["p","0521db9531096dff700dcf410b01db47ab6598de7e5ef2c5a2bd7e1160315bf6"],["p","8fb140b4e8ddef97ce4b821d247278a1a4353362623f64021484b372f948000c"],["p","c998a5739f04f7fff202c54962aa5782b34ecb10d6f915bdfdd7582963bf9171"],["p","cc9976d96708729c89027c1137340399ff511f7c741563b44a5b1fda7bb8508b"],["p","efba4c3bc34558d20ff0a433dd81a0fbce0c3734d7b579d6d020d8629bbdcb79"],["p","36cf900822d149d494e281ce9ccf2ad3b18ec6e04f328a82cd36cf662994afb7"],["p","5df21e8ec11e21e7b710ac7d6c94427407ae69e93a7fcf0d0a3ee2fac4fdc84b"],["p","cfe3b4316d905335b6ce056ba0ec230b587a334381e82bf9a02a184f2d068f8d"],["p","2af01e0d6bd1b9fbb9e3d43157d64590fb27dcfbcabe28784a5832e17befb87b"],["p","492bf025bc7394a95e83dd64995669bcf0d909536e30ef6cd73c86c53e34ff10"],["p","1d709fcffc239692a87ae22bf6c99a10f1a271a5ac5187adf2a6376355f49da5"],["p","cc8d072efdcc676fcbac14f6cd6825edc3576e55eb786a2a975ee034a6a026cb"],["p","7a78fbfec68c2b3ab6084f1f808321ba3b5ea47502c41115902013e648e76288"],["p","c6209b5936aea5092e677e3817b25329e1fb5f206ea8b8e97c59d4ab35ac6e0c"],["p","566516663d91d4fef824eaeccbf9c2631a8d8a2efee8048ca5ee6095e6e5c843"],["p","0904cd8792f87042bae46ff1d24516dbd4ee3d3fcdf9d8f52d7016a5100b8c70"],["p","762a3c15c6fa90911bf13d50fc3a29f1663dc1f04b4397a89eef604f622ecd60"],["p","4ce6abbd68dab6e9fdf6e8e9912a8e12f9b539e078c634c55a9bff2994a514dd"],["p","880f967145ab66b53d9dc279d44a9722ba875d232c73f3df4707d1e79c4336ce"],["p","59f97730b917e0e4bcbcd65309dbee76bf1d94339ec590256c037f50fdfbfb14"],["p","94215f42a96335c87fcb9e881a0bbb62b9a795519e109cf5f9d2ef617681f622"],["p","7df2cb72b7e415d54bcddfec676faa33d2bdca74974ae668c68fc93ca7e42657"],["p","e6c282d1a1a1bdc7254b1b6932df32c516a2f7f1036d199b37b9e13129a3af26"],["p","bdb96ad31ac6af123c7683c55775ee2138da0f8f011e3994d56a27270e692575"],["p","3958a92875068eca331ce9c20142ccca7bccaa8927b1debc790c9dc56ec13730"],["p","50054d07e2cdf32b1035777bd9cf73992a4ae22f91c14a762efdaa5bf61f4755"],["p","03d50fb85255bfde8ac3632fdbb93ce54b6f9f172e091ec1452767990f1b2245"],["p","ee412c944f1c4de093e1f1f3f65208d093f466c7860d3b53efd5267f2c4c0b00"],["p","48f396bf659c0b5e8356b3ea655d223e72d48d96304250e20475f6215804d77c"],["p","cf165b633276728704aee823daa1f10b02368ac264a4cf9d2c92baa77ccf8bf3"],["p","a367f9eb1cb3a241a7f3646f31cd6d597bbbbf8eaeb5cd2e707d09b00633efea"],["p","efe5d120df0cc290fa748727fb45ac487caad346d4f2293ab069e8f01fc51981"],["p","a2c33e11f6b8fed942d2f92877a09ce2f3ae9345678559a5863a35c47288460b"],["p","31cd0e7d8fe9228bcd6bc379f578646e0d375fad70dd8ff4dc45d2872f37d5e5"],["p","a23dbf6c6cc83e14cc3df4e56cc71845f611908084cfe620e83e40c06ccdd3d0"],["p","36b0fdab5a4a5519ce36a2a33b0ad92179103aca1f37bf59fb780256eeafb8a0"],["p","be309f8cd1767274e2ebbe58ee959530b9b0802a5a9eafe3822e8a6f2c9654bd"],["p","bd31b09ec4815b61120cabafd8bb821f3adefeeb91d4220a70c40da4d9caca49"],["p","77ec966fcd64f901152cad5dc7731c7c831fe22e02e3ae99ff14637e5a48ef9c"],["p","604e96e099936a104883958b040b47672e0f048c98ac793f37ffe4c720279eb2"],["p","b7ed68b062de6b4a12e51fd5285c1e1e0ed0e5128cda93ab11b4150b55ed32fc"],["p","70e55b8cb48246671554beb9e0b49c9ee726ccd96d5e7190b460df2a7d7a5b49"],["p","2c1a3e122beeb04d22815f650bfed597be4d2e6d71e3013efa2d59468caf6cf9"],["p","c6b2cbf389e4d94746d01509ef81208cc5bad36ae39602a42745e6c4f7fc5077"],["p","c5151a315a03f2c1484322fbb090d664b479d71a721aed575481765b98284683"],["p","1e7168756bccf2309ca35661d06bab6bcd0448420ab349aabddaaf02746b1515"],["p","a31a17d6778d3aac3f2d06c52094a19f056cdc7757c9f45e36339e8b34e8856e"],["p","e6618db6961dc7b91478e0fa78c4c1b6699009981526693bd5e273972550860c"],["p","6073dfc010e50ad3625360b0f79a6f742b35c21a2b243f9c1fe3648c883714be"],["p","8f6568deec0120acb2a4d1e4e311c881d306c396a9288b0a07f564d20f186268"],["p","8ea485266b2285463b13bf835907161c22bb3da1e652b443db14f9cee6720a43"],["p","41674dbac15a8cdf81ba5bca586e6dc5950733bfd251488065a7f52917394457"],["p","81b102ff7f96268b13eebe9410f3328774767bf43073d1aef27136a7840afb59"],["p","6007cea03df4ae72b6841415c348a26c925979596f6c06afd0d9e26dbc1b3de8"],["p","c1fc7771f5fa418fd3ac49221a18f19b42ccb7a663da8f04cbbf6c08c80d20b1"],["p","5e30c668a63c81e1cd1c2da472c463c51db0afc3e626929a35b6e90bf5232eea"],["p","b9ce2f313bf6e7d116a89a82aed030eb782b06e34a8336acdda99906e841120e"],["p","1728b6cc1e76a2946e0911e8bcfb3a1651e22beb380e32c8341611a3c924f464"],["p","8ca1cf46ac5ea8d195826af35f28fc66d88ee60cec10f760f10413306f1c8dc8"],["p","03fbf7d2eae31727a42773bd95fde16ca76e6f51392622af76e47345292467f6"],["p","e9f90f307e89e7ec31a6bbd0d5cd7400876a833105bf7dc070ad3ff02ad53f50"],["p","1df39284d04fcb8749ce40765bb524b7b44b0a3fa1b90caf13423bd19d4eb65d"],["p","22818ec33d8078e64964b561c839b74433f2371552d7f5bd6ab0af325a79f429"],["p","55e1b51046886030429c801d5bcbdd5cff7b4066ffdd277be4951e871afa7617"],["p","3226ea107845ef2a6288e26570d75fa37e9e6ced2fe8894d43d547e3d97e2e48"],["p","6f29c525dca3d164c9a875e1f3eb63f27492b7e38d285b4117a6c2df83503d29"],["p","efc37e97fa4fad679e464b7a6184009b7cc7605aceb0c5f56b464d2b986a60f0"],["p","958607db789f1608b33190268db8d12b35f8577c4a515a5b8cd8b345c34b1fd2"],["p","909efa6667b28627f107764ce3c28895c46fffd1811b7415dcab03f48c44b597"],["p","3e8ae146713812efb59ac63a11be9991f9e8858db8594331f251394497430a8b"],["p","4add4db6f62e142c431e48a0f6188d32da081e499299da65027d2f9dc3747c3a"],["p","e62f419a0e16607b96ff10ecb00249af7d4b69c7d121e4b04130c61cc998c32e"],["p","126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f"],["p","e150f701fa4eac9dce3c0731b6626b9391c1ee31feb5c1e1d67e6f4d9d077d24"],["p","f16ac67afff1340501cf27b7ebe9e3086fb62e97f044ba407e38b5d8425a73f2"],["p","508f28656b8db436153d5239de5034abc0351b8c90ac33e6b156d1fea64b2960"],["p","97b988fbf4f8880493f925711e1bd806617b508fd3d28312288507e42f8a3368"],["p","7d4e04503ab26615dd5f29ec08b52943cbe5f17bacc3012b26220caa232ab14c"],["p","b02c50baaa756285c460173ec4f5b9b18dae7d18196a0af2d4ed96ff4e716778"],["p","460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"],["p","e731ca427c18059d66636ddfaeeeb15012bc2db3cdd27b9e4cade5057a6e82ed"],["p","b6960fcbc7c04536bc98f55c48d9f9fee55983e7f763c124453af728af82311d"],["t","damus"],["t","nashville"],["p","bcea2b98506d1d5dd2cc0455a402701e342c76d70f46e38739aadde77ccef3c9"],["t","haskell"],["p","21335073401a310cc9179fe3a77e9666710cfdf630dfd840f972c183a244b1ad"],["t","asknostr"],["t","winestr"],["t","introductions"],["p","ea57b25f7a57c61d7dd0bf62411244a580d6709e42a20428fd381f89ef8d63db"],["t","foodstr"],["t","grownostr"],["p","0a830eae1e28c8119fe19fcaba01b9b243e7bcdad3c1043947a9980ac1e5f806"],["t","metaverse"],["p","26eb0b67b66fa45e4885580f65c602139e56e43d7b79718abdf32395b3a94722"],["t","weirdstr"],["t","lmdb"],["t","sqlite"],["t","midjourney"],["p","5aeb250b3075a12bd05e16c8a3c40da91a553fa92164a39915a3a0615fe51864"],["p","6c2795794d27e2cf0f87c1323213655b4b63cba65268a1f3302f14292f4dc278"],["p","d71ef76761bfb43ff822676b770f016e16f2fdcf99bb6f6063a3743656d1e82d"],["p","1586fd57ac81b66177b0087bb0c0fa465f30b9895949c8936836ec5e6cd13132"],["p","7dd022d6f2cff4166eb276b6b2d75688da15871dbb56a82c68546525465de5fa"],["p","74fae6664c9e79980c6ac11c5775ed30932be926f5c45be8d655e00e35eca288"]]} """ +let test_thread_note_1 = NdbNote.owned_from_json(json: "{\"kind\":1,\"created_at\":1718180574,\"tags\":[],\"id\":\"d98e197facb1c9fdeddc1a1caf53060114138e9af73745fd2eb0f7f432df806c\",\"pubkey\":\"4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0\",\"content\":\"Is Damus using Negentropy yet?\",\"sig\":\"d1f0adb18d3482c21281da80228a6aa6cfe8f382fba3ca00ec3c8e0284e68e42358a3fd1c607137d40e266c9fa31a741c94e8cc97c8f8d2fb6c17d7c3dde3473\"}")! +let test_thread_note_2 = NdbNote.owned_from_json(json: "{\"created_at\":1718181181,\"kind\":1,\"id\":\"18cf472a0216e684ebe51a8f517c8657ece7089c6b2d8910cceb2b508cb6b9d6\",\"tags\":[[\"e\",\"d98e197facb1c9fdeddc1a1caf53060114138e9af73745fd2eb0f7f432df806c\",\"\",\"root\"],[\"p\",\"4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0\"]],\"sig\":\"ff94367654a25697165f228395ca99a5829cdec7e59700a7364ce1af86b4a68304fe14c1acc66740c95e4504e5f5f4638023b279c9a29a77048f8627885e4ef8\",\"pubkey\":\"b61e4b6dd3e0a1a721403db6f63d5c74cac4202a00458d0f0dc1e6788b28df4b\",\"content\":\"I guess the Devices running Damus do, by consuming electrical charge and emitting thermal energy.\nIf that is what you were going for.\"}")! +let test_thread_note_3 = NdbNote.owned_from_json(json: "{\"kind\":1,\"tags\":[[\"e\",\"d98e197facb1c9fdeddc1a1caf53060114138e9af73745fd2eb0f7f432df806c\",\"\",\"root\"],[\"e\",\"18cf472a0216e684ebe51a8f517c8657ece7089c6b2d8910cceb2b508cb6b9d6\",\"wss://a.nos.lol\",\"reply\"],[\"p\",\"b61e4b6dd3e0a1a721403db6f63d5c74cac4202a00458d0f0dc1e6788b28df4b\"]],\"sig\":\"5706957eb0f55670a42d389642dfba1393190b2fbf6a5507c5f661e1858c44d4928967c79ca97ecec92132496c727050aa746ac07dd82523aee4698b2c350b57\",\"id\":\"0e0fd2501c7d0ea26ec66fef4a7aa0666f7d2b2a138ce69254543d4b65f34b7d\",\"content\":\"I guess what he means is the protocol for syncing events across different relays. Strfry has it, but you can turn it off.\",\"pubkey\":\"c21b1a6cdb247ccbd938dcb16b15a4fa382d00ffd7b12d5cbbad172a0cd4d170\",\"created_at\":1718181626}")! +let test_thread_note_4 = NdbNote.owned_from_json(json: "{\"created_at\":1718181626,\"kind\":1,\"tags\":[[\"e\",\"d98e197facb1c9fdeddc1a1caf53060114138e9af73745fd2eb0f7f432df806c\",\"\",\"root\"],[\"e\",\"18cf472a0216e684ebe51a8f517c8657ece7089c6b2d8910cceb2b508cb6b9d6\",\"wss://a.nos.lol\",\"reply\"],[\"p\",\"b61e4b6dd3e0a1a721403db6f63d5c74cac4202a00458d0f0dc1e6788b28df4b\"]],\"content\":\"I guess what he means is the protocol for syncing events across different relays. Strfry has it, but you can turn it off.\",\"pubkey\":\"c21b1a6cdb247ccbd938dcb16b15a4fa382d00ffd7b12d5cbbad172a0cd4d170\",\"id\":\"0e0fd2501c7d0ea26ec66fef4a7aa0666f7d2b2a138ce69254543d4b65f34b7d\",\"sig\":\"5706957eb0f55670a42d389642dfba1393190b2fbf6a5507c5f661e1858c44d4928967c79ca97ecec92132496c727050aa746ac07dd82523aee4698b2c350b57\"}")! +let test_thread_note_5 = NdbNote.owned_from_json(json: "{\"created_at\":1718188975,\"tags\":[[\"e\",\"d98e197facb1c9fdeddc1a1caf53060114138e9af73745fd2eb0f7f432df806c\",\"\",\"root\"],[\"p\",\"4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"]],\"pubkey\":\"59cacbd83ad5c54ad91dacf51a49c06e0bef730ac0e7c235a6f6fa29b9230f02\",\"kind\":1,\"content\":\"I’m pretty sure it doesn’t. nostr:npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s\",\"sig\":\"74919775e50588aa76de57ef75541678b9618d35849fbee9ea70d84fc38a15610a13d82c48c1d2b62b2c9fe0117be91fa2291b0d93b8fe0209dc3ae3749188d8\",\"id\":\"6e953d06e3cdf6119b7cc4bfdef5139acb410b9bf79c02cd4ca0f2e1bbe6b572\"}")! +let test_thread_note_6 = NdbNote.owned_from_json(json: "{\"pubkey\":\"17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4\",\"content\":\"😢\n\ncc nostr:npub13v47pg9dxjq96an8jfev9znhm0k7ntwtlh9y335paj9kyjsjpznqzzl3l8 \n\nSoon™️\",\"sig\":\"3ba1b651bc6c288b31948ee1f8680b7735d8c00e2daa55c3ac8973d111d7bfef2db28bb4a36c912dd803238b2bee9b7054aea387418aa4db33d4991c61a253e1\",\"kind\":1,\"id\":\"62769f438ec80edfd3995358159a9427bf037283affe7790d4f1cacd5837d88f\",\"created_at\":1718403197,\"tags\":[[\"e\",\"d98e197facb1c9fdeddc1a1caf53060114138e9af73745fd2eb0f7f432df806c\",\"\",\"root\"],[\"e\",\"6e953d06e3cdf6119b7cc4bfdef5139acb410b9bf79c02cd4ca0f2e1bbe6b572\",\"\",\"reply\"],[\"p\",\"4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"],[\"p\",\"59cacbd83ad5c54ad91dacf51a49c06e0bef730ac0e7c235a6f6fa29b9230f02\"],[\"p\",\"8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6\"]]}")! +let test_thread_note_7 = NdbNote.owned_from_json(json: "{\"kind\":1,\"id\":\"06007f699f5240f90a6e508d2e89e8bef75348c3ebdde43d58d72311f49693a3\",\"tags\":[[\"e\",\"d98e197facb1c9fdeddc1a1caf53060114138e9af73745fd2eb0f7f432df806c\",\"\",\"root\"],[\"e\",\"62769f438ec80edfd3995358159a9427bf037283affe7790d4f1cacd5837d88f\",\"\",\"reply\"],[\"p\",\"4523be58d395b1b196a9b8c82b038b6895cb02b683d0c253a955068dba1facd0\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"],[\"p\",\"8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6\"],[\"p\",\"17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4\"]],\"created_at\":1718403771,\"pubkey\":\"59cacbd83ad5c54ad91dacf51a49c06e0bef730ac0e7c235a6f6fa29b9230f02\",\"sig\":\"72ca242d2c82d0440dbb198822b3a1885a4dac5bc44ef02d8f9760a591d16340ab66649339e796b2810894d89f56c240194bf8d8bfefc833cbbd72d004077c74\",\"content\":\"Would love to see it! 🌐\"}")! + + diff --git a/damus/Util/EventCache.swift b/damus/Util/EventCache.swift @@ -97,13 +97,13 @@ class EventCache { // TODO: remove me and change code to use ndb directly private let ndb: Ndb private var events: [NoteId: NostrEvent] = [:] - private var replies = ReplyMap() private var cancellable: AnyCancellable? private var image_metadata: [String: ImageMetadataState] = [:] // lowercased URL key private var event_data: [NoteId: EventData] = [:] + var replies = ReplyMap() //private var thread_latest: [String: Int64] - + init(ndb: Ndb) { self.ndb = ndb cancellable = NotificationCenter.default.publisher( @@ -187,7 +187,7 @@ class EventCache { replies.add(id: reply, reply_id: ev.id) } } - + func child_events(event: NostrEvent) -> [NostrEvent] { guard let xs = replies.lookup(event.id) else { return [] diff --git a/damus/Util/Extensions/VectorMath.swift b/damus/Util/Extensions/VectorMath.swift @@ -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) + } +} diff --git a/damus/Util/Router.swift b/damus/Util/Router.swift @@ -93,7 +93,8 @@ enum Route: Hashable { case .FirstAidSettings(settings: let settings): FirstAidSettingsView(damus_state: damusState, settings: settings) case .Thread(let thread): - ThreadView(state: damusState, thread: thread) + ChatroomThreadView(damus: damusState, thread: thread) + //ThreadView(state: damusState, thread: thread) case .Reposts(let reposts): RepostsView(damus_state: damusState, model: reposts) case .QuoteReposts(let quote_reposts): diff --git a/damus/Views/ActionBar/EventActionBar.swift b/damus/Views/ActionBar/EventActionBar.swift @@ -7,12 +7,15 @@ import SwiftUI import MCEmojiPicker +import SwipeActions struct EventActionBar: View { let damus_state: DamusState let event: NostrEvent let generator = UIImpactFeedbackGenerator(style: .medium) let userProfile : ProfileModel + let swipe_context: SwipeContext? + let options: Options // just used for previews @State var show_share_sheet: Bool = false @@ -23,11 +26,13 @@ struct EventActionBar: View { @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.event = event _bar = ObservedObject(wrappedValue: bar ?? make_actionbar_model(ev: event.id, damus: damus_state)) self.userProfile = ProfileModel(pubkey: event.pubkey, damus: damus_state) + self.options = options + self.swipe_context = swipe_context } var lnurl: String? { @@ -44,60 +49,176 @@ struct EventActionBar: View { return true } - var body: some View { - HStack { - if damus_state.keypair.privkey != nil { - HStack(spacing: 4) { - EventActionButton(img: "bubble2", col: bar.replied ? DamusColors.purple : Color.gray) { - notify(.compose(.replying_to(event))) - } - .accessibilityLabel(NSLocalizedString("Reply", comment: "Accessibility label for reply button")) - Text(verbatim: "\(bar.replies > 0 ? "\(bar.replies)" : "")") - .font(.footnote.weight(.medium)) - .foregroundColor(bar.replied ? DamusColors.purple : Color.gray) - } + var space_if_spread: AnyView { + if options.contains(.no_spread) { + 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) { + EventActionButton(img: "bubble2", col: bar.replied ? DamusColors.purple : Color.gray) { + notify(.compose(.replying_to(event))) } - Spacer() - HStack(spacing: 4) { - - EventActionButton(img: "repost", col: bar.boosted ? Color.green : nil) { - self.show_repost_action = true + .accessibilityLabel(NSLocalizedString("Reply", comment: "Accessibility label for reply button")) + Text(verbatim: "\(bar.replies > 0 ? "\(bar.replies)" : "")") + .font(.footnote.weight(.medium)) + .foregroundColor(bar.replied ? DamusColors.purple : Color.gray) + } + } + + var repost_button: some View { + HStack(spacing: 4) { + + EventActionButton(img: "repost", col: bar.boosted ? Color.green : nil) { + self.show_repost_action = true + } + .accessibilityLabel(NSLocalizedString("Reposts", comment: "Accessibility label for boosts button")) + Text(verbatim: "\(bar.boosts > 0 ? "\(bar.boosts)" : "")") + .font(.footnote.weight(.medium)) + .foregroundColor(bar.boosted ? Color.green : Color.gray) + } + } + + var like_button: some View { + 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 + if bar.liked { + //notify(.delete, bar.our_like) + } else { + send_like(emoji: emoji) } - .accessibilityLabel(NSLocalizedString("Reposts", comment: "Accessibility label for boosts button")) - Text(verbatim: "\(bar.boosts > 0 ? "\(bar.boosts)" : "")") - .font(.footnote.weight(.medium)) - .foregroundColor(bar.boosted ? Color.green : Color.gray) } - + + Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")") + .font(.footnote.weight(.medium)) + .nip05_colorized(gradient: bar.liked) + } + } + + var share_button: some View { + EventActionButton(img: "upload", col: Color.gray) { + show_share_action = true + } + .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 { - Spacer() - - 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 - if bar.liked { - //notify(.delete, bar.our_like) - } else { - send_like(emoji: emoji) - } - } - - Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")") - .font(.footnote.weight(.medium)) - .nip05_colorized(gradient: bar.liked) - } + self.like_swipe_button } - - if let lnurl = self.lnurl { - 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) + } + } + + 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 - Spacer() - EventActionButton(img: "upload", col: Color.gray) { - show_share_action = true + 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 } - .accessibilityLabel(NSLocalizedString("Share", comment: "Button to share a note")) + + 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 { self.bar.update(damus: damus_state, evid: self.event.id) } @@ -164,6 +285,17 @@ struct EventActionBar: View { 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) + } } @@ -299,7 +431,6 @@ struct LikeButton: View { } } - struct EventActionBar_Previews: PreviewProvider { static var previews: some View { let ds = test_damus_state @@ -324,7 +455,44 @@ struct EventActionBar_Previews: PreviewProvider { 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: bar, options: [.no_spread]) } .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) + } + } +} diff --git a/damus/Views/Chat/ChatBubbleView.swift b/damus/Views/Chat/ChatBubbleView.swift @@ -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) + } +} diff --git a/damus/Views/Chat/ChatEventView.swift b/damus/Views/Chat/ChatEventView.swift @@ -0,0 +1,324 @@ +// +// ChatView.swift +// damus +// +// Created by William Casarin on 2022-04-19. +// + +import SwiftUI +import MCEmojiPicker +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: String = "" + + @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 + .emojiPicker( + 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 + } + }), + selectedEmoji: $selected_emoji, + arrowDirection: isOnTopHalfOfScreen ? .down : .up, + isDismissAfterChoosing: false + ) + .onChange(of: selected_emoji) { newSelectedEmoji in + if newSelectedEmoji != "" { + send_like(emoji: newSelectedEmoji) + 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) +} diff --git a/damus/Views/Chat/ChatroomThreadView.swift b/damus/Views/Chat/ChatroomThreadView.swift @@ -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) +} diff --git a/damus/Views/Chat/ReplyQuoteView.swift b/damus/Views/Chat/ReplyQuoteView.swift @@ -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]) + } +} diff --git a/damus/Views/Events/Longform/LongformPreview.swift b/damus/Views/Events/Longform/LongformPreview.swift @@ -51,13 +51,13 @@ struct LongformPreviewBody: View { func truncatedText(content: CompatibleText) -> some View { Group { 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) .foregroundColor(.gray) .padding(.horizontal, 10) } else if truncate { - TruncatedText(text: content) + TruncatedText(text: content, show_show_more_button: !options.contains(.no_show_more)) .font(header ? .body : .caption) .foregroundColor(.gray) .padding(.horizontal, 10) diff --git a/damus/Views/Events/TextEvent.swift b/damus/Views/Events/TextEvent.swift @@ -21,9 +21,11 @@ struct EventViewOptions: OptionSet { static let no_mentions = EventViewOptions(rawValue: 1 << 9) static let no_media = EventViewOptions(rawValue: 1 << 10) 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_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 { diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift @@ -78,11 +78,11 @@ struct NoteContentView: View { func truncatedText(content: CompatibleText) -> some View { Group { 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)) } 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)) } else { content.text @@ -185,18 +185,22 @@ struct NoteContentView: View { invoicesView(invoices: artifacts.invoices) } } - - if damus_state.settings.media_previews { + + if damus_state.settings.media_previews, has_previews { if with_padding { previewView(links: artifacts.links).padding(.horizontal) } else { previewView(links: artifacts.links) } } - + } } - + + var has_previews: Bool { + !options.contains(.no_previews) + } + func loadMediaButton(artifacts: NoteArtifactsSeparated) -> some View { Button(action: { load_media = true @@ -397,6 +401,14 @@ struct NoteContentView_Previews: PreviewProvider { .border(Color.red) } .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") } } }