[PATCH damus 0/1] New chatroom-style thread view

3 views
Skip to first unread message

Daniel D’Aquino

unread,
Jun 19, 2024, 2:41:44 PMJun 19
to pat...@damus.io, Daniel D’Aquino
Hi Will,

These are the changes for the new chatroom-style thread view.

I also pushed these changes to this branch for CI purposes: https://github.com/damus-io/damus/tree/%231126_option_4_refined
All tests are passing in CI: https://appstoreconnect.apple.com/teams/69a6de74-1a7a-47e3-e053-5b8c7c11a4d1/apps/1628663131/ci/builds/90bf7792-bb1f-4bf5-9d83-6998b023c8ec

More details can be found in the commit itself. Please let me know if you need any changes!

Note: I listed the known issues in the commit message. I think we can track those in separate tickets to get the external TestFlight published and get user feedback.

Cheers!
Daniel

Daniel D’Aquino (1):
New chat thread view

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


base-commit: 8bcd8317f1c4376608a17339e1ac11d37317a592
--
2.44.0


Daniel D’Aquino

unread,
Jun 19, 2024, 2:41:58 PMJun 19
to pat...@damus.io, Daniel D’Aquino
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 <dan...@daquino.me>
---
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
index 77bf2182..290d3a7f 100644
--- 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
index 814ee72a..ab8d81d4 100644
--- 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
new file mode 100644
index 00000000..9c0c4521
--- /dev/null
+++ 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
new file mode 100644
index 00000000..0c33b9b9
--- /dev/null
+++ 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
new file mode 100644
index 00000000..afbc93a9
--- /dev/null
+++ 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
new file mode 100644
index 00000000..6862c599
--- /dev/null
+++ 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
new file mode 100644
index 00000000..cb36310c
--- /dev/null
+++ 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
index 1ae8e79f..fed3d2ba 100644
--- 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
index 8fca6895..682f6a9b 100644
--- 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
index 85003cb5..2f3b2844 100644
--- 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
index d7e9c977..98e31256 100644
--- 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
index 0419a9e9..dcc62916 100644
--- 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
new file mode 100644
index 00000000..0dfe083f
--- /dev/null
+++ 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
index 928efcb5..b67a358b 100644
--- 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
index 8413ab0d..0897436e 100644
--- 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
new file mode 100644
index 00000000..bd79a746
--- /dev/null
+++ 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
new file mode 100644
index 00000000..484f1d5b
--- /dev/null
+++ 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
new file mode 100644
index 00000000..16594929
--- /dev/null
+++ 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
new file mode 100644
index 00000000..660318cb
--- /dev/null
+++ 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
index 96442841..26235804 100644
--- 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
index c1bedb6d..09fa4502 100644
--- 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
index 491fe4ac..45b320ed 100644
--- 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")
}
}
}
--
2.44.0


Daniel D'Aquino

unread,
Jun 19, 2024, 3:38:39 PMJun 19
to sembene_truestar via patches

On Jun 19, 2024, at 11:41, Daniel D’Aquino <dan...@daquino.me> wrote:

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 <dan...@daquino.me>
---

Talked to Will on Signal and got the approval to push this. Pushed to https://github.com/damus-io/damus/commit/23c3130a8267c6bfaa37a392c515ac261813d1bb
Reply all
Reply to author
Forward
0 new messages